diff --git a/.eslintignore b/.eslintignore index c730b7f006..e16a858f24 100644 --- a/.eslintignore +++ b/.eslintignore @@ -159,7 +159,9 @@ packages/app-desktop/commands/exportNotes.js packages/app-desktop/commands/focusElement.js packages/app-desktop/commands/index.js packages/app-desktop/commands/openNoteInNewWindow.js +packages/app-desktop/commands/openPrimaryAppInstance.js packages/app-desktop/commands/openProfileDirectory.js +packages/app-desktop/commands/openSecondaryAppInstance.js packages/app-desktop/commands/replaceMisspelling.js packages/app-desktop/commands/restoreNoteRevision.js packages/app-desktop/commands/startExternalEditing.js @@ -264,6 +266,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHan packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTabIndenter.js +packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTextPatternsLookup.js packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js packages/app-desktop/gui/NoteEditor/NoteEditor.js packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js @@ -272,6 +275,7 @@ packages/app-desktop/gui/NoteEditor/WarningBanner/BannerContent.js packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js +packages/app-desktop/gui/NoteEditor/commands/focusElementNoteViewer.js packages/app-desktop/gui/NoteEditor/commands/focusElementToolbar.js packages/app-desktop/gui/NoteEditor/commands/index.js packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js @@ -353,13 +357,16 @@ packages/app-desktop/gui/NoteSearchBar.js packages/app-desktop/gui/NoteStatusBar.js packages/app-desktop/gui/NoteTextViewer.js packages/app-desktop/gui/NoteToolbar/NoteToolbar.js -packages/app-desktop/gui/NotyfContext.js packages/app-desktop/gui/OneDriveLoginScreen.js packages/app-desktop/gui/PasswordInput/LabelledPasswordInput.js packages/app-desktop/gui/PasswordInput/PasswordInput.js packages/app-desktop/gui/PasswordInput/types.js packages/app-desktop/gui/PdfViewer.js packages/app-desktop/gui/PluginNotification/PluginNotification.js +packages/app-desktop/gui/PopupNotification/NotificationItem.js +packages/app-desktop/gui/PopupNotification/PopupNotificationList.js +packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.js +packages/app-desktop/gui/PopupNotification/types.js packages/app-desktop/gui/PromptDialog.js packages/app-desktop/gui/ResizableLayout/LayoutItemContainer.js packages/app-desktop/gui/ResizableLayout/MoveButtons.js @@ -421,6 +428,7 @@ packages/app-desktop/gui/ToolbarBase.js packages/app-desktop/gui/ToolbarButton/ToolbarButton.js packages/app-desktop/gui/ToolbarSpace.js packages/app-desktop/gui/TrashNotification/TrashNotification.js +packages/app-desktop/gui/TrashNotification/TrashNotificationMessage.js packages/app-desktop/gui/UpdateNotification/UpdateNotification.js packages/app-desktop/gui/WindowCommandsAndDialogs/AppDialogs.js packages/app-desktop/gui/WindowCommandsAndDialogs/ModalMessageOverlay.js @@ -547,13 +555,16 @@ packages/app-desktop/services/plugins/UserWebview.js packages/app-desktop/services/plugins/UserWebviewDialog.js packages/app-desktop/services/plugins/UserWebviewDialogButtonBar.js packages/app-desktop/services/plugins/hooks/useContentSize.js +packages/app-desktop/services/plugins/hooks/useFormData.js packages/app-desktop/services/plugins/hooks/useHtmlLoader.js +packages/app-desktop/services/plugins/hooks/useMessageHandler.js packages/app-desktop/services/plugins/hooks/useScriptLoader.js packages/app-desktop/services/plugins/hooks/useSubmitHandler.js packages/app-desktop/services/plugins/hooks/useThemeCss.test.js packages/app-desktop/services/plugins/hooks/useThemeCss.js packages/app-desktop/services/plugins/hooks/useViewIsReady.js packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js +packages/app-desktop/services/plugins/types.js packages/app-desktop/services/restart.js packages/app-desktop/services/sortOrder/PerFolderSortOrderService.test.js packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js @@ -581,6 +592,7 @@ packages/app-desktop/utils/restartInSafeModeFromMain.test.js packages/app-desktop/utils/restartInSafeModeFromMain.js packages/app-desktop/utils/window/types.js packages/app-mobile/PluginAssetsLoader.js +packages/app-mobile/commands/dismissPluginPanels.js packages/app-mobile/commands/index.js packages/app-mobile/commands/newNote.test.js packages/app-mobile/commands/newNote.js @@ -590,6 +602,7 @@ packages/app-mobile/commands/scrollToHash.js packages/app-mobile/commands/util/goToNote.js packages/app-mobile/commands/util/showResource.js packages/app-mobile/components/BetaChip.js +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 @@ -683,9 +696,14 @@ packages/app-mobile/components/SelectDateTimeDialog.js packages/app-mobile/components/SideMenu.js packages/app-mobile/components/SideMenuContentNote.js packages/app-mobile/components/TextInput.js -packages/app-mobile/components/ToggleSpaceButton.js -packages/app-mobile/components/accessibility/AccessibleModalMenu.js +packages/app-mobile/components/accessibility/AccessibleView.test.js packages/app-mobile/components/accessibility/AccessibleView.js +packages/app-mobile/components/accessibility/FocusControl/AutoFocusProvider.js +packages/app-mobile/components/accessibility/FocusControl/FocusControl.js +packages/app-mobile/components/accessibility/FocusControl/FocusControlProvider.js +packages/app-mobile/components/accessibility/FocusControl/MainAppContent.js +packages/app-mobile/components/accessibility/FocusControl/ModalWrapper.js +packages/app-mobile/components/accessibility/FocusControl/types.js packages/app-mobile/components/app-nav.js packages/app-mobile/components/base-screen.js packages/app-mobile/components/biometrics/BiometricPopup.js @@ -693,6 +711,7 @@ packages/app-mobile/components/biometrics/biometricAuthenticate.js packages/app-mobile/components/biometrics/sensorInfo.js packages/app-mobile/components/buttons/FloatingActionButton.js packages/app-mobile/components/buttons/MultiTouchableOpacity.js +packages/app-mobile/components/buttons/LabelledIconButton.js packages/app-mobile/components/buttons/TextButton.js packages/app-mobile/components/buttons/index.js packages/app-mobile/components/getResponsiveValue.test.js @@ -739,8 +758,11 @@ packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.j packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.js packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js packages/app-mobile/components/screens/ConfigScreen/SettingItem.js +packages/app-mobile/components/screens/ConfigScreen/SettingTextInput.js packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js +packages/app-mobile/components/screens/ConfigScreen/ValidatedIntegerInput.test.js +packages/app-mobile/components/screens/ConfigScreen/ValidatedIntegerInput.js packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js @@ -780,7 +802,9 @@ packages/app-mobile/components/screens/Note/commands/setTags.js packages/app-mobile/components/screens/Note/commands/toggleVisiblePanes.js packages/app-mobile/components/screens/Note/types.js packages/app-mobile/components/screens/NoteTagsDialog.js -packages/app-mobile/components/screens/Notes.js +packages/app-mobile/components/screens/Notes/NewNoteButton.test.js +packages/app-mobile/components/screens/Notes/NewNoteButton.js +packages/app-mobile/components/screens/Notes/Notes.js packages/app-mobile/components/screens/SearchScreen/SearchResults.js packages/app-mobile/components/screens/SearchScreen/index.js packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.js @@ -835,6 +859,7 @@ packages/app-mobile/utils/createRootStyle.js packages/app-mobile/utils/database-driver-react-native.js packages/app-mobile/utils/database-driver-react-native.web.js packages/app-mobile/utils/debounce.js +packages/app-mobile/utils/focusView.js packages/app-mobile/utils/fs-driver/constants.js packages/app-mobile/utils/fs-driver/fs-driver-rn.js packages/app-mobile/utils/fs-driver/fs-driver-rn.web.js @@ -847,9 +872,10 @@ 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/useKeyboardVisible.js +packages/app-mobile/utils/hooks/useKeyboardState.js packages/app-mobile/utils/hooks/useOnLongPressProps.js packages/app-mobile/utils/hooks/useReduceMotionEnabled.js +packages/app-mobile/utils/hooks/useSafeAreaPadding.js packages/app-mobile/utils/image/fileToImage.web.js packages/app-mobile/utils/image/getImageDimensions.js packages/app-mobile/utils/image/resizeImage.js @@ -1027,6 +1053,8 @@ packages/lib/commands/renderMarkup.test.js packages/lib/commands/renderMarkup.js packages/lib/commands/showEditorPlugin.js packages/lib/commands/synchronize.js +packages/lib/commands/toggleAllFolders.test.js +packages/lib/commands/toggleAllFolders.js packages/lib/commands/toggleEditorPlugin.js packages/lib/components/EncryptionConfigScreen/utils.test.js packages/lib/components/EncryptionConfigScreen/utils.js @@ -1063,6 +1091,8 @@ packages/lib/fs-driver-base.js packages/lib/fs-driver-node.js packages/lib/fsDriver.test.js packages/lib/geolocation-node.js +packages/lib/getAppName.test.js +packages/lib/getAppName.js packages/lib/hooks/useAsyncEffect.js packages/lib/hooks/useElementSize.js packages/lib/hooks/useEventListener.js @@ -1121,6 +1151,9 @@ packages/lib/models/settings/builtInMetadata.js packages/lib/models/settings/settingValidations.test.js packages/lib/models/settings/settingValidations.js packages/lib/models/settings/types.js +packages/lib/models/utils/areAllFoldersCollapsed.test.js +packages/lib/models/utils/areAllFoldersCollapsed.js +packages/lib/models/utils/getCanBeCollapsedFolderIds.js packages/lib/models/utils/getCollator.js packages/lib/models/utils/getConflictFolderId.js packages/lib/models/utils/isItemId.js diff --git a/.eslintrc.js b/.eslintrc.js index 2f8f2c9def..3aeb75a9fc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -57,6 +57,8 @@ module.exports = { 'tinymce': 'readonly', 'JSX': 'readonly', + + 'NodeJS': 'readonly', }, 'parserOptions': { 'ecmaVersion': 2018, @@ -309,7 +311,7 @@ module.exports = { selector: 'interface', format: null, 'filter': { - 'regex': '^(RSA|RSAKeyPair)$', + 'regex': '^(RSA|RSAKeyPair|iOS.*)$', 'match': true, }, }, diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 65349818fa..b02e16a9dc 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ -blank_issues_enabled: false +blank_issues_enabled: true contact_links: - name: Feature Requests url: https://discourse.joplinapp.org/c/features/ about: Discuss ideas for new features or changes - name: Support url: https://discourse.joplinapp.org/c/support/ - about: Please ask for help here \ No newline at end of file + about: Please ask for help here diff --git a/.github/scripts/publish_docker_manifest.sh b/.github/scripts/publish_docker_manifest.sh new file mode 100644 index 0000000000..3a02135484 --- /dev/null +++ b/.github/scripts/publish_docker_manifest.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +VERSION=$(echo "$GIT_TAG_NAME" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + +echo "GIT_TAG_NAME=$GIT_TAG_NAME" +echo "VERSION=$VERSION" +echo "SERVER_TAG_PREFIX=$SERVER_TAG_PREFIX" +echo "SERVER_REPOSITORY=$SERVER_REPOSITORY" + +# Check if it's a server release, otherwise exit +if [[ $GIT_TAG_NAME != $SERVER_TAG_PREFIX-* ]]; then + exit 0 +fi + +docker manifest inspect $SERVER_REPOSITORY:arm64-$VERSION > /dev/null 2>&1 +if [ $? -ne 0 ]; then + echo "Image $SERVER_REPOSITORY:arm64-$VERSION does not exist on the remote registry." + exit 0 +fi + +docker manifest inspect $SERVER_REPOSITORY:amd64-$VERSION > /dev/null 2>&1 +if [ $? -ne 0 ]; then + echo "Image $SERVER_REPOSITORY:amd64-$VERSION does not exist on the remote registry." + exit 0 +fi + +docker manifest create $SERVER_REPOSITORY:$VERSION \ + $SERVER_REPOSITORY:arm64-$VERSION \ + $SERVER_REPOSITORY:amd64-$VERSION + +docker manifest annotate $SERVER_REPOSITORY:$VERSION $SERVER_REPOSITORY:arm64-$VERSION --arch arm64 +docker manifest annotate $SERVER_REPOSITORY:$VERSION $SERVER_REPOSITORY:amd64-$VERSION --arch amd64 + +docker manifest push $SERVER_REPOSITORY:$VERSION diff --git a/.github/scripts/run_ci.sh b/.github/scripts/run_ci.sh index e644c3bda3..5fddbe12c2 100755 --- a/.github/scripts/run_ci.sh +++ b/.github/scripts/run_ci.sh @@ -35,6 +35,8 @@ else IS_MACOS=1 fi +DOCKER_IMAGE_PLATFORM="linux/amd64" + # Tests can randomly fail in some cases, so only run them when not publishing # a release RUN_TESTS=0 @@ -43,10 +45,33 @@ if [ "$IS_SERVER_RELEASE" = 0 ] && [ "$IS_DESKTOP_RELEASE" = 0 ]; then RUN_TESTS=1 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 + # Canvas is only needed for tests and it doesn't build in ARM64 so remove it + RUN_TESTS=0 + cd "$ROOT_DIR/packages/lib" + yarn remove canvas + cd "$ROOT_DIR" + + DOCKER_IMAGE_PLATFORM="linux/arm64" + + # Delete certain directories because `yarn install` will fail on ARM64. + rm -rf app-desktop + rm -rf app-mobile +fi + # ============================================================================= # Print environment # ============================================================================= +echo "RUNNER_OS=$RUNNER_OS" +echo "RUNNER_ARCH=$RUNNER_ARCH" echo "GITHUB_WORKFLOW=$GITHUB_WORKFLOW" echo "GITHUB_EVENT_NAME=$GITHUB_EVENT_NAME" echo "GITHUB_REF=$GITHUB_REF" @@ -55,6 +80,7 @@ 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 "DOCKER_IMAGE_PLATFORM=$DOCKER_IMAGE_PLATFORM" echo "IS_CONTINUOUS_INTEGRATION=$IS_CONTINUOUS_INTEGRATION" echo "IS_PULL_REQUEST=$IS_PULL_REQUEST" @@ -277,7 +303,7 @@ if [ "$IS_DESKTOP_RELEASE" == "1" ]; then elif [[ $IS_LINUX = 1 ]] && [ "$IS_SERVER_RELEASE" == "1" ]; then echo "Step: Building Docker Image..." cd "$ROOT_DIR" - yarn buildServerDocker --tag-name $GIT_TAG_NAME --push-images --repository $SERVER_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..." diff --git a/.github/workflows/github-actions-main.yml b/.github/workflows/github-actions-main.yml index ca2e938271..6c918bc53c 100644 --- a/.github/workflows/github-actions-main.yml +++ b/.github/workflows/github-actions-main.yml @@ -9,47 +9,12 @@ jobs: matrix: # Do not use unbuntu-latest because it causes `The operation was canceled` failures: # https://github.com/actions/runner-images/issues/6709 - os: [macos-13, ubuntu-20.04, windows-2019] + os: [macos-13, ubuntu-22.04, windows-2019, ubuntu-22.04-arm] steps: + - uses: actions/checkout@v4 - # Trying to fix random networking issues on Windows - # https://github.com/actions/runner-images/issues/1187#issuecomment-686735760 - - name: Disable TCP/UDP offload on Windows - if: runner.os == 'Windows' - run: Disable-NetAdapterChecksumOffload -Name * -TcpIPv4 -UdpIPv4 -TcpIPv6 -UdpIPv6 - - - name: Disable TCP/UDP offload on Linux - if: runner.os == 'Linux' - run: sudo ethtool -K eth0 tx off rx off - - - name: Disable TCP/UDP offload on macOS - if: runner.os == 'macOS' - run: | - sudo sysctl -w net.link.generic.system.hwcksum_tx=0 - sudo sysctl -w net.link.generic.system.hwcksum_rx=0 - - # Silence apt-get update errors (for example when a module doesn't - # exist) since otherwise it will make the whole build fails, even though - # it might work without update. libsecret-1-dev is required for keytar - - # https://github.com/atom/node-keytar - - - name: Install Linux dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update || true - sudo apt-get install -y gettext - sudo apt-get install -y libsecret-1-dev - sudo apt-get install -y translate-toolkit - sudo apt-get install -y rsync - # Provides a virtual display on Linux. Used for Playwright integration - # testing. - sudo apt-get install -y xvfb - - - name: Install macOs dependencies - if: runner.os == 'macOS' - run: | - # Required for building the canvas package - brew install pango + - name: Setup build environment + uses: ./.github/workflows/shared/setup-build-environment - name: Install Docker Engine # if: runner.os == 'Linux' && startsWith(github.ref, 'refs/tags/server-v') @@ -62,26 +27,11 @@ jobs: sudo apt-get install -y lsb-release curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo \ - "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ + "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update || true - sudo apt-get install -y docker-ce docker-ce-cli containerd.io + sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin - - uses: actions/checkout@v4 - - uses: olegtarasov/get-tag@v2.1.3 - - uses: dtolnay/rust-toolchain@stable - - uses: actions/setup-node@v4 - 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.15.0' - cache: 'yarn' - - - name: Install Yarn - run: | - # https://yarnpkg.com/getting-started/install - corepack enable - # Login to Docker only if we're on a server release tag. If we run this on # a pull request it will fail because the PR doesn't have access to # secrets @@ -91,15 +41,6 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - # macos-latest ships with Python 3.12 by default, but this removes a - # utility that's used by electron-builder (distutils) so we need to pin - # Python to an earlier version. - # Fixes error `ModuleNotFoundError: No module named 'distutils'` - # Ref: https://github.com/nodejs/node-gyp/issues/2869 - - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: Run tests, build and publish Linux and macOS apps if: runner.os == 'Linux' || runner.os == 'macOs' env: @@ -143,6 +84,15 @@ jobs: run: | yarn install && cd packages/app-desktop && yarn dist --publish=never + - name: Publish Docker manifest + if: runner.os == 'Linux' + env: + SERVER_REPOSITORY: joplin/server + SERVER_TAG_PREFIX: server + run: | + chmod 700 "${GITHUB_WORKSPACE}/.github/scripts/publish_docker_manifest.sh" + "${GITHUB_WORKSPACE}/.github/scripts/publish_docker_manifest.sh" + ServerDockerImage: if: github.repository == 'laurent22/joplin' runs-on: ${{ matrix.os }} @@ -150,7 +100,7 @@ jobs: matrix: # Do not use unbuntu-latest because it causes `The operation was canceled` failures: # https://github.com/actions/runner-images/issues/6709 - os: [ubuntu-20.04] + os: [ubuntu-22.04, ubuntu-22.04-arm] steps: - name: Install Docker Engine @@ -162,10 +112,10 @@ jobs: sudo apt-get install -y lsb-release curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo \ - "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ + "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update || true - sudo apt-get install -y docker-ce docker-ce-cli containerd.io + sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin - uses: actions/checkout@v4 @@ -183,17 +133,30 @@ jobs: env: BUILD_SEQUENCIAL: 1 run: | + if [ "$RUNNER_ARCH" == "ARM64" ]; then + DOCKER_IMAGE_PLATFORM="linux/arm64" + fi + + echo "RUNNER_OS=$RUNNER_OS" + echo "RUNNER_ARCH=$RUNNER_ARCH" + echo "DOCKER_IMAGE_PLATFORM=$DOCKER_IMAGE_PLATFORM" + + # Canvas is only needed for tests and it doesn't build in ARM64 so remove it + cd packages/lib + yarn remove canvas + cd ../.. + yarn install - yarn buildServerDocker --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. - docker run joplin/server:0.0.0-beta node dist/app.js migrate list + # code 0 if it works. + docker run joplin/server:$(dpkg --print-architecture)-0.0.0 node dist/app.js migrate list - name: Check HTTP request run: | # Need to pass environment variables: - docker run -p 22300:22300 joplin/server:0.0.0-beta node dist/app.js --env dev & + 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 30 diff --git a/.github/workflows/shared/setup-build-environment/action.yml b/.github/workflows/shared/setup-build-environment/action.yml new file mode 100644 index 0000000000..b9b6b08ed0 --- /dev/null +++ b/.github/workflows/shared/setup-build-environment/action.yml @@ -0,0 +1,72 @@ +name: 'Setup build environment' +description: 'Install Joplin build dependencies' +runs: + using: 'composite' + steps: + # Trying to fix random networking issues on Windows + # https://github.com/actions/runner-images/issues/1187#issuecomment-686735760 + - name: Disable TCP/UDP offload on Windows + if: runner.os == 'Windows' + shell: pwsh + run: Disable-NetAdapterChecksumOffload -Name * -TcpIPv4 -UdpIPv4 -TcpIPv6 -UdpIPv6 + + - name: Disable TCP/UDP offload on Linux + if: runner.os == 'Linux' + shell: bash + run: sudo ethtool -K eth0 tx off rx off + + - name: Disable TCP/UDP offload on macOS + if: runner.os == 'macOS' + shell: bash + run: | + sudo sysctl -w net.link.generic.system.hwcksum_tx=0 + sudo sysctl -w net.link.generic.system.hwcksum_rx=0 + + # Silence apt-get update errors (for example when a module doesn't + # exist) since otherwise it will make the whole build fails, even though + # it might work without update. libsecret-1-dev is required for keytar - + # https://github.com/atom/node-keytar + + - name: Install Linux dependencies + if: runner.os == 'Linux' + shell: bash + run: | + sudo apt-get update || true + sudo apt-get install -y gettext + sudo apt-get install -y libsecret-1-dev + sudo apt-get install -y translate-toolkit + sudo apt-get install -y rsync + # Provides a virtual display on Linux. Used for Playwright integration + # testing. + sudo apt-get install -y xvfb + + - name: Install macOs dependencies + if: runner.os == 'macOS' + shell: bash + run: | + # Required for building the canvas package + brew install pango + + - uses: olegtarasov/get-tag@v2.1.3 + - uses: dtolnay/rust-toolchain@stable + - uses: actions/setup-node@v4 + 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.15.0' + cache: 'yarn' + + - name: Install Yarn + shell: bash + run: | + # https://yarnpkg.com/getting-started/install + corepack enable + + # macos-latest ships with Python 3.12 by default, but this removes a + # utility that's used by electron-builder (distutils) so we need to pin + # Python to an earlier version. + # Fixes error `ModuleNotFoundError: No module named 'distutils'` + # Ref: https://github.com/nodejs/node-gyp/issues/2869 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' \ No newline at end of file diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml new file mode 100644 index 0000000000..0f31586d84 --- /dev/null +++ b/.github/workflows/ui-tests.yml @@ -0,0 +1,29 @@ +name: Joplin UI tests +on: [push, pull_request] +permissions: + contents: read +jobs: + Main: + # Don't run on forks + if: github.repository == 'laurent22/joplin' + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-13, ubuntu-22.04] + steps: + - uses: actions/checkout@v4 + - name: Setup build environment + uses: ./.github/workflows/shared/setup-build-environment + - name: Build + run: yarn install + - name: Run UI tests + run: | + cd ${GITHUB_WORKSPACE}/packages/app-desktop/ + bash ./integration-tests/run-ci.sh + # See https://playwright.dev/docs/ci-intro#setting-up-github-actions + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report-${{ matrix.os }} + path: packages/app-desktop/playwright-report/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index ac49132089..f8c1c4895a 100644 --- a/.gitignore +++ b/.gitignore @@ -134,7 +134,9 @@ packages/app-desktop/commands/exportNotes.js packages/app-desktop/commands/focusElement.js packages/app-desktop/commands/index.js packages/app-desktop/commands/openNoteInNewWindow.js +packages/app-desktop/commands/openPrimaryAppInstance.js packages/app-desktop/commands/openProfileDirectory.js +packages/app-desktop/commands/openSecondaryAppInstance.js packages/app-desktop/commands/replaceMisspelling.js packages/app-desktop/commands/restoreNoteRevision.js packages/app-desktop/commands/startExternalEditing.js @@ -239,6 +241,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useKeyboardRefocusHan packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTabIndenter.js +packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTextPatternsLookup.js packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js packages/app-desktop/gui/NoteEditor/NoteEditor.js packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js @@ -247,6 +250,7 @@ packages/app-desktop/gui/NoteEditor/WarningBanner/BannerContent.js packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.js packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.js +packages/app-desktop/gui/NoteEditor/commands/focusElementNoteViewer.js packages/app-desktop/gui/NoteEditor/commands/focusElementToolbar.js packages/app-desktop/gui/NoteEditor/commands/index.js packages/app-desktop/gui/NoteEditor/commands/pasteAsText.js @@ -328,13 +332,16 @@ packages/app-desktop/gui/NoteSearchBar.js packages/app-desktop/gui/NoteStatusBar.js packages/app-desktop/gui/NoteTextViewer.js packages/app-desktop/gui/NoteToolbar/NoteToolbar.js -packages/app-desktop/gui/NotyfContext.js packages/app-desktop/gui/OneDriveLoginScreen.js packages/app-desktop/gui/PasswordInput/LabelledPasswordInput.js packages/app-desktop/gui/PasswordInput/PasswordInput.js packages/app-desktop/gui/PasswordInput/types.js packages/app-desktop/gui/PdfViewer.js packages/app-desktop/gui/PluginNotification/PluginNotification.js +packages/app-desktop/gui/PopupNotification/NotificationItem.js +packages/app-desktop/gui/PopupNotification/PopupNotificationList.js +packages/app-desktop/gui/PopupNotification/PopupNotificationProvider.js +packages/app-desktop/gui/PopupNotification/types.js packages/app-desktop/gui/PromptDialog.js packages/app-desktop/gui/ResizableLayout/LayoutItemContainer.js packages/app-desktop/gui/ResizableLayout/MoveButtons.js @@ -396,6 +403,7 @@ packages/app-desktop/gui/ToolbarBase.js packages/app-desktop/gui/ToolbarButton/ToolbarButton.js packages/app-desktop/gui/ToolbarSpace.js packages/app-desktop/gui/TrashNotification/TrashNotification.js +packages/app-desktop/gui/TrashNotification/TrashNotificationMessage.js packages/app-desktop/gui/UpdateNotification/UpdateNotification.js packages/app-desktop/gui/WindowCommandsAndDialogs/AppDialogs.js packages/app-desktop/gui/WindowCommandsAndDialogs/ModalMessageOverlay.js @@ -522,13 +530,16 @@ packages/app-desktop/services/plugins/UserWebview.js packages/app-desktop/services/plugins/UserWebviewDialog.js packages/app-desktop/services/plugins/UserWebviewDialogButtonBar.js packages/app-desktop/services/plugins/hooks/useContentSize.js +packages/app-desktop/services/plugins/hooks/useFormData.js packages/app-desktop/services/plugins/hooks/useHtmlLoader.js +packages/app-desktop/services/plugins/hooks/useMessageHandler.js packages/app-desktop/services/plugins/hooks/useScriptLoader.js packages/app-desktop/services/plugins/hooks/useSubmitHandler.js packages/app-desktop/services/plugins/hooks/useThemeCss.test.js packages/app-desktop/services/plugins/hooks/useThemeCss.js packages/app-desktop/services/plugins/hooks/useViewIsReady.js packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js +packages/app-desktop/services/plugins/types.js packages/app-desktop/services/restart.js packages/app-desktop/services/sortOrder/PerFolderSortOrderService.test.js packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js @@ -556,6 +567,7 @@ packages/app-desktop/utils/restartInSafeModeFromMain.test.js packages/app-desktop/utils/restartInSafeModeFromMain.js packages/app-desktop/utils/window/types.js packages/app-mobile/PluginAssetsLoader.js +packages/app-mobile/commands/dismissPluginPanels.js packages/app-mobile/commands/index.js packages/app-mobile/commands/newNote.test.js packages/app-mobile/commands/newNote.js @@ -565,6 +577,7 @@ packages/app-mobile/commands/scrollToHash.js packages/app-mobile/commands/util/goToNote.js packages/app-mobile/commands/util/showResource.js packages/app-mobile/components/BetaChip.js +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 @@ -658,9 +671,14 @@ packages/app-mobile/components/SelectDateTimeDialog.js packages/app-mobile/components/SideMenu.js packages/app-mobile/components/SideMenuContentNote.js packages/app-mobile/components/TextInput.js -packages/app-mobile/components/ToggleSpaceButton.js -packages/app-mobile/components/accessibility/AccessibleModalMenu.js +packages/app-mobile/components/accessibility/AccessibleView.test.js packages/app-mobile/components/accessibility/AccessibleView.js +packages/app-mobile/components/accessibility/FocusControl/AutoFocusProvider.js +packages/app-mobile/components/accessibility/FocusControl/FocusControl.js +packages/app-mobile/components/accessibility/FocusControl/FocusControlProvider.js +packages/app-mobile/components/accessibility/FocusControl/MainAppContent.js +packages/app-mobile/components/accessibility/FocusControl/ModalWrapper.js +packages/app-mobile/components/accessibility/FocusControl/types.js packages/app-mobile/components/app-nav.js packages/app-mobile/components/base-screen.js packages/app-mobile/components/biometrics/BiometricPopup.js @@ -668,6 +686,7 @@ packages/app-mobile/components/biometrics/biometricAuthenticate.js packages/app-mobile/components/biometrics/sensorInfo.js packages/app-mobile/components/buttons/FloatingActionButton.js packages/app-mobile/components/buttons/MultiTouchableOpacity.js +packages/app-mobile/components/buttons/LabelledIconButton.js packages/app-mobile/components/buttons/TextButton.js packages/app-mobile/components/buttons/index.js packages/app-mobile/components/getResponsiveValue.test.js @@ -714,8 +733,11 @@ packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.j packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.js packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js packages/app-mobile/components/screens/ConfigScreen/SettingItem.js +packages/app-mobile/components/screens/ConfigScreen/SettingTextInput.js packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js +packages/app-mobile/components/screens/ConfigScreen/ValidatedIntegerInput.test.js +packages/app-mobile/components/screens/ConfigScreen/ValidatedIntegerInput.js packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.js packages/app-mobile/components/screens/ConfigScreen/plugins/InstalledPluginBox.js @@ -755,7 +777,9 @@ packages/app-mobile/components/screens/Note/commands/setTags.js packages/app-mobile/components/screens/Note/commands/toggleVisiblePanes.js packages/app-mobile/components/screens/Note/types.js packages/app-mobile/components/screens/NoteTagsDialog.js -packages/app-mobile/components/screens/Notes.js +packages/app-mobile/components/screens/Notes/NewNoteButton.test.js +packages/app-mobile/components/screens/Notes/NewNoteButton.js +packages/app-mobile/components/screens/Notes/Notes.js packages/app-mobile/components/screens/SearchScreen/SearchResults.js packages/app-mobile/components/screens/SearchScreen/index.js packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.js @@ -810,6 +834,7 @@ packages/app-mobile/utils/createRootStyle.js packages/app-mobile/utils/database-driver-react-native.js packages/app-mobile/utils/database-driver-react-native.web.js packages/app-mobile/utils/debounce.js +packages/app-mobile/utils/focusView.js packages/app-mobile/utils/fs-driver/constants.js packages/app-mobile/utils/fs-driver/fs-driver-rn.js packages/app-mobile/utils/fs-driver/fs-driver-rn.web.js @@ -822,9 +847,10 @@ 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/useKeyboardVisible.js +packages/app-mobile/utils/hooks/useKeyboardState.js packages/app-mobile/utils/hooks/useOnLongPressProps.js packages/app-mobile/utils/hooks/useReduceMotionEnabled.js +packages/app-mobile/utils/hooks/useSafeAreaPadding.js packages/app-mobile/utils/image/fileToImage.web.js packages/app-mobile/utils/image/getImageDimensions.js packages/app-mobile/utils/image/resizeImage.js @@ -1002,6 +1028,8 @@ packages/lib/commands/renderMarkup.test.js packages/lib/commands/renderMarkup.js packages/lib/commands/showEditorPlugin.js packages/lib/commands/synchronize.js +packages/lib/commands/toggleAllFolders.test.js +packages/lib/commands/toggleAllFolders.js packages/lib/commands/toggleEditorPlugin.js packages/lib/components/EncryptionConfigScreen/utils.test.js packages/lib/components/EncryptionConfigScreen/utils.js @@ -1038,6 +1066,8 @@ packages/lib/fs-driver-base.js packages/lib/fs-driver-node.js packages/lib/fsDriver.test.js packages/lib/geolocation-node.js +packages/lib/getAppName.test.js +packages/lib/getAppName.js packages/lib/hooks/useAsyncEffect.js packages/lib/hooks/useElementSize.js packages/lib/hooks/useEventListener.js @@ -1096,6 +1126,9 @@ packages/lib/models/settings/builtInMetadata.js packages/lib/models/settings/settingValidations.test.js packages/lib/models/settings/settingValidations.js packages/lib/models/settings/types.js +packages/lib/models/utils/areAllFoldersCollapsed.test.js +packages/lib/models/utils/areAllFoldersCollapsed.js +packages/lib/models/utils/getCanBeCollapsedFolderIds.js packages/lib/models/utils/getCollator.js packages/lib/models/utils/getConflictFolderId.js packages/lib/models/utils/isItemId.js diff --git a/.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch b/.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch deleted file mode 100644 index 13886e9be1..0000000000 --- a/.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java b/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -index a5bb95eec3337b93a2338a2869a2bda176c91cae..87817688eb280c2f702c26dc35558c6a0a4db1ea 100644 ---- a/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -+++ b/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -@@ -42,12 +42,20 @@ public class ReactSliderManager extends SimpleViewManager implement - public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { - ReactSlider slider = (ReactSlider)seekbar; - -- if(progress < slider.getLowerLimit()) { -- progress = slider.getLowerLimit(); -- seekbar.setProgress(progress); -- } else if (progress > slider.getUpperLimit()) { -- progress = slider.getUpperLimit(); -- seekbar.setProgress(progress); -+ // During initialization, lowerLimit can be greater than upperLimit. -+ // -+ // If a change event is received during this, we need a check to prevent -+ // infinite recursion. -+ // -+ // Issue: https://github.com/callstack/react-native-slider/issues/571 -+ if (slider.getLowerLimit() <= slider.getUpperLimit()) { -+ if(progress < slider.getLowerLimit()) { -+ progress = slider.getLowerLimit(); -+ seekbar.setProgress(progress); -+ } else if (progress > slider.getUpperLimit()) { -+ progress = slider.getUpperLimit(); -+ seekbar.setProgress(progress); -+ } - } - - ReactContext reactContext = (ReactContext) seekbar.getContext(); -diff --git a/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java b/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -index 3ff5930f85a3cd92c2549925f41058abb188a57e..ab3681fdfe0b736c97020e1434e450c8183e6f18 100644 ---- a/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -+++ b/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -@@ -30,12 +30,20 @@ public class ReactSliderManager extends SimpleViewManager { - public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { - ReactSlider slider = (ReactSlider)seekbar; - -- if(progress < slider.getLowerLimit()) { -- progress = slider.getLowerLimit(); -- seekbar.setProgress(progress); -- } else if(progress > slider.getUpperLimit()) { -- progress = slider.getUpperLimit(); -- seekbar.setProgress(progress); -+ // During initialization, lowerLimit can be greater than upperLimit. -+ // -+ // If a change event is received during this, we need a check to prevent -+ // infinite recursion. -+ // -+ // Issue: https://github.com/callstack/react-native-slider/issues/571 -+ if (slider.getLowerLimit() <= slider.getUpperLimit()) { -+ if(progress < slider.getLowerLimit()) { -+ progress = slider.getLowerLimit(); -+ seekbar.setProgress(progress); -+ } else if (progress > slider.getUpperLimit()) { -+ progress = slider.getUpperLimit(); -+ seekbar.setProgress(progress); -+ } - } - - ReactContext reactContext = (ReactContext) seekbar.getContext(); diff --git a/.yarn/patches/react-native-popup-menu-npm-0.16.1-28fd66ecb5.patch b/.yarn/patches/react-native-popup-menu-npm-0.16.1-28fd66ecb5.patch new file mode 100644 index 0000000000..af0442f6d3 --- /dev/null +++ b/.yarn/patches/react-native-popup-menu-npm-0.16.1-28fd66ecb5.patch @@ -0,0 +1,55 @@ +# This patch improves the note actions menu (the kebab menu)'s accessibility +# by labelling its dismiss button. +diff --git a/build/rnpm.js b/build/rnpm.js +index 1111c2de99b3d4c5651ca4eee3ba59c0ce8e13e1..d410ee12b38d02c399b0a40973217da0082d73c0 100644 +--- a/build/rnpm.js ++++ b/build/rnpm.js +@@ -1573,7 +1573,9 @@ + onPress = _this$props.onPress, + style = _this$props.style; + return /*#__PURE__*/React__default.createElement(reactNative.TouchableWithoutFeedback, { +- onPress: onPress ++ onPress: onPress, ++ accessibilityLabel: _this$props.accessibilityLabel, ++ accessibilityRole: 'button', + }, /*#__PURE__*/React__default.createElement(reactNative.Animated.View, { + style: [styles.fullscreen, { + opacity: this.fadeAnim +@@ -1588,7 +1590,8 @@ + }(React.Component); + + Backdrop.propTypes = { +- onPress: propTypes.func.isRequired ++ onPress: propTypes.func.isRequired, ++ accessibilityLabel: propTypes.string, + }; + var styles = reactNative.StyleSheet.create({ + fullscreen: { +@@ -1658,6 +1661,7 @@ + style: styles$1.placeholder + }, /*#__PURE__*/React__default.createElement(Backdrop, { + onPress: ctx._onBackdropPress, ++ accessibilityLabel: this.props.closeButtonLabel, + style: backdropStyles, + ref: ctx.onBackdropRef + }), ctx._makeOptions()); +@@ -2090,6 +2094,7 @@ + }), /*#__PURE__*/React__default.createElement(MenuPlaceholder, { + ctx: this, + backdropStyles: customStyles.backdrop, ++ closeButtonLabel: this.props.closeButtonLabel, + ref: this._onPlaceholderRef + })))); + } +diff --git a/src/index.d.ts b/src/index.d.ts +index 1db1e643a915e4bfb715e33354678ec1be219f50..007157e366d1935368bdd8eff5e7a0773e183d0f 100644 +--- a/src/index.d.ts ++++ b/src/index.d.ts +@@ -18,6 +18,7 @@ declare module "react-native-popup-menu" { + menuProviderWrapper?: StyleProp; + backdrop?: StyleProp; + }; ++ closeButtonLabel: string; + backHandler?: boolean | Function; + skipInstanceCheck?: boolean; + children: React.ReactNode; diff --git a/Assets/JoplinLetterBlue.svg b/Assets/JoplinLetterBlue.svg new file mode 100644 index 0000000000..02a9369957 --- /dev/null +++ b/Assets/JoplinLetterBlue.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Assets/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.ts b/Assets/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.ts index d97845a413..60d97fbff4 100644 --- a/Assets/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.ts +++ b/Assets/TinyMCE/JoplinLists/src/main/ts/actions/ToggleList.ts @@ -48,7 +48,7 @@ const updateListWithDetails = function (dom, el, detail) { }; const removeStyles = (dom, element: HTMLElement, styles: string[]) => { - Tools.each(styles, (style) => dom.setStyle(element, { [style]: '' })); + Tools.each(styles, (style) => dom.setStyle(element, style, '')); }; const getEndPointNode = function (editor, rng, start, root) { diff --git a/Assets/WebsiteAssets/images/sponsors/EssayWriterPro.png b/Assets/WebsiteAssets/images/sponsors/EssayWriterPro.png new file mode 100644 index 0000000000..3840bf5372 Binary files /dev/null and b/Assets/WebsiteAssets/images/sponsors/EssayWriterPro.png differ diff --git a/Assets/WebsiteAssets/images/sponsors/TiktokRise.jpg b/Assets/WebsiteAssets/images/sponsors/TiktokRise.jpg new file mode 100644 index 0000000000..1bffc23fc8 Binary files /dev/null and b/Assets/WebsiteAssets/images/sponsors/TiktokRise.jpg differ diff --git a/Assets/WebsiteAssets/js/script.js b/Assets/WebsiteAssets/js/script.js index f61464fbb4..3b28cf08eb 100644 --- a/Assets/WebsiteAssets/js/script.js +++ b/Assets/WebsiteAssets/js/script.js @@ -80,7 +80,7 @@ async function setupDownloadPage() { if (href.indexOf('-Setup') > 0) downloadLinks['windows'] = href; if (href.indexOf('.dmg') > 0) downloadLinks['macOs'] = href; - if (href.endsWith('arm64.DMG')) downloadLinks['macOsM1'] = href; + if (href.indexOf('arm64.DMG') > 0) downloadLinks['macOsM1'] = href; if (href.indexOf('.AppImage') > 0) downloadLinks['linux'] = href; }); @@ -98,6 +98,8 @@ async function setupDownloadPage() { } else { const os = await getOs(); + console.info('Found OS: ' + os); + if (os === 'macOsUndefined') { // If we don't know which macOS version it is, we let the user choose. $('.main-content .intro').html('

The macOS release is available for Intel processors or for Apple Silicon (M1) processors. Please select your version:

'); diff --git a/Assets/WebsiteAssets/templates/front.mustache b/Assets/WebsiteAssets/templates/front.mustache index c4fd77a860..9e8cfa56ae 100644 --- a/Assets/WebsiteAssets/templates/front.mustache +++ b/Assets/WebsiteAssets/templates/front.mustache @@ -398,7 +398,7 @@
{{#sponsors.orgs}} - + {{/sponsors.orgs}}
diff --git a/Dockerfile.server b/Dockerfile.server index 076da5859a..2ab61b9404 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -7,6 +7,9 @@ FROM node:18 AS builder RUN apt-get update \ && apt-get install -y \ python3 tini \ + # needed for node-canvas for ARM32 platform. + # See also https://github.com/Automattic/node-canvas/wiki/Installation:-Ubuntu-and-other-Debian-based-systems + libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev \ && rm -rf /var/lib/apt/lists/* # Enables Yarn @@ -47,9 +50,9 @@ RUN sed --in-place '/onenote-converter/d' ./packages/lib/package.json # Note that `yarn install` ignores `NODE_ENV=production` and will install dev # dependencies too, but this is fine because we need them to build the app. -RUN BUILD_SEQUENCIAL=1 yarn install --inline-builds \ - && yarn cache clean \ - && rm -rf .yarn/berry +RUN --mount=type=cache,target=/build/.yarn/cache --mount=type=cache,target=/build/.yarn/berry/cache\ + BUILD_SEQUENCIAL=1 yarn config set cacheFolder /build/.yarn/cache \ + && yarn install --inline-builds # ============================================================================= # Final stage - we copy only the relevant files from the build stage and start @@ -81,10 +84,11 @@ CMD ["yarn", "start-prod"] ARG BUILD_DATE ARG REVISION ARG VERSION +ARG SOURCE LABEL org.opencontainers.image.created="$BUILD_DATE" \ org.opencontainers.image.title="Joplin Server" \ org.opencontainers.image.description="Docker image for Joplin Server" \ org.opencontainers.image.url="https://joplinapp.org/" \ org.opencontainers.image.revision="$REVISION" \ - org.opencontainers.image.source="https://github.com/laurent22/joplin.git" \ - org.opencontainers.image.version="${VERSION}" + org.opencontainers.image.source="$SOURCE" \ + org.opencontainers.image.version="$VERSION" diff --git a/Joplin_install_and_update.sh b/Joplin_install_and_update.sh index cffef34372..0cd76d878f 100755 --- a/Joplin_install_and_update.sh +++ b/Joplin_install_and_update.sh @@ -67,10 +67,23 @@ showHelp() { fi } +#----------------------------------------------------- +# Setup Download Helper: DL +#----------------------------------------------------- +if [[ `command -v wget2` ]]; then + DL='wget2 -qO' +elif [[ `command -v wget` ]]; then + DL='wget -qO' +elif [[ `command -v curl` ]]; then + DL='curl -sLo' +else + print "${COLOR_RED}Error: wget2, wget, and curl not found. Please install one of these tools.${COLOR_RESET}" + exit 1 +fi + #----------------------------------------------------- # PARSE ARGUMENTS #----------------------------------------------------- - optspec=":h-:" while getopts "${optspec}" OPT; do [ "${OPT}" = " " ] && continue @@ -140,9 +153,9 @@ fi # Get the latest version to download if [[ "$INCLUDE_PRE_RELEASE" == true ]]; then - RELEASE_VERSION=$(wget -qO - "https://api.github.com/repos/laurent22/joplin/releases" | grep -Po '"tag_name": ?"v\K.*?(?=")' | sort -rV | head -1) + RELEASE_VERSION=$($DL - "https://api.github.com/repos/laurent22/joplin/releases" | grep -Po '"tag_name": ?"v\K.*?(?=")' | sort -rV | head -1) else - RELEASE_VERSION=$(wget -qO - "https://api.github.com/repos/laurent22/joplin/releases/latest" | grep -Po '"tag_name": ?"v\K.*?(?=")') + RELEASE_VERSION=$($DL - "https://api.github.com/repos/laurent22/joplin/releases/latest" | grep -Po '"tag_name": ?"v\K.*?(?=")') fi # Check if it's in the latest version @@ -163,8 +176,8 @@ fi #----------------------------------------------------- print 'Downloading Joplin...' TEMP_DIR=$(mktemp -d) -wget -O "${TEMP_DIR}/Joplin.AppImage" "https://objects.joplinusercontent.com/v${RELEASE_VERSION}/Joplin-${RELEASE_VERSION}.AppImage?source=LinuxInstallScript&type=$DOWNLOAD_TYPE" -wget -O "${TEMP_DIR}/joplin.png" https://joplinapp.org/images/Icon512.png +$DL "${TEMP_DIR}/Joplin.AppImage" "https://objects.joplinusercontent.com/v${RELEASE_VERSION}/Joplin-${RELEASE_VERSION}.AppImage?source=LinuxInstallScript&type=$DOWNLOAD_TYPE" +$DL "${TEMP_DIR}/joplin.png" https://joplinapp.org/images/Icon512.png #----------------------------------------------------- print 'Installing Joplin...' @@ -287,7 +300,7 @@ echo "$RELEASE_VERSION" > "${INSTALL_DIR}/VERSION" #----------------------------------------------------- if [[ "$SHOW_CHANGELOG" == true ]]; then - NOTES=$(wget -qO - https://api.github.com/repos/laurent22/joplin/releases/latest | grep -Po '"body": "\K.*(?=")') + NOTES=$($DL - https://api.github.com/repos/laurent22/joplin/releases/latest | grep -Po '"body": "\K.*(?=")') print "${COLOR_BLUE}Changelog:${COLOR_RESET}\n${NOTES}" fi diff --git a/README.md b/README.md index 5a0fe08e3b..9a9ab7545d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read # Sponsors - EduBirdie web design agency RealGambling.ca write an essay online with EssayPro casino without making any upfront cost + topagency RealGambling.ca write an essay online with EssayPro casino without making any upfront cost Tiktok Rise * * * @@ -42,7 +42,7 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read |
[Akhil-CM](https://github.com/Akhil-CM) |
[andypiper](https://github.com/andypiper) |
[avanderberg](https://github.com/avanderberg) |
[chr15m](https://github.com/chr15m) | |
[felixstorm](https://github.com/felixstorm) |
[Galliver7](https://github.com/Galliver7) |
[Hegghammer](https://github.com/Hegghammer) |
[KentBrockman](https://github.com/KentBrockman) | |
[marcdw1289](https://github.com/marcdw1289) |
[maxtruxa](https://github.com/maxtruxa) |
[sif](https://github.com/sif) |
[taskcruncher](https://github.com/taskcruncher) | -| | | | | +|
[ugoertz](https://github.com/ugoertz) | | | | # Community @@ -50,9 +50,10 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read Name | Description --- | --- [Support Forum](https://discourse.joplinapp.org/) | This is the main place for general discussion about Joplin, user support, software development questions, and to discuss new features. Also where the latest beta versions are released and discussed. +[Patreon page](https://www.patreon.com/joplin) |The latest news are often posted there [Bluesky feed](https://bsky.app/profile/joplinapp.bsky.social) | Follow us on Bluesky [Mastodon feed](https://mastodon.social/@joplinapp) | Follow us on Mastodon -[Patreon page](https://www.patreon.com/joplin) |The latest news are often posted there +[YouTube](https://www.youtube.com/@joplinapp) | Discover information and tutorials on how to use the apps [Discord server](https://discord.gg/VSj7AFHvpq) | Our chat server [LinkedIn](https://www.linkedin.com/company/joplin) | Our LinkedIn page [Lemmy Community](https://sopuli.xyz/c/joplinapp) | Also a good place to get help diff --git a/devbox.json b/devbox.json index 16225415d5..a99a1ab2f9 100644 --- a/devbox.json +++ b/devbox.json @@ -25,7 +25,8 @@ "version": "latest", "excluded_platforms": ["aarch64-darwin", "x86_64-darwin"], }, - "git": "latest", + "git": "latest", + "giflib": "latest", }, "shell": { "init_hook": [ diff --git a/node b/node new file mode 100644 index 0000000000..e69de29bb2 diff --git a/package.json b/package.json index f8a69010ee..d59774c516 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,6 @@ "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", - "@react-native-community/slider": "patch:@react-native-community/slider@npm%3A4.4.4#./.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch", "husky": "patch:husky@npm%3A3.1.0#./.yarn/patches/husky-npm-3.1.0-5cc13e4e34.patch", "chokidar@^2.0.0": "3.5.3", "react-native@0.74.1": "patch:react-native@npm%3A0.74.1#./.yarn/patches/react-native-npm-0.74.1-754c02ae9e.patch", @@ -116,6 +115,7 @@ "app-builder-lib@26.0.0-alpha.7": "patch:app-builder-lib@npm%3A26.0.0-alpha.7#./.yarn/patches/app-builder-lib-npm-26.0.0-alpha.7-e1b3dca119.patch", "app-builder-lib@24.13.3": "patch:app-builder-lib@npm%3A24.13.3#./.yarn/patches/app-builder-lib-npm-24.13.3-86a66c0bf3.patch", "react-native-sqlite-storage@6.0.1": "patch:react-native-sqlite-storage@npm%3A6.0.1#./.yarn/patches/react-native-sqlite-storage-npm-6.0.1-8369d747bd.patch", - "react-native-paper@5.13.1": "patch:react-native-paper@npm%3A5.13.1#./.yarn/patches/react-native-paper-npm-5.13.1-f153e542e2.patch" + "react-native-paper@5.13.1": "patch:react-native-paper@npm%3A5.13.1#./.yarn/patches/react-native-paper-npm-5.13.1-f153e542e2.patch", + "react-native-popup-menu@0.16.1": "patch:react-native-popup-menu@npm%3A0.16.1#./.yarn/patches/react-native-popup-menu-npm-0.16.1-28fd66ecb5.patch" } } diff --git a/packages/app-cli/tests/ocr_samples/low_confidence_testing.png b/packages/app-cli/tests/ocr_samples/low_confidence_testing.png new file mode 100644 index 0000000000..97eff62e77 Binary files /dev/null and b/packages/app-cli/tests/ocr_samples/low_confidence_testing.png differ diff --git a/packages/app-cli/tests/support/onenote/note_with_audio_embedded.zip b/packages/app-cli/tests/support/onenote/note_with_audio_embedded.zip new file mode 100644 index 0000000000..617d09606a Binary files /dev/null and b/packages/app-cli/tests/support/onenote/note_with_audio_embedded.zip differ diff --git a/packages/app-desktop/ElectronAppWrapper.ts b/packages/app-desktop/ElectronAppWrapper.ts index 4a5afd9294..004680dcba 100644 --- a/packages/app-desktop/ElectronAppWrapper.ts +++ b/packages/app-desktop/ElectronAppWrapper.ts @@ -1,11 +1,12 @@ -import Logger, { LoggerWrapper } from '@joplin/utils/Logger'; +import Logger, { LoggerWrapper, TargetType } from '@joplin/utils/Logger'; import { PluginMessage } from './services/plugins/PluginRunner'; import AutoUpdaterService, { defaultUpdateInterval, initialUpdateStartup } from './services/autoUpdater/AutoUpdaterService'; import type ShimType from '@joplin/lib/shim'; const shim: typeof ShimType = require('@joplin/lib/shim').default; import { isCallbackUrl } from '@joplin/lib/callbackUrlUtils'; - -import { BrowserWindow, Tray, WebContents, screen } from 'electron'; +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 } from 'electron'; import bridge from './bridge'; const url = require('url'); const path = require('path'); @@ -19,6 +20,9 @@ import handleCustomProtocols, { CustomProtocolHandler } from './utils/customProt import { clearTimeout, setTimeout } from 'timers'; import { resolve } from 'path'; import { defaultWindowId } from '@joplin/lib/reducer'; +import { msleep, Second } from '@joplin/utils/time'; +import determineBaseAppDirs from '@joplin/lib/determineBaseAppDirs'; +import getAppName from '@joplin/lib/getAppName'; interface RendererProcessQuitReply { canClose: boolean; @@ -34,13 +38,21 @@ interface SecondaryWindowData { electronId: number; } +export interface Options { + env: string; + profilePath: string|null; + isDebugMode: boolean; + isEndToEndTesting: boolean; + initialCallbackUrl: string; +} + export default class ElectronAppWrapper { private logger_: Logger = null; - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - private electronApp_: any; + private electronApp_: App; private env_: string; private isDebugMode_: boolean; private profilePath_: string; + private isEndToEndTesting_: boolean; private win_: BrowserWindow = null; private mainWindowHidden_ = true; @@ -58,13 +70,29 @@ export default class ElectronAppWrapper { private customProtocolHandler_: CustomProtocolHandler = null; private updatePollInterval_: ReturnType|null = null; - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - public constructor(electronApp: any, env: string, profilePath: string|null, isDebugMode: boolean, initialCallbackUrl: string) { + private profileLocker_: FileLocker|null = null; + private ipcServer_: IpcServer|null = null; + private ipcStartPort_ = 2658; + + private ipcLogger_: Logger; + + public constructor(electronApp: App, { env, profilePath, isDebugMode, initialCallbackUrl, isEndToEndTesting }: Options) { this.electronApp_ = electronApp; this.env_ = env; this.isDebugMode_ = isDebugMode; this.profilePath_ = profilePath; this.initialCallbackUrl_ = initialCallbackUrl; + this.isEndToEndTesting_ = isEndToEndTesting; + + this.profileLocker_ = new FileLocker(`${this.profilePath_}/lock`); + + // Note: in certain contexts `this.logger_` doesn't seem to be available, especially for IPC + // calls, either because it hasn't been set or other issue. So we set one here specifically + // for this. + this.ipcLogger_ = new Logger(); + this.ipcLogger_.addTarget(TargetType.File, { + path: `${profilePath}/log-cross-app-ipc.txt`, + }); } public electronApp() { @@ -184,7 +212,6 @@ export default class ElectronAppWrapper { spellcheck: true, enableRemoteModule: true, }, - webviewTag: true, // We start with a hidden window, which is then made visible depending on the showTrayIcon setting // https://github.com/laurent22/joplin/issues/2031 // @@ -410,7 +437,7 @@ export default class ElectronAppWrapper { if (message.target === 'plugin') { const win = this.pluginWindows_[message.pluginId]; if (!win) { - this.logger().error(`Trying to send IPC message to non-existing plugin window: ${message.pluginId}`); + this.ipcLogger_.error(`Trying to send IPC message to non-existing plugin window: ${message.pluginId}`); return; } @@ -465,12 +492,24 @@ export default class ElectronAppWrapper { }); } - public quit() { + private onExit() { this.stopPeriodicUpdateCheck(); + this.profileLocker_.unlockSync(); + + // Probably doesn't matter if the server is not closed cleanly? Thus the lack of `await` + // eslint-disable-next-line promise/prefer-await-to-then -- Needed here because onExit() is not async + void stopServer(this.ipcServer_).catch(_error => { + // Ignore it since we're stopping, and to prevent unnecessary messages. + }); + } + + public quit() { + this.onExit(); this.electronApp_.quit(); } public exit(errorCode = 0) { + this.onExit(); this.electronApp_.exit(errorCode); } @@ -536,20 +575,32 @@ export default class ElectronAppWrapper { this.tray_ = null; } - public ensureSingleInstance() { - if (this.env_ === 'dev') return false; + public async sendCrossAppIpcMessage(message: Message, port: number|null = null, options: SendMessageOptions = null) { + this.ipcLogger_.info('Sending message:', message); - const gotTheLock = this.electronApp_.requestSingleInstanceLock(); + if (port === null) port = this.ipcStartPort_; - if (!gotTheLock) { - // Another instance is already running - exit - this.quit(); - return true; + return await sendMessage(port, { + ...message, + sourcePort: this.ipcServer_.port, + secretKey: this.ipcServer_.secretKey, + }, { + logger: this.ipcLogger_, + ...options, + }); + } + + public async ensureSingleInstance() { + // When end-to-end testing, multiple instances of Joplin are intentionally created at the same time, + // or very close to one another. The single instance handling logic can interfere with this, so disable it. + if (this.isEndToEndTesting_) return false; + + interface OnSecondInstanceMessageData { + profilePath: string; + argv: string[]; } - // Someone tried to open a second instance - focus our window instead - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - this.electronApp_.on('second-instance', (_e: any, argv: string[]) => { + const activateWindow = (argv: string[]) => { const win = this.mainWindow(); if (!win) return; if (win.isMinimized()) win.restore(); @@ -562,13 +613,96 @@ export default class ElectronAppWrapper { void this.openCallbackUrl(url); } } + }; + + const messageHandlers: Record = { + 'onSecondInstance': async (message) => { + const data = message.data as OnSecondInstanceMessageData; + if (data.profilePath === this.profilePath_) activateWindow(data.argv); + }, + + 'restartAltInstance': async (message) => { + if (bridge().altInstanceId()) return false; + + // We do this in a timeout after a short interval because we need this call to + // return the response immediately, so that the caller can call `quit()` + setTimeout(async () => { + const maxWait = 10000; + const interval = 300; + const loopCount = Math.ceil(maxWait / interval); + let callingAppGone = false; + + for (let i = 0; i < loopCount; i++) { + const response = await this.sendCrossAppIpcMessage({ + action: 'ping', + data: null, + secretKey: this.ipcServer_.secretKey, + }, message.sourcePort, { + sendToSpecificPortOnly: true, + }); + + if (!response.length) { + callingAppGone = true; + break; + } + + await msleep(interval); + } + + if (callingAppGone) { + // Wait a bit more because even if the app is not responding, the process + // might still be there for a short while. + await msleep(1000); + this.ipcLogger_.warn('restartAltInstance: App is gone - restarting it'); + void bridge().launchAltAppInstance(this.env()); + } else { + this.ipcLogger_.warn('restartAltInstance: Could not restart calling app because it was still open'); + } + }, 100); + + return true; + }, + + 'ping': async (_message) => { + return true; + }, + }; + + const defaultProfileDir = determineBaseAppDirs('', getAppName(true, this.env() === 'dev'), '').rootProfileDir; + const secretKeyFilePath = `${defaultProfileDir}/ipc_secret_key.txt`; + + this.ipcLogger_.info('Starting server using secret key:', secretKeyFilePath); + + this.ipcServer_ = await startServer(this.ipcStartPort_, secretKeyFilePath, async (message) => { + if (messageHandlers[message.action]) { + this.ipcLogger_.info('Got message:', message); + return messageHandlers[message.action](message); + } + + throw newHttpError(404); + }, { + logger: this.ipcLogger_, }); - return false; - } + // First check that no other app is running from that profile folder + const gotAppLock = await this.profileLocker_.lock(); + if (gotAppLock) return false; - public initializeCustomProtocolHandler(logger: LoggerWrapper) { - this.customProtocolHandler_ ??= handleCustomProtocols(logger); + const message: Message = { + action: 'onSecondInstance', + data: { + senderPort: this.ipcServer_.port, + profilePath: this.profilePath_, + argv: process.argv, + }, + secretKey: this.ipcServer_.secretKey, + }; + + await this.sendCrossAppIpcMessage(message); + + this.quit(); + if (this.env() === 'dev') console.warn(`Closing the application because another instance is already running, or the previous instance was force-quit within the last ${Math.round(this.profileLocker_.options.interval / Second)} seconds.`); + return true; } // Electron's autoUpdater has to be init from the main process @@ -606,9 +740,10 @@ export default class ElectronAppWrapper { // the "ready" event. So we use the function below to make sure that the app is ready. await this.waitForElectronAppReady(); - const alreadyRunning = this.ensureSingleInstance(); + const alreadyRunning = await this.ensureSingleInstance(); if (alreadyRunning) return; + this.customProtocolHandler_ = handleCustomProtocols(); this.createWindow(); this.electronApp_.on('before-quit', () => { diff --git a/packages/app-desktop/app.ts b/packages/app-desktop/app.ts index 803a6df158..1b3d75c01d 100644 --- a/packages/app-desktop/app.ts +++ b/packages/app-desktop/app.ts @@ -456,9 +456,6 @@ class Application extends BaseApplication { bridge().openDevTools(); } - bridge().electronApp().initializeCustomProtocolHandler( - Logger.create('handleCustomProtocols'), - ); this.protocolHandler_ = bridge().electronApp().getCustomProtocolHandler(); this.protocolHandler_.allowReadAccessToDirectory(__dirname); // App bundle directory this.protocolHandler_.allowReadAccessToDirectory(Setting.value('cacheDir')); @@ -617,10 +614,11 @@ class Application extends BaseApplication { 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 (Setting.value('clipperServer.autoStart')) { + if (ClipperServer.instance().enabled() && Setting.value('clipperServer.autoStart')) { void ClipperServer.instance().start(); } diff --git a/packages/app-desktop/bridge.ts b/packages/app-desktop/bridge.ts index 2c73b84e4a..7439a9f688 100644 --- a/packages/app-desktop/bridge.ts +++ b/packages/app-desktop/bridge.ts @@ -6,7 +6,6 @@ import { dirname, toSystemSlashes } from '@joplin/lib/path-utils'; import { fileUriToPath } from '@joplin/utils/url'; import { urlDecode } from '@joplin/lib/string-utils'; import * as Sentry from '@sentry/electron/main'; -import { ErrorEvent } from '@sentry/types/types'; import { homedir } from 'os'; import { msleep } from '@joplin/utils/time'; import { pathExists, pathExistsSync, writeFileSync } from 'fs-extra'; @@ -15,6 +14,7 @@ import isSafeToOpen from './utils/isSafeToOpen'; import { closeSync, openSync, readSync, statSync } from 'fs'; import { KB } from '@joplin/utils/bytes'; import { defaultWindowId } from '@joplin/lib/reducer'; +import { execCommand } from '@joplin/utils'; interface LastSelectedPath { file: string; @@ -43,16 +43,18 @@ export class Bridge { private appName_: string; private appId_: string; private logFilePath_ = ''; + private altInstanceId_ = ''; private extraAllowedExtensions_: string[] = []; private onAllowedExtensionsChangeListener_: OnAllowedExtensionsChange = ()=>{}; - public constructor(electronWrapper: ElectronAppWrapper, appId: string, appName: string, rootProfileDir: string, autoUploadCrashDumps: boolean) { + public constructor(electronWrapper: ElectronAppWrapper, appId: string, appName: string, rootProfileDir: string, autoUploadCrashDumps: boolean, altInstanceId: string) { this.electronWrapper_ = electronWrapper; this.appId_ = appId; this.appName_ = appName; this.rootProfileDir_ = rootProfileDir; this.autoUploadCrashDumps_ = autoUploadCrashDumps; + this.altInstanceId_ = altInstanceId; this.lastSelectedPaths_ = { file: null, directory: null, @@ -98,9 +100,9 @@ export class Bridge { if (logAttachment) hint.attachments = [logAttachment]; const date = (new Date()).toISOString().replace(/[:-]/g, '').split('.')[0]; - interface ErrorEventWithLog extends ErrorEvent { + type ErrorEventWithLog = (typeof event) & { log: string[]; - } + }; const errorEventWithLog: ErrorEventWithLog = { ...event, @@ -120,6 +122,10 @@ export class Bridge { }, integrations: [Sentry.electronMinidumpIntegration()], + + // Using the default ipcMode value causes ; } diff --git a/packages/app-desktop/services/plugins/UserWebviewDialog.tsx b/packages/app-desktop/services/plugins/UserWebviewDialog.tsx index 6c74eb419d..8d7b86b6db 100644 --- a/packages/app-desktop/services/plugins/UserWebviewDialog.tsx +++ b/packages/app-desktop/services/plugins/UserWebviewDialog.tsx @@ -46,9 +46,9 @@ export default function UserWebviewDialog(props: Props) { const buttons: ButtonSpec[] = (props.buttons ? props.buttons : defaultButtons()).map((b: ButtonSpec) => { return { ...b, - onClick: () => { + onClick: async () => { const response: DialogResult = { id: b.id }; - const formData = webviewRef.current.formData(); + const formData = await webviewRef.current.formData(); if (formData && Object.keys(formData).length) response.formData = formData; viewController().closeWithResponse(response); }, diff --git a/packages/app-desktop/services/plugins/UserWebviewIndex.js b/packages/app-desktop/services/plugins/UserWebviewIndex.js index b2d22f8492..8ad9681ea7 100644 --- a/packages/app-desktop/services/plugins/UserWebviewIndex.js +++ b/packages/app-desktop/services/plugins/UserWebviewIndex.js @@ -1,6 +1,43 @@ // This is the API that JS files loaded from the webview can see const webviewApiPromises_ = {}; let viewMessageHandler_ = () => {}; +const postMessage = (message) => { + parent.postMessage(message, '*'); +}; + + +function serializeForm(form) { + const output = {}; + const formData = new FormData(form); + for (const key of formData.keys()) { + output[key] = formData.get(key); + } + return output; +} + +function serializeForms(document) { + const forms = document.getElementsByTagName('form'); + const output = {}; + let untitledIndex = 0; + + for (const form of forms) { + const name = `${form.getAttribute('name')}` || (`form${untitledIndex++}`); + output[name] = serializeForm(form); + } + + return output; +} + +function watchElementSize(element, onChange) { + const emitSizeChange = () => { + onChange(element.getBoundingClientRect()); + }; + const observer = new ResizeObserver(emitSizeChange); + observer.observe(element); + + // Initial size + requestAnimationFrame(emitSizeChange); +} // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars const webviewApi = { @@ -11,7 +48,7 @@ const webviewApi = { webviewApiPromises_[messageId] = { resolve, reject }; }); - window.postMessage({ + postMessage({ target: 'postMessageService.message', message: { from: 'userWebview', @@ -26,7 +63,7 @@ const webviewApi = { onMessage: function(viewMessageHandler) { viewMessageHandler_ = viewMessageHandler; - window.postMessage({ + postMessage({ target: 'postMessageService.registerViewMessageHandler', }); }, @@ -90,14 +127,14 @@ const webviewApi = { window.requestAnimationFrame(() => { // eslint-disable-next-line no-console console.debug('UserWebviewIndex: setting html callback', args.hash); - window.postMessage({ target: 'UserWebview', message: 'htmlIsSet', hash: args.hash }, '*'); + postMessage({ target: 'UserWebview', message: 'htmlIsSet', hash: args.hash }); }); }, setScript: (args) => { const { script, key } = args; - const scriptPath = `file://${script}`; + const scriptPath = `joplin-content://plugin-webview/${script}`; const elementId = `joplin-script-${key}`; if (addedScripts[elementId]) { @@ -114,7 +151,7 @@ const webviewApi = { if (!scripts) return; for (let i = 0; i < scripts.length; i++) { - const scriptPath = `file://${scripts[i]}`; + const scriptPath = `joplin-content://plugin-webview/${scripts[i]}`; if (addedScripts[scriptPath]) continue; addedScripts[scriptPath] = true; @@ -123,6 +160,14 @@ const webviewApi = { } }, + serializeForms: () => { + postMessage({ + target: 'UserWebview', + message: 'serializedForms', + formData: serializeForms(document), + }); + }, + 'postMessageService.response': (event) => { const message = event.message; const promise = webviewApiPromises_[message.responseId]; @@ -171,7 +216,33 @@ const webviewApi = { window.requestAnimationFrame(() => { // eslint-disable-next-line no-console console.debug('UserWebViewIndex: calling isReady'); - window.postMessage({ target: 'UserWebview', message: 'ready' }, '*'); + postMessage({ target: 'UserWebview', message: 'ready' }); + }); + + + const sendFormSubmit = () => { + postMessage({ target: 'UserWebview', message: 'form-submit' }); + }; + const sendDismiss = () => { + postMessage({ target: 'UserWebview', message: 'dismiss' }); + }; + document.addEventListener('submit', () => { + sendFormSubmit(); + }); + document.addEventListener('keydown', event => { + if (event.key === 'Enter' && event.target.tagName === 'INPUT' && event.target.type === 'text') { + sendFormSubmit(); + } else if (event.key === 'Escape') { + sendDismiss(); + } + }); + + watchElementSize(document.getElementById('joplin-plugin-content'), size => { + postMessage({ + target: 'UserWebview', + message: 'updateContentSize', + size, + }); }); }); })(); diff --git a/packages/app-desktop/services/plugins/hooks/useContentSize.ts b/packages/app-desktop/services/plugins/hooks/useContentSize.ts index 54382eb6e1..4a5156f6e9 100644 --- a/packages/app-desktop/services/plugins/hooks/useContentSize.ts +++ b/packages/app-desktop/services/plugins/hooks/useContentSize.ts @@ -1,4 +1,5 @@ -import { useEffect, useState } from 'react'; +import { RefObject, useState } from 'react'; +import useMessageHandler from './useMessageHandler'; interface Size { width: number; @@ -6,59 +7,29 @@ interface Size { hash: string; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied -export default function(frameWindow: any, htmlHash: string, minWidth: number, minHeight: number, fitToContent: boolean, isReady: boolean) { +export default function(viewRef: RefObject, htmlHash: string, minWidth: number, minHeight: number) { const [contentSize, setContentSize] = useState({ width: minWidth, height: minHeight, hash: '', }); - function updateContentSize(hash: string) { - if (!frameWindow) return; - - const rect = frameWindow.document.getElementById('joplin-plugin-content').getBoundingClientRect(); + useMessageHandler(viewRef, event => { + if (event.data.message !== 'updateContentSize') return; + const rect = event.data.size; let w = rect.width; let h = rect.height; if (w < minWidth) w = minWidth; if (h < minHeight) h = minHeight; - const newSize = { width: w, height: h, hash: hash }; + const newSize = { width: w, height: h, hash: htmlHash }; setContentSize((current: Size) => { - if (current.width === newSize.width && current.height === newSize.height && current.hash === hash) return current; + if (current.width === newSize.width && current.height === newSize.height && current.hash === htmlHash) return current; return newSize; }); - } - - useEffect(() => { - updateContentSize(htmlHash); - // eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied - }, [htmlHash]); - - useEffect(() => { - if (!fitToContent || !isReady) return () => {}; - - function onTick() { - updateContentSize(htmlHash); - } - - // The only reliable way to make sure that the iframe has the same dimensions - // as its content is to poll the dimensions at regular intervals. Other methods - // work most of the time but will fail in various edge cases. Most reliable way - // is probably iframe-resizer package, but still with 40 unfixed bugs. - // - // Polling in our case is fine since this is only used when displaying plugin - // dialogs, which should be short lived. updateContentSize() is also optimised - // to do nothing when size hasn't changed. - const updateFrameSizeIID = setInterval(onTick, 100); - - return () => { - clearInterval(updateFrameSizeIID); - }; - // eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied - }, [fitToContent, isReady, minWidth, minHeight, htmlHash]); + }); return contentSize; } diff --git a/packages/app-desktop/services/plugins/hooks/useFormData.ts b/packages/app-desktop/services/plugins/hooks/useFormData.ts new file mode 100644 index 0000000000..337495b5a7 --- /dev/null +++ b/packages/app-desktop/services/plugins/hooks/useFormData.ts @@ -0,0 +1,39 @@ +import { RefObject, useMemo, useRef } from 'react'; +import { PostMessage } from '../types'; +import useMessageHandler from './useMessageHandler'; + +type FormDataRecord = Record; +type FormDataListener = (formData: FormDataRecord)=> void; + +const useFormData = (viewRef: RefObject, postMessage: PostMessage) => { + const formDataListenersRef = useRef([]); + useMessageHandler(viewRef, (event) => { + if (event.data.message === 'serializedForms') { + const formData = event.data.formData; + if (typeof formData !== 'object') { + throw new Error('Invalid formData result.'); + } + + const listeners = [...formDataListenersRef.current]; + formDataListenersRef.current = []; + for (const listener of listeners) { + listener(formData); + } + } + }); + + return useMemo(() => { + return { + getFormData: () => { + return new Promise(resolve => { + postMessage('serializeForms', null); + formDataListenersRef.current.push((data) => { + resolve(data); + }); + }); + }, + }; + }, [postMessage]); +}; + +export default useFormData; diff --git a/packages/app-desktop/services/plugins/hooks/useHtmlLoader.ts b/packages/app-desktop/services/plugins/hooks/useHtmlLoader.ts index 8affdf09a2..a5c439a4d2 100644 --- a/packages/app-desktop/services/plugins/hooks/useHtmlLoader.ts +++ b/packages/app-desktop/services/plugins/hooks/useHtmlLoader.ts @@ -1,41 +1,31 @@ -import { useEffect, useState, useMemo } from 'react'; +import { useEffect, useState, useMemo, RefObject } from 'react'; +import useMessageHandler from './useMessageHandler'; const md5 = require('md5'); -// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any -- Old code before rule was applied, Old code before rule was applied -export default function(frameWindow: any, isReady: boolean, postMessage: Function, html: string) { +// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied, Old code before rule was applied +export default function(viewRef: RefObject, isReady: boolean, postMessage: Function, html: string) { const [loadedHtmlHash, setLoadedHtmlHash] = useState(''); const htmlHash = useMemo(() => { return md5(html); }, [html]); - useEffect(() => { - if (!frameWindow) return () => {}; + useMessageHandler(viewRef, event => { + const data = event.data; - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - function onMessage(event: any) { - const data = event.data; + if (!data || data.target !== 'UserWebview') return; - if (!data || data.target !== 'UserWebview') return; + // eslint-disable-next-line no-console + console.info('useHtmlLoader: message', data); - // eslint-disable-next-line no-console - console.info('useHtmlLoader: message', data); - - // We only update if the HTML that was loaded is the same as - // the active one. Otherwise it means the content has been - // changed between the moment it was set by the user and the - // moment it was loaded in the view. - if (data.message === 'htmlIsSet' && data.hash === htmlHash) { - setLoadedHtmlHash(data.hash); - } + // We only update if the HTML that was loaded is the same as + // the active one. Otherwise it means the content has been + // changed between the moment it was set by the user and the + // moment it was loaded in the view. + if (data.message === 'htmlIsSet' && data.hash === htmlHash) { + setLoadedHtmlHash(data.hash); } - - frameWindow.addEventListener('message', onMessage); - - return () => { - if (frameWindow.removeEventListener) frameWindow.removeEventListener('message', onMessage); - }; - }, [frameWindow, htmlHash]); + }); useEffect(() => { // eslint-disable-next-line no-console diff --git a/packages/app-desktop/services/plugins/hooks/useMessageHandler.ts b/packages/app-desktop/services/plugins/hooks/useMessageHandler.ts new file mode 100644 index 0000000000..7b38da0abd --- /dev/null +++ b/packages/app-desktop/services/plugins/hooks/useMessageHandler.ts @@ -0,0 +1,27 @@ +import { RefObject, useEffect, useRef } from 'react'; + +type OnMessage = (event: MessageEvent)=> void; + +const useMessageHandler = (viewRef: RefObject, onMessage: OnMessage) => { + const onMessageRef = useRef(onMessage); + onMessageRef.current = onMessage; + + useEffect(() => { + function onMessage_(event: MessageEvent) { + if (event.source !== viewRef.current.contentWindow) { + return; + } + + onMessageRef.current(event); + } + + const containerWindow = (viewRef.current.getRootNode() as Document).defaultView; + containerWindow.addEventListener('message', onMessage_); + + return () => { + containerWindow.removeEventListener('message', onMessage_); + }; + }, [viewRef]); +}; + +export default useMessageHandler; diff --git a/packages/app-desktop/services/plugins/hooks/useScriptLoader.ts b/packages/app-desktop/services/plugins/hooks/useScriptLoader.ts index 37874827b8..4008fadb3f 100644 --- a/packages/app-desktop/services/plugins/hooks/useScriptLoader.ts +++ b/packages/app-desktop/services/plugins/hooks/useScriptLoader.ts @@ -1,16 +1,23 @@ -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; +import bridge from '../../bridge'; // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied export default function(postMessage: Function, isReady: boolean, scripts: string[], cssFilePath: string) { - useEffect(() => { - if (!isReady) return; - postMessage('setScripts', { scripts: scripts }); - // eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied - }, [scripts, isReady]); + const protocolHandler = useMemo(() => { + return bridge().electronApp().getCustomProtocolHandler(); + }, []); useEffect(() => { - if (!isReady || !cssFilePath) return; + if (!isReady) return () => {}; + postMessage('setScripts', { scripts: scripts }); + const { remove } = protocolHandler.allowReadAccessToFiles(scripts); + return remove; + }, [scripts, isReady, postMessage, protocolHandler]); + + useEffect(() => { + if (!isReady || !cssFilePath) return () => {}; postMessage('setScript', { script: cssFilePath, key: 'themeCss' }); - // eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied - }, [isReady, cssFilePath]); + const { remove } = protocolHandler.allowReadAccessToFile(cssFilePath); + return remove; + }, [isReady, cssFilePath, postMessage, protocolHandler]); } diff --git a/packages/app-desktop/services/plugins/hooks/useSubmitHandler.ts b/packages/app-desktop/services/plugins/hooks/useSubmitHandler.ts index 49a5b49198..d25eab8470 100644 --- a/packages/app-desktop/services/plugins/hooks/useSubmitHandler.ts +++ b/packages/app-desktop/services/plugins/hooks/useSubmitHandler.ts @@ -1,41 +1,14 @@ -import { useEffect } from 'react'; +import { RefObject } from 'react'; +import useMessageHandler from './useMessageHandler'; // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any -- Old code before rule was applied, Old code before rule was applied -export default function(frameWindow: any, onSubmit: Function, onDismiss: Function, loadedHtmlHash: string) { - const document = frameWindow && frameWindow.document ? frameWindow.document : null; - - useEffect(() => { - if (!document) return () => {}; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - function onFormSubmit(event: any) { - event.preventDefault(); - if (onSubmit) onSubmit(); +export default function(viewRef: RefObject, onSubmit: Function, onDismiss: Function) { + useMessageHandler(viewRef, event => { + const message = event.data?.message; + if (message === 'form-submit') { + onSubmit(); + } else if (message === 'dismiss') { + onDismiss(); } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - function onKeyDown(event: any) { - if (event.key === 'Escape') { - if (onDismiss) onDismiss(); - } - - if (event.key === 'Enter') { - // - // Disable enter key from submitting when a text area is in focus! - // https://github.com/laurent22/joplin/issues/4766 - // - if (document.activeElement.tagName !== 'TEXTAREA') { - if (onSubmit) onSubmit(); - } - } - } - - document.addEventListener('submit', onFormSubmit); - document.addEventListener('keydown', onKeyDown); - - return () => { - if (document) document.removeEventListener('submit', onFormSubmit); - if (document) document.removeEventListener('keydown', onKeyDown); - }; - }, [document, loadedHtmlHash, onSubmit, onDismiss]); + }); } diff --git a/packages/app-desktop/services/plugins/hooks/useViewIsReady.ts b/packages/app-desktop/services/plugins/hooks/useViewIsReady.ts index f3141d84cc..c28997fef3 100644 --- a/packages/app-desktop/services/plugins/hooks/useViewIsReady.ts +++ b/packages/app-desktop/services/plugins/hooks/useViewIsReady.ts @@ -1,7 +1,7 @@ -import { useEffect, useState } from 'react'; +import { RefObject, useEffect, useState } from 'react'; +import useMessageHandler from './useMessageHandler'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied -export default function useViewIsReady(viewRef: any) { +export default function useViewIsReady(viewRef: RefObject) { // Just checking if the iframe is ready is not sufficient because its content // might not be ready (for example, IPC listeners might not be initialised). // So we also listen to a custom "ready" message coming from the webview content @@ -9,6 +9,18 @@ export default function useViewIsReady(viewRef: any) { const [iframeReady, setIFrameReady] = useState(false); const [iframeContentReady, setIFrameContentReady] = useState(false); + useMessageHandler(viewRef, event => { + const data = event.data; + if (!data || data.target !== 'UserWebview') return; + + // eslint-disable-next-line no-console + console.debug('useViewIsReady: message', data); + + if (data.message === 'ready') { + setIFrameContentReady(true); + } + }); + useEffect(() => { // eslint-disable-next-line no-console console.debug('useViewIsReady ============== Setup Listeners'); @@ -19,20 +31,6 @@ export default function useViewIsReady(viewRef: any) { setIFrameReady(true); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - function onMessage(event: any) { - const data = event.data; - - if (!data || data.target !== 'UserWebview') return; - - // eslint-disable-next-line no-console - console.debug('useViewIsReady: message', data); - - if (data.message === 'ready') { - setIFrameContentReady(true); - } - } - const iframeDocument = viewRef.current.contentWindow.document; // eslint-disable-next-line no-console @@ -42,20 +40,15 @@ export default function useViewIsReady(viewRef: any) { onIFrameReady(); } - viewRef.current.addEventListener('dom-ready', onIFrameReady); - viewRef.current.addEventListener('load', onIFrameReady); - viewRef.current.contentWindow.addEventListener('message', onMessage); + const view = viewRef.current; + view.addEventListener('dom-ready', onIFrameReady); + view.addEventListener('load', onIFrameReady); return () => { - if (viewRef.current) { - viewRef.current.removeEventListener('dom-ready', onIFrameReady); - viewRef.current.removeEventListener('load', onIFrameReady); - // eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied - viewRef.current.contentWindow.removeEventListener('message', onMessage); - } + view.removeEventListener('dom-ready', onIFrameReady); + view.removeEventListener('load', onIFrameReady); }; - // eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied - }, []); + }, [viewRef]); return iframeReady && iframeContentReady; } diff --git a/packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.ts b/packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.ts index aacf8758f5..779fc740bc 100644 --- a/packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.ts +++ b/packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.ts @@ -1,8 +1,9 @@ import PostMessageService, { MessageResponse, ResponderComponentType } from '@joplin/lib/services/PostMessageService'; -import { useEffect } from 'react'; +import { RefObject, useEffect } from 'react'; -// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any -- Old code before rule was applied, Old code before rule was applied -export default function(frameWindow: any, isReady: boolean, pluginId: string, viewId: string, windowId: string, postMessage: Function) { + +// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied, Old code before rule was applied +export default function(webviewRef: RefObject, isReady: boolean, pluginId: string, viewId: string, windowId: string, postMessage: Function) { useEffect(() => { PostMessageService.instance().registerResponder(ResponderComponentType.UserWebview, viewId, windowId, (message: MessageResponse) => { postMessage('postMessageService.response', { message }); @@ -15,12 +16,8 @@ export default function(frameWindow: any, isReady: boolean, pluginId: string, vi }, [viewId]); useEffect(() => { - if (!frameWindow) return () => {}; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - function onMessage_(event: any) { - - if (!event.data || !event.data.target) { + function onMessage_(event: MessageEvent) { + if (!event.data || event.source !== webviewRef.current.contentWindow) { return; } @@ -38,11 +35,11 @@ export default function(frameWindow: any, isReady: boolean, pluginId: string, vi } } - frameWindow.addEventListener('message', onMessage_); + const containerWindow = (webviewRef.current.getRootNode() as Document).defaultView; + containerWindow.addEventListener('message', onMessage_); return () => { - if (frameWindow?.removeEventListener) frameWindow.removeEventListener('message', onMessage_); + containerWindow.removeEventListener('message', onMessage_); }; - // eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied - }, [frameWindow, isReady, pluginId, windowId, viewId]); + }, [webviewRef, isReady, pluginId, windowId, viewId, postMessage]); } diff --git a/packages/app-desktop/services/plugins/types.ts b/packages/app-desktop/services/plugins/types.ts new file mode 100644 index 0000000000..f7ed9fc593 --- /dev/null +++ b/packages/app-desktop/services/plugins/types.ts @@ -0,0 +1 @@ +export type PostMessage = (message: string, args: unknown)=> void; diff --git a/packages/app-desktop/services/restart.ts b/packages/app-desktop/services/restart.ts index c268037b13..e505045017 100644 --- a/packages/app-desktop/services/restart.ts +++ b/packages/app-desktop/services/restart.ts @@ -2,9 +2,9 @@ import Setting from '@joplin/lib/models/Setting'; import bridge from './bridge'; -export default async (linuxSafeRestart = true) => { +export default async () => { Setting.setValue('wasClosedSuccessfully', true); await Setting.saveAll(); - bridge().restart(linuxSafeRestart); + await bridge().restart(); }; diff --git a/packages/app-desktop/style.scss b/packages/app-desktop/style.scss index 6a0ddc6966..8638ec29b2 100644 --- a/packages/app-desktop/style.scss +++ b/packages/app-desktop/style.scss @@ -9,7 +9,6 @@ @use 'gui/JoplinCloudLoginScreen.scss' as joplin-cloud-login-screen; @use 'gui/NoteListHeader/style.scss' as note-list-header; @use 'gui/UpdateNotification/style.scss' as update-notification; -@use 'gui/TrashNotification/style.scss' as trash-notification; @use 'gui/Sidebar/style.scss' as sidebar-styles; @use 'gui/NoteEditor/style.scss' as note-editor-styles; @use 'gui/KeymapConfig/style.scss' as keymap-styles; diff --git a/packages/app-desktop/tools/electronRebuild.js b/packages/app-desktop/tools/electronRebuild.js index 455f2775f5..a0ad253844 100644 --- a/packages/app-desktop/tools/electronRebuild.js +++ b/packages/app-desktop/tools/electronRebuild.js @@ -25,7 +25,7 @@ async function main() { // wrong one. However it means it will have to be manually upgraded for each // new Electron release. Some ABI map there: // https://github.com/electron/node-abi/tree/master/test - const forceAbiArgs = '--force-abi 132'; + const forceAbiArgs = '--force-abi 134'; if (isWindows()) { // Cannot run this in parallel, or the 64-bit version might end up diff --git a/packages/app-desktop/utils/customProtocols/constants.ts b/packages/app-desktop/utils/customProtocols/constants.ts index 64ed9ee3e0..6a5853f54a 100644 --- a/packages/app-desktop/utils/customProtocols/constants.ts +++ b/packages/app-desktop/utils/customProtocols/constants.ts @@ -1,3 +1,2 @@ - // eslint-disable-next-line import/prefer-default-export export const contentProtocolName = 'joplin-content'; diff --git a/packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.ts b/packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.ts index a5a488df42..e066a635b6 100644 --- a/packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.ts +++ b/packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.ts @@ -19,7 +19,6 @@ jest.doMock('electron', () => { }; }); -import Logger from '@joplin/utils/Logger'; import handleCustomProtocols from './handleCustomProtocols'; import { supportDir } from '@joplin/lib/testing/test-utils'; import { join } from 'path'; @@ -27,8 +26,7 @@ import { stat } from 'fs-extra'; import { toForwardSlashes } from '@joplin/utils/path'; const setUpProtocolHandler = () => { - const logger = Logger.create('test-logger'); - const protocolHandler = handleCustomProtocols(logger); + const protocolHandler = handleCustomProtocols(); expect(handleProtocolMock).toHaveBeenCalledTimes(1); @@ -56,9 +54,8 @@ const toAccessUrl = (path: string, { host = 'note-viewer' }: ExpectBlockedOption const expectPathToBeBlocked = async (onRequestListener: ProtocolHandler, filePath: string, options?: ExpectBlockedOptions) => { const url = toAccessUrl(filePath, options); - await expect( - async () => await onRequestListener(new Request(url)), - ).rejects.toThrow(/Read access not granted for URL|Invalid or missing media access key|Media access denied/); + const response = await onRequestListener(new Request(url)); + expect(response.status).toBe(403); // Forbidden }; const expectPathToBeUnblocked = async (onRequestListener: ProtocolHandler, filePath: string, options?: ExpectBlockedOptions) => { diff --git a/packages/app-desktop/utils/customProtocols/handleCustomProtocols.ts b/packages/app-desktop/utils/customProtocols/handleCustomProtocols.ts index 354be211ce..6bd1996a0a 100644 --- a/packages/app-desktop/utils/customProtocols/handleCustomProtocols.ts +++ b/packages/app-desktop/utils/customProtocols/handleCustomProtocols.ts @@ -3,7 +3,6 @@ import { dirname, resolve, normalize } from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; import { contentProtocolName } from './constants'; import resolvePathWithinDir from '@joplin/lib/utils/resolvePathWithinDir'; -import { LoggerWrapper } from '@joplin/utils/Logger'; import * as fs from 'fs-extra'; import { createReadStream } from 'fs'; import { fromFilename } from '@joplin/lib/mime-utils'; @@ -17,6 +16,7 @@ export interface CustomProtocolHandler { // note-viewer/ URLs allowReadAccessToDirectory(path: string): void; allowReadAccessToFile(path: string): AccessController; + allowReadAccessToFiles(paths: string[]): AccessController; // file-media/ URLs setMediaAccessEnabled(enabled: boolean): void; @@ -124,6 +124,12 @@ const handleRangeRequest = async (request: Request, targetPath: string) => { ); }; +const makeAccessDeniedResponse = (message: string) => { + return new Response(message, { + status: 403, // Forbidden + }); +}; + // Creating a custom protocol allows us to isolate iframes by giving them // different domain names from the main Joplin app. // @@ -134,10 +140,10 @@ const handleRangeRequest = async (request: Request, targetPath: string) => { // // TODO: Use Logger.create (doesn't work for now because Logger is only initialized // in the main process.) -const handleCustomProtocols = (logger: LoggerWrapper): CustomProtocolHandler => { - logger = { - ...logger, - debug: () => {}, +const handleCustomProtocols = (): CustomProtocolHandler => { + const logger = { + // Disabled for now + debug: (..._message: unknown[]) => {}, }; // Allow-listed files/directories for joplin-content://note-viewer/ @@ -155,14 +161,16 @@ const handleCustomProtocols = (logger: LoggerWrapper): CustomProtocolHandler => // See https://security.stackexchange.com/a/123723 if (pathname.startsWith('..')) { - throw new Error(`Invalid URL (not absolute), ${request.url}`); + return new Response('Invalid URL (not absolute)', { + status: 400, + }); } pathname = resolve(appBundleDirectory, pathname); let canRead = false; let mediaOnly = true; - if (host === 'note-viewer') { + if (host === 'note-viewer' || host === 'plugin-webview') { if (readableFiles.has(pathname)) { canRead = true; } else { @@ -177,7 +185,7 @@ const handleCustomProtocols = (logger: LoggerWrapper): CustomProtocolHandler => mediaOnly = false; } else if (host === 'file-media') { if (!mediaAccessKey) { - throw new Error('Media access denied. This must be enabled with .setMediaAccessEnabled'); + return makeAccessDeniedResponse('Media access denied. This must be enabled with .setMediaAccessEnabled'); } canRead = true; @@ -185,14 +193,16 @@ const handleCustomProtocols = (logger: LoggerWrapper): CustomProtocolHandler => const accessKey = url.searchParams.get('access-key'); if (accessKey !== mediaAccessKey) { - throw new Error(`Invalid or missing media access key (was ${accessKey}). An allow-listed ?access-key= parameter must be provided.`); + return makeAccessDeniedResponse('Invalid or missing media access key. An allow-listed ?access-key= parameter must be provided.'); } } else { - throw new Error(`Invalid URL ${request.url}`); + return new Response(`Invalid request URL (${request.url})`, { + status: 400, + }); } if (!canRead) { - throw new Error(`Read access not granted for URL ${request.url}`); + return makeAccessDeniedResponse(`Read access not granted for URL (${request.url})`); } const asFileUrl = pathToFileURL(pathname).toString(); @@ -214,7 +224,7 @@ const handleCustomProtocols = (logger: LoggerWrapper): CustomProtocolHandler => // This is an extra check to prevent loading text/html and arbitrary non-media content from the URL. const contentType = response.headers.get('Content-Type'); if (!contentType || !contentType.match(/^(image|video|audio)\//)) { - throw new Error(`Attempted to access non-media file from ${request.url}, which is media-only. Content type was ${contentType}.`); + return makeAccessDeniedResponse(`Attempted to access non-media file from ${request.url}, which is media-only. Content type was ${contentType}.`); } } @@ -222,7 +232,7 @@ const handleCustomProtocols = (logger: LoggerWrapper): CustomProtocolHandler => }); const appBundleDirectory = dirname(dirname(__dirname)); - return { + const result: CustomProtocolHandler = { allowReadAccessToDirectory: (path: string) => { path = resolve(appBundleDirectory, path); logger.debug('protocol handler: Allow read access to directory', path); @@ -250,6 +260,18 @@ const handleCustomProtocols = (logger: LoggerWrapper): CustomProtocolHandler => }, }; }, + allowReadAccessToFiles: (paths: string[]) => { + const handles = paths.map(path => { + return result.allowReadAccessToFile(path); + }); + return { + remove: () => { + for (const handle of handles) { + handle.remove(); + } + }, + }; + }, setMediaAccessEnabled: (enabled: boolean) => { if (enabled) { mediaAccessKey ||= createSecureRandom(); @@ -263,6 +285,7 @@ const handleCustomProtocols = (logger: LoggerWrapper): CustomProtocolHandler => return mediaAccessKey || null; }, }; + return result; }; export default handleCustomProtocols; diff --git a/packages/app-desktop/utils/restartInSafeModeFromMain.ts b/packages/app-desktop/utils/restartInSafeModeFromMain.ts index dd4b9d3cf7..2c37ac31f9 100644 --- a/packages/app-desktop/utils/restartInSafeModeFromMain.ts +++ b/packages/app-desktop/utils/restartInSafeModeFromMain.ts @@ -21,14 +21,14 @@ const restartInSafeModeFromMain = async () => { shimInit({}); const startFlags = await processStartFlags(bridge().processArgv()); - const { rootProfileDir } = determineBaseAppDirs(startFlags.matched.profileDir, appName); + const { rootProfileDir } = determineBaseAppDirs(startFlags.matched.profileDir, appName, Setting.value('altInstanceId')); const { profileDir } = await initProfile(rootProfileDir); // We can't access the database, so write to a file instead. const safeModeFlagFile = join(profileDir, safeModeFlagFilename); await writeFile(safeModeFlagFile, 'true', 'utf8'); - bridge().restart(); + await bridge().restart(); }; export default restartInSafeModeFromMain; diff --git a/packages/app-mobile/android/app/build.gradle b/packages/app-mobile/android/app/build.gradle index 07b20cb839..42690899f8 100644 --- a/packages/app-mobile/android/app/build.gradle +++ b/packages/app-mobile/android/app/build.gradle @@ -86,8 +86,8 @@ android { applicationId "net.cozic.joplin" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 2097764 - versionName "3.3.1" + versionCode 2097768 + versionName "3.3.5" ndk { abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64" } diff --git a/packages/app-mobile/android/app/src/main/cpp/utils/WhisperSession.cpp b/packages/app-mobile/android/app/src/main/cpp/utils/WhisperSession.cpp index 4f6f0fd214..13f10fb290 100644 --- a/packages/app-mobile/android/app/src/main/cpp/utils/WhisperSession.cpp +++ b/packages/app-mobile/android/app/src/main/cpp/utils/WhisperSession.cpp @@ -7,8 +7,8 @@ #include "findLongestSilence.h" #include "androidUtil.h" -WhisperSession::WhisperSession(const std::string& modelPath, std::string lang, std::string prompt) - : lang_ {std::move(lang)}, prompt_ {std::move(prompt)} { +WhisperSession::WhisperSession(const std::string& modelPath, std::string lang, std::string prompt, bool shortAudioContext) + : lang_ {std::move(lang)}, prompt_ {std::move(prompt)}, shortAudioContext_ {shortAudioContext} { whisper_context_params contextParams = whisper_context_default_params(); // Lifetime(pModelPath): Whisper.cpp creates a copy of pModelPath and stores it in a std::string. @@ -34,9 +34,9 @@ WhisperSession::buildWhisperParams_() { // WHISPER_SAMPLING_BEAM_SEARCH is an alternative to greedy: // params.beam_search = { .beam_size = 2 }; params.print_realtime = false; - // Disable timestamps: They make creating custom Whisper models more difficult: + // Disable timestamps: They make creating custom Whisper models more difficult: params.print_timestamps = false; - params.no_timestamps = true; + params.no_timestamps = true; params.print_progress = false; params.translate = false; @@ -54,6 +54,7 @@ WhisperSession::buildWhisperParams_() { params.initial_prompt = prompt_.c_str(); params.prompt_tokens = nullptr; params.prompt_n_tokens = 0; + params.audio_ctx = 0; // Lifetime: lifetime(params) < lifetime(lang_) = lifetime(this). params.language = lang_.c_str(); @@ -63,12 +64,32 @@ WhisperSession::buildWhisperParams_() { std::string WhisperSession::transcribe_(const std::vector& audio, size_t transcribeCount) { - int minTranscribeLength = WHISPER_SAMPLE_RATE / 2; // 0.5s + // Whisper won't transcribe anything shorter than 1s. + int minTranscribeLength = WHISPER_SAMPLE_RATE; // 1s if (transcribeCount < minTranscribeLength) { return ""; } + float seconds = static_cast(transcribeCount) / WHISPER_SAMPLE_RATE; + if (seconds > 30.0f) { + LOGW("Warning: Audio is longer than 30 seconds. Not all audio will be transcribed"); + } + whisper_full_params params = buildWhisperParams_(); + + // If supported by the model, allow shortening the transcription. This can significantly + // improve performance, but requires a fine-tuned model. + // See https://github.com/futo-org/whisper-acft + if (this->shortAudioContext_) { + // audio_ctx: 1500 every 30 seconds (50 units in one second). + // See https://github.com/futo-org/whisper-acft/issues/6 + float padding = 64.0f; + params.audio_ctx = static_cast(seconds * (1500.0f / 30.0f) + padding); + + if (params.audio_ctx > 1500) { + params.audio_ctx = 1500; + } + } whisper_reset_timings(pContext_); transcribeCount = std::min(audio.size(), transcribeCount); @@ -104,51 +125,125 @@ WhisperSession::splitAndTranscribeBefore_(int transcribeUpTo, int trimTo) { return result; } -std::string -WhisperSession::transcribeNextChunk(const float *pAudio, int sizeAudio) { - std::string finalizedContent; +bool WhisperSession::isBufferSilent_() { + int toleranceSamples = WHISPER_SAMPLE_RATE / 8; // 0.125s + auto silence = findLongestSilence( + audioBuffer_, + LongestSilenceOptions { + .sampleRate = WHISPER_SAMPLE_RATE, + .minSilenceLengthSeconds = 0.0f, + .maximumSilenceStartSamples = toleranceSamples, // 0.5s + .returnFirstMatch = true + } + ); + return silence.end >= audioBuffer_.size() - toleranceSamples; +} - // Update the local audio buffer - for (int i = 0; i < sizeAudio; i++) { - audioBuffer_.push_back(pAudio[i]); +std::string +WhisperSession::transcribeNextChunk() { + std::stringstream result; + + // Handles a silence detected between (splitStart, splitEnd). + auto splitAndProcess = [&] (int splitStart, int splitEnd) { + int tolerance = WHISPER_SAMPLE_RATE / 20; // 0.05s + bool isCompletelySilent = splitStart < tolerance && splitEnd > audioBuffer_.size() - tolerance; + LOGD("WhisperSession: Found silence range from %.2f -> %.2f", splitStart / (float) WHISPER_SAMPLE_RATE, splitEnd / (float) WHISPER_SAMPLE_RATE); + + if (isCompletelySilent) { + audioBuffer_.clear(); + return false; + } else if (splitEnd > tolerance) { // Anything to transcribe? + // Include some of the silence between the start and the end. Excluding it + // seems to make Whisper more likely to omit trailing punctuation. + int maximumSilentSamples = WHISPER_SAMPLE_RATE; + int silentSamplesToAdd = std::min(maximumSilentSamples, (splitEnd - splitStart) / 2); + splitStart += silentSamplesToAdd; + + result << splitAndTranscribeBefore_(splitStart, splitEnd) << "\n\n"; + return true; + } + + return false; + }; + + int maximumSamples = WHISPER_SAMPLE_RATE * 25; + + // Handle paragraph breaks indicated by long pauses + while (audioBuffer_.size() > WHISPER_SAMPLE_RATE * 3) { + LOGD("WhisperSession: Checking for a longer pauses."); + // Allow brief pauses to create new paragraphs: + float minSilenceSeconds = 1.5f; + auto splitPoint = findLongestSilence( + audioBuffer_, + LongestSilenceOptions { + .sampleRate = WHISPER_SAMPLE_RATE, + .minSilenceLengthSeconds = minSilenceSeconds, + .maximumSilenceStartSamples = maximumSamples, + .returnFirstMatch = true + } + ); + if (!splitPoint.isValid) { + break; + } + if (!splitAndProcess(splitPoint.start, splitPoint.end)) { + break; + } } - // Does the audio buffer need to be split somewhere? - int maximumSamples = WHISPER_SAMPLE_RATE * 25; + // If there are no long pauses, force a paragraph break somewhere if (audioBuffer_.size() >= maximumSamples) { + LOGD("WhisperSession: Allowing shorter pauses to break."); float minSilenceSeconds = 0.3f; auto silenceRange = findLongestSilence( - audioBuffer_, WHISPER_SAMPLE_RATE, minSilenceSeconds, maximumSamples + audioBuffer_, + LongestSilenceOptions { + .sampleRate = WHISPER_SAMPLE_RATE, + .minSilenceLengthSeconds = minSilenceSeconds, + .maximumSilenceStartSamples = maximumSamples, + .returnFirstMatch = false + } ); // In this case, the audio is long enough that it needs to be split somewhere. If there's // no suitable pause available, default to splitting in the middle. int halfBufferSize = audioBuffer_.size() / 2; - int transcribeTo = silenceRange.isValid ? silenceRange.start : halfBufferSize; - int trimTo = silenceRange.isValid ? silenceRange.end : halfBufferSize; - - finalizedContent = splitAndTranscribeBefore_(transcribeTo, trimTo); - } else if (audioBuffer_.size() > WHISPER_SAMPLE_RATE * 3) { - // Allow brief pauses to create new paragraphs: - float minSilenceSeconds = 2.0f; - auto splitPoint = findLongestSilence( - audioBuffer_, WHISPER_SAMPLE_RATE, minSilenceSeconds, maximumSamples - ); - if (splitPoint.isValid) { - int tolerance = WHISPER_SAMPLE_RATE / 20; // 0.05s - bool isCompletelySilent = splitPoint.start < tolerance && splitPoint.end > audioBuffer_.size() - tolerance; - if (isCompletelySilent) { - audioBuffer_.clear(); - } else { - finalizedContent = splitAndTranscribeBefore_(splitPoint.start, splitPoint.end); - } - } + int splitStart = silenceRange.isValid ? silenceRange.start : halfBufferSize; + int splitEnd = silenceRange.isValid ? silenceRange.end : halfBufferSize; + splitAndProcess(splitStart, splitEnd); } - previewText_ = transcribe_(audioBuffer_, audioBuffer_.size()); - return finalizedContent; + return result.str(); } -std::string WhisperSession::getPreview() { - return previewText_; + +void WhisperSession::addAudio(const float *pAudio, int sizeAudio) { + // Update the local audio buffer + for (int i = 0; i < sizeAudio; i++) { + audioBuffer_.push_back(pAudio[i]); + } +} + +std::string WhisperSession::transcribeAll() { + if (isBufferSilent_()) { + return ""; + } + + std::stringstream result; + + std::string transcribed; + auto update_transcribed = [&] { + transcribed = transcribeNextChunk(); + return !transcribed.empty(); + }; + while (update_transcribed()) { + result << transcribed << "\n\n"; + } + + // Transcribe content considered by transcribeNextChunk as partial: + if (!isBufferSilent_()) { + result << transcribe_(audioBuffer_, audioBuffer_.size()); + } + audioBuffer_.clear(); + + return result.str(); } diff --git a/packages/app-mobile/android/app/src/main/cpp/utils/WhisperSession.h b/packages/app-mobile/android/app/src/main/cpp/utils/WhisperSession.h index 17a7d71b5a..7eaa3c6866 100644 --- a/packages/app-mobile/android/app/src/main/cpp/utils/WhisperSession.h +++ b/packages/app-mobile/android/app/src/main/cpp/utils/WhisperSession.h @@ -5,22 +5,26 @@ class WhisperSession { public: - WhisperSession(const std::string& modelPath, std::string lang, std::string prompt); + WhisperSession(const std::string& modelPath, std::string lang, std::string prompt, bool shortAudioContext); ~WhisperSession(); - std::string transcribeNextChunk(const float *pAudio, int sizeAudio); - std::string getPreview(); + // Adds to the buffer + void addAudio(const float *pAudio, int sizeAudio); + // Returns the next finalized slice of audio (if any) and updates the preview. + std::string transcribeNextChunk(); + // Transcribes all buffered audio data that hasn't been finalized yet + std::string transcribeAll(); private: - // Current preview state - std::string previewText_; - whisper_full_params buildWhisperParams_(); std::string transcribe_(const std::vector& audio, size_t samplesToTranscribe); std::string splitAndTranscribeBefore_(int transcribeUpTo, int trimTo); + bool isBufferSilent_(); + whisper_context *pContext_; const std::string lang_; const std::string prompt_; + const bool shortAudioContext_; std::vector audioBuffer_; }; diff --git a/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence.cpp b/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence.cpp index 876f5dde60..22f646ffb0 100644 --- a/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence.cpp +++ b/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence.cpp @@ -19,14 +19,18 @@ static void highpass(std::vector& data, int sampleRate) { SilenceRange findLongestSilence( const std::vector& audioData, - int sampleRate, - float minSilenceLengthSeconds, - int maxSilencePosition + LongestSilenceOptions options ) { + // Options variables + int sampleRate = options.sampleRate; + int maxSilencePosition = options.maximumSilenceStartSamples; + float minSilenceLengthSeconds = options.minSilenceLengthSeconds; + bool returnFirstMatch = options.returnFirstMatch; + + // State int bestCandidateLength = 0; int bestCandidateStart = -1; int bestCandidateEnd = -1; - int currentCandidateStart = -1; std::vector processedAudio { audioData }; @@ -35,7 +39,7 @@ SilenceRange findLongestSilence( // Break into windows of size `windowSize`: int windowSize = 256; int windowsPerSecond = sampleRate / windowSize; - int quietWindows = 0; + int quietWindows = 0; // Number of relatively quiet windows encountered // Finishes the current candidate for longest silence auto finalizeCandidate = [&] (int currentOffset) { @@ -86,12 +90,20 @@ SilenceRange findLongestSilence( } int minQuietWindows = static_cast(windowsPerSecond * minSilenceLengthSeconds); - if (quietWindows >= minQuietWindows && currentCandidateStart == -1) { - // Found a candidate. Start it. - currentCandidateStart = windowOffset; - } else if (quietWindows == 0) { + if (quietWindows >= minQuietWindows && currentCandidateStart == -1) { // Found silence + // Ignore the first window, which probably contains some of the start of the audio + // and the most recent window, which came after windowOffset. + int windowsToIgnore = 2; + int estimatedQuietSamples = std::max(0, quietWindows - windowsToIgnore) * windowSize; + currentCandidateStart = windowOffset - estimatedQuietSamples; + } else if (quietWindows == 0) { // Silence ended // Ended a candidate. Is it better than the best? finalizeCandidate(windowOffset); + + // Search for more candidates or return now? + if (returnFirstMatch && bestCandidateLength > 0) { + break; + } } } diff --git a/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence.h b/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence.h index b30a28556b..4bec6a5c1e 100644 --- a/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence.h +++ b/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence.h @@ -10,15 +10,24 @@ struct SilenceRange { int end; }; +struct LongestSilenceOptions { + int sampleRate; + + // Minimum length of a silence range (e.g. 3.0 seconds) + float minSilenceLengthSeconds; + + // The maximum position for a silence range to start (ignore + // all silences after this position). + int maximumSilenceStartSamples; + + // Return the first silence satisfying the conditions instead of + // the longest. + bool returnFirstMatch; +}; + SilenceRange findLongestSilence( const std::vector& audioData, - int sampleRate, - - // Minimum length of silence in seconds - float minSilenceLengthSeconds, - - // Doesn't check for silence at a position greater than maximumSilenceStart - int maximumSilenceStart + LongestSilenceOptions options ); diff --git a/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence_test.cpp b/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence_test.cpp index 0a8096eb90..fb91e6dd94 100644 --- a/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence_test.cpp +++ b/packages/app-mobile/android/app/src/main/cpp/utils/findLongestSilence_test.cpp @@ -122,9 +122,12 @@ static float samplesToSeconds(int samples, int sampleRate) { static void expectNoSilence(const GeneratedAudio& audio, const std::string& testLabel) { auto silence = findLongestSilence( audio.data, - audio.sampleRate, - 0.02f, - audio.sampleCount + LongestSilenceOptions { + .sampleRate = audio.sampleRate, + .minSilenceLengthSeconds = 0.02f, + .maximumSilenceStartSamples = audio.sampleCount, + .returnFirstMatch = false, + } ); if (silence.isValid) { std::stringstream errorBuilder; @@ -141,9 +144,12 @@ static void expectNoSilence(const GeneratedAudio& audio, const std::string& test static void expectSilenceBetween(const GeneratedAudio& audio, float startTimeSeconds, float stopTimeSeconds, const std::string& testLabel) { auto silenceResult = findLongestSilence( audio.data, - audio.sampleRate, - 0.02f, - audio.sampleCount + LongestSilenceOptions { + .sampleRate = audio.sampleRate, + .minSilenceLengthSeconds = 0.02f, + .maximumSilenceStartSamples = audio.sampleCount, + .returnFirstMatch = false, + } ); if (!silenceResult.isValid) { diff --git a/packages/app-mobile/android/app/src/main/cpp/whisperWrapper.cpp b/packages/app-mobile/android/app/src/main/cpp/whisperWrapper.cpp index 4d054cbc2f..e6089ca3cd 100644 --- a/packages/app-mobile/android/app/src/main/cpp/whisperWrapper.cpp +++ b/packages/app-mobile/android/app/src/main/cpp/whisperWrapper.cpp @@ -54,13 +54,14 @@ Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_init( jobject thiz, jstring modelPath, jstring language, - jstring prompt + jstring prompt, + jboolean useShortAudioContext ) { whisper_log_set(log_android, nullptr); try { auto *pSession = new WhisperSession( - stringToCXX(env, modelPath), stringToCXX(env, language), stringToCXX(env, prompt) + stringToCXX(env, modelPath), stringToCXX(env, language), stringToCXX(env, prompt), useShortAudioContext ); return (jlong) pSession; } catch (const std::exception& exception) { @@ -78,8 +79,8 @@ Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_free(JNIEnv *env, jo } extern "C" -JNIEXPORT jstring JNICALL -Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_fullTranscribe(JNIEnv *env, +JNIEXPORT void JNICALL +Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_addAudio(JNIEnv *env, jobject thiz, jlong pointer, jfloatArray audio_data) { @@ -89,28 +90,53 @@ Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_fullTranscribe(JNIEn std::string result; try { - LOGD("Starting Whisper, transcribe %d", lenAudioData); - result = pSession->transcribeNextChunk(pAudioData, lenAudioData); - auto preview = pSession->getPreview(); - LOGD("Ran Whisper. Got %s (preview %s)", result.c_str(), preview.c_str()); + pSession->addAudio(pAudioData, lenAudioData); } catch (const std::exception& exception) { - LOGW("Failed to run whisper: %s", exception.what()); + LOGW("Failed to add to audio buffer: %s", exception.what()); throwException(env, exception.what()); } // JNI_ABORT: "free the buffer without copying back the possible changes", pass 0 to copy // changes (there should be no changes) env->ReleaseFloatArrayElements(audio_data, pAudioData, JNI_ABORT); +} + +extern "C" +JNIEXPORT jstring JNICALL +Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_transcribeNextChunk(JNIEnv *env, + jobject thiz, + jlong pointer) { + auto *pSession = reinterpret_cast (pointer); + std::string result; + + try { + result = pSession->transcribeNextChunk(); + } catch (const std::exception& exception) { + LOGW("Failed to run whisper: %s", exception.what()); + throwException(env, exception.what()); + return nullptr; + } return stringToJava(env, result); } + extern "C" JNIEXPORT jstring JNICALL -Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_getPreview( - JNIEnv *env, jobject thiz, jlong pointer -) { +Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_transcribeRemaining(JNIEnv *env, + jobject thiz, + jlong pointer) { auto *pSession = reinterpret_cast (pointer); - return stringToJava(env, pSession->getPreview()); + std::string result; + + try { + result = pSession->transcribeAll(); + } catch (const std::exception& exception) { + LOGW("Failed to run whisper: %s", exception.what()); + throwException(env, exception.what()); + return nullptr; + } + + return stringToJava(env, result); } extern "C" @@ -122,4 +148,4 @@ Java_net_cozic_joplin_audio_NativeWhisperLib_00024Companion_runTests(JNIEnv *env LOGW("Failed to run tests: %s", exception.what()); throwException(env, exception.what()); } -} \ No newline at end of file +} diff --git a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/AudioRecorder.kt b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/AudioRecorder.kt index 174bd0bfcd..a6f848687c 100644 --- a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/AudioRecorder.kt +++ b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/AudioRecorder.kt @@ -15,7 +15,9 @@ typealias AudioRecorderFactory = (context: Context)->AudioRecorder; class AudioRecorder(context: Context) : Closeable { private val sampleRate = 16_000 - private val maxLengthSeconds = 30 // Whisper supports a maximum of 30s + // Don't allow the unprocessed audio buffer to grow indefinitely -- discard + // data if longer than this: + private val maxLengthSeconds = 120 private val maxBufferSize = sampleRate * maxLengthSeconds private val buffer = FloatArray(maxBufferSize) private var bufferWriteOffset = 0 diff --git a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/NativeWhisperLib.kt b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/NativeWhisperLib.kt index 5cc9db59a4..a07af37099 100644 --- a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/NativeWhisperLib.kt +++ b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/NativeWhisperLib.kt @@ -6,6 +6,7 @@ class NativeWhisperLib( modelPath: String, languageCode: String, prompt: String, + shortAudioContext: Boolean, ) : Closeable { companion object { init { @@ -16,30 +17,39 @@ class NativeWhisperLib( // TODO: The example whisper.cpp project transfers pointers as Longs to the Kotlin code. // This seems unsafe. Try changing how this is managed. - private external fun init(modelPath: String, languageCode: String, prompt: String): Long; + private external fun init(modelPath: String, languageCode: String, prompt: String, shortAudioContext: Boolean): Long; private external fun free(pointer: Long): Unit; - private external fun fullTranscribe(pointer: Long, audioData: FloatArray): String; - private external fun getPreview(pointer: Long): String; + private external fun addAudio(pointer: Long, audioData: FloatArray): Unit; + private external fun transcribeNextChunk(pointer: Long): String; + private external fun transcribeRemaining(pointer: Long): String; } private var closed = false - private val pointer: Long = init(modelPath, languageCode, prompt) + private val pointer: Long = init(modelPath, languageCode, prompt, shortAudioContext) - fun transcribe(audioData: FloatArray): String { + fun addAudio(audioData: FloatArray) { + if (closed) { + throw Exception("Cannot add audio data to a closed session") + } + + Companion.addAudio(pointer, audioData) + } + + fun transcribeNextChunk(): String { if (closed) { throw Exception("Cannot transcribe using a closed session") } - return fullTranscribe(pointer, audioData) + return Companion.transcribeNextChunk(pointer) } - fun getPreview(): String { + fun transcribeRemaining(): String { if (closed) { - throw Exception("Cannot get preview from a closed session") + throw Exception("Cannot transcribeAll using a closed session") } - return getPreview(pointer) + return Companion.transcribeRemaining(pointer) } override fun close() { diff --git a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextConverter.kt b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextConverter.kt index 2988c7ad6e..c2f752ae29 100644 --- a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextConverter.kt +++ b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextConverter.kt @@ -8,6 +8,7 @@ class SpeechToTextConverter( modelPath: String, locale: String, prompt: String, + useShortAudioCtx: Boolean, recorderFactory: AudioRecorderFactory, context: Context, ) : Closeable { @@ -17,6 +18,7 @@ class SpeechToTextConverter( modelPath, languageCode, prompt, + useShortAudioCtx, ) fun start() { @@ -25,7 +27,8 @@ class SpeechToTextConverter( private fun convert(data: FloatArray): String { Log.d("Whisper", "Pre-transcribe data of size ${data.size}") - val result = whisper.transcribe(data) + whisper.addAudio(data) + val result = whisper.transcribeNextChunk() Log.d("Whisper", "Post transcribe. Got $result") return result; } @@ -47,11 +50,8 @@ class SpeechToTextConverter( // Converts as many seconds of buffered data as possible, without waiting fun convertRemaining(): String { val buffer = recorder.pullAvailable() - return convert(buffer) - } - - fun getPreview(): String { - return whisper.getPreview() + whisper.addAudio(buffer) + return whisper.transcribeRemaining() } override fun close() { diff --git a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextPackage.kt b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextPackage.kt index 12ed9c29ce..2e6f03e150 100644 --- a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextPackage.kt +++ b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextPackage.kt @@ -43,11 +43,11 @@ class SpeechToTextPackage : ReactPackage { } @ReactMethod - fun openSession(modelPath: String, locale: String, prompt: String, promise: Promise) { + fun openSession(modelPath: String, locale: String, prompt: String, useShortAudioCtx: Boolean, promise: Promise) { val appContext = context.applicationContext try { - val sessionId = sessionManager.openSession(modelPath, locale, prompt, appContext) + val sessionId = sessionManager.openSession(modelPath, locale, prompt, useShortAudioCtx, appContext) promise.resolve(sessionId) } catch (exception: Throwable) { promise.reject(exception) @@ -79,11 +79,6 @@ class SpeechToTextPackage : ReactPackage { sessionManager.convertAvailable(sessionId, promise) } - @ReactMethod - fun getPreview(sessionId: Int, promise: Promise) { - sessionManager.getPreview(sessionId, promise) - } - @ReactMethod fun closeSession(sessionId: Int, promise: Promise) { sessionManager.closeSession(sessionId, promise) diff --git a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextSessionManager.kt b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextSessionManager.kt index bd1cdcb27c..c770cc79af 100644 --- a/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextSessionManager.kt +++ b/packages/app-mobile/android/app/src/main/java/net/cozic/joplin/audio/SpeechToTextSessionManager.kt @@ -21,12 +21,13 @@ class SpeechToTextSessionManager( modelPath: String, locale: String, prompt: String, + useShortAudioCtx: Boolean, context: Context, ): Int { val sessionId = nextSessionId++ sessions[sessionId] = SpeechToTextSession( SpeechToTextConverter( - modelPath, locale, prompt, recorderFactory = AudioRecorder.factory, context, + modelPath, locale, prompt, useShortAudioCtx, recorderFactory = AudioRecorder.factory, context, ) ) return sessionId @@ -101,13 +102,6 @@ class SpeechToTextSessionManager( } } - fun getPreview(sessionId: Int, promise: Promise) { - this.concurrentWithSession(sessionId, promise::reject) { session -> - val result = session.converter.getPreview() - promise.resolve(result) - } - } - fun closeSession(sessionId: Int, promise: Promise) { this.concurrentWithSession(sessionId) { session -> session.converter.close() diff --git a/packages/app-mobile/commands/dismissPluginPanels.ts b/packages/app-mobile/commands/dismissPluginPanels.ts new file mode 100644 index 0000000000..744c007a9b --- /dev/null +++ b/packages/app-mobile/commands/dismissPluginPanels.ts @@ -0,0 +1,16 @@ +import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; + +export const declaration: CommandDeclaration = { + name: 'dismissPluginPanels', +}; + +export const runtime = (): CommandRuntime => { + return { + execute: async (context: CommandContext) => { + context.dispatch({ + type: 'SET_PLUGIN_PANELS_DIALOG_VISIBLE', + visible: false, + }); + }, + }; +}; diff --git a/packages/app-mobile/commands/index.ts b/packages/app-mobile/commands/index.ts index 5a88342481..adc76ec48e 100644 --- a/packages/app-mobile/commands/index.ts +++ b/packages/app-mobile/commands/index.ts @@ -1,10 +1,12 @@ // AUTO-GENERATED using `gulp buildScriptIndexes` +import * as dismissPluginPanels from './dismissPluginPanels'; import * as newNote from './newNote'; import * as openItem from './openItem'; import * as openNote from './openNote'; import * as scrollToHash from './scrollToHash'; const index: any[] = [ + dismissPluginPanels, newNote, openItem, openNote, diff --git a/packages/app-mobile/components/BottomDrawer.tsx b/packages/app-mobile/components/BottomDrawer.tsx new file mode 100644 index 0000000000..fd8c2f3cc7 --- /dev/null +++ b/packages/app-mobile/components/BottomDrawer.tsx @@ -0,0 +1,106 @@ +import * as React from 'react'; +import { connect } from 'react-redux'; +import { useCallback, useMemo } from 'react'; +import { NativeScrollEvent, NativeSyntheticEvent, StyleSheet, useWindowDimensions, View } from 'react-native'; +import useSafeAreaPadding from '../utils/hooks/useSafeAreaPadding'; +import { themeStyle, ThemeStyle } from './global-style'; +import Modal from './Modal'; +import { AppState } from '../utils/types'; + +interface Props { + themeId: number; + children: React.ReactNode; + visible: boolean; + onDismiss: ()=> void; + onShow: ()=> void; +} + +const useStyles = (theme: ThemeStyle) => { + const { width: windowWidth } = useWindowDimensions(); + const safeAreaPadding = useSafeAreaPadding(); + + return useMemo(() => { + const isSmallWidthScreen = windowWidth < 500; + const menuGapLeft = safeAreaPadding.paddingLeft + 6; + const menuGapRight = safeAreaPadding.paddingRight + 6; + + return StyleSheet.create({ + menuStyle: { + alignSelf: 'flex-end', + ...(isSmallWidthScreen ? { + // Center on small screens, rather than float right. + alignSelf: 'center', + } : {}), + flexDirection: 'row', + marginRight: menuGapRight, + marginLeft: menuGapLeft, + paddingBottom: 0, + + backgroundColor: theme.backgroundColor, + borderRadius: 16, + borderBottomRightRadius: 0, + borderBottomLeftRadius: 0, + maxWidth: Math.min(400, windowWidth - menuGapRight - menuGapLeft), + }, + contentContainer: { + padding: 20, + paddingBottom: 14, + gap: 8, + flexDirection: 'row', + flexWrap: 'wrap', + }, + modalBackground: { + paddingTop: 0, + paddingLeft: 0, + paddingRight: 0, + paddingBottom: 0, + justifyContent: 'flex-end', + flexDirection: 'column', + }, + dismissButton: { + top: 0, + bottom: undefined, + height: 12, + }, + }); + }, [theme, safeAreaPadding, windowWidth]); +}; + +const BottomDrawer: React.FC = props => { + const theme = themeStyle(props.themeId); + const styles = useStyles(theme); + + const onContainerScroll = useCallback((event: NativeSyntheticEvent) => { + const offsetY = event.nativeEvent.contentOffset.y; + if (offsetY < -50) { + props.onDismiss(); + } + }, [props.onDismiss]); + + return + + {props.children} + + ; +}; + +export default connect((state: AppState) => { + return { + themeId: state.settings.theme, + }; +})(BottomDrawer); diff --git a/packages/app-mobile/components/DialogManager/index.tsx b/packages/app-mobile/components/DialogManager/index.tsx index 4b49929194..5ebf14b9e1 100644 --- a/packages/app-mobile/components/DialogManager/index.tsx +++ b/packages/app-mobile/components/DialogManager/index.tsx @@ -8,6 +8,7 @@ import makeShowMessageBox from '../../utils/makeShowMessageBox'; import { DialogControl, PromptDialogData } from './types'; import useDialogControl from './hooks/useDialogControl'; import PromptDialog from './PromptDialog'; +import { themeStyle } from '../global-style'; export type { DialogControl } from './types'; export const DialogContext = createContext(null); @@ -49,6 +50,7 @@ const DialogManager: React.FC = props => { }; }, []); + const theme = themeStyle(props.themeId); const styles = useStyles(); const dialogComponents: React.ReactNode[] = []; @@ -73,7 +75,7 @@ const DialogManager: React.FC = props => { scrollOverflow={true} containerStyle={styles.modalContainer} animationType='fade' - backgroundColor='rgba(0, 0, 0, 0.1)' + backgroundColor={theme.backgroundColorTransparent2} transparent={true} onRequestClose={dialogModels[dialogComponents.length - 1]?.onDismiss} > diff --git a/packages/app-mobile/components/DismissibleDialog.tsx b/packages/app-mobile/components/DismissibleDialog.tsx index a8c429775a..618f9a8083 100644 --- a/packages/app-mobile/components/DismissibleDialog.tsx +++ b/packages/app-mobile/components/DismissibleDialog.tsx @@ -69,6 +69,7 @@ const useStyles = (themeId: number, containerStyle: ViewStyle, size: DialogSize) }; const DismissibleDialog: React.FC = props => { + const theme = themeStyle(props.themeId); const styles = useStyles(props.themeId, props.containerStyle, props.size); const heading = props.heading ? ( @@ -92,7 +93,7 @@ const DismissibleDialog: React.FC = props => { onRequestClose={props.onDismiss} containerStyle={styles.dialogContainer} animationType='fade' - backgroundColor='rgba(0, 0, 0, 0.1)' + backgroundColor={theme.backgroundColorTransparent2} transparent={true} > diff --git a/packages/app-mobile/components/EditorToolbar/utils/allToolbarCommandNamesFromState.ts b/packages/app-mobile/components/EditorToolbar/utils/allToolbarCommandNamesFromState.ts index e6b0d8806f..e0a78c80c3 100644 --- a/packages/app-mobile/components/EditorToolbar/utils/allToolbarCommandNamesFromState.ts +++ b/packages/app-mobile/components/EditorToolbar/utils/allToolbarCommandNamesFromState.ts @@ -22,6 +22,8 @@ const builtInCommandNames = [ '-', EditorCommandType.IndentLess, EditorCommandType.IndentMore, + `editor.${EditorCommandType.SwapLineDown}`, + `editor.${EditorCommandType.SwapLineUp}`, '-', 'insertDateTime', '-', diff --git a/packages/app-mobile/components/EditorToolbar/utils/selectedCommandNamesFromState.ts b/packages/app-mobile/components/EditorToolbar/utils/selectedCommandNamesFromState.ts index c4a3ec67e1..f955f0c345 100644 --- a/packages/app-mobile/components/EditorToolbar/utils/selectedCommandNamesFromState.ts +++ b/packages/app-mobile/components/EditorToolbar/utils/selectedCommandNamesFromState.ts @@ -1,3 +1,4 @@ +import { EditorCommandType } from '@joplin/editor/types'; import { AppState } from '../../../utils/types'; import allToolbarCommandNamesFromState from './allToolbarCommandNamesFromState'; import { Platform } from 'react-native'; @@ -7,6 +8,8 @@ const omitFromDefault: string[] = [ 'editor.textHeading3', 'editor.textHeading4', 'editor.textHeading5', + `editor.${EditorCommandType.SwapLineDown}`, + `editor.${EditorCommandType.SwapLineUp}`, ]; // The "hide keyboard" button is only needed on iOS, so only show it there by default. diff --git a/packages/app-mobile/components/Modal.tsx b/packages/app-mobile/components/Modal.tsx index e847add545..8d70fbf1f3 100644 --- a/packages/app-mobile/components/Modal.tsx +++ b/packages/app-mobile/components/Modal.tsx @@ -1,35 +1,35 @@ import * as React from 'react'; -import { RefObject, useCallback, useMemo, useRef } from 'react'; -import { GestureResponderEvent, Modal, ModalProps, ScrollView, StyleSheet, View, ViewStyle, useWindowDimensions } from 'react-native'; -import { hasNotch } from 'react-native-device-info'; +import { RefObject, useCallback, useMemo, useRef, useState } from 'react'; +import { GestureResponderEvent, Modal, ModalProps, Platform, Pressable, ScrollView, ScrollViewProps, StyleSheet, View, ViewStyle } from 'react-native'; +import FocusControl from './accessibility/FocusControl/FocusControl'; +import { msleep, Second } from '@joplin/utils/time'; +import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect'; +import { ModalState } from './accessibility/FocusControl/types'; +import useSafeAreaPadding from '../utils/hooks/useSafeAreaPadding'; +import { _ } from '@joplin/lib/locale'; interface ModalElementProps extends ModalProps { children: React.ReactNode; containerStyle?: ViewStyle; backgroundColor?: string; + modalBackgroundStyle?: ViewStyle; + // Extra styles for the accessibility tools dismiss button. For example, + // this might be used to display the dismiss button near the top of the + // screen (rather than the bottom). + dismissButtonStyle?: ViewStyle; // If scrollOverflow is provided, the modal is wrapped in a vertical // ScrollView. This allows the user to scroll parts of dialogs into // view that would otherwise be clipped by the screen edge. - scrollOverflow?: boolean; + scrollOverflow?: boolean|ScrollViewProps; } const useStyles = (hasScrollView: boolean, backgroundColor: string|undefined) => { - const { width: windowWidth, height: windowHeight } = useWindowDimensions(); - const isLandscape = windowWidth > windowHeight; + const safeAreaPadding = useSafeAreaPadding(); return useMemo(() => { - const backgroundPadding: ViewStyle = isLandscape ? { - paddingRight: hasNotch() ? 60 : 0, - paddingLeft: hasNotch() ? 60 : 0, - paddingTop: 15, - paddingBottom: 15, - } : { - paddingTop: hasNotch() ? 65 : 15, - paddingBottom: hasNotch() ? 35 : 15, - }; return StyleSheet.create({ modalBackground: { - ...backgroundPadding, + ...safeAreaPadding, flexGrow: 1, flexShrink: 1, @@ -49,8 +49,15 @@ const useStyles = (hasScrollView: boolean, backgroundColor: string|undefined) => // This makes it possible to vertically center the content of scrollable modals. flexGrow: 1, }, + dismissButton: { + position: 'absolute', + bottom: 0, + height: 12, + width: '100%', + zIndex: -1, + }, }); - }, [hasScrollView, isLandscape, backgroundColor]); + }, [hasScrollView, safeAreaPadding, backgroundColor]); }; const useBackgroundTouchListeners = (onRequestClose: (event: GestureResponderEvent)=> void, backdropRef: RefObject) => { @@ -67,14 +74,46 @@ const useBackgroundTouchListeners = (onRequestClose: (event: GestureResponderEve return { onShouldBackgroundCaptureTouch, onBackgroundTouchFinished }; }; +const useModalStatus = (containerComponent: View|null, visible: boolean) => { + const contentMounted = !!containerComponent; + const [controlsFocus, setControlsFocus] = useState(false); + useAsyncEffect(async (event) => { + if (contentMounted) { + setControlsFocus(true); + } else { + // Accessibility: Work around Android's default focus-setting behavior. + // By default, React Native's Modal on Android sets focus about 0.8 seconds + // after the modal is dismissed. As a result, the Modal controls focus until + // roughly one second after the modal is dismissed. + if (Platform.OS === 'android') { + await msleep(Second); + } + + if (!event.cancelled) { + setControlsFocus(false); + } + } + }, [contentMounted]); + + let modalStatus = ModalState.Closed; + if (controlsFocus) { + modalStatus = visible ? ModalState.Open : ModalState.Closing; + } else if (visible) { + modalStatus = ModalState.Open; + } + return modalStatus; +}; + const ModalElement: React.FC = ({ children, containerStyle, backgroundColor, scrollOverflow, + modalBackgroundStyle: extraModalBackgroundStyles, + dismissButtonStyle, ...modalProps }) => { - const styles = useStyles(scrollOverflow, backgroundColor); + const styles = useStyles(!!scrollOverflow, backgroundColor); // contentWrapper adds padding. To allow styling the region outside of the modal // (e.g. to add a background), the content is wrapped twice. @@ -84,29 +123,50 @@ const ModalElement: React.FC = ({ ); - const backgroundRef = useRef(); - const { onShouldBackgroundCaptureTouch, onBackgroundTouchFinished } = useBackgroundTouchListeners(modalProps.onRequestClose, backgroundRef); + + const [containerComponent, setContainerComponent] = useState(null); + const modalStatus = useModalStatus(containerComponent, modalProps.visible); + + const containerRef = useRef(null); + containerRef.current = containerComponent; + const { onShouldBackgroundCaptureTouch, onBackgroundTouchFinished } = useBackgroundTouchListeners(modalProps.onRequestClose, containerRef); + + // A close button for accessibility tools. Since iOS accessibility focus order is based on the position + // of the element on the screen, the close button is placed after the modal content, rather than behind. + const closeButton = modalProps.onRequestClose ? : null; const contentAndBackdrop = {content}; + > + {content} + {closeButton} + ; - // supportedOrientations: On iOS, this allows the dialog to be shown in non-portrait orientations. + const extraScrollViewProps = (typeof scrollOverflow === 'object' ? scrollOverflow : {}); return ( - - {scrollOverflow ? ( - {contentAndBackdrop} - ) : contentAndBackdrop} - + + + {scrollOverflow ? ( + {contentAndBackdrop} + ) : contentAndBackdrop} + + ); }; diff --git a/packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.tsx b/packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.tsx index 949b717c5c..c7156a48c6 100644 --- a/packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.tsx +++ b/packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.tsx @@ -7,7 +7,6 @@ import '@testing-library/jest-native/extend-expect'; import NoteBodyViewer from './NoteBodyViewer'; import Setting from '@joplin/lib/models/Setting'; -import { MenuProvider } from 'react-native-popup-menu'; import { resourceFetcher, setupDatabaseAndSynchronizer, supportDir, switchClient, synchronizerStart } from '@joplin/lib/testing/test-utils'; import { MarkupLanguage } from '@joplin/renderer'; import { HandleMessageCallback, OnMarkForDownloadCallback } from './hooks/useOnMessage'; @@ -16,6 +15,8 @@ import shim from '@joplin/lib/shim'; import Note from '@joplin/lib/models/Note'; import { ResourceInfo } from './hooks/useRerenderHandler'; import getWebViewDomById from '../../utils/testing/getWebViewDomById'; +import TestProviderStack from '../testing/TestProviderStack'; +import createMockReduxStore from '../../utils/testing/createMockReduxStore'; interface WrapperProps { noteBody: string; @@ -29,6 +30,7 @@ interface WrapperProps { const emptyObject = {}; const emptyArray: string[] = []; const noOpFunction = () => {}; +const testStore = createMockReduxStore(); const WrappedNoteViewer: React.FC = ( { noteBody, @@ -39,7 +41,7 @@ const WrappedNoteViewer: React.FC = ( onMarkForDownload, }: WrapperProps, ) => { - return + return = ( onScroll={onScroll} pluginStates={emptyObject} /> - ; + ; }; const getNoteViewerDom = async () => { diff --git a/packages/app-mobile/components/NoteEditor/commandDeclarations.ts b/packages/app-mobile/components/NoteEditor/commandDeclarations.ts index 5ea3b46d38..470ce195e4 100644 --- a/packages/app-mobile/components/NoteEditor/commandDeclarations.ts +++ b/packages/app-mobile/components/NoteEditor/commandDeclarations.ts @@ -97,6 +97,16 @@ const declarations: CommandDeclaration[] = [ label: () => _('Increase indent level'), iconName: 'ant indent-right', }, + { + name: `editor.${EditorCommandType.SwapLineDown}`, + label: () => _('Swap line down'), + iconName: 'material chevron-double-down', + }, + { + name: `editor.${EditorCommandType.SwapLineUp}`, + label: () => _('Swap line up'), + iconName: 'material chevron-double-up', + }, { name: EditorCommandType.ToggleSearch, label: () => _('Search'), diff --git a/packages/app-mobile/components/ScreenHeader/Menu.tsx b/packages/app-mobile/components/ScreenHeader/Menu.tsx index c51acae3cd..ed969beecb 100644 --- a/packages/app-mobile/components/ScreenHeader/Menu.tsx +++ b/packages/app-mobile/components/ScreenHeader/Menu.tsx @@ -1,10 +1,12 @@ import * as React from 'react'; import { useCallback, useMemo, useState } from 'react'; -import { StyleSheet, TextStyle, View, Text, ScrollView, useWindowDimensions } from 'react-native'; +import { StyleSheet, TextStyle, View, Text, ScrollView, useWindowDimensions, Platform } from 'react-native'; import { themeStyle } from '../global-style'; import { Menu, MenuOption as MenuOptionComponent, MenuOptions, MenuTrigger } from 'react-native-popup-menu'; import AccessibleView from '../accessibility/AccessibleView'; import debounce from '../../utils/debounce'; +import FocusControl from '../accessibility/FocusControl/FocusControl'; +import { ModalState } from '../accessibility/FocusControl/types'; interface MenuOptionDivider { isDivider: true; @@ -81,18 +83,22 @@ const MenuComponent: React.FC = props => { // When undefined/null: Don't auto-focus anything. const [refocusCounter, setRefocusCounter] = useState(undefined); - let key = 0; + let keyCounter = 0; let isFirst = true; for (const option of props.options) { if (option.isDivider === true) { menuOptionComponents.push( - , + , ); } else { - const canAutoFocus = isFirst; + // Don't auto-focus on iOS -- as of RN 0.74, this causes focus to get stuck. However, + // the auto-focus seems to be necessary on web (and possibly Android) to avoid first focusing + // the dismiss button and other items not in the menu: + const canAutoFocus = isFirst && Platform.OS !== 'ios'; + const key = `menuOption_${option.key ?? keyCounter++}`; menuOptionComponents.push( - - + + = props => { } } + const [open, setOpen] = useState(false); + const onMenuItemSelect = useCallback((value: unknown) => { if (typeof value === 'function') { value(); } setRefocusCounter(undefined); + setOpen(false); }, []); // debounce: If the menu is focused during its transition animation, it briefly // appears to be in the wrong place. As such, add a brief delay before focusing. - const onMenuOpen = useMemo(() => debounce(() => { + const onMenuOpened = useMemo(() => debounce(() => { setRefocusCounter(counter => (counter ?? 0) + 1); + setOpen(true); }, 200), []); // Resetting the refocus counter to undefined causes the menu to not be focused immediately // after opening. - const onMenuClose = useCallback(() => { + const onMenuClosed = useCallback(() => { setRefocusCounter(undefined); + setOpen(false); }, []); return ( {props.children} - {menuOptionComponents} + + {menuOptionComponents} + ); diff --git a/packages/app-mobile/components/ScreenHeader/index.tsx b/packages/app-mobile/components/ScreenHeader/index.tsx index e428f4168e..0efa66e02e 100644 --- a/packages/app-mobile/components/ScreenHeader/index.tsx +++ b/packages/app-mobile/components/ScreenHeader/index.tsx @@ -502,15 +502,15 @@ class ScreenHeaderComponent extends PureComponent - - - - + description={_('Sort notes by')} + iconName='ionicon filter-outline' + contentWrapperStyle={styles.iconButton} + iconStyle={styles.topIcon} + /> ); } diff --git a/packages/app-mobile/components/SideMenu.tsx b/packages/app-mobile/components/SideMenu.tsx index 2acb95e1fd..4e239f6ba6 100644 --- a/packages/app-mobile/components/SideMenu.tsx +++ b/packages/app-mobile/components/SideMenu.tsx @@ -271,12 +271,14 @@ const SideMenuComponent: React.FC = props => { {props.menu} @@ -287,8 +289,9 @@ const SideMenuComponent: React.FC = props => { - + {props.children} ); diff --git a/packages/app-mobile/components/ToggleSpaceButton.tsx b/packages/app-mobile/components/ToggleSpaceButton.tsx deleted file mode 100644 index 7ec8a66698..0000000000 --- a/packages/app-mobile/components/ToggleSpaceButton.tsx +++ /dev/null @@ -1,97 +0,0 @@ - -// On some devices, the SafeAreaView conflicts with the KeyboardAvoidingView, creating -// additional (or a lack of additional) space at the bottom of the screen. Because this -// is different on different devices, this button allows toggling additional space a the bottom -// of the screen to compensate. - -// Works around https://github.com/facebook/react-native/issues/13393 by adding additional -// space below the given component when the keyboard is visible unless a button is pressed. - -import Setting from '@joplin/lib/models/Setting'; -import { themeStyle } from '@joplin/lib/theme'; - -import * as React from 'react'; -import { ReactNode, useCallback, useState, useEffect } from 'react'; -import { Platform, useWindowDimensions, View, ViewStyle } from 'react-native'; -import IconButton from './IconButton'; -import useKeyboardVisible from '../utils/hooks/useKeyboardVisible'; - -interface Props { - children: ReactNode; - themeId: number; - style?: ViewStyle; -} - -const ToggleSpaceButton = (props: Props) => { - const [additionalSpace, setAdditionalSpace] = useState(0); - const [decreaseSpaceBtnVisible, setDecreaseSpaceBtnVisible] = useState(true); - - // Some devices need space added, others need space removed. - const additionalPositiveSpace = 14; - const additionalNegativeSpace = -14; - - // Switch from adding +14px to -14px. - const onDecreaseSpace = useCallback(() => { - setAdditionalSpace(additionalNegativeSpace); - setDecreaseSpaceBtnVisible(false); - Setting.setValue('editor.mobile.removeSpaceBelowToolbar', true); - }, [setAdditionalSpace, setDecreaseSpaceBtnVisible, additionalNegativeSpace]); - - useEffect(() => { - if (Setting.value('editor.mobile.removeSpaceBelowToolbar')) { - onDecreaseSpace(); - } - }, [onDecreaseSpace]); - - const theme = themeStyle(props.themeId); - - const decreaseSpaceButton = ( - <> - - - - ); - - const { keyboardVisible } = useKeyboardVisible(); - const windowSize = useWindowDimensions(); - const isPortrait = windowSize.height > windowSize.width; - const spaceApplicable = keyboardVisible && Platform.OS === 'ios' && isPortrait; - - const style: ViewStyle = { - marginBottom: spaceApplicable ? additionalSpace : 0, - ...props.style, - }; - - return ( - - {props.children} - { decreaseSpaceBtnVisible && spaceApplicable ? decreaseSpaceButton : null } - - ); -}; - -export default ToggleSpaceButton; diff --git a/packages/app-mobile/components/accessibility/AccessibleModalMenu.js b/packages/app-mobile/components/accessibility/AccessibleModalMenu.js new file mode 100644 index 0000000000..bfbb3423fa --- /dev/null +++ b/packages/app-mobile/components/accessibility/AccessibleModalMenu.js @@ -0,0 +1,34 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const React = require('react'); +const react_native_1 = require('react-native'); +const Modal_1 = require('../Modal'); +const react_1 = require('react'); +const locale_1 = require('@joplin/lib/locale'); +const buttons_1 = require('../buttons'); +// react-native-paper's floating action button menu is inaccessible on web +// (can't be activated by a screen reader, and, in some cases, by the tab key). +// This component provides an alternative. +const AccessibleModalMenu = props => { + let _a; + const [open, setOpen] = (0, react_1.useState)(false); + const onClick = (0, react_1.useCallback)(() => { + if (props.onPress) { + props.onPress(); + } else { + setOpen(!open); + } + }, [open, props.onPress]); + const options = []; + for (const action of ((_a = props.actions) !== null && _a !== void 0 ? _a : [])) { + options.push(React.createElement(buttons_1.PrimaryButton, { key: action.label, onPress: action.onPress }, action.label)); + } + const modal = (React.createElement(Modal_1.default, { visible: open }, + options, + React.createElement(buttons_1.SecondaryButton, { onPress: onClick }, (0, locale_1._)('Close menu')))); + return React.createElement(react_native_1.View, { style: { height: 0, overflow: 'visible' } }, + modal, + React.createElement(buttons_1.SecondaryButton, { onPress: onClick }, props.label)); +}; +exports.default = AccessibleModalMenu; +// # sourceMappingURL=AccessibleModalMenu.js.map diff --git a/packages/app-mobile/components/accessibility/AccessibleModalMenu.tsx b/packages/app-mobile/components/accessibility/AccessibleModalMenu.tsx deleted file mode 100644 index 32dc20eddf..0000000000 --- a/packages/app-mobile/components/accessibility/AccessibleModalMenu.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from 'react'; -import { View } from 'react-native'; -import Modal from '../Modal'; -import { useCallback, useState } from 'react'; -import { _ } from '@joplin/lib/locale'; -import { PrimaryButton, SecondaryButton } from '../buttons'; - -interface MenuItem { - label: string; - onPress?: ()=> void; -} - -interface Props { - label: string; - onPress: ()=> void; - actions: MenuItem[]|null; -} - -// react-native-paper's floating action button menu is inaccessible on web -// (can't be activated by a screen reader, and, in some cases, by the tab key). -// This component provides an alternative. - -const AccessibleModalMenu: React.FC = props => { - const [open, setOpen] = useState(false); - - const onClick = useCallback(() => { - if (props.onPress) { - props.onPress(); - } else { - setOpen(!open); - } - }, [open, props.onPress]); - - const options: React.ReactElement[] = []; - for (const action of (props.actions ?? [])) { - options.push( - - {action.label} - , - ); - } - - const modal = ( - - {options} - {_('Close menu')} - - ); - - return - {modal} - {props.label} - ; -}; - -export default AccessibleModalMenu; diff --git a/packages/app-mobile/components/accessibility/AccessibleView.test.tsx b/packages/app-mobile/components/accessibility/AccessibleView.test.tsx new file mode 100644 index 0000000000..b62acca65f --- /dev/null +++ b/packages/app-mobile/components/accessibility/AccessibleView.test.tsx @@ -0,0 +1,75 @@ +import * as React from 'react'; +import FocusControl from './FocusControl/FocusControl'; +import { render } from '@testing-library/react-native'; +import AccessibleView from './AccessibleView'; +import { AccessibilityInfo } from 'react-native'; +import ModalWrapper from './FocusControl/ModalWrapper'; +import { ModalState } from './FocusControl/types'; + +interface TestContentWrapperProps { + mainContent: React.ReactNode; + dialogs: React.ReactNode; +} + +const TestContentWrapper: React.FC = props => { + return + {props.dialogs} + + {props.mainContent} + + ; +}; + +jest.mock('react-native', () => { + const ReactNative = jest.requireActual('react-native'); + ReactNative.AccessibilityInfo.setAccessibilityFocus = jest.fn(); + return ReactNative; +}); + +describe('AccessibleView', () => { + test('should wait for the currently-open dialog to dismiss before applying focus requests', () => { + const setFocusMock = AccessibilityInfo.setAccessibilityFocus as jest.Mock; + setFocusMock.mockClear(); + + interface TestContentOptions { + modalState: ModalState; + refocusCounter: undefined|number; + } + const renderTestContent = ({ modalState, refocusCounter }: TestContentOptions) => { + const mainContent = ; + const visibleDialog = {null}; + return ; + }; + + render(renderTestContent({ + refocusCounter: undefined, + modalState: ModalState.Open, + })); + + // Increasing the refocusCounter for a background view while a dialog is visible + // should not try to focus the background view. + render(renderTestContent({ + refocusCounter: 1, + modalState: ModalState.Open, + })); + expect(setFocusMock).not.toHaveBeenCalled(); + + // Focus should not be set until done closing + render(renderTestContent({ + refocusCounter: 1, + modalState: ModalState.Closing, + })); + expect(setFocusMock).not.toHaveBeenCalled(); + + // Keeping the same refocus counter, but dismissing the dialog should focus + // the test view. + render(renderTestContent({ + refocusCounter: 1, + modalState: ModalState.Closed, + })); + expect(setFocusMock).toHaveBeenCalled(); + }); +}); diff --git a/packages/app-mobile/components/accessibility/AccessibleView.tsx b/packages/app-mobile/components/accessibility/AccessibleView.tsx index 519fa9baeb..668d16afdc 100644 --- a/packages/app-mobile/components/accessibility/AccessibleView.tsx +++ b/packages/app-mobile/components/accessibility/AccessibleView.tsx @@ -1,8 +1,9 @@ -import { focus } from '@joplin/lib/utils/focusHandler'; -import Logger from '@joplin/utils/Logger'; import * as React from 'react'; -import { useEffect, useState } from 'react'; -import { AccessibilityInfo, findNodeHandle, Platform, UIManager, View, ViewProps } from 'react-native'; +import { useContext, useEffect, useRef, useState } from 'react'; +import { Platform, View, ViewProps } from 'react-native'; +import { AutoFocusContext } from './FocusControl/AutoFocusProvider'; +import Logger from '@joplin/utils/Logger'; +import focusView from '../../utils/focusView'; const logger = Logger.create('AccessibleView'); @@ -16,9 +17,42 @@ interface Props extends ViewProps { refocusCounter?: number; } +const useAutoFocus = (refocusCounter: number|null, containerNode: View|HTMLElement|null, debugLabel: string) => { + const autoFocusControl = useContext(AutoFocusContext); + const autoFocusControlRef = useRef(autoFocusControl); + autoFocusControlRef.current = autoFocusControl; + const debugLabelRef = useRef(debugLabel); + debugLabelRef.current = debugLabel; + + useEffect(() => { + if ((refocusCounter ?? null) === null) return () => {}; + if (!containerNode) return () => {}; + + const focusContainer = () => { + focusView(`AccessibleView::${debugLabelRef.current}`, containerNode); + }; + + const canFocusNow = !autoFocusControlRef.current || autoFocusControlRef.current.canAutoFocus(); + if (canFocusNow) { + focusContainer(); + return () => {}; + } else { // Delay autofocus + logger.debug(`Delaying autofocus for ${debugLabelRef.current}`); + // Allows the view to be refocused when, for example, a dialog is dismissed + autoFocusControlRef.current?.setAutofocusCallback(focusContainer); + return () => { + autoFocusControlRef.current?.removeAutofocusCallback(focusContainer); + }; + } + }, [containerNode, refocusCounter]); +}; + const AccessibleView: React.FC = ({ inert, refocusCounter, children, ...viewProps }) => { const [containerRef, setContainerRef] = useState(null); + const debugLabel = viewProps.testID ?? 'AccessibleView'; + useAutoFocus(refocusCounter, containerRef, debugLabel); + // On web, there's no clear way to disable keyboard focus for an element **and its descendants** // without accessing the underlying HTML. useEffect(() => { @@ -32,39 +66,6 @@ const AccessibleView: React.FC = ({ inert, refocusCounter, children, ...v } }, [containerRef, inert]); - useEffect(() => { - if ((refocusCounter ?? null) === null) return; - if (!containerRef) return; - - const autoFocus = () => { - if (Platform.OS === 'web') { - // react-native-web defines UIManager.focus for setting the keyboard focus. However, - // this property is not available in standard react-native. As such, access it using type - // narrowing: - // eslint-disable-next-line no-restricted-properties - if (!('focus' in UIManager) || typeof UIManager.focus !== 'function') { - throw new Error('Failed to focus sidebar. UIManager.focus is not a function.'); - } - - // Disable the "use focusHandler for all focus calls" rule -- UIManager.focus requires - // an argument, which is not supported by focusHandler. - // eslint-disable-next-line no-restricted-properties - UIManager.focus(containerRef); - } else { - const handle = findNodeHandle(containerRef as View); - if (handle !== null) { - AccessibilityInfo.setAccessibilityFocus(handle); - } else { - logger.warn('Couldn\'t find a view to focus.'); - } - } - }; - - focus('AccessibleView', { - focus: autoFocus, - }); - }, [containerRef, refocusCounter]); - const canFocus = (refocusCounter ?? null) !== null; return void; + +interface AutoFocusControl { + // It isn't always possible to autofocus (e.g. due to a dialog obscuring focus). + canAutoFocus(): boolean; + + // Sets the callback to be triggered when it becomes possible to autofocus + setAutofocusCallback(callback: AutoFocusCallback): void; + removeAutofocusCallback(callback: AutoFocusCallback): void; +} + +export const AutoFocusContext = createContext(null); + +interface Props { + children: React.ReactNode; + allowAutoFocus: boolean; +} + +const AutoFocusProvider: React.FC = ({ allowAutoFocus, children }) => { + const [autoFocusCallback, setAutofocusCallback] = useState(null); + const allowAutoFocusRef = useRef(allowAutoFocus); + allowAutoFocusRef.current = allowAutoFocus; + + useEffect(() => { + if (allowAutoFocus && autoFocusCallback) { + autoFocusCallback(); + setAutofocusCallback(null); + } + }, [autoFocusCallback, allowAutoFocus]); + + const removeAutofocusCallback = useCallback((toRemove: AutoFocusCallback) => { + setAutofocusCallback(callback => { + // Update the callback only if it's different + if (callback === toRemove) { + return null; + } else { + return callback; + } + }); + }, []); + + const autoFocusControl = useMemo((): AutoFocusControl => { + return { + canAutoFocus: () => { + return allowAutoFocusRef.current; + }, + setAutofocusCallback: (callback) => { + setAutofocusCallback(() => callback); + }, + removeAutofocusCallback, + }; + }, [removeAutofocusCallback, setAutofocusCallback]); + + return + {children} + ; +}; + +export default AutoFocusProvider; diff --git a/packages/app-mobile/components/accessibility/FocusControl/FocusControl.tsx b/packages/app-mobile/components/accessibility/FocusControl/FocusControl.tsx new file mode 100644 index 0000000000..5c3cbd380a --- /dev/null +++ b/packages/app-mobile/components/accessibility/FocusControl/FocusControl.tsx @@ -0,0 +1,11 @@ +import FocusControlProvider from './FocusControlProvider'; +import MainAppContent from './MainAppContent'; +import ModalWrapper from './ModalWrapper'; + +const FocusControl = { + Provider: FocusControlProvider, + ModalWrapper, + MainAppContent, +}; + +export default FocusControl; diff --git a/packages/app-mobile/components/accessibility/FocusControl/FocusControlProvider.tsx b/packages/app-mobile/components/accessibility/FocusControl/FocusControlProvider.tsx new file mode 100644 index 0000000000..6f9b68941a --- /dev/null +++ b/packages/app-mobile/components/accessibility/FocusControl/FocusControlProvider.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import { createContext, useCallback, useMemo, useState } from 'react'; +import { ModalState } from './types'; + +export interface FocusControl { + setModalState(dialogId: string, state: ModalState): void; + + hasOpenModal: boolean; + hasClosingModal: boolean; +} + +export const FocusControlContext = createContext(null); + +interface Props { + children: React.ReactNode; +} + +const FocusControlProvider: React.FC = props => { + type ModalStates = Record; + const [modalStates, setModalStates] = useState({}); + + const setModalOpen = useCallback((dialogId: string, state: ModalState) => { + setModalStates(modalStates => { + modalStates = { ...modalStates }; + if (state === ModalState.Closed) { + delete modalStates[dialogId]; + } else { + modalStates[dialogId] = state; + } + return modalStates; + }); + }, []); + + const modalStateValues = Object.values(modalStates); + const hasOpenModal = modalStateValues.includes(ModalState.Open); + const hasClosingModal = modalStateValues.includes(ModalState.Closing); + + const focusControl = useMemo((): FocusControl => { + return { + hasOpenModal: hasOpenModal, + hasClosingModal: hasClosingModal, + setModalState: setModalOpen, + }; + }, [hasOpenModal, hasClosingModal, setModalOpen]); + + return + {props.children} + ; +}; + +export default FocusControlProvider; diff --git a/packages/app-mobile/components/accessibility/FocusControl/MainAppContent.tsx b/packages/app-mobile/components/accessibility/FocusControl/MainAppContent.tsx new file mode 100644 index 0000000000..ffaaa271da --- /dev/null +++ b/packages/app-mobile/components/accessibility/FocusControl/MainAppContent.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import AccessibleView from '../AccessibleView'; +import { FocusControlContext } from './FocusControlProvider'; +import { useContext } from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; +import AutoFocusProvider from './AutoFocusProvider'; + +interface Props { + children: React.ReactNode; + style?: StyleProp; +} + +// A region that should not be accessibility focusable while a dialog +// is open. +const MainAppContent: React.FC = props => { + const { hasOpenModal, hasClosingModal } = useContext(FocusControlContext); + const blockFocus = hasOpenModal; + const allowAutoFocus = !hasClosingModal && !blockFocus; + + return + + {props.children} + + ; +}; + +export default MainAppContent; diff --git a/packages/app-mobile/components/accessibility/FocusControl/ModalWrapper.tsx b/packages/app-mobile/components/accessibility/FocusControl/ModalWrapper.tsx new file mode 100644 index 0000000000..ed22886229 --- /dev/null +++ b/packages/app-mobile/components/accessibility/FocusControl/ModalWrapper.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { useContext, useEffect, useId } from 'react'; +import { FocusControlContext } from './FocusControlProvider'; +import { ModalState } from './types'; + +interface Props { + children: React.ReactNode; + state: ModalState; +} + +// A wrapper component that notifies the focus handler that a modal-like component +// is visible. Modals that capture focus should wrap their content in this component. +const ModalWrapper: React.FC = props => { + const { setModalState: setDialogState } = useContext(FocusControlContext); + const id = useId(); + useEffect(() => { + if (!setDialogState) { + throw new Error('ModalContent components must have a FocusControlProvider as an ancestor. Is FocusControlProvider part of the provider stack?'); + } + setDialogState(id, props.state); + }, [id, props.state, setDialogState]); + + useEffect(() => { + return () => { + setDialogState?.(id, ModalState.Closed); + }; + }, [id, setDialogState]); + + return <> + {props.children} + ; +}; + +export default ModalWrapper; diff --git a/packages/app-mobile/components/accessibility/FocusControl/types.ts b/packages/app-mobile/components/accessibility/FocusControl/types.ts new file mode 100644 index 0000000000..6b4853c62b --- /dev/null +++ b/packages/app-mobile/components/accessibility/FocusControl/types.ts @@ -0,0 +1,13 @@ + +// eslint-disable-next-line import/prefer-default-export -- FocusControl currently only has one shared type for external use +export enum ModalState { + // When `Open`, a modal blocks focus for the main app content. + Open, + // When `Closing`, a modal doesn't block main app content focus, but focus + // shouldn't be moved to the main app content yet. + // This is useful for native Modals, which have their own focus handling logic. + // If Joplin moves accessibility focus before the native Modal focus handling + // has completed, the Joplin-specified accessibility focus may be ignored. + Closing, + Closed, +} diff --git a/packages/app-mobile/components/app-nav.tsx b/packages/app-mobile/components/app-nav.tsx index 9248fb7140..f56ad87af3 100644 --- a/packages/app-mobile/components/app-nav.tsx +++ b/packages/app-mobile/components/app-nav.tsx @@ -1,16 +1,13 @@ import * as React from 'react'; import { connect } from 'react-redux'; -import NotesScreen from './screens/Notes'; +import NotesScreen from './screens/Notes/Notes'; import SearchScreen from './screens/SearchScreen'; -import { Component } from 'react'; -import { KeyboardAvoidingView, Keyboard, Platform, View, KeyboardEvent, Dimensions, EmitterSubscription } from 'react-native'; +import { KeyboardAvoidingView, Platform, View } from 'react-native'; import { AppState } from '../utils/types'; import { themeStyle } from './global-style'; - -interface State { - autoCompletionBarExtraHeight: number; - floatingKeyboardEnabled: boolean; -} +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import useKeyboardState from '../utils/hooks/useKeyboardState'; +import usePrevious from '@joplin/lib/hooks/usePrevious'; interface Props { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied @@ -22,107 +19,58 @@ interface Props { themeId: number; } -class AppNavComponent extends Component { - private previousRouteName_: string|null = null; - private keyboardDidShowListener: EmitterSubscription|null = null; - private keyboardDidHideListener: EmitterSubscription|null = null; - private keyboardWillChangeFrameListener: EmitterSubscription|null = null; +const AppNavComponent: React.FC = (props) => { + const keyboardState = useKeyboardState(); + const safeAreaPadding = useSafeAreaInsets(); - public constructor(props: Props) { - super(props); + if (!props.route) throw new Error('Route must not be null'); - this.previousRouteName_ = null; - this.state = { - autoCompletionBarExtraHeight: 0, // Extra padding for the auto completion bar at the top of the keyboard - floatingKeyboardEnabled: false, - }; + // Note: certain screens are kept into memory, in particular Notes and Search + // so that the scroll position is not lost when the user navigate away from them. + + const route = props.route; + let Screen = null; + let notesScreenVisible = false; + let searchScreenVisible = false; + + if (route.routeName === 'Notes') { + notesScreenVisible = true; + } else if (route.routeName === 'Search') { + searchScreenVisible = true; + } else { + Screen = props.screens[route.routeName].screen; } - public UNSAFE_componentWillMount() { - if (Platform.OS === 'ios') { - this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow.bind(this)); - this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide.bind(this)); - this.keyboardWillChangeFrameListener = Keyboard.addListener('keyboardWillChangeFrame', this.keyboardWillChangeFrame); - } - } + const previousRouteName = usePrevious(route.routeName, ''); - public componentWillUnmount() { - this.keyboardDidShowListener?.remove(); - this.keyboardDidHideListener?.remove(); - this.keyboardWillChangeFrameListener?.remove(); + // Keep the search screen loaded if the user is viewing a note from that search screen + // so that if the back button is pressed, the screen is still loaded. However, unload + // it if navigating away. + const searchScreenLoaded = searchScreenVisible || (previousRouteName === 'Search' && route.routeName === 'Note'); - this.keyboardDidShowListener = null; - this.keyboardDidHideListener = null; - this.keyboardWillChangeFrameListener = null; - } + const theme = themeStyle(props.themeId); - public keyboardDidShow() { - this.setState({ autoCompletionBarExtraHeight: 30 }); - } + const style = { flex: 1, backgroundColor: theme.backgroundColor }; - public keyboardDidHide() { - this.setState({ autoCompletionBarExtraHeight: 0 }); - } + // When the floating keyboard is enabled, the KeyboardAvoidingView can have a very small + // height. Don't use the KeyboardAvoidingView when the floating keyboard is enabled. + // See https://github.com/facebook/react-native/issues/29473 + const keyboardAvoidingViewEnabled = !keyboardState.isFloatingKeyboard; + const autocompletionBarPadding = Platform.OS === 'ios' && keyboardState.keyboardVisible ? safeAreaPadding.top : 0; - private keyboardWillChangeFrame = (evt: KeyboardEvent) => { - const windowWidth = Dimensions.get('window').width; - - // If the keyboard isn't as wide as the window, the floating keyboard is disabled. - // See https://github.com/facebook/react-native/issues/29473#issuecomment-696658937 - this.setState({ - floatingKeyboardEnabled: evt.endCoordinates.width < windowWidth, - }); - }; - - public render() { - if (!this.props.route) throw new Error('Route must not be null'); - - // Note: certain screens are kept into memory, in particular Notes and Search - // so that the scroll position is not lost when the user navigate away from them. - - const route = this.props.route; - let Screen = null; - let notesScreenVisible = false; - let searchScreenVisible = false; - - if (route.routeName === 'Notes') { - notesScreenVisible = true; - } else if (route.routeName === 'Search') { - searchScreenVisible = true; - } else { - Screen = this.props.screens[route.routeName].screen; - } - - // Keep the search screen loaded if the user is viewing a note from that search screen - // so that if the back button is pressed, the screen is still loaded. However, unload - // it if navigating away. - const searchScreenLoaded = searchScreenVisible || (this.previousRouteName_ === 'Search' && route.routeName === 'Note'); - - this.previousRouteName_ = route.routeName; - - const theme = themeStyle(this.props.themeId); - - const style = { flex: 1, backgroundColor: theme.backgroundColor }; - - // When the floating keyboard is enabled, the KeyboardAvoidingView can have a very small - // height. Don't use the KeyboardAvoidingView when the floating keyboard is enabled. - // See https://github.com/facebook/react-native/issues/29473 - const keyboardAvoidingViewEnabled = !this.state.floatingKeyboardEnabled; - - return ( - - - {searchScreenLoaded && } - {!notesScreenVisible && !searchScreenVisible && } - - - ); - } -} + return ( + + + {searchScreenLoaded && } + {!notesScreenVisible && !searchScreenVisible && } + + + ); +}; const AppNav = connect((state: AppState) => { return { diff --git a/packages/app-mobile/components/buttons/FloatingActionButton.tsx b/packages/app-mobile/components/buttons/FloatingActionButton.tsx index 8e241f97e4..08fa6c7461 100644 --- a/packages/app-mobile/components/buttons/FloatingActionButton.tsx +++ b/packages/app-mobile/components/buttons/FloatingActionButton.tsx @@ -1,16 +1,13 @@ -const React = require('react'); -import { useState, useCallback, useMemo } from 'react'; -import { FAB, Portal } from 'react-native-paper'; +import * as React from 'react'; +import { useState, useCallback, useMemo, useRef } from 'react'; +import { FAB } from 'react-native-paper'; import { _ } from '@joplin/lib/locale'; import { Dispatch } from 'redux'; -import { Platform, View, ViewStyle } from 'react-native'; -import shim from '@joplin/lib/shim'; -import AccessibleWebMenu from '../accessibility/AccessibleModalMenu'; +import { AccessibilityActionEvent, AccessibilityActionInfo, View } from 'react-native'; +import { connect } from 'react-redux'; +import BottomDrawer from '../BottomDrawer'; const Icon = require('react-native-vector-icons/Ionicons').default; -// eslint-disable-next-line no-undef -- Don't know why it says React is undefined when it's defined above -type FABGroupProps = React.ComponentProps; - type OnButtonPress = ()=> void; interface ButtonSpec { icon: string; @@ -20,14 +17,18 @@ interface ButtonSpec { } interface ActionButtonProps { - buttons?: ButtonSpec[]; - // If not given, an "add" button will be used. - mainButton?: ButtonSpec; + mainButton: ButtonSpec; dispatch: Dispatch; -} -const defaultOnPress = () => {}; + menuContent?: React.ReactNode; + onMenuShow?: ()=> void; + + accessibilityActions?: readonly AccessibilityActionInfo[]; + // Can return a Promise to simplify unit testing + onAccessibilityAction?: (event: AccessibilityActionEvent)=> void|Promise; + accessibilityHint?: string; +} // Returns a render function compatible with React Native Paper. const getIconRenderFunction = (iconName: string) => { @@ -43,95 +44,55 @@ const useIcon = (iconName: string) => { const FloatingActionButton = (props: ActionButtonProps) => { const [open, setOpen] = useState(false); - const onMenuToggled: FABGroupProps['onStateChange'] = useCallback(state => { + const onMenuToggled = useCallback(() => { props.dispatch({ type: 'SIDE_MENU_CLOSE', }); - setOpen(state.open); - }, [setOpen, props.dispatch]); + const newOpen = !open; + setOpen(newOpen); + }, [setOpen, open, props.dispatch]); - const actions = useMemo(() => (props.buttons ?? []).map(button => { - return { - ...button, - icon: getIconRenderFunction(button.icon), - onPress: button.onPress ?? defaultOnPress, - }; - }), [props.buttons]); + const onDismiss = useCallback(() => { + if (open) onMenuToggled(); + }, [open, onMenuToggled]); + + const mainButtonRef = useRef(); const closedIcon = useIcon(props.mainButton?.icon ?? 'add'); const openIcon = useIcon('close'); - // To work around an Android accessibility bug, we decrease the - // size of the container for the FAB. According to the documentation for - // RN Paper, a large action button has size 96x96. As such, we allocate - // a larger than this space for the button. - // - // To prevent the accessibility issue from regressing (which makes it - // very hard to access some UI features), we also enable this when Talkback - // is disabled. - // - // See https://github.com/callstack/react-native-paper/issues/4064 - // May be possible to remove if https://github.com/callstack/react-native-paper/pull/4514 - // is merged. - const adjustMargins = !open && shim.mobilePlatform() === 'android'; - const marginStyles = useMemo((): ViewStyle => { - if (!adjustMargins) { - return {}; - } - - // Internally, React Native Paper uses absolute positioning to make its - // (usually invisible) view fill the screen. Setting top and left to - // undefined causes the view to take up only part of the screen. - return { - top: undefined, - left: undefined, - }; - }, [adjustMargins]); - const label = props.mainButton?.label ?? _('Add new'); - // On Web, FAB.Group can't be used at all with accessibility tools. Work around this - // by hiding the FAB for accessibility, and providing a screen-reader-only custom menu. - const isWeb = Platform.OS === 'web'; - const accessibleMenu = isWeb ? ( - - ) : null; - - const menuContent = ; - const mainMenu = isWeb ? ( - {menuContent} - ) : menuContent; - return ( - - {mainMenu} - {accessibleMenu} - - ); + return <> + + {menuButton} + + + {props.menuContent} + + ; }; -export default FloatingActionButton; +export default connect()(FloatingActionButton); diff --git a/packages/app-mobile/components/buttons/LabelledIconButton.tsx b/packages/app-mobile/components/buttons/LabelledIconButton.tsx new file mode 100644 index 0000000000..fdbfb116aa --- /dev/null +++ b/packages/app-mobile/components/buttons/LabelledIconButton.tsx @@ -0,0 +1,67 @@ +import * as React from 'react'; +import { Text, TouchableRipple } from 'react-native-paper'; +import Icon from '../Icon'; +import { themeStyle } from '../global-style'; +import { connect } from 'react-redux'; +import { AppState } from '../../utils/types'; +import { StyleSheet, View, ViewProps } from 'react-native'; +import { useMemo } from 'react'; + +interface Props extends ViewProps { + themeId: number; + title: string; + icon: string; + onPress: ()=> void; +} + +const useStyles = (themeId: number) => { + return useMemo(() => { + const theme = themeStyle(themeId); + + return StyleSheet.create({ + icon: { + fontSize: 27, + width: 44, + height: 44, + textAlign: 'center', + overflow: 'hidden', + + color: theme.color3, + borderColor: theme.codeBorderColor, // TODO: Use a different theme variable + borderRadius: 22, + padding: 6, + borderWidth: 2, + backgroundColor: theme.backgroundColor3, + }, + buttonContent: { + flexDirection: 'column', + alignItems: 'center', + gap: 6, + }, + button: { + borderRadius: 8, + padding: 8, + }, + }); + }, [themeId]); +}; + +const LabelledIconButton: React.FC = ({ title, icon, style, themeId, ...otherProps }) => { + const styles = useStyles(themeId); + return + + + {title} + + ; +}; + +export default connect((state: AppState) => { + return { themeId: state.settings.theme }; +})(LabelledIconButton); diff --git a/packages/app-mobile/components/buttons/TextButton.tsx b/packages/app-mobile/components/buttons/TextButton.tsx index acc93b574e..dbc7da4072 100644 --- a/packages/app-mobile/components/buttons/TextButton.tsx +++ b/packages/app-mobile/components/buttons/TextButton.tsx @@ -4,6 +4,7 @@ import { themeStyle } from '../global-style'; import { Button, ButtonProps } from 'react-native-paper'; import { connect } from 'react-redux'; import { AppState } from '../../utils/types'; +import { TextStyle, StyleSheet, ViewStyle, StyleProp } from 'react-native'; export enum ButtonType { Primary, @@ -12,9 +13,16 @@ export enum ButtonType { Link, } -interface Props extends Omit { +export enum ButtonSize { + Normal, + Larger, +} + +interface Props extends Omit { themeId: number; type: ButtonType; + size?: ButtonSize; + style?: TextStyle; onPress: ()=> void; children: ReactNode; } @@ -41,12 +49,25 @@ const useStyles = ({ themeId }: Props) => { primaryButton: { }, }; - return { themeOverride }; + return { + themeOverride, + styles: StyleSheet.create({ + largeContainer: { + paddingVertical: 2, + borderWidth: 2, + borderRadius: 10, + }, + largeLabel: { + fontSize: theme.fontSize, + fontWeight: 'bold', + }, + }), + }; }, [themeId]); }; const TextButton: React.FC = props => { - const { themeOverride } = useStyles(props); + const { themeOverride, styles } = useStyles(props); let mode: ButtonProps['mode']; let theme: ButtonProps['theme']; @@ -68,8 +89,19 @@ const TextButton: React.FC = props => { return exhaustivenessCheck; } + let labelStyle: TextStyle|undefined = undefined; + const containerStyle: StyleProp[] = []; + if (props.size === ButtonSize.Larger) { + labelStyle = styles.largeLabel; + containerStyle.push(styles.largeContainer); + } + + if (props.style) containerStyle.push(props.style); + return ; const allowReDownload = recorderState === RecorderState.Error || modelIsOutdated; @@ -173,6 +197,7 @@ const SpeechToTextComponent: React.FC = props => { {allowReDownload ? reDownloadButton : null} {_('Done')} ; diff --git a/packages/app-mobile/ios/.xcode.env b/packages/app-mobile/ios/.xcode.env index b32032ca15..4d27f3bf56 100644 --- a/packages/app-mobile/ios/.xcode.env +++ b/packages/app-mobile/ios/.xcode.env @@ -7,4 +7,7 @@ # Customize the NODE_BINARY variable here. # For example, to use nvm with brew, add the following line # . "$(brew --prefix nvm)/nvm.sh" --no-use -export NODE_BINARY=$(command -v node) + +# Note: `$(command -v node)` doesn't work so hardcode the path here - but it means it needs to be +# manually updated when Node is upgraded. +export NODE_BINARY=/opt/homebrew/opt/node@20/bin/node diff --git a/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj b/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj index 0b9a7ba33c..d5148f3dad 100644 --- a/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj +++ b/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj @@ -535,13 +535,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements; - CURRENT_PROJECT_VERSION = 134; + CURRENT_PROJECT_VERSION = 136; DEVELOPMENT_TEAM = A9BXAFS6CT; ENABLE_BITCODE = NO; INFOPLIST_FILE = Joplin/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 13.3.1; + MARKETING_VERSION = 13.3.3; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -567,12 +567,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements; - CURRENT_PROJECT_VERSION = 134; + CURRENT_PROJECT_VERSION = 136; DEVELOPMENT_TEAM = A9BXAFS6CT; INFOPLIST_FILE = Joplin/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 13.3.1; + MARKETING_VERSION = 13.3.3; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -758,14 +758,14 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 134; + CURRENT_PROJECT_VERSION = 136; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = A9BXAFS6CT; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 13.3.1; + MARKETING_VERSION = 13.3.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( @@ -797,14 +797,14 @@ CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 134; + CURRENT_PROJECT_VERSION = 136; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = A9BXAFS6CT; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 13.3.1; + MARKETING_VERSION = 13.3.3; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( "$(inherited)", diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/Contents.json b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/Contents.json index 523aa8211b..3c7e2a44da 100755 --- a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,116 +1,517 @@ { - "images": [ + "images" : [ { - "filename": "ios_marketing1024x1024.png", - "idiom": "ios-marketing", - "size": "1024x1024", - "scale": "1x" + "filename" : "ios20x20@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "20x20" }, { - "filename": "iphone_notification20x20@2x.png", - "idiom": "iphone", - "size": "20x20", - "scale": "2x" + "filename" : "ios20x20@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "20x20" }, { - "filename": "iphone_notification20x20@3x.png", - "idiom": "iphone", - "size": "20x20", - "scale": "3x" + "filename" : "ios29x29@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "29x29" }, { - "filename": "iphone_settings29x29@2x.png", - "idiom": "iphone", - "size": "29x29", - "scale": "2x" + "filename" : "ios29x29@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "29x29" }, { - "filename": "iphone_settings29x29@3x.png", - "idiom": "iphone", - "size": "29x29", - "scale": "3x" + "filename" : "ios38x38@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "38x38" }, { - "filename": "iphone_spotlight40x40@2x.png", - "idiom": "iphone", - "size": "40x40", - "scale": "2x" + "filename" : "ios38x38@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "38x38" }, { - "filename": "iphone_spotlight40x40@3x.png", - "idiom": "iphone", - "size": "40x40", - "scale": "3x" + "filename" : "ios40x40@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "40x40" }, { - "filename": "iphone_app60x60@2x.png", - "idiom": "iphone", - "size": "60x60", - "scale": "2x" + "filename" : "ios40x40@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "40x40" }, { - "filename": "iphone_app60x60@3x.png", - "idiom": "iphone", - "size": "60x60", - "scale": "3x" + "filename" : "ios60x60@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "60x60" }, { - "filename": "ipad_notification20x20.png", - "idiom": "ipad", - "size": "20x20", - "scale": "1x" + "filename" : "ios60x60@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "60x60" }, { - "filename": "ipad_notification20x20@2x.png", - "idiom": "ipad", - "size": "20x20", - "scale": "2x" + "filename" : "ios64x64@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "64x64" }, { - "filename": "ipad_settings29x29.png", - "idiom": "ipad", - "size": "29x29", - "scale": "1x" + "filename" : "ios64x64@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "64x64" }, { - "filename": "ipad_settings29x29@2x.png", - "idiom": "ipad", - "size": "29x29", - "scale": "2x" + "filename" : "ios68x68@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "68x68" }, { - "filename": "ipad_spotlight40x40.png", - "idiom": "ipad", - "size": "40x40", - "scale": "1x" + "filename" : "ios76x76@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "76x76" }, { - "filename": "ipad_spotlight40x40@2x.png", - "idiom": "ipad", - "size": "40x40", - "scale": "2x" + "filename" : "ios83.5x83.5@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "83.5x83.5" }, { - "filename": "ipad_app76x76.png", - "idiom": "ipad", - "size": "76x76", - "scale": "1x" + "filename" : "ios1024x1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" }, { - "filename": "ipad_app76x76@2x.png", - "idiom": "ipad", - "size": "76x76", - "scale": "2x" + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark20x20@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "20x20" }, { - "filename": "ipad_pro_app83.5x83.5@2x.png", - "idiom": "ipad", - "size": "83.5x83.5", - "scale": "2x" + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark20x20@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "20x20" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark29x29@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "29x29" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark29x29@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "29x29" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark38x38@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "38x38" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark38x38@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "38x38" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark40x40@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "40x40" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark40x40@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "40x40" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark60x60@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "60x60" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark60x60@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "60x60" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark64x64@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "64x64" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark64x64@3x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "64x64" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark68x68@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "68x68" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark76x76@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "76x76" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark83.5x83.5@2x.png", + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "ios_dark1024x1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "20x20" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "20x20" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "29x29" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "29x29" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "38x38" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "38x38" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "40x40" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "40x40" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "60x60" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "60x60" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "64x64" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "3x", + "size" : "64x64" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "68x68" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "76x76" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" } ], - "info": { - "version": 1, - "author": "xcode" + "info" : { + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios1024x1024.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios1024x1024.png new file mode 100644 index 0000000000..3a2f2ef03a Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios1024x1024.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios20x20@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios20x20@2x.png new file mode 100644 index 0000000000..980bedd0a1 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios20x20@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios20x20@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios20x20@3x.png new file mode 100644 index 0000000000..a8bb462fac Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios20x20@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios29x29@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios29x29@2x.png new file mode 100644 index 0000000000..0d279224ed Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios29x29@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios29x29@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios29x29@3x.png new file mode 100644 index 0000000000..6941164577 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios29x29@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios38x38@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios38x38@2x.png new file mode 100644 index 0000000000..cff0e76e90 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios38x38@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios38x38@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios38x38@3x.png new file mode 100644 index 0000000000..36f3a5c12a Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios38x38@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios40x40@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios40x40@2x.png new file mode 100644 index 0000000000..0387879f1a Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios40x40@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios40x40@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios40x40@3x.png new file mode 100644 index 0000000000..a014269344 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios40x40@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios60x60@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios60x60@2x.png new file mode 100644 index 0000000000..a014269344 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios60x60@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios60x60@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios60x60@3x.png new file mode 100644 index 0000000000..7f0a517afd Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios60x60@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios64x64@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios64x64@2x.png new file mode 100644 index 0000000000..b494e80019 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios64x64@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios64x64@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios64x64@3x.png new file mode 100644 index 0000000000..a11d1561ef Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios64x64@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios68x68@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios68x68@2x.png new file mode 100644 index 0000000000..5d891ffc21 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios68x68@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios76x76@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios76x76@2x.png new file mode 100644 index 0000000000..29a95a07e3 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios76x76@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios83.5x83.5@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios83.5x83.5@2x.png new file mode 100644 index 0000000000..9f5a3207ef Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios83.5x83.5@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark1024x1024.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark1024x1024.png new file mode 100644 index 0000000000..543f02ed5a Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark1024x1024.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark20x20@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark20x20@2x.png new file mode 100644 index 0000000000..5ac5e5b8fa Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark20x20@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark20x20@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark20x20@3x.png new file mode 100644 index 0000000000..fdeb1d40cd Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark20x20@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark29x29@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark29x29@2x.png new file mode 100644 index 0000000000..defd4a49b5 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark29x29@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark29x29@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark29x29@3x.png new file mode 100644 index 0000000000..3bff8a514a Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark29x29@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark38x38@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark38x38@2x.png new file mode 100644 index 0000000000..c600ac78f7 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark38x38@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark38x38@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark38x38@3x.png new file mode 100644 index 0000000000..48f2fa8d53 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark38x38@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark40x40@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark40x40@2x.png new file mode 100644 index 0000000000..911738da85 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark40x40@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark40x40@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark40x40@3x.png new file mode 100644 index 0000000000..ee9f6b4b3a Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark40x40@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark60x60@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark60x60@2x.png new file mode 100644 index 0000000000..ee9f6b4b3a Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark60x60@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark60x60@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark60x60@3x.png new file mode 100644 index 0000000000..edeb879449 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark60x60@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark64x64@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark64x64@2x.png new file mode 100644 index 0000000000..ceda99737f Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark64x64@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark64x64@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark64x64@3x.png new file mode 100644 index 0000000000..cf5cd8624e Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark64x64@3x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark68x68@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark68x68@2x.png new file mode 100644 index 0000000000..e0895654af Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark68x68@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark76x76@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark76x76@2x.png new file mode 100644 index 0000000000..1fdaf9c5ba Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark76x76@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark83.5x83.5@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark83.5x83.5@2x.png new file mode 100644 index 0000000000..d852a9bb85 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark83.5x83.5@2x.png differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_marketing1024x1024.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_marketing1024x1024.png deleted file mode 100755 index 711e64a4f7..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_marketing1024x1024.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76.png deleted file mode 100755 index f7556cf247..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76@2x.png deleted file mode 100755 index bae6a354ae..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76@2x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20.png deleted file mode 100755 index 5c818e8470..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20@2x.png deleted file mode 100755 index c4b9e4c9e1..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20@2x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_pro_app83.5x83.5@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_pro_app83.5x83.5@2x.png deleted file mode 100755 index 9ea41ab0a1..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_pro_app83.5x83.5@2x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29.png deleted file mode 100755 index 957a5b4d20..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29@2x.png deleted file mode 100755 index bed468976e..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29@2x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40.png deleted file mode 100755 index c4b9e4c9e1..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40@2x.png deleted file mode 100755 index 2650ef5a5e..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40@2x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@2x.png deleted file mode 100755 index 2241e82565..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@2x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@3x.png deleted file mode 100755 index fb2f9c48fd..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@3x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@2x.png deleted file mode 100755 index c4b9e4c9e1..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@2x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@3x.png deleted file mode 100755 index 6d053189d7..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@3x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@2x.png deleted file mode 100755 index bed468976e..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@2x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@3x.png deleted file mode 100755 index ae72ab5e07..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@3x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@2x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@2x.png deleted file mode 100755 index 2650ef5a5e..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@2x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@3x.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@3x.png deleted file mode 100755 index 2241e82565..0000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@3x.png and /dev/null differ diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/Contents.json b/packages/app-mobile/ios/Joplin/Images.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/packages/app-mobile/ios/Joplin/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/app-mobile/ios/Podfile.lock b/packages/app-mobile/ios/Podfile.lock index 4f188a7406..fe0fbf63c6 100644 --- a/packages/app-mobile/ios/Podfile.lock +++ b/packages/app-mobile/ios/Podfile.lock @@ -1030,11 +1030,11 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-image-resizer (3.0.10): + - react-native-image-resizer (3.0.11): - React-Core - react-native-netinfo (11.3.3): - React-Core - - react-native-quick-crypto (0.7.5): + - react-native-quick-crypto (0.7.12): - DoubleConversion - glog - hermes-engine @@ -1063,27 +1063,6 @@ PODS: - React-Core - react-native-safe-area-context (4.10.8): - React-Core - - react-native-slider (4.4.4): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - react-native-sqlite-storage (6.0.1): - React-Core - react-native-version-info (1.1.1): @@ -1446,7 +1425,6 @@ DEPENDENCIES: - react-native-rsa-native (from `../node_modules/react-native-rsa-native`) - "react-native-saf-x (from `../node_modules/@joplin/react-native-saf-x`)" - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - - "react-native-slider (from `../node_modules/@react-native-community/slider`)" - react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`) - react-native-version-info (from `../node_modules/react-native-version-info`) - react-native-webview (from `../node_modules/react-native-webview`) @@ -1600,8 +1578,6 @@ EXTERNAL SOURCES: :path: "../node_modules/@joplin/react-native-saf-x" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" - react-native-slider: - :path: "../node_modules/@react-native-community/slider" react-native-sqlite-storage: :path: "../node_modules/react-native-sqlite-storage" react-native-version-info: @@ -1686,97 +1662,96 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: d3f49c53809116a5d38da093a8aa78bf551aed09 DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 - EXAV: afa491e598334bbbb92a92a2f4dd33d7149ad37f - EXConstants: 409690fbfd5afea964e5e9d6c4eb2c2b59222c59 - Expo: f3e39cddde295c79d206e972a59693cbb329ef46 - ExpoAsset: 323700f291684f110fb55f0d4022a3362ea9f875 - ExpoCamera: 929be541d1c1319fcf32f9f5d9df8b97804346b5 - ExpoFileSystem: 80bfe850b1f9922c16905822ecbf97acd711dc51 - ExpoFont: 00756e6c796d8f7ee8d211e29c8b619e75cbf238 - ExpoModulesCore: 5440e96a8ee014f4fd88e77264985fd0a65f5f8c + EXAV: 64e72329d2f8c2ba13608fed4a713af4e793242d + EXConstants: 89d35611505a8ce02550e64e43cd05565da35f9a + Expo: 647dd60db7a75898e3148258fb018b8fdb686291 + ExpoAsset: 286fee7ba711ce66bf20b315e68106b13b8629fc + ExpoCamera: cf49d2d121a9f883be0f98dde15a2185a1dd42be + ExpoFileSystem: 2988caaf68b7cb706e36d382829d99811d9d76a5 + ExpoFont: 38dddf823e32740c2a9f37c926a33aeca736b5c4 + ExpoModulesCore: cad1227f619a67c82c52e0e71fc70514cd93def8 FBLazyVector: 898d14d17bf19e2435cafd9ea2a1033efe445709 fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 hermes-engine: 16b8530de1b383cdada1476cf52d1b52f0692cbc JoplinCommonShareExtension: a8b60b02704d85a7305627912c0240e94af78db7 - JoplinRNShareExtension: 485f3e6dad83b7b77f1572eabc249f869ee55c02 + JoplinRNShareExtension: e158a4b53ee0aa9cd3037a16221dc8adbd6f7860 OpenSSL-Universal: b60a3702c9fea8b3145549d421fdb018e53ab7b4 - RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47 + RCT-Folly: 5dc73daec3476616d19e8a53f0156176f7b55461 RCTDeprecation: efb313d8126259e9294dc4ee0002f44a6f676aba RCTRequired: f49ea29cece52aee20db633ae7edc4b271435562 RCTTypeSafety: a11979ff0570d230d74de9f604f7d19692157bc4 React: 88794fad7f460349dbc9df8a274d95f37a009f5d React-callinvoker: 7a7023e34a55c89ea2aa62486bb3c1164ab0be0c - React-Codegen: af31a9323ce23988c255c9afd0ae9415ff894939 - React-Core: 60075333bc22b5a793d3f62e207368b79bff2e64 - React-CoreModules: 147c314d6b3b1e069c9ad64cbbbeba604854ff86 - React-cxxreact: 5de27fd8bff4764acb2eac3ee66001e0e2b910e7 + React-Codegen: 118828b0731a9ecf9021270b788f958f9ccb2e19 + React-Core: 74cc07109071b230de904d394c2bf15b9f886bff + React-CoreModules: 8beb4863375aafeac52c49a3962b81d137577585 + React-cxxreact: d0b0d575214ba236dff569e14dd4411ac82b3566 React-debug: 6397f0baf751b40511d01e984b01467d7e6d8127 - React-Fabric: 6fa475e16e0a37b38d462cec32b70fd5cf886305 - React-FabricImage: 7e09b3704e3fa084b4d44b5b5ef6e2e3d3334ec0 + React-Fabric: 37f29709a9caefd2a9fece6f695bc88a0af77f40 + React-FabricImage: 9c3f6125b2f5908a2e7d0947cfb74022c1a0b294 React-featureflags: 2eb79dd9df4095bff519379f2a4c915069e330bb - React-graphics: 82a482a3aa5d9659b74cdf2c8b57faf67eaa10fb - React-hermes: d93936b02de2fd7e67c11e92c16d4278a14d0134 - React-ImageManager: ebb3c4812e2c5acba5a89728c2d77729471329ad - React-jserrorhandler: a08e0adcf1612900dde82b8bf8e93e7d2ad953b3 - React-jsi: f46d09ee5079a4f3b637d30d0e59b8ea6470632c - React-jsiexecutor: e73579560957aa3ca9dc02ab90e163454279d48c - React-jsinspector: e8ba20dde269c7c1d45784b858fa1cf4383f0bbb - React-jsitracing: 233d1a798fe0ff33b8e630b8f00f62c4a8115fbc - React-logger: 7e7403a2b14c97f847d90763af76b84b152b6fce - React-Mapbuffer: 11029dcd47c5c9e057a4092ab9c2a8d10a496a33 - react-native-alarm-notification: 0c7c6b84a4b73da3cd594c2008bd289fdda4296c - react-native-document-picker: 5b97e24a7f1a1e4a50a72c540a043f32d29a70a2 - react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe - react-native-geolocation: 5c5dd5de4571c55014e9e98214b273eed854f293 - react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06 - react-native-image-picker: d3a65af2538ac5407e5329e50f057fb2456f15f8 - react-native-image-resizer: fd0c333eca55147bd55c5e054cac95dcd0da6814 - react-native-netinfo: 9af975c142e5673d643093aa5afdfa26f46b71b4 - react-native-quick-crypto: 7085e4e4607e7e8fa57f4193f994d5262d351e45 - react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a - react-native-saf-x: b868b5334ba23684a89a5b86749ee359c8bb6932 - react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371 - react-native-slider: 03f213d3ffbf919b16298c7896c1b60101d8e137 - react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261 - react-native-version-info: a106f23009ac0db4ee00de39574eb546682579b9 - react-native-webview: 553abd09f58e340fdc7746c9e2ae096839e99911 + React-graphics: d0b9a0a174fb86bfed50bf4fb7c835183a546ab5 + React-hermes: 06e8316213d56ab914afb9a829763123fcfacf22 + React-ImageManager: 821a1182139cc986598868d0e9a00b3a021feddb + React-jserrorhandler: 1dd2a75b24dd9a318ee88fa6792e98524879af24 + React-jsi: e381545475da5ea77777e7b5513031a434ced04b + React-jsiexecutor: ce91dde1a61efd519a5ff7ac0f64b61a14217072 + React-jsinspector: 627ac44b1d090fc6a8039b1df723677bc7d86fe4 + React-jsitracing: dd0e541a34027b3ab668ad94cf268482ad6f82fb + React-logger: 6070f362a1657bb53335eb1fc903d3f49fd79842 + React-Mapbuffer: 2c95cbabc3d75a17747452381e998c35208ea3ee + react-native-alarm-notification: e0ec4c4041f705cdd27281dac168e5e26e5671e9 + react-native-document-picker: 6b08d834d4e4252bb8aad13d852e27666294b5b9 + react-native-fingerprint-scanner: 91bf6825709dd7bd3abc4588a4772eb097a2b2d8 + react-native-geolocation: 29755191062842d349d7a5160d0638f25b9bd168 + react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba + react-native-image-picker: 10f58d521d8da62b9747cd107045bce399cf5a1e + react-native-image-resizer: 24c5d06fae2176dc0caed4b6396e02befb44064a + react-native-netinfo: 28c2462c85067fe653615f6f595673bdca93a287 + react-native-quick-crypto: 38fde7e5ddfb667b54536c25e6a6ac6d404633d3 + react-native-rsa-native: a7931cdda1f73a8576a46d7f431378c5550f0c38 + react-native-saf-x: 318d0cdb38f4618bd7ef5840d4f5c12e097dfbe7 + react-native-safe-area-context: b72c4611af2e86d80a59ac76279043d8f75f454c + react-native-sqlite-storage: 0c84826214baaa498796c7e46a5ccc9a82e114ed + react-native-version-info: f0b04e16111c4016749235ff6d9a757039189141 + react-native-webview: a71525b1ab760230fbf37303d8371fbe72051c7d React-nativeconfig: b0073a590774e8b35192fead188a36d1dca23dec - React-NativeModulesApple: df46ff3e3de5b842b30b4ca8a6caae6d7c8ab09f + React-NativeModulesApple: 61b07ab32af3ea4910ba553932c0a779e853c082 React-perflogger: 3d31e0d1e8ad891e43a09ac70b7b17a79773003a React-RCTActionSheet: c4a3a134f3434c9d7b0c1054f1a8cfed30c7a093 - React-RCTAnimation: 0e5d15320eeece667fcceb6c785acf9a184e9da1 - React-RCTAppDelegate: c4f6c0700b8950a8b18c2e004996eec1807d430a - React-RCTBlob: c46aaaee693d371a1c7cae2a8c8ee2aa7fbc1adb - React-RCTFabric: 0dbf28ce96c7f2843483e32a725a5b5793584ff3 - React-RCTImage: a04dba5fcc823244f5822192c130ecf09623a57f - React-RCTLinking: 533bf13c745fcb2a0c14e0e49fd149586a7f0d14 - React-RCTNetwork: a29e371e0d363d7b4c10ab907bc4d6ae610541e9 - React-RCTSettings: 127813224780861d0d30ecda17a40d1dfebe7d73 - React-RCTText: 8a823f245ecf82edb7569646e3c4d8041deb800a - React-RCTVibration: 46b5fae74e63f240f22f39de16ad6433da3b65d9 - React-rendererdebug: 4653f8da6ab1d7b01af796bdf8ca47a927539e39 + React-RCTAnimation: dab04683056694845eb7a9e283f4c63eec7fa633 + React-RCTAppDelegate: 1785d42459138c45175b2fa18e86cd2aee829a93 + React-RCTBlob: a0a8f6bfd8926bff0e2814ec3f8cd5514f2db243 + React-RCTFabric: f69d856b74b6d385c4cf4bd128c330161ce18306 + React-RCTImage: 51db983bcc5075fa9bf3e094e5c6c1f5b5575472 + React-RCTLinking: 3430cd1023a5ac86a96ed6d4fbf7a8ed7b2e44d5 + React-RCTNetwork: 52198f8a8c823639dcc8f6725ca5b360d66ea1a0 + React-RCTSettings: c127440c2c538128f92fb45524e976e25cb69bd6 + React-RCTText: 640b2d0bfb51d88d8a76c6a1a7ea1f94667bf231 + React-RCTVibration: bd20c8156b649cd745c70db3341c409ae3b42821 + React-rendererdebug: 16394ffe0d852967123b3b76a630233b90ec8e63 React-rncore: 4f1e645acb5107bd4b4cf29eff17b04a7cd422f3 - React-RuntimeApple: 013b606e743efb5ee14ef03c32379b78bfe74354 - React-RuntimeCore: 7205be45a25713b5418bbf2db91ddfcca0761d8b + React-RuntimeApple: 97d0a5c655467c57b88076434427ec32413e7802 + React-RuntimeCore: a55443ddb73e6666b441963d8951a16ba5cfc223 React-runtimeexecutor: a278d4249921853d4a3f24e4d6e0ff30688f3c16 - React-RuntimeHermes: 44c628568ce8feedc3acfbd48fc07b7f0f6d2731 - React-runtimescheduler: e2152ed146b6a35c07386fc2ac4827b27e6aad12 - React-utils: 3285151c9d1e3a28a9586571fc81d521678c196d - ReactCommon: f42444e384d82ab89184aed5d6f3142748b54768 - rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba - RNCClipboard: 2821ac938ef46f736a8de0c8814845dde2dcbdfb - RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8 - RNDateTimePicker: 40ffda97d071a98a10fdca4fa97e3977102ccd14 - RNDeviceInfo: 59344c19152c4b2b32283005f9737c5c64b42fba - RNExitApp: 00036cabe7bacbb413d276d5520bf74ba39afa6a - RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592 - RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 - RNLocalize: 4f22418187ecd5ca693231093ff1d912d1b3c9bc - RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93 - RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef - RNShare: 0fad69ae2d71de9d1f7b9a43acf876886a6cb99c - RNVectorIcons: 2a2f79274248390b80684ea3c4400bd374a15c90 + React-RuntimeHermes: 6273f0755fef304453fc3c356b25abf17e915b83 + React-runtimescheduler: 87b14969bb0b10538014fb8407d472f9904bc8cd + React-utils: 67574b07bff4429fd6c4d43a7fad8254d814ee20 + ReactCommon: 64c64f4ae1f2debe3fab1800e00cb8466a4477b7 + rn-fetch-blob: 25612b6d6f6e980c6f17ed98ba2f58f5696a51ca + RNCClipboard: 7c3e3b5f71d84ef61690ad377b6c50cf27864ff5 + RNCPushNotificationIOS: 6c4ca3388c7434e4a662b92e4dfeeee858e6f440 + RNDateTimePicker: 818460dc31b0dc5ec58289003e27dd8d022fb79c + RNDeviceInfo: 98bb51ba1519cd3f19f14e7236b5bb1c312c780f + RNExitApp: 4432b9b7cc5ccec9f91c94e507849891282befd4 + RNFileViewer: 4b5d83358214347e4ab2d4ca8d5c1c90d869e251 + RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8 + RNLocalize: e7378161f0b6a6365407eb2377aab46cc38047d8 + RNQuickAction: c2c8f379e614428be0babe4d53a575739667744d + RNSecureRandom: b64d263529492a6897e236a22a2c4249aa1b53dc + RNShare: 694e19d7f74ac4c04de3a8af0649e9ccc03bd8b1 + RNVectorIcons: f733cb2133a8e1f7dc723b97a1d70dad681124c3 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 348f8b538c3ed4423eb58a8e5730feec50bce372 ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5 diff --git a/packages/app-mobile/package.json b/packages/app-mobile/package.json index 491c9529f7..2254237367 100644 --- a/packages/app-mobile/package.json +++ b/packages/app-mobile/package.json @@ -33,7 +33,6 @@ "@react-native-community/geolocation": "3.3.0", "@react-native-community/netinfo": "11.3.3", "@react-native-community/push-notification-ios": "1.11.0", - "@react-native-community/slider": "4.5.5", "assert-browserify": "2.0.0", "buffer": "6.0.3", "color": "3.2.1", @@ -65,7 +64,7 @@ "react-native-paper": "5.13.1", "react-native-popup-menu": "0.16.1", "react-native-quick-actions": "0.3.13", - "react-native-quick-crypto": "0.7.5", + "react-native-quick-crypto": "0.7.12", "react-native-rsa-native": "2.0.5", "react-native-safe-area-context": "4.10.8", "react-native-securerandom": "1.0.1", @@ -93,7 +92,7 @@ "@babel/preset-env": "7.24.7", "@babel/runtime": "7.24.7", "@joplin/tools": "~3.3", - "@js-draw/material-icons": "1.27.2", + "@js-draw/material-icons": "1.29.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@react-native/babel-preset": "0.74.86", "@react-native/metro-config": "0.74.87", @@ -119,7 +118,7 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jetifier": "2.0.0", - "js-draw": "1.27.2", + "js-draw": "1.29.2", "jsdom": "24.1.1", "nodemon": "3.1.7", "punycode": "2.3.1", diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx index 69d20d20dd..a449a3071c 100644 --- a/packages/app-mobile/root.tsx +++ b/packages/app-mobile/root.tsx @@ -55,7 +55,7 @@ import Revision from '@joplin/lib/models/Revision'; import RevisionService from '@joplin/lib/services/RevisionService'; import JoplinDatabase from '@joplin/lib/JoplinDatabase'; import Database from '@joplin/lib/database'; -import NotesScreen from './components/screens/Notes'; +import NotesScreen from './components/screens/Notes/Notes'; import TagsScreen from './components/screens/tags'; import ConfigScreen from './components/screens/ConfigScreen/ConfigScreen'; const { FolderScreen } = require('./components/screens/folder.js'); @@ -140,6 +140,7 @@ import lockToSingleInstance from './utils/lockToSingleInstance'; import { AppState } from './utils/types'; import { getDisplayParentId } from '@joplin/lib/services/trash'; import PluginNotification from './components/plugins/PluginNotification'; +import FocusControl from './components/accessibility/FocusControl/FocusControl'; const logger = Logger.create('root'); @@ -1312,7 +1313,7 @@ class AppComponent extends React.Component { disableGestures={disableSideMenuGestures} > - + @@ -1326,7 +1327,7 @@ class AppComponent extends React.Component { sensorInfo={this.state.sensorInfo} /> } - + @@ -1338,44 +1339,53 @@ class AppComponent extends React.Component { // Wrap everything in a PaperProvider -- this allows using components from react-native-paper return ( - + - - {mainContent} - - + }}> + + + + {mainContent} + + + + + ); } } diff --git a/packages/app-mobile/services/voiceTyping/VoiceTyping.ts b/packages/app-mobile/services/voiceTyping/VoiceTyping.ts index c7b0e5f31a..f108d5e9f4 100644 --- a/packages/app-mobile/services/voiceTyping/VoiceTyping.ts +++ b/packages/app-mobile/services/voiceTyping/VoiceTyping.ts @@ -2,7 +2,6 @@ import shim from '@joplin/lib/shim'; import Logger from '@joplin/utils/Logger'; import { PermissionsAndroid, Platform } from 'react-native'; import unzip from './utils/unzip'; -import { _ } from '@joplin/lib/locale'; const md5 = require('md5'); const logger = Logger.create('voiceTyping'); @@ -19,6 +18,7 @@ export interface SpeechToTextCallbacks { export interface VoiceTypingSession { start(): Promise; stop(): Promise; + cancel(): Promise; } export interface BuildProviderOptions { @@ -86,12 +86,7 @@ export default class VoiceTyping { } public async clearDownloads() { - const confirmed = await shim.showConfirmationDialog( - _('Delete model and re-download?\nThis cannot be undone.'), - ); - if (confirmed) { - await this.provider.deleteCachedModels(this.locale); - } + await this.provider.deleteCachedModels(this.locale); } public async download() { diff --git a/packages/app-mobile/services/voiceTyping/vosk.android.ts b/packages/app-mobile/services/voiceTyping/vosk.android.ts index bda1894015..74f8b1f6e3 100644 --- a/packages/app-mobile/services/voiceTyping/vosk.android.ts +++ b/packages/app-mobile/services/voiceTyping/vosk.android.ts @@ -154,6 +154,14 @@ export const startRecording = (vosk: Vosk, options: StartOptions): VoiceTypingSe completeRecording(e.data, null); })); + const stopOrCancel = () => { + if (state_ === State.Recording) { + logger.info('Cancelling...'); + state_ = State.Completing; + vosk.stopOnly(); + completeRecording('', null); + } + }; return { start: async () => { @@ -161,12 +169,10 @@ export const startRecording = (vosk: Vosk, options: StartOptions): VoiceTypingSe await vosk.start(); }, stop: async () => { - if (state_ === State.Recording) { - logger.info('Cancelling...'); - state_ = State.Completing; - vosk.stopOnly(); - completeRecording('', null); - } + stopOrCancel(); + }, + cancel: async () => { + stopOrCancel(); }, }; }; diff --git a/packages/app-mobile/services/voiceTyping/whisper.test.ts b/packages/app-mobile/services/voiceTyping/whisper.test.ts index 26628bd5a5..a4aa98ab9c 100644 --- a/packages/app-mobile/services/voiceTyping/whisper.test.ts +++ b/packages/app-mobile/services/voiceTyping/whisper.test.ts @@ -11,7 +11,6 @@ jest.mock('react-native', () => { // See https://github.com/facebook/react-native/issues/28839. reactNative.NativeModules.SpeechToTextModule = { convertNext: () => 'Test. This is test output. Test!', - getPreview: () => 'A preview of future output.', runTests: ()=> {}, openSession: jest.fn(() => { const someId = 1234; @@ -19,6 +18,7 @@ jest.mock('react-native', () => { }), closeSession: jest.fn(), startRecording: jest.fn(), + convertAvailable: jest.fn(() => ''), }; return reactNative; @@ -83,6 +83,6 @@ describe('whisper', () => { // Should have applied string, then regex replacements. expect( lastFinalizedText, - ).toBe('replaced again!. This is test output. replaced again!!'); + ).toBe('\n\nreplaced again!. This is test output. replaced again!!'); }); }); diff --git a/packages/app-mobile/services/voiceTyping/whisper.ts b/packages/app-mobile/services/voiceTyping/whisper.ts index 82000d4fa6..7be04f2c85 100644 --- a/packages/app-mobile/services/voiceTyping/whisper.ts +++ b/packages/app-mobile/services/voiceTyping/whisper.ts @@ -13,6 +13,7 @@ const { SpeechToTextModule } = NativeModules; class WhisperConfig { public prompts: Map = new Map(); + public supportsShortAudioCtx = false; public stringReplacements: [string, string][] = []; public regexReplacements: [RegExp, string][] = []; @@ -69,13 +70,18 @@ class WhisperConfig { } }; + // Models fine-tuned as per https://github.com/futo-org/whisper-acft should have + // "shortAudioContext": true in their config.json. + if ('shortAudioContext' in json) { + this.supportsShortAudioCtx = !!json.shortAudioContext; + } + processPrompts(); processOutputSettings(); } } class Whisper implements VoiceTypingSession { - private lastPreviewData = ''; private closeCounter = 0; private isFirstParagraph = true; @@ -87,15 +93,25 @@ class Whisper implements VoiceTypingSession { } private postProcessSpeech(data: string) { - data = data.trim(); + const paragraphs = data.split('\n\n'); - for (const [key, value] of this.config.stringReplacements) { - data = data.split(key).join(value); + const result = []; + for (let paragraph of paragraphs) { + paragraph = paragraph.trim(); + + for (const [key, value] of this.config.stringReplacements) { + paragraph = paragraph.split(key).join(value); + } + for (const [key, value] of this.config.regexReplacements) { + paragraph = paragraph.replace(key, value); + } + + if (paragraph) { + result.push(paragraph); + } } - for (const [key, value] of this.config.regexReplacements) { - data = data.replace(key, value); - } - return data; + + return result.join('\n\n'); } private onDataFinalize(data: string) { @@ -123,34 +139,41 @@ class Whisper implements VoiceTypingSession { this.onDataFinalize(data); logger.debug('done reading block. Length', data?.length); - if (this.sessionId !== null) { - this.lastPreviewData = await SpeechToTextModule.getPreview(this.sessionId); - this.callbacks.onPreview(this.postProcessSpeech(this.lastPreviewData)); - } } } catch (error) { logger.error('Whisper error:', error); - this.lastPreviewData = ''; - await this.stop(); + await this.cancel(); throw error; } } - public stop() { + public async stop() { if (this.sessionId === null) { logger.debug('Session already closed.'); return; } + try { + const data: string = await SpeechToTextModule.convertAvailable(this.sessionId); + this.onDataFinalize(data); + } catch (error) { + logger.error('Error stopping session: ', error); + } + + return this.cancel(); + } + + public cancel() { + if (this.sessionId === null) { + logger.debug('No session to cancel.'); + return; + } + logger.info('Closing session...'); const sessionId = this.sessionId; this.sessionId = null; this.closeCounter ++; - if (this.lastPreviewData) { - this.onDataFinalize(this.lastPreviewData); - } - return SpeechToTextModule.closeSession(sessionId); } } @@ -175,14 +198,14 @@ const whisper: VoiceTypingProvider = { let urlTemplate = rtrimSlashes(Setting.value('voiceTypingBaseUrl').trim()); if (!urlTemplate) { - urlTemplate = 'https://github.com/personalizedrefrigerator/joplin-voice-typing-test/releases/download/v0.0.3/{task}.zip'; + urlTemplate = 'https://github.com/joplin/voice-typing-models/releases/download/v0.2.0/{task}.zip'; } - // Note: whisper-base-q8_0 is also available and works on many Android devices. On some low - // resource devices, however, it will fail. - // TODO: Auto-select the model size? + // Note: whisper-base-q8_0.fr is also available and may have better performance on French-language + // input. + // TODO: Auto-select the model? return urlTemplate - .replace(/\{task\}/g, 'whisper-tiny-q8_0') + .replace(/\{task\}/g, 'whisper-base-q8_0') .replace(/\{lang\}/g, lang); }, deleteCachedModels: async (locale) => { @@ -225,8 +248,9 @@ const whisper: VoiceTypingProvider = { throw new Error(`Model not found at path ${modelPath}`); } + logger.debug('Starting whisper session', config.supportsShortAudioCtx ? '(short audio context)' : ''); const sessionId = await SpeechToTextModule.openSession( - modelPath, locale, getPrompt(locale, config.prompts), + modelPath, locale, getPrompt(locale, config.prompts), config.supportsShortAudioCtx, ); return new Whisper(sessionId, callbacks, config); }, diff --git a/packages/app-mobile/utils/focusView.ts b/packages/app-mobile/utils/focusView.ts new file mode 100644 index 0000000000..ade7e35091 --- /dev/null +++ b/packages/app-mobile/utils/focusView.ts @@ -0,0 +1,37 @@ +import { focus } from '@joplin/lib/utils/focusHandler'; +import Logger from '@joplin/utils/Logger'; +import { AccessibilityInfo, findNodeHandle, Platform, UIManager, View } from 'react-native'; + +const logger = Logger.create('focusView'); + +const focusView = (source: string, view: View|HTMLElement) => { + const autoFocus = () => { + if (Platform.OS === 'web') { + // react-native-web defines UIManager.focus for setting the keyboard focus. However, + // this property is not available in standard react-native. As such, access it using type + // narrowing: + // eslint-disable-next-line no-restricted-properties + if (!('focus' in UIManager) || typeof UIManager.focus !== 'function') { + throw new Error('Failed to focus sidebar. UIManager.focus is not a function.'); + } + + // Disable the "use focusHandler for all focus calls" rule -- UIManager.focus requires + // an argument, which is not supported by focusHandler. + // eslint-disable-next-line no-restricted-properties + UIManager.focus(view); + } else { + const handle = findNodeHandle(view as View); + if (handle !== null) { + AccessibilityInfo.setAccessibilityFocus(handle); + } else { + logger.warn('Couldn\'t find a view to focus.'); + } + } + }; + + focus(`focusView:${source}`, { + focus: autoFocus, + }); +}; + +export default focusView; diff --git a/packages/app-mobile/utils/hooks/useKeyboardState.ts b/packages/app-mobile/utils/hooks/useKeyboardState.ts new file mode 100644 index 0000000000..a731eb0d63 --- /dev/null +++ b/packages/app-mobile/utils/hooks/useKeyboardState.ts @@ -0,0 +1,35 @@ +import { useEffect, useMemo, useState } from 'react'; +import { Dimensions, Keyboard } from 'react-native'; + +const useKeyboardState = () => { + const [keyboardVisible, setKeyboardVisible] = useState(false); + const [hasSoftwareKeyboard, setHasSoftwareKeyboard] = useState(false); + const [isFloatingKeyboard, setIsFloatingKeyboard] = useState(false); + useEffect(() => { + const showListener = Keyboard.addListener('keyboardDidShow', () => { + setKeyboardVisible(true); + setHasSoftwareKeyboard(true); + }); + const hideListener = Keyboard.addListener('keyboardDidHide', () => { + setKeyboardVisible(false); + }); + const floatingListener = Keyboard.addListener('keyboardWillChangeFrame', (evt) => { + const windowWidth = Dimensions.get('window').width; + // If the keyboard isn't as wide as the window, the floating keyboard is disabled. + // See https://github.com/facebook/react-native/issues/29473#issuecomment-696658937 + setIsFloatingKeyboard(evt.endCoordinates.width < windowWidth); + }); + + return (() => { + showListener.remove(); + hideListener.remove(); + floatingListener.remove(); + }); + }); + + return useMemo(() => { + return { keyboardVisible, hasSoftwareKeyboard, isFloatingKeyboard }; + }, [keyboardVisible, hasSoftwareKeyboard, isFloatingKeyboard]); +}; + +export default useKeyboardState; diff --git a/packages/app-mobile/utils/hooks/useKeyboardVisible.ts b/packages/app-mobile/utils/hooks/useKeyboardVisible.ts deleted file mode 100644 index a6aa8499cb..0000000000 --- a/packages/app-mobile/utils/hooks/useKeyboardVisible.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useEffect, useMemo, useState } from 'react'; -import { Keyboard } from 'react-native'; - -const useKeyboardVisible = () => { - const [keyboardVisible, setKeyboardVisible] = useState(false); - const [hasSoftwareKeyboard, setHasSoftwareKeyboard] = useState(false); - useEffect(() => { - const showListener = Keyboard.addListener('keyboardDidShow', () => { - setKeyboardVisible(true); - setHasSoftwareKeyboard(true); - }); - const hideListener = Keyboard.addListener('keyboardDidHide', () => { - setKeyboardVisible(false); - }); - - return (() => { - showListener.remove(); - hideListener.remove(); - }); - }); - - return useMemo(() => { - return { keyboardVisible, hasSoftwareKeyboard }; - }, [keyboardVisible, hasSoftwareKeyboard]); -}; - -export default useKeyboardVisible; diff --git a/packages/app-mobile/utils/hooks/useSafeAreaPadding.ts b/packages/app-mobile/utils/hooks/useSafeAreaPadding.ts new file mode 100644 index 0000000000..0a770736b6 --- /dev/null +++ b/packages/app-mobile/utils/hooks/useSafeAreaPadding.ts @@ -0,0 +1,24 @@ +import { useMemo } from 'react'; +import { useWindowDimensions } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +const useSafeAreaPadding = () => { + const { width: windowWidth, height: windowHeight } = useWindowDimensions(); + const safeAreaInsets = useSafeAreaInsets(); + const isLandscape = windowWidth > windowHeight; + return useMemo(() => { + return isLandscape ? { + paddingRight: safeAreaInsets.right, + paddingLeft: safeAreaInsets.left, + paddingTop: 15, + paddingBottom: 15, + } : { + paddingTop: safeAreaInsets.top, + paddingBottom: safeAreaInsets.bottom, + paddingLeft: 0, + paddingRight: 0, + }; + }, [isLandscape, safeAreaInsets]); +}; + +export default useSafeAreaPadding; diff --git a/packages/app-mobile/utils/shim-init-react/index.ts b/packages/app-mobile/utils/shim-init-react/index.ts index bd57fd54ec..21f97c42fc 100644 --- a/packages/app-mobile/utils/shim-init-react/index.ts +++ b/packages/app-mobile/utils/shim-init-react/index.ts @@ -168,6 +168,10 @@ export default function shimInit() { return Platform.OS; }; + shim.isAppleSilicon = () => { + return false; + }; + shim.appVersion = () => { const p = require('react-native-version-info').default; return p.appVersion; diff --git a/packages/app-mobile/utils/shim-init-react/shimInitShared.ts b/packages/app-mobile/utils/shim-init-react/shimInitShared.ts index 87d73b1462..d97f0e0746 100644 --- a/packages/app-mobile/utils/shim-init-react/shimInitShared.ts +++ b/packages/app-mobile/utils/shim-init-react/shimInitShared.ts @@ -79,6 +79,10 @@ const shimInitShared = () => { return Platform.OS; }; + shim.platformArch = () => { + return ''; // Not supported + }; + shim.injectedJs = function(name) { if (!(name in injectedJs)) throw new Error(`Cannot find injectedJs file (add it to "injectedJs" object): ${name}`); return injectedJs[name as keyof typeof injectedJs]; diff --git a/packages/default-plugins/pluginRepositories.json b/packages/default-plugins/pluginRepositories.json index 22e8454b7c..c9217353b9 100644 --- a/packages/default-plugins/pluginRepositories.json +++ b/packages/default-plugins/pluginRepositories.json @@ -7,6 +7,6 @@ "io.github.personalizedrefrigerator.js-draw": { "cloneUrl": "https://github.com/personalizedrefrigerator/joplin-plugin-freehand-drawing.git", "branch": "main", - "commit": "94a78496fac0b3bc7b0f6a896a982480adad3994" + "commit": "3153fb0fc8dab23426accdcd4319b0654d364d2b" } } diff --git a/packages/doc-builder/docusaurus.config.js b/packages/doc-builder/docusaurus.config.js index 7d163e13c0..5a0f5e5586 100644 --- a/packages/doc-builder/docusaurus.config.js +++ b/packages/doc-builder/docusaurus.config.js @@ -252,6 +252,10 @@ const config = { label: 'Patreon', href: 'https://www.patreon.com/joplin', }, + { + label: 'YouTube', + href: 'https://www.youtube.com/@joplinapp', + }, { label: 'LinkedIn', href: 'https://www.linkedin.com/company/joplin', diff --git a/packages/editor/CodeMirror/createEditor.ts b/packages/editor/CodeMirror/createEditor.ts index 0fa7fff4ed..411ff4abcb 100644 --- a/packages/editor/CodeMirror/createEditor.ts +++ b/packages/editor/CodeMirror/createEditor.ts @@ -268,8 +268,6 @@ const createEditor = ( }, }), - EditorState.tabSize.of(4), - // Apply styles to entire lines (block-display decorations) decoratorExtension, dropCursor(), diff --git a/packages/editor/CodeMirror/markdown/markdownCommands.bulletedVsChecklist.test.ts b/packages/editor/CodeMirror/markdown/markdownCommands.bulletedVsChecklist.test.ts index 5da09a5c58..98d4544a10 100644 --- a/packages/editor/CodeMirror/markdown/markdownCommands.bulletedVsChecklist.test.ts +++ b/packages/editor/CodeMirror/markdown/markdownCommands.bulletedVsChecklist.test.ts @@ -12,25 +12,25 @@ describe('markdownCommands.bulletedVsChecklist', () => { const initialDocText = `${bulletedListPart}\n\n${checklistPart}`; const expectedTags = ['BulletList', 'Task']; - it('should remove a checklist following a bulleted list without modifying the bulleted list', async () => { + it('should remove a checklist following a bulleted list without modifying the bulleted list only at the selected line', async () => { const editor = await createTestEditor( initialDocText, EditorSelection.cursor(bulletedListPart.length + 5), expectedTags, ); toggleList(ListType.CheckList)(editor); expect(editor.state.doc.toString()).toBe( - `${bulletedListPart}\n\nThis is a checklist\nwith multiple items.\n☑`, + `${bulletedListPart}\n\nThis is a checklist\n- [ ] with multiple items.\n- [ ] ☑`, ); }); - it('should remove an unordered list following a checklist without modifying the checklist', async () => { + it('should remove an unordered list only at the selected line following a checklist without modifying the checklist', async () => { const editor = await createTestEditor( initialDocText, EditorSelection.cursor(bulletedListPart.length - 5), expectedTags, ); toggleList(ListType.UnorderedList)(editor); expect(editor.state.doc.toString()).toBe( - `Test\nThis is a test.\n3\n4\n5\n\n${checklistPart}`, + `- Test\n- This is a test.\n- 3\n4\n- 5\n\n${checklistPart}`, ); }); diff --git a/packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.ts b/packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.ts index a8e57bd90a..d4275c96ee 100644 --- a/packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.ts +++ b/packages/editor/CodeMirror/markdown/markdownCommands.toggleList.test.ts @@ -9,7 +9,7 @@ describe('markdownCommands.toggleList', () => { jest.retryTimes(2); - it('should remove the same type of list', async () => { + it('should remove the list only at the selected line', async () => { const initialDocText = '- testing\n- this is a `test`\n'; const editor = await createTestEditor( @@ -20,11 +20,11 @@ describe('markdownCommands.toggleList', () => { toggleList(ListType.UnorderedList)(editor); expect(editor.state.doc.toString()).toBe( - 'testing\nthis is a `test`\n', + 'testing\n- this is a `test`\n', ); }); - it('should insert a numbered list with correct numbering', async () => { + it('should insert a numbered list with correct numbering only at the selected line', async () => { const initialDocText = 'Testing...\nThis is a test\nof list toggling...'; const editor = await createTestEditor( initialDocText, @@ -50,7 +50,7 @@ describe('markdownCommands.toggleList', () => { const unorderedListText = '- 1\n- 2\n- 3\n- 4\n- 5\n- 6\n- 7'; - it('should correctly replace an unordered list with a numbered list', async () => { + it('should correctly replace an unordered list with a numbered list only at the selected line', async () => { const editor = await createTestEditor( unorderedListText, EditorSelection.cursor(unorderedListText.length), @@ -59,7 +59,7 @@ describe('markdownCommands.toggleList', () => { toggleList(ListType.OrderedList)(editor); expect(editor.state.doc.toString()).toBe( - '1. 1\n2. 2\n3. 3\n4. 4\n5. 5\n6. 6\n7. 7', + '- 1\n- 2\n- 3\n- 4\n- 5\n- 6\n1. 7', ); }); @@ -180,7 +180,7 @@ describe('markdownCommands.toggleList', () => { // ); // }); - it('should toggle a numbered list without changing its sublists', async () => { + it('should toggle only a numbered list at selected line without changing its sublists', async () => { const initialDocText = '1. Foo\n2. Bar\n3. Baz\n\t- Test\n\t- of\n\t- sublists\n4. Foo'; const editor = await createTestEditor( @@ -191,7 +191,7 @@ describe('markdownCommands.toggleList', () => { toggleList(ListType.CheckList)(editor); expect(editor.state.doc.toString()).toBe( - '- [ ] Foo\n- [ ] Bar\n- [ ] Baz\n\t- Test\n\t- of\n\t- sublists\n- [ ] Foo', + '- [ ] Foo\n2. Bar\n3. Baz\n\t- Test\n\t- of\n\t- sublists\n4. Foo', ); }); @@ -218,7 +218,7 @@ describe('markdownCommands.toggleList', () => { ); }); - it('should toggle lists properly within block quotes', async () => { + it('should toggle only list on the selected line properly within block quotes', async () => { const preSubListText = '> # List test\n> * This\n> * is\n'; const initialDocText = `${preSubListText}> \t* a\n> \t* test\n> * of list toggling`; const editor = await createTestEditor( @@ -228,9 +228,9 @@ describe('markdownCommands.toggleList', () => { toggleList(ListType.OrderedList)(editor); expect(editor.state.doc.toString()).toBe( - '> # List test\n> * This\n> * is\n> \t1. a\n> \t2. test\n> * of list toggling', + '> # List test\n> * This\n> * is\n> \t1. a\n> \t* test\n> * of list toggling', ); - expect(editor.state.selection.main.from).toBe(preSubListText.length); + expect(editor.state.selection.main.from).toBe(preSubListText.length + 7); }); it('should not treat a list of IP addresses as a numbered list', async () => { diff --git a/packages/editor/CodeMirror/markdown/markdownCommands.ts b/packages/editor/CodeMirror/markdown/markdownCommands.ts index a230d50c32..70961be99a 100644 --- a/packages/editor/CodeMirror/markdown/markdownCommands.ts +++ b/packages/editor/CodeMirror/markdown/markdownCommands.ts @@ -13,7 +13,6 @@ import { RegionSpec } from '../utils/formatting/RegionSpec'; import toggleInlineFormatGlobally from '../utils/formatting/toggleInlineFormatGlobally'; import stripBlockquote from './utils/stripBlockquote'; import isIndentationEquivalent from '../utils/formatting/isIndentationEquivalent'; -import growSelectionToNode from '../utils/growSelectionToNode'; import tabsToSpaces from '../utils/formatting/tabsToSpaces'; import renumberSelectedLists from './utils/renumberSelectedLists'; import toggleSelectedLinesStartWith from '../utils/formatting/toggleSelectedLinesStartWith'; @@ -126,9 +125,6 @@ export const toggleList = (listType: ListType): Command => { let state = view.state; let doc = state.doc; - const orderedListTag = 'OrderedList'; - const unorderedListTag = 'BulletList'; - // RegExps for different list types. The regular expressions MUST // be mutually exclusive. // `(?!\[[ xX]+\])` means "not followed by [x] or [ ]". @@ -188,15 +184,6 @@ export const toggleList = (listType: ListType): Command => { const origFirstLineIndentation = firstLineIndentation; const origContainerType = containerType; - // Grow `sel` to the smallest containing list, unless the - // cursor is on an empty line, in which case, the user - // probably wants to add a list item (and not select the entire - // list). - if (sel.empty && fromLine.text.trim() !== '') { - sel = growSelectionToNode(state, sel, [orderedListTag, unorderedListTag]); - computeSelectionProps(); - } - // Reset the selection if it seems likely the user didn't want the selection // to be expanded const isIndentationDiff = diff --git a/packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.ts b/packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.ts index 17d777a0bb..ee7e9a0f0a 100644 --- a/packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.ts +++ b/packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.test.ts @@ -64,4 +64,92 @@ describe('renumberSelectedLists', () => { '# End', ].join('\n')); }); + + it.each([ + { + // Should handle the case where a single item is over-indented + before: [ + '- This', + '- is', + '\t1. a', + '\t\t2. test', + '\t3. of', + '\t4. lists', + ].join('\n'), + after: [ + '- This', + '- is', + '\t1. a', + '\t\t1. test', + '\t2. of', + '\t3. lists', + ].join('\n'), + }, + { + // Should handle the case where multiple sublists need to be renumbered + before: [ + '- This', + '- is', + '\t1. a', + '\t\t2. test', + '\t3. of', + '\t\t4. lists', + '\t\t5. lists', + '\t\t6. lists', + '\t7. lists', + '', + '', + '1. New list', + '\t3. Item', + ].join('\n'), + after: [ + '- This', + '- is', + '\t1. a', + '\t\t1. test', + '\t2. of', + '\t\t1. lists', + '\t\t2. lists', + '\t\t3. lists', + '\t3. lists', + '', + '', + '1. New list', + '\t1. Item', + ].join('\n'), + }, + { + before: [ + '2. This', + '\t1. is', + '\t2. a', + '\t\t3. test', + '\t4. test', + '\t5. test', + '\t6. test', + ].join('\n'), + after: [ + '2. This', + '\t1. is', + '\t2. a', + '\t\t1. test', + '\t3. test', + '\t4. test', + '\t5. test', + ].join('\n'), + }, + ])('should handle nested lists (case %#)', async ({ before, after }) => { + const suffix = '\n\n# End'; + before += suffix; + after += suffix; + const editor = await createTestEditor( + before, + EditorSelection.range(0, before.length - suffix.length), + ['OrderedList', 'ATXHeading1'], + ); + + editor.dispatch(renumberSelectedLists(editor.state)); + + expect(editor.state.doc.toString()).toBe(after); + }); }); diff --git a/packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.ts b/packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.ts index e67db80b3e..3ec3ca6260 100644 --- a/packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.ts +++ b/packages/editor/CodeMirror/markdown/utils/renumberSelectedLists.ts @@ -49,11 +49,16 @@ const renumberSelectedLists = (state: EditorState): TransactionSpec => { const indentation = match[1]; const indentationLen = tabsToSpaces(state, indentation).length; - let targetIndentLen = tabsToSpaces(state, currentGroupIndentation).length; - if (targetIndentLen < indentationLen) { - listNumberStack.push({ nextListNumber, indentationLength: indentationLen }); + let currentGroupIndentLength = tabsToSpaces(state, currentGroupIndentation).length; + const indentIncreased = indentationLen > currentGroupIndentLength; + const indentDecreased = indentationLen < currentGroupIndentLength; + if (indentIncreased) { + // Save the state of the previous group so that it can be restored later. + listNumberStack.push({ + nextListNumber, indentationLength: currentGroupIndentLength, + }); nextListNumber = 1; - } else if (targetIndentLen > indentationLen) { + } else if (indentDecreased) { nextListNumber = parseInt(match[2], 10); // Handle the case where we deindent multiple times. For example, @@ -61,22 +66,20 @@ const renumberSelectedLists = (state: EditorState): TransactionSpec => { // 1. test // 1. test // 2. test - while (targetIndentLen > indentationLen) { + while (indentationLen < currentGroupIndentLength) { const listNumberRecord = listNumberStack.pop(); if (!listNumberRecord) { break; } else { - targetIndentLen = listNumberRecord.indentationLength; + currentGroupIndentLength = listNumberRecord.indentationLength; nextListNumber = listNumberRecord.nextListNumber; } } } - if (targetIndentLen !== indentationLen) { - currentGroupIndentation = indentation; - } + currentGroupIndentation = indentation; const from = line.to - filteredText.length; const to = from + match[0].length; diff --git a/packages/editor/CodeMirror/theme.ts b/packages/editor/CodeMirror/theme.ts index 745cd6b643..304f820661 100644 --- a/packages/editor/CodeMirror/theme.ts +++ b/packages/editor/CodeMirror/theme.ts @@ -106,6 +106,14 @@ const createTheme = (theme: EditorTheme): Extension[] => { marginLeft: `${theme.marginLeft}px`, marginRight: `${theme.marginRight}px`, }, + + '& .cm-listItem': { + // Needs to be !important because the tab-size is directly set on the element style + // attribute by CodeMirror. And the `EditorState.tabSize` function only accepts a + // number, while we need a "em" value to make it match the viewer tab size. + tabSize: `${theme.listTabSize} !important`, + }, + '&.cm-focused .cm-cursor': baseCursorStyle, // The desktop app sets the font for these elements to a specific font. diff --git a/packages/editor/types.ts b/packages/editor/types.ts index 0d2b08994b..5fb64f96c7 100644 --- a/packages/editor/types.ts +++ b/packages/editor/types.ts @@ -146,6 +146,7 @@ export interface EditorTheme extends Theme { contentMaxWidth?: number; marginLeft?: number; marginRight?: number; + listTabSize?: string; } export interface EditorSettings { diff --git a/packages/generator-joplin/generators/app/templates/api/Joplin.d.ts b/packages/generator-joplin/generators/app/templates/api/Joplin.d.ts index 4dae70d4a1..15c672c043 100644 --- a/packages/generator-joplin/generators/app/templates/api/Joplin.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/Joplin.d.ts @@ -73,4 +73,8 @@ export default class Joplin { */ require(_path: string): any; versionInfo(): Promise; + /** + * Tells whether the current theme is a dark one or not. + */ + shouldUseDarkColors(): Promise; } diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinSettings.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinSettings.d.ts index 9a4638bd85..ab9663dc1a 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinSettings.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinSettings.d.ts @@ -52,11 +52,15 @@ export default class JoplinSettings { */ setValue(key: string, value: any): Promise; /** - * Gets a global setting value, including app-specific settings and those set by other plugins. + * Gets global setting values, including app-specific settings and those set by other plugins. * * The list of available settings is not documented yet, but can be found by looking at the source code: * - * https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Setting.ts#L142 + * https://github.com/laurent22/joplin/blob/dev/packages/lib/models/settings/builtInMetadata.ts + */ + globalValues(keys: string[]): Promise; + /** + * @deprecated Use joplin.settings.globalValues() */ globalValue(key: string): Promise; /** diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinViewsDialogs.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinViewsDialogs.d.ts index e1831a3e4d..55db51851e 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinViewsDialogs.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinViewsDialogs.d.ts @@ -43,6 +43,9 @@ export default class JoplinViewsDialogs { * Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel" */ showMessageBox(message: string): Promise; + /** + * Displays a Toast notification in the corner of the application screen. + */ showToast(toast: Toast): Promise; /** * Displays a dialog to select a file or a directory. Same options and diff --git a/packages/generator-joplin/generators/app/templates/api/JoplinViewsEditor.d.ts b/packages/generator-joplin/generators/app/templates/api/JoplinViewsEditor.d.ts index cab36bba95..bd4dd1631e 100644 --- a/packages/generator-joplin/generators/app/templates/api/JoplinViewsEditor.d.ts +++ b/packages/generator-joplin/generators/app/templates/api/JoplinViewsEditor.d.ts @@ -60,14 +60,14 @@ export default class JoplinViewsEditors { */ onMessage(handle: ViewHandle, callback: Function): Promise; /** - * Emitted when the editor can potentially be activated - this for example when the current note - * is changed, or when the application is opened. At that point should can check the current - * note and decide whether your editor should be activated or not. If it should return `true`, - * otherwise return `false`. + * Emitted when the editor can potentially be activated - this is for example when the current + * note is changed, or when the application is opened. At that point you should check the + * current note and decide whether your editor should be activated or not. If it should, return + * `true`, otherwise return `false`. */ onActivationCheck(handle: ViewHandle, callback: ActivationCheckCallback): Promise; /** - * Emitted when the editor content should be updated. This for example when the currently + * Emitted when your editor content should be updated. This is for example when the currently * selected note changes, or when the user makes the editor visible. */ onUpdate(handle: ViewHandle, callback: UpdateCallback): Promise; diff --git a/packages/generator-joplin/generators/app/templates/api/types.ts b/packages/generator-joplin/generators/app/templates/api/types.ts index 4dfa8b66cd..3cd7b0f0ae 100644 --- a/packages/generator-joplin/generators/app/templates/api/types.ts +++ b/packages/generator-joplin/generators/app/templates/api/types.ts @@ -626,7 +626,7 @@ export interface CodeMirrorControl { /** * A CodeMirror [facet](https://codemirror.net/docs/ref/#state.EditorState.facet) that contains * the ID of the note currently open in the editor. - * + * * Access the value of this facet using * ```ts * const noteIdFacet = editorControl.joplinExtensions.noteIdFacet; @@ -636,6 +636,11 @@ export interface CodeMirrorControl { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- No better type available noteIdFacet: any; + /** + * A CodeMirror [StateEffect](https://codemirror.net/docs/ref/#state.StateEffect) that is + * included in a [Transaction](https://codemirror.net/docs/ref/#state.Transaction) when the + * note ID changes. + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- No better type available setNoteIdEffect: any; }; diff --git a/packages/generator-joplin/package.json b/packages/generator-joplin/package.json index cacfbb3548..a7fe299382 100644 --- a/packages/generator-joplin/package.json +++ b/packages/generator-joplin/package.json @@ -1,6 +1,6 @@ { "name": "generator-joplin", - "version": "3.3.0", + "version": "3.3.1", "description": "Scaffolds out a new Joplin plugin", "homepage": "https://github.com/laurent22/joplin/tree/dev/packages/generator-joplin", "author": { diff --git a/packages/lib/BaseApplication.ts b/packages/lib/BaseApplication.ts index 074c7f3b6e..bc00a6f526 100644 --- a/packages/lib/BaseApplication.ts +++ b/packages/lib/BaseApplication.ts @@ -65,6 +65,7 @@ import processStartFlags from './utils/processStartFlags'; import { setupAutoDeletion } from './services/trash/permanentlyDeleteOldItems'; import determineProfileAndBaseDir from './determineBaseAppDirs'; import NavService from './services/NavService'; +import getAppName from './getAppName'; const appLogger: LoggerWrapper = Logger.create('App'); @@ -679,15 +680,16 @@ export default class BaseApplication { let appName = options.appName; if (!appName) { - appName = initArgs.env === 'dev' ? 'joplindev' : 'joplin'; - if (Setting.value('appId').indexOf('-desktop') >= 0) appName += '-desktop'; + appName = getAppName(Setting.value('appId').indexOf('-desktop') >= 0, initArgs.env === 'dev'); } Setting.setConstant('appName', appName); // https://immerjs.github.io/immer/docs/freezing setAutoFreeze(initArgs.env === 'dev'); - const { rootProfileDir, homeDir } = determineProfileAndBaseDir(options.rootProfileDir ?? initArgs.profileDir, appName); + const altInstanceId = initArgs.altInstanceId || ''; + + const { rootProfileDir, homeDir } = determineProfileAndBaseDir(options.rootProfileDir ?? initArgs.profileDir, appName, altInstanceId); const { profileDir, profileConfig, isSubProfile } = await initProfile(rootProfileDir); this.profileConfig_ = profileConfig; @@ -781,6 +783,8 @@ export default class BaseApplication { Setting.setValue('isSafeMode', true); } + Setting.setValue('altInstanceId', altInstanceId); + const safeModeFlagFile = join(profileDir, safeModeFlagFilename); if (await fs.pathExists(safeModeFlagFile) && fs.readFileSync(safeModeFlagFile, 'utf8') === 'true') { appLogger.info(`Safe mode enabled because of file: ${safeModeFlagFile}`); diff --git a/packages/lib/ClipperServer.ts b/packages/lib/ClipperServer.ts index 683a0acca3..d720ea0d5e 100644 --- a/packages/lib/ClipperServer.ts +++ b/packages/lib/ClipperServer.ts @@ -23,6 +23,7 @@ export default class ClipperServer { private api_: Api = null; // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied private dispatch_: Function; + private enabled_ = true; private static instance_: ClipperServer = null; @@ -40,6 +41,18 @@ export default class ClipperServer { return this.api_; } + public enabled() { + return this.enabled_; + } + + public setEnabled(v: boolean) { + this.enabled_ = v; + + if (!this.enabled_ && this.isRunning()) { + void this.stop(); + } + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied public initialize(actionApi: any = null) { this.api_ = new Api(() => { @@ -106,6 +119,8 @@ export default class ClipperServer { } public async start() { + if (!this.enabled()) throw new Error('Cannot start clipper server because it is disabled'); + this.setPort(null); this.setStartState(StartState.Starting); @@ -251,8 +266,11 @@ export default class ClipperServer { } public async stop() { - this.server_.destroy(); - this.server_ = null; + if (this.server_) { + this.server_.destroy(); + this.server_ = null; + } + this.setStartState(StartState.Idle); this.setPort(null); } diff --git a/packages/lib/clipperUtils.ts b/packages/lib/clipperUtils.ts index 351fb3e5c4..4f8a869880 100644 --- a/packages/lib/clipperUtils.ts +++ b/packages/lib/clipperUtils.ts @@ -34,6 +34,9 @@ function baseUrl() { output2.pop(); output = output2.join('/'); } + if (output[output.length - 1] === '/') { + output = output.slice(0, -1); + } return output; } diff --git a/packages/lib/commands/index.ts b/packages/lib/commands/index.ts index f3bef142e7..38403bb83c 100644 --- a/packages/lib/commands/index.ts +++ b/packages/lib/commands/index.ts @@ -7,6 +7,7 @@ import * as permanentlyDeleteNote from './permanentlyDeleteNote'; import * as renderMarkup from './renderMarkup'; import * as showEditorPlugin from './showEditorPlugin'; import * as synchronize from './synchronize'; +import * as toggleAllFolders from './toggleAllFolders'; import * as toggleEditorPlugin from './toggleEditorPlugin'; const index: any[] = [ @@ -18,6 +19,7 @@ const index: any[] = [ renderMarkup, showEditorPlugin, synchronize, + toggleAllFolders, toggleEditorPlugin, ]; diff --git a/packages/lib/commands/renderMarkup.ts b/packages/lib/commands/renderMarkup.ts index a5cdb35a32..196d74fda9 100644 --- a/packages/lib/commands/renderMarkup.ts +++ b/packages/lib/commands/renderMarkup.ts @@ -1,6 +1,7 @@ import markupLanguageUtils from '../markupLanguageUtils'; import Setting from '../models/Setting'; import { CommandRuntime, CommandDeclaration, CommandContext } from '../services/CommandService'; +import shim from '../shim'; import { themeStyle } from '../theme'; import attachedResources from '../utils/attachedResources'; import { MarkupLanguage } from '@joplin/renderer'; @@ -12,8 +13,13 @@ export const declaration: CommandDeclaration = { }; const getMarkupToHtml = () => { + // In the desktop app, resources accessed with file:// URLs can't be displayed in certain places (e.g. the note + // viewer and plugin WebViews). On mobile, however, joplin-content:// URLs don't work. As such, use different + // protocols on different platforms: + const protocol = shim.isElectron() ? 'joplin-content://note-viewer/' : 'file://'; + return markupLanguageUtils.newMarkupToHtml({}, { - resourceBaseUrl: `file://${Setting.value('resourceDir')}/`, + resourceBaseUrl: `${protocol}${Setting.value('resourceDir')}/`, customCss: '', }); }; diff --git a/packages/lib/commands/toggleAllFolders.test.ts b/packages/lib/commands/toggleAllFolders.test.ts new file mode 100644 index 0000000000..ba7d7574cf --- /dev/null +++ b/packages/lib/commands/toggleAllFolders.test.ts @@ -0,0 +1,36 @@ +import { setupDatabase, switchClient } from '../testing/test-utils'; +import { runtime } from './toggleAllFolders'; +import Setting from '../models/Setting'; +import { CommandContext } from '../services/CommandService'; +import { defaultState } from '../reducer'; + +const command = runtime(); + +const makeContext = (): CommandContext => { + return { + state: defaultState, + dispatch: ()=>{}, + }; +}; + +describe('toggleAllFolders', () => { + + beforeEach(async () => { + await setupDatabase(0); + await switchClient(0); + }); + + test('expanding all should expand the folders header, if previously collapsed', async () => { + Setting.setValue('folderHeaderIsExpanded', false); + + // Collapsing all should leave the folder header as-is + const context = makeContext(); + await command.execute(context, true); + expect(Setting.value('folderHeaderIsExpanded')).toBe(false); + + // Expanding all should also expand the folder header + await command.execute(context, false); + expect(Setting.value('folderHeaderIsExpanded')).toBe(true); + }); + +}); diff --git a/packages/lib/commands/toggleAllFolders.ts b/packages/lib/commands/toggleAllFolders.ts new file mode 100644 index 0000000000..5c575f9abe --- /dev/null +++ b/packages/lib/commands/toggleAllFolders.ts @@ -0,0 +1,24 @@ +import { CommandRuntime, CommandDeclaration, CommandContext } from '../services/CommandService'; +import { _ } from '../locale'; +import getCanBeCollapsedFolderIds from '../models/utils/getCanBeCollapsedFolderIds'; +import Setting from '../models/Setting'; + +export const declaration: CommandDeclaration = { + name: 'toggleAllFolders', + label: () => _('Toggle all notebooks'), +}; + +export const runtime = (): CommandRuntime => { + return { + execute: async (context: CommandContext, collapseAll: boolean) => { + if (!collapseAll && !Setting.value('folderHeaderIsExpanded')) { + Setting.setValue('folderHeaderIsExpanded', true); + } + + context.dispatch({ + type: 'FOLDER_SET_COLLAPSED', + ids: collapseAll ? getCanBeCollapsedFolderIds(context.state.folders) : [], + }); + }, + }; +}; diff --git a/packages/lib/commands/toggleEditorPlugin.ts b/packages/lib/commands/toggleEditorPlugin.ts index 9b991fc61c..76317bc6a9 100644 --- a/packages/lib/commands/toggleEditorPlugin.ts +++ b/packages/lib/commands/toggleEditorPlugin.ts @@ -45,5 +45,7 @@ export const runtime = (): CommandRuntime => { }); } }, + + enabledCondition: 'hasActivePluginEditor', }; }; diff --git a/packages/lib/determineBaseAppDirs.ts b/packages/lib/determineBaseAppDirs.ts index 92198e09ef..32d8652e48 100644 --- a/packages/lib/determineBaseAppDirs.ts +++ b/packages/lib/determineBaseAppDirs.ts @@ -1,7 +1,7 @@ import { homedir } from 'os'; import { toSystemSlashes } from './path-utils'; -export default (profileFromArgs: string, appName: string) => { +export default (profileFromArgs: string, appName: string, altInstanceId: string) => { let profileDir = ''; let homeDir = ''; @@ -12,7 +12,11 @@ export default (profileFromArgs: string, appName: string) => { profileDir = `${process.env.PORTABLE_EXECUTABLE_DIR}/JoplinProfile`; homeDir = process.env.PORTABLE_EXECUTABLE_DIR; } else { - profileDir = `${homedir()}/.config/${appName}`; + if (!altInstanceId) { + profileDir = `${homedir()}/.config/${appName}`; + } else { + profileDir = `${homedir()}/.config/${appName}-${altInstanceId}`; + } homeDir = homedir(); } diff --git a/packages/lib/getAppName.test.ts b/packages/lib/getAppName.test.ts new file mode 100644 index 0000000000..ae1c0280da --- /dev/null +++ b/packages/lib/getAppName.test.ts @@ -0,0 +1,12 @@ +import getAppName from './getAppName'; + +describe('getAppName', () => { + + it('should get the app name', () => { + expect(getAppName(true, true)).toBe('joplindev-desktop'); + expect(getAppName(true, false)).toBe('joplin-desktop'); + expect(getAppName(false, false)).toBe('joplin'); + expect(getAppName(false, true)).toBe('joplindev'); + }); + +}); diff --git a/packages/lib/getAppName.ts b/packages/lib/getAppName.ts new file mode 100644 index 0000000000..0b7a865fbe --- /dev/null +++ b/packages/lib/getAppName.ts @@ -0,0 +1,5 @@ +export default (isDesktop: boolean, isDev: boolean) => { + let appName = isDev ? 'joplindev' : 'joplin'; + if (isDesktop) appName += '-desktop'; + return appName; +}; diff --git a/packages/lib/models/Note_CustomSortOrder.test.js b/packages/lib/models/Note_CustomSortOrder.test.js index 4e2c3b64c6..357144f775 100644 --- a/packages/lib/models/Note_CustomSortOrder.test.js +++ b/packages/lib/models/Note_CustomSortOrder.test.js @@ -1,5 +1,5 @@ const time = require('../time').default; -const { setupDatabaseAndSynchronizer, switchClient } = require('../testing/test-utils.js'); +const { setupDatabaseAndSynchronizer, switchClient, msleep } = require('../testing/test-utils.js'); const Folder = require('../models/Folder').default; const Note = require('../models/Note').default; @@ -95,6 +95,8 @@ describe('models/Note_CustomSortOrder', () => { const timeBefore = time.unixMs(); + await msleep(10); + await Note.insertNotesAt(folder1.id, [note2.id], 0); await Note.insertNotesAt(folder1.id, [note1.id], 1); diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index 5c3352cf93..2579f427ee 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -91,8 +91,7 @@ interface SettingSections { interface DefaultMigration { name: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - previousDefault: any; + previousDefault: string | boolean | number; } // To create a default migration: @@ -117,6 +116,10 @@ const defaultMigrations: DefaultMigration[] = [ name: 'themeAutoDetect', previousDefault: false, }, + { + name: 'ocr.enabled', + previousDefault: false, + }, ]; // "UserSettingMigration" are used to migrate existing user setting to a new setting. With a way diff --git a/packages/lib/models/settings/builtInMetadata.ts b/packages/lib/models/settings/builtInMetadata.ts index 795b234615..eb42dd5155 100644 --- a/packages/lib/models/settings/builtInMetadata.ts +++ b/packages/lib/models/settings/builtInMetadata.ts @@ -62,6 +62,16 @@ const builtInMetadata = (Setting: typeof SettingType) => { type: SettingItemType.String, public: false, }, + + 'altInstanceId': { + value: '', + type: SettingItemType.String, + public: false, + appTypes: [AppType.Desktop], + storage: SettingStorage.File, + isGlobal: true, + }, + 'editor.codeView': { value: true, type: SettingItemType.Bool, @@ -674,6 +684,17 @@ const builtInMetadata = (Setting: typeof SettingType) => { storage: SettingStorage.File, isGlobal: true, }, + 'editor.enableTextPatterns': { + value: true, + type: SettingItemType.Bool, + public: true, + section: 'note', + appTypes: [AppType.Desktop], + label: () => _('Auto-format Markdown in the Rich Text Editor'), + description: () => _('Enables Markdown pattern replacement in the Rich Text Editor. For example, when enabled, typing **bold** creates bold text.'), + storage: SettingStorage.File, + isGlobal: true, + }, 'editor.toolbarButtons': { value: [] as string[], public: false, @@ -818,22 +839,6 @@ const builtInMetadata = (Setting: typeof SettingType) => { isGlobal: true, }, - // Works around a bug in which additional space is visible beneath the toolbar on some devices. - // See https://github.com/laurent22/joplin/pull/6823 - 'editor.mobile.removeSpaceBelowToolbar': { - value: false, - type: SettingItemType.Bool, - section: 'note', - public: true, - appTypes: [AppType.Mobile], - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - show: (settings: any) => settings['editor.mobile.removeSpaceBelowToolbar'], - label: () => 'Remove extra space below the markdown toolbar', - description: () => 'Works around bug on some devices where the markdown toolbar does not touch the bottom of the screen.', - storage: SettingStorage.File, - isGlobal: true, - }, - newTodoFocus: { value: 'title', type: SettingItemType.String, diff --git a/packages/lib/models/utils/areAllFoldersCollapsed.test.ts b/packages/lib/models/utils/areAllFoldersCollapsed.test.ts new file mode 100644 index 0000000000..204abe19a3 --- /dev/null +++ b/packages/lib/models/utils/areAllFoldersCollapsed.test.ts @@ -0,0 +1,35 @@ +import { setupDatabaseAndSynchronizer, switchClient } from '../../testing/test-utils'; +import Folder from '../Folder'; +import areAllFoldersCollapsed from './areAllFoldersCollapsed'; + +describe('areAllFoldersCollapsed', () => { + + beforeEach(async () => { + await setupDatabaseAndSynchronizer(1); + await switchClient(1); + }); + + it('should tell if all folders are collapsed', async () => { + const folder1 = await Folder.save({}); + await Folder.save({ parent_id: folder1.id }); + await Folder.save({ parent_id: folder1.id }); + + const folder2 = await Folder.save({ }); + const folder2a = await Folder.save({ parent_id: folder2.id }); + await Folder.save({ parent_id: folder2a.id }); + + expect(areAllFoldersCollapsed(await Folder.all(), [])).toBe(false); + + expect(areAllFoldersCollapsed(await Folder.all(), [ + folder1.id, + folder2.id, + ])).toBe(false); + + expect(areAllFoldersCollapsed(await Folder.all(), [ + folder1.id, + folder2.id, + folder2a.id, + ])).toBe(true); + }); + +}); diff --git a/packages/lib/models/utils/areAllFoldersCollapsed.ts b/packages/lib/models/utils/areAllFoldersCollapsed.ts new file mode 100644 index 0000000000..146d6cdc99 --- /dev/null +++ b/packages/lib/models/utils/areAllFoldersCollapsed.ts @@ -0,0 +1,10 @@ +import { FolderEntity } from '../../services/database/types'; +import getCanBeCollapsedFolderIds from './getCanBeCollapsedFolderIds'; + +export default (folders: FolderEntity[], collapsedFolderIds: string[]) => { + const canBeCollapsedIds = getCanBeCollapsedFolderIds(folders); + if (collapsedFolderIds.length !== canBeCollapsedIds.length) return false; + collapsedFolderIds = collapsedFolderIds.slice().sort(); + canBeCollapsedIds.sort(); + return JSON.stringify(collapsedFolderIds) === JSON.stringify(canBeCollapsedIds); +}; diff --git a/packages/lib/models/utils/getCanBeCollapsedFolderIds.ts b/packages/lib/models/utils/getCanBeCollapsedFolderIds.ts new file mode 100644 index 0000000000..cfa4ea9688 --- /dev/null +++ b/packages/lib/models/utils/getCanBeCollapsedFolderIds.ts @@ -0,0 +1,21 @@ +import { FolderEntity } from '../../services/database/types'; +import Folder, { FolderEntityWithChildren } from '../Folder'; + +export default (folders: FolderEntity[]) => { + const tree = Folder.buildTree(folders); + + const canBeCollapsedIds: string[] = []; + + const processTree = (folders: FolderEntityWithChildren[]) => { + for (const folder of folders) { + if (folder.children.length) { + canBeCollapsedIds.push(folder.id); + processTree(folder.children); + } + } + }; + + processTree(tree); + + return canBeCollapsedIds; +}; diff --git a/packages/lib/reducer.ts b/packages/lib/reducer.ts index 1e972efa77..9c6c3c7216 100644 --- a/packages/lib/reducer.ts +++ b/packages/lib/reducer.ts @@ -449,6 +449,11 @@ function stateHasEncryptedItems(state: State) { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied function folderSetCollapsed(draft: Draft, action: any) { + if (action.ids) { + draft.collapsedFolderIds = action.ids; + return; + } + const collapsedFolderIds = draft.collapsedFolderIds.slice(); const idx = collapsedFolderIds.indexOf(action.id); diff --git a/packages/lib/services/DecryptionWorker.ts b/packages/lib/services/DecryptionWorker.ts index 86ab592bd5..0db9aded16 100644 --- a/packages/lib/services/DecryptionWorker.ts +++ b/packages/lib/services/DecryptionWorker.ts @@ -18,6 +18,16 @@ interface DecryptionResult { error: any; } +// Key for use with the KvStore. +const decryptionErrorKeyPrefix = 'decryptErrorLabel:'; +const decryptionErrorKey = (type: number, id: string) => { + return `${decryptionErrorKeyPrefix}${type}:${id}`; +}; +const decryptionCounterKeyPrefix = 'decrypt:'; +const decryptionCounterKey = (type: number, id: string) => { + return `${decryptionCounterKeyPrefix}${type}:${id}`; +}; + export default class DecryptionWorker { public static instance_: DecryptionWorker = null; @@ -96,24 +106,29 @@ export default class DecryptionWorker { } public async decryptionDisabledItems() { - let items = await this.kvStore().searchByPrefix('decrypt:'); + let items = await this.kvStore().searchByPrefix(decryptionCounterKeyPrefix); items = items.filter(item => item.value > this.maxDecryptionAttempts_); - items = items.map(item => { + return await Promise.all(items.map(async item => { const s = item.key.split(':'); + const type_ = Number(s[1]); + const id = s[2]; + const errorDescription = await this.kvStore().value(decryptionErrorKey(type_, id)); return { - type_: Number(s[1]), - id: s[2], + type_, + id, + reason: errorDescription, }; - }); - return items; + })); } - public async clearDisabledItem(typeId: string, itemId: string) { - await this.kvStore().deleteValue(`decrypt:${typeId}:${itemId}`); + public async clearDisabledItem(typeId: number, itemId: string) { + await this.kvStore().deleteValue(decryptionCounterKey(typeId, itemId)); + await this.kvStore().deleteValue(decryptionErrorKey(typeId, itemId)); } public async clearDisabledItems() { - await this.kvStore().deleteByPrefix('decrypt:'); + await this.kvStore().deleteByPrefix(decryptionCounterKeyPrefix); + await this.kvStore().deleteByPrefix(decryptionErrorKeyPrefix); } // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied @@ -193,10 +208,14 @@ export default class DecryptionWorker { itemCount: items.length, }); - const counterKey = `decrypt:${item.type_}:${item.id}`; + const counterKey = decryptionCounterKey(item.type_, item.id); + const errorKey = decryptionErrorKey(item.type_, item.id); const clearDecryptionCounter = async () => { await this.kvStore().deleteValue(counterKey); + // The decryption error key stores the reason for the decryption counter's value. + // As such, the error should be reset when the decryption counter is reset: + await this.kvStore().deleteValue(errorKey); }; // Don't log in production as it results in many messages when importing many items @@ -253,6 +272,8 @@ export default class DecryptionWorker { throw error; } + await this.kvStore().setValue(errorKey, String(error)); + if (options.errorHandler === 'log') { this.logger().warn(`DecryptionWorker: error for: ${item.id} (${ItemClass.tableName()})`, error); this.logger().debug('Item with error:', item); diff --git a/packages/lib/services/KeymapService.ts b/packages/lib/services/KeymapService.ts index d5d62bb441..6e5995c227 100644 --- a/packages/lib/services/KeymapService.ts +++ b/packages/lib/services/KeymapService.ts @@ -42,6 +42,7 @@ const defaultKeymapItems = { { accelerator: 'Option+Cmd+S', command: 'toggleSideBar' }, { accelerator: 'Option+Cmd+L', command: 'toggleNoteList' }, { accelerator: 'Cmd+L', command: 'toggleVisiblePanes' }, + { accelerator: 'Option+Cmd+V', command: 'toggleEditorPlugin' }, { accelerator: 'Cmd+0', command: 'zoomActualSize' }, { accelerator: 'Cmd+E', command: 'toggleExternalEditing' }, { accelerator: 'Option+Cmd+T', command: 'setTags' }, @@ -93,6 +94,7 @@ const defaultKeymapItems = { { accelerator: 'Ctrl+Shift+M', command: 'toggleMenuBar' }, { accelerator: 'F11', command: 'toggleNoteList' }, { accelerator: 'Ctrl+L', command: 'toggleVisiblePanes' }, + { accelerator: 'Alt+Ctrl+V', command: 'toggleEditorPlugin' }, { accelerator: 'Ctrl+0', command: 'zoomActualSize' }, { accelerator: 'Ctrl+E', command: 'toggleExternalEditing' }, { accelerator: 'Ctrl+Alt+T', command: 'setTags' }, diff --git a/packages/lib/services/KvStore.ts b/packages/lib/services/KvStore.ts index 16220111e8..68cb4cb2ce 100644 --- a/packages/lib/services/KvStore.ts +++ b/packages/lib/services/KvStore.ts @@ -6,6 +6,13 @@ enum ValueType { Text = 2, } +interface KvStoreKeyValue { + key: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partial refactoring of old code from before rule was applied + value: any; + type: ValueType; +} + export default class KvStore extends BaseService { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied @@ -110,7 +117,7 @@ export default class KvStore extends BaseService { } } - public async searchByPrefix(prefix: string) { + public async searchByPrefix(prefix: string): Promise { const results = await this.db().selectAll('SELECT `key`, `value`, `type` FROM key_values WHERE `key` LIKE ?', [`${prefix}%`]); return this.formatValues_(results); } diff --git a/packages/lib/services/ReportService.test.ts b/packages/lib/services/ReportService.test.ts index 54bd9f1cc7..c108a12315 100644 --- a/packages/lib/services/ReportService.test.ts +++ b/packages/lib/services/ReportService.test.ts @@ -1,11 +1,15 @@ import { _ } from '../locale'; import ReportService, { ReportSection } from './ReportService'; -import { createNTestNotes, decryptionWorker, setupDatabaseAndSynchronizer, supportDir, switchClient, syncTargetId, synchronizer, synchronizerStart } from '../testing/test-utils'; +import { createNTestNotes, decryptionWorker, encryptionService, loadEncryptionMasterKey, setupDatabaseAndSynchronizer, supportDir, switchClient, syncTargetId, synchronizer, synchronizerStart } from '../testing/test-utils'; import Folder from '../models/Folder'; import BaseItem from '../models/BaseItem'; -import DecryptionWorker from './DecryptionWorker'; import Note from '../models/Note'; import shim from '../shim'; +import SyncTargetRegistry from '../SyncTargetRegistry'; +import { loadMasterKeysFromSettings, setupAndEnableEncryption } from './e2ee/utils'; +import Setting from '../models/Setting'; +import DecryptionWorker from './DecryptionWorker'; +import { ModelType } from '../BaseModel'; const firstSectionWithTitle = (report: ReportSection[], title: string) => { @@ -22,6 +26,10 @@ const getIgnoredSection = (report: ReportSection[]) => { return firstSectionWithTitle(report, _('Ignored items that cannot be synchronised')); }; +const getDecryptionErrorSection = (report: ReportSection[]): ReportSection|null => { + return firstSectionWithTitle(report, _('Items that cannot be decrypted')); +}; + const sectionBodyToText = (section: ReportSection) => { return section.body.map(item => { if (typeof item === 'string') { @@ -32,13 +40,71 @@ const sectionBodyToText = (section: ReportSection) => { }).join('\n'); }; +const getListItemsInBodyStartingWith = (section: ReportSection, keyPrefix: string) => { + return section.body.filter(item => + typeof item !== 'string' && item.type === 'openList' && item.key.startsWith(keyPrefix), + ); +}; + +const addCannotDecryptNotes = async (corruptedNoteCount: number) => { + await switchClient(2); + + const notes = []; + for (let i = 0; i < corruptedNoteCount; i++) { + notes.push(await Note.save({ title: `Note ${i}` })); + } + + await synchronizerStart(); + await switchClient(1); + await synchronizerStart(); + + // First, simulate a broken note and check that the decryption worker + // gives up decrypting after a number of tries. This is mainly relevant + // for data that crashes the mobile application - we don't want to keep + // decrypting these. + + for (const note of notes) { + await Note.save({ id: note.id, encryption_cipher_text: 'bad' }); + } + + return notes.map(note => note.id); +}; + +const addRemoteNotes = async (noteCount: number) => { + await switchClient(2); + + const notes = []; + for (let i = 0; i < noteCount; i++) { + notes.push(await Note.save({ title: `Test Note ${i}` })); + } + + await synchronizerStart(); + await switchClient(1); + + return notes.map(note => note.id); +}; + +const setUpLocalAndRemoteEncryption = async () => { + await switchClient(2); + + // Encryption setup + const masterKey = await loadEncryptionMasterKey(); + await setupAndEnableEncryption(encryptionService(), masterKey, '123456'); + await synchronizerStart(); + + // Give both clients the same master key + await switchClient(1); + await synchronizerStart(); + + Setting.setObjectValue('encryption.passwordCache', masterKey.id, '123456'); + await loadMasterKeysFromSettings(encryptionService()); +}; + describe('ReportService', () => { beforeEach(async () => { await setupDatabaseAndSynchronizer(1); await setupDatabaseAndSynchronizer(2); await switchClient(1); - // For compatibility with code that calls DecryptionWorker.instance() - DecryptionWorker.instance_ = decryptionWorker(); }); it('should move sync errors to the "ignored" section after clicking "ignore"', async () => { @@ -129,6 +195,7 @@ describe('ReportService', () => { let report = await service.status(syncTargetId()); const unsyncableSection = getCannotSyncSection(report); + expect(unsyncableSection).not.toBeNull(); expect(sectionBodyToText(unsyncableSection)).toContain('could not be downloaded'); // Item for the download error should be ignorable @@ -159,4 +226,85 @@ describe('ReportService', () => { expect(getIgnoredSection(report)).toBeNull(); expect(getCannotSyncSection(report)).toBeNull(); }); + + it('should associate decryption failures with error message headers when errors are known', async () => { + await setUpLocalAndRemoteEncryption(); + + const service = new ReportService(); + const syncTargetId = SyncTargetRegistry.nameToId('joplinServer'); + let report = await service.status(syncTargetId); + + // Initially, should not have a "cannot be decrypted section" + expect(getDecryptionErrorSection(report)).toBeNull(); + + const corruptedNoteIds = await addCannotDecryptNotes(4); + await addRemoteNotes(10); + await synchronizerStart(); + + for (let i = 0; i < 3; i++) { + report = await service.status(syncTargetId); + expect(getDecryptionErrorSection(report)).toBeNull(); + + // .start needs to be run multiple times for items to be disabled and thus + // added to the report + await decryptionWorker().start(); + } + + // After adding corrupted notes, it should have such a section. + report = await service.status(syncTargetId); + const decryptionErrorsSection = getDecryptionErrorSection(report); + expect(decryptionErrorsSection).not.toBeNull(); + + // There should be a list of errors (all errors are known) + const errorLists = getListItemsInBodyStartingWith(decryptionErrorsSection, 'itemsWithError'); + expect(errorLists).toHaveLength(1); + + // There should, however, be testIds.length ReportItems with the IDs of the notes. + const decryptionErrorsText = sectionBodyToText(decryptionErrorsSection); + for (const noteId of corruptedNoteIds) { + expect(decryptionErrorsText).toContain(noteId); + } + }); + + it('should not associate decryption failures with error message headers when errors are unknown', async () => { + const decryption = decryptionWorker(); + + // Create decryption errors: + const testIds = ['0123456789012345601234567890123456', '0123456789012345601234567890123457', '0123456789012345601234567890123458']; + + // Adds items to the decryption error list **without also adding the reason**. This matches + // the format of older decryption errors. + const addIdsToDecryptionErrorList = async (worker: DecryptionWorker, ids: string[]) => { + for (const id of ids) { + // A value that is more than the maximum number of attempts: + const numDecryptionAttempts = 3; + + // Add the failure manually so that the error message is unknown + await worker.kvStore().setValue( + `decrypt:${ModelType.Note}:${id}`, numDecryptionAttempts, + ); + } + }; + + await addIdsToDecryptionErrorList(decryption, testIds); + + const service = new ReportService(); + const syncTargetId = SyncTargetRegistry.nameToId('joplinServer'); + const report = await service.status(syncTargetId); + + // Report should have an "Items that cannot be decrypted" section + const decryptionErrorSection = getDecryptionErrorSection(report); + expect(decryptionErrorSection).not.toBeNull(); + + // There should not be any lists of errors (no errors associated with the item). + const errorLists = getListItemsInBodyStartingWith(decryptionErrorSection, 'itemsWithError'); + expect(errorLists).toHaveLength(0); + + // There should be items with the correct messages: + const expectedMessages = testIds.map(id => `Note: ${id}`); + const bodyText = sectionBodyToText(decryptionErrorSection); + for (const message of expectedMessages) { + expect(bodyText).toContain(message); + } + }); }); diff --git a/packages/lib/services/ReportService.ts b/packages/lib/services/ReportService.ts index 57b19de8ce..c841d40539 100644 --- a/packages/lib/services/ReportService.ts +++ b/packages/lib/services/ReportService.ts @@ -16,7 +16,7 @@ enum CanRetryType { ItemSync = 'itemSync', } -enum ReportItemType { +export enum ReportItemType { OpenList = 'openList', CloseList = 'closeList', } @@ -255,17 +255,51 @@ export default class ReportService { section.body.push(''); + const errorMessagesToItems: Map = new Map(); + for (let i = 0; i < decryptionDisabledItems.length; i++) { const row = decryptionDisabledItems[i]; - section.body.push({ - text: _('%s: %s', toTitleCase(BaseModel.modelTypeToName(row.type_)), row.id), + + const resourceTypeName = toTitleCase(BaseModel.modelTypeToName(row.type_)); + const message = _('%s: %s', resourceTypeName, row.id); + + const item: ReportItem = { + text: message, canRetry: true, canRetryType: CanRetryType.E2EE, retryHandler: async () => { await DecryptionWorker.instance().clearDisabledItem(row.type_, row.id); void DecryptionWorker.instance().scheduleStart(); }, - }); + }; + + const itemError = row.reason; + if (itemError) { + // If the error message is known, postpone adding the report item. + // Instead, add it under the error message as a heading + if (errorMessagesToItems.has(itemError)) { + errorMessagesToItems.get(itemError).push(item); + } else { + errorMessagesToItems.set(itemError, [item]); + } + } else { + // If there's no known error, add directly: + section.body.push(item); + } + } + + // Categorize any items under each known error: + let errorIdx = 0; + for (const itemError of errorMessagesToItems.keys()) { + section.body.push(_('Items with error: %s', itemError)); + + errorIdx++; + section.body.push({ type: ReportItemType.OpenList, key: `itemsWithError${errorIdx}` }); + + // Add all items associated with the header + section.body.push(...errorMessagesToItems.get(itemError)); + + section.body.push({ type: ReportItemType.CloseList }); } section = this.addRetryAllHandler(section); diff --git a/packages/lib/services/commands/stateToWhenClauseContext.test.ts b/packages/lib/services/commands/stateToWhenClauseContext.test.ts index 2c0dc9922f..a772c9aa8b 100644 --- a/packages/lib/services/commands/stateToWhenClauseContext.test.ts +++ b/packages/lib/services/commands/stateToWhenClauseContext.test.ts @@ -50,6 +50,7 @@ describe('stateToWhenClauseContext', () => { it('should be in trash if command folder is deleted', async () => { const applicationState = { notes: [], + notesParentType: 'Folder', folders: [ { id: '1', deleted_time: 1722567036580, share_id: '', parent_id: '' }, ], @@ -71,4 +72,14 @@ describe('stateToWhenClauseContext', () => { expect(resultingState.inTrash).toBe(false); }); + it('should not be in trash if viewing all notes', async () => { + const applicationState = { + selectedFolderId: 'folder', + notesParentType: 'SmartFolder', + } as State; + const resultingState = stateToWhenClauseContext(applicationState); + + expect(resultingState.inTrash).toBe(false); + }); + }); diff --git a/packages/lib/services/commands/stateToWhenClauseContext.ts b/packages/lib/services/commands/stateToWhenClauseContext.ts index 5d380247f1..fa885ff77f 100644 --- a/packages/lib/services/commands/stateToWhenClauseContext.ts +++ b/packages/lib/services/commands/stateToWhenClauseContext.ts @@ -7,6 +7,7 @@ import { FolderEntity, NoteEntity } from '../database/types'; import { itemIsReadOnlySync, ItemSlice } from '../../models/utils/readOnly'; import ItemChange from '../../models/ItemChange'; import { getTrashFolderId } from '../trash'; +import getActivePluginEditorView from '../plugins/utils/getActivePluginEditorView'; export interface WhenClauseContextOptions { commandFolderId?: string; @@ -43,6 +44,7 @@ export interface WhenClauseContext { oneNoteSelected: boolean; someNotesSelected: boolean; syncStarted: boolean; + hasActivePluginEditor: boolean; } export default function stateToWhenClauseContext(state: State, options: WhenClauseContextOptions = null): WhenClauseContext { @@ -58,9 +60,11 @@ export default function stateToWhenClauseContext(state: State, options: WhenClau const selectedNote: NoteEntity = selectedNoteId ? BaseModel.byId(windowState.notes, selectedNoteId) : null; const selectedNotes = BaseModel.modelsByIds(windowState.notes ?? [], selectedNoteIds); - const commandFolderId = options.commandFolderId || windowState.selectedFolderId; + const commandFolderId = state.notesParentType === 'Folder' ? (options.commandFolderId || windowState.selectedFolderId) : ''; const commandFolder: FolderEntity = commandFolderId ? BaseModel.byId(state.folders, commandFolderId) : null; + const { editorPlugin } = state.pluginService ? getActivePluginEditorView(state.pluginService.plugins) : { editorPlugin: null }; + const settings = state.settings || {}; return { @@ -108,5 +112,7 @@ export default function stateToWhenClauseContext(state: State, options: WhenClau joplinServerConnected: [9, 10].includes(settings['sync.target']), joplinCloudAccountType: settings['sync.target'] === 10 ? settings['sync.10.accountType'] : 0, hasMultiProfiles: state.profileConfig && state.profileConfig.profiles.length > 1, + + hasActivePluginEditor: !!editorPlugin, }; } diff --git a/packages/lib/services/interop/InteropService_Importer_OneNote.test.ts b/packages/lib/services/interop/InteropService_Importer_OneNote.test.ts index 293b68ab31..766c1c168e 100644 --- a/packages/lib/services/interop/InteropService_Importer_OneNote.test.ts +++ b/packages/lib/services/interop/InteropService_Importer_OneNote.test.ts @@ -220,4 +220,17 @@ describe('InteropService_Importer_OneNote', () => { } BaseModel.setIdGenerator(originalIdGenerator); }); + + skipIfNotCI('should render audio as links to resource', async () => { + let idx = 0; + const originalIdGenerator = BaseModel.setIdGenerator(() => String(idx++)); + const notes = await importNote(`${supportDir}/onenote/note_with_audio_embedded.zip`); + + expect(notes.length).toBe(2); + + for (const note of notes) { + expect(note.body).toMatchSnapshot(note.title); + } + BaseModel.setIdGenerator(originalIdGenerator); + }); }); diff --git a/packages/lib/services/interop/__snapshots__/InteropService_Importer_OneNote.test.js.snap b/packages/lib/services/interop/__snapshots__/InteropService_Importer_OneNote.test.js.snap index ffa60e8849..f4f457dd63 100644 --- a/packages/lib/services/interop/__snapshots__/InteropService_Importer_OneNote.test.js.snap +++ b/packages/lib/services/interop/__snapshots__/InteropService_Importer_OneNote.test.js.snap @@ -1166,6 +1166,148 @@ exports[`InteropService_Importer_OneNote should remove hyperlink from title: 风 " `; +exports[`InteropService_Importer_OneNote should render audio as links to resource: My title 1`] = ` +" + + + + My title + + + + +
My title
+
Friday, March 7, 2025
+
11:16 AM
+

 

+

 

+

Some text here

+

 

+
+ + + +" +`; + +exports[`InteropService_Importer_OneNote should render audio as links to resource: Quick Notes 1`] = ` +" + + + + Quick Notes + + + + + + + + + + + +" +`; + exports[`InteropService_Importer_OneNote should render links properly by ignoring wrongly set indices when the first character is a hyperlink marker: Is Mexico safe for shooting Street Photography 1`] = ` " diff --git a/packages/lib/services/ocr/OcrService.test.ts b/packages/lib/services/ocr/OcrService.test.ts index a8b0cccedd..a598126af8 100644 --- a/packages/lib/services/ocr/OcrService.test.ts +++ b/packages/lib/services/ocr/OcrService.test.ts @@ -248,4 +248,28 @@ describe('OcrService', () => { // await service.dispose(); // }); + it('should generate text even on cases of lower confidence', async () => { + const { resource } = await createNoteAndResource({ path: `${ocrSampleDir}/low_confidence_testing.png` }); + + const service = newOcrService(); + await service.processResources(); + + const processedResource: ResourceEntity = await Resource.load(resource.id); + expect(processedResource.ocr_text.includes('1.')).toBe(true); + // cSpell:disable + expect(processedResource.ocr_text.includes('eback Mountain (2005)')).toBe(true); + // cSpell:enable + + expect(processedResource.ocr_text.includes('2.')).toBe(true); + expect(processedResource.ocr_text.includes('Havoc (2005)')).toBe(true); + + expect(processedResource.ocr_text.includes('3.')).toBe(true); + expect(processedResource.ocr_text.includes('Love & Other Drugs (2010)')).toBe(true); + + expect(processedResource.ocr_text.includes('4.')).toBe(true); + expect(processedResource.ocr_text.includes('The Last Thing He Wanted (2020)')).toBe(true); + + await service.dispose(); + }); + }); diff --git a/packages/lib/services/ocr/drivers/OcrDriverTesseract.ts b/packages/lib/services/ocr/drivers/OcrDriverTesseract.ts index d5534ea2c1..e2792ee3fd 100644 --- a/packages/lib/services/ocr/drivers/OcrDriverTesseract.ts +++ b/packages/lib/services/ocr/drivers/OcrDriverTesseract.ts @@ -23,10 +23,15 @@ const formatTesseractBoundingBox = (boundingBox: Tesseract.Bbox): RecognizeResul return [boundingBox.x0, boundingBox.x1, boundingBox.y0, boundingBox.y1]; }; -// Empirically, it seems anything below 70 is not usable. Between 70 and 75 it's -// hit and miss, but often it's good enough that we should keep the result. -// Above this is usually reliable. -const minConfidence = 70; +// 2023-12-13: Empirically, it seems anything below 70 is not usable. Between 70 +// and 75 it's hit and miss, but often it's good enough that we should keep the result. +// Above this is usually reliable. Using 70 for now. +// +// 2025-04-03: Changed to 55 to detect text in images that are supported in +// other tools but were not in Joplin. +// +// https://github.com/laurent22/joplin/issues/11608 +const minConfidence = 55; interface Options { workerPath: string; diff --git a/packages/lib/services/plugins/api/JoplinSettings.ts b/packages/lib/services/plugins/api/JoplinSettings.ts index 1624141d33..c6c8d93f48 100644 --- a/packages/lib/services/plugins/api/JoplinSettings.ts +++ b/packages/lib/services/plugins/api/JoplinSettings.ts @@ -177,11 +177,23 @@ export default class JoplinSettings { } /** - * Gets a global setting value, including app-specific settings and those set by other plugins. + * Gets global setting values, including app-specific settings and those set by other plugins. * * The list of available settings is not documented yet, but can be found by looking at the source code: * - * https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Setting.ts#L142 + * https://github.com/laurent22/joplin/blob/dev/packages/lib/models/settings/builtInMetadata.ts + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied + public async globalValues(keys: string[]): Promise { + const output: (string|number|boolean)[] = []; + for (const key of keys) { + output.push(Setting.value(key)); + } + return output; + } + + /** + * @deprecated Use joplin.settings.globalValues() */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied public async globalValue(key: string): Promise { diff --git a/packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.ts b/packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.ts index 0f79c79c8b..ccb1f494c8 100644 --- a/packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.ts +++ b/packages/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo.ts @@ -8,6 +8,7 @@ const getDefaultPluginsInfo = (): DefaultPluginsInfo => { settings: { 'path': `${Setting.value('homeDir')}`, 'createSubfolderPerProfile': true, + 'backupRetention': 7, }, // Joplin Portable is more likely to run on a device with low write speeds diff --git a/packages/lib/services/share/ShareService.ts b/packages/lib/services/share/ShareService.ts index 584f66b11b..43c992ebb5 100644 --- a/packages/lib/services/share/ShareService.ts +++ b/packages/lib/services/share/ShareService.ts @@ -329,6 +329,7 @@ export default class ShareService { let recipientMasterKey: MasterKeyEntity = null; if (getEncryptionEnabled()) { + if (!recipientEmail) throw new Error(_('Please provide the recipient email')); const syncInfo = localSyncInfo(); const masterKey = syncInfo.masterKeys.find(m => m.id === masterKeyId); if (!masterKey) throw new Error(`Cannot find master key with ID "${masterKeyId}"`); diff --git a/packages/lib/services/style/themeToCss.test.ts b/packages/lib/services/style/themeToCss.test.ts index 8ff2f2c39b..30aafe889f 100644 --- a/packages/lib/services/style/themeToCss.test.ts +++ b/packages/lib/services/style/themeToCss.test.ts @@ -27,6 +27,7 @@ const input: Theme = { colorError2: '#ff6c6c', colorWarn2: '#ffcb81', colorWarn3: '#ff7626', + backgroundColorTransparent2: 'rgba(0, 0, 0, 0.1)', // Color scheme "3" is used for the config screens for example/ // It's dark text over gray background. @@ -71,6 +72,7 @@ const expected = ` --joplin-background-color4: #ffffff; --joplin-background-color-hover3: #CBDAF1; --joplin-background-color-transparent: rgba(255,255,255,0.9); + --joplin-background-color-transparent2: rgba(0, 0, 0, 0.1); --joplin-block-quote-opacity: 0.7; --joplin-code-background-color: rgb(243, 243, 243); --joplin-code-border-color: rgb(220, 220, 220); diff --git a/packages/lib/services/synchronizer/Synchronizer.e2ee.test.ts b/packages/lib/services/synchronizer/Synchronizer.e2ee.test.ts index 3a878e1a57..c0e5efce77 100644 --- a/packages/lib/services/synchronizer/Synchronizer.e2ee.test.ts +++ b/packages/lib/services/synchronizer/Synchronizer.e2ee.test.ts @@ -409,7 +409,7 @@ describe('Synchronizer.e2ee', () => { expect(disabledItems.length).toBe(1); expect(disabledItems[0].id).toBe(note.id); - expect((await kvStore().all()).length).toBe(1); + expect((await kvStore().searchByPrefix('decrypt:')).length).toBe(1); await kvStore().clear(); // Now check that if it fails once but succeed the second time, the note diff --git a/packages/lib/shim-init-node.test.ts b/packages/lib/shim-init-node.test.ts index cc98d91a0d..30527c2910 100644 --- a/packages/lib/shim-init-node.test.ts +++ b/packages/lib/shim-init-node.test.ts @@ -1,7 +1,7 @@ const { shimInit } = require('./shim-init-node'); import shim from './shim'; -import { setupDatabaseAndSynchronizer, supportDir } from './testing/test-utils'; +import { createTempDir, setupDatabaseAndSynchronizer, supportDir } from './testing/test-utils'; import { copyFile } from 'fs-extra'; describe('shim-init-node', () => { @@ -19,8 +19,11 @@ describe('shim-init-node', () => { }); test('should preserve the file extension if one is provided regardless of the mime type', async () => { + const tempDir = await createTempDir(); + const originalFilePath = `${supportDir}/valid_pdf_without_ext`; - const fileWithDifferentExtension = `${originalFilePath}.mscz`; + const fileWithDifferentExtension = `${tempDir}/valid_pdf.mscz`; + await copyFile(originalFilePath, fileWithDifferentExtension); const resource = await shim.createResourceFromPath(fileWithDifferentExtension); diff --git a/packages/lib/shim-init-node.ts b/packages/lib/shim-init-node.ts index 0086323eeb..160868709e 100644 --- a/packages/lib/shim-init-node.ts +++ b/packages/lib/shim-init-node.ts @@ -19,6 +19,7 @@ import FileApiDriverLocal from './file-api-driver-local'; import * as mimeUtils from './mime-utils'; import BaseItem from './models/BaseItem'; import { Size } from '@joplin/utils/types'; +import { arch } from 'os'; const { _ } = require('./locale'); const http = require('http'); const https = require('https'); @@ -170,6 +171,14 @@ function shimInit(options: ShimInitOptions = null) { return Array.from(buffer); }; + shim.isAppleSilicon = () => { + return shim.isMac() && arch() === 'arm64'; + }; + + shim.platformArch = () => { + return arch(); + }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied shim.detectAndSetLocale = function(Setting: any) { let locale = shim.isElectron() ? shim.electronBridge().getLocale() : process.env.LANG; diff --git a/packages/lib/shim.ts b/packages/lib/shim.ts index 68d690a4be..39dfdb99f0 100644 --- a/packages/lib/shim.ts +++ b/packages/lib/shim.ts @@ -156,6 +156,12 @@ const shim = { return typeof process !== 'undefined' && process.platform === 'darwin'; }, + // Tells whether the computer **CPU** is an Apple Silicon (not whether the running version was + // built for ARM64) + isAppleSilicon: (): boolean => { + throw new Error('Not implemented: isAppleSilicon'); + }, + platformName: () => { if (shim.isReactNative()) return shim.mobilePlatform(); if (shim.isMac()) return 'darwin'; @@ -166,6 +172,23 @@ const shim = { throw new Error('Cannot determine platform'); }, + // Tells the computer CPU architecture. Which if different from the architecture the running + // version was built for. For example, the laptop CPU may be an ARM64, while the version was + // built for x64 architecture. Here we want to know the laptop CPU. + platformArch: (): string => { + throw new Error('Not implemented: platformArch'); + }, + + deviceString: () => { + const output: string[] = []; + + output.push(shim.platformName()); + + if (shim.platformArch()) output.push(shim.platformArch()); + + return output.join(', '); + }, + // "ios" or "android", or "" if not on mobile mobilePlatform: () => { return ''; // Default if we're not on mobile (React Native) diff --git a/packages/lib/testing/test-utils.ts b/packages/lib/testing/test-utils.ts index efa84d3cff..7babf900ea 100644 --- a/packages/lib/testing/test-utils.ts +++ b/packages/lib/testing/test-utils.ts @@ -287,6 +287,7 @@ async function switchClient(id: number, options: any = null) { Resource.encryptionService_ = encryptionServices_[id]; BaseItem.revisionService_ = revisionServices_[id]; ResourceFetcher.instance_ = resourceFetchers_[id]; + DecryptionWorker.instance_ = decryptionWorker(id); await Setting.reset(); Setting.settingFilename = settingFilename(id); @@ -549,7 +550,7 @@ function revisionService(id: number = null) { function decryptionWorker(id: number = null) { if (id === null) id = currentClient_; const o = decryptionWorkers_[id]; - o.setKvStore(kvStore(id)); + o?.setKvStore(kvStore(id)); return o; } diff --git a/packages/lib/theme.ts b/packages/lib/theme.ts index 1f470abf0b..2125199393 100644 --- a/packages/lib/theme.ts +++ b/packages/lib/theme.ts @@ -52,6 +52,7 @@ const globalStyle = (() => { mainPadding: 12, topRowHeight: 50, editorPaddingLeft: 8, + listTabSize: '1.7em', margin: margin, marginRight: margin, diff --git a/packages/lib/themes/light.ts b/packages/lib/themes/light.ts index 0755695016..def3367641 100644 --- a/packages/lib/themes/light.ts +++ b/packages/lib/themes/light.ts @@ -27,6 +27,7 @@ const theme: Theme = { colorError2: '#ff7070', colorWarn2: '#ffcb81', colorWarn3: '#ff7626', + backgroundColorTransparent2: 'rgba(0, 0, 0, 0.1)', // Color scheme "3" is used for the config screens for example/ // It's dark text over gray background. diff --git a/packages/lib/themes/type.ts b/packages/lib/themes/type.ts index 49a6291317..765330f1f8 100644 --- a/packages/lib/themes/type.ts +++ b/packages/lib/themes/type.ts @@ -24,6 +24,7 @@ export interface Theme { // Color scheme "2" is used for the sidebar. It's white text over // dark blue background. backgroundColor2: string; + backgroundColorTransparent2: string; // Used for dimmed region outside modals color2: string; selectedColor2: string; colorError2: string; diff --git a/packages/lib/utils/processStartFlags.ts b/packages/lib/utils/processStartFlags.ts index 18e2f3c71e..4f437cc7f6 100644 --- a/packages/lib/utils/processStartFlags.ts +++ b/packages/lib/utils/processStartFlags.ts @@ -13,6 +13,7 @@ export interface MatchedStartFlags { logLevel?: LogLevel; allowOverridingDnsResultOrder?: boolean; devPlugins?: string[]; + altInstanceId?: string; } // Handles the initial flags passed to main script and @@ -118,6 +119,12 @@ const processStartFlags = async (argv: string[], setDefaults = true) => { continue; } + if (arg === '--alt-instance-id') { + matched.altInstanceId = nextArg; + argv.splice(0, 2); + continue; + } + if (arg.indexOf('--remote-debugging-port=') === 0) { // Electron-specific flag used for debugging - ignore it. Electron expects this flag in '--x=y' form, a single string. argv.splice(0, 1); @@ -181,6 +188,12 @@ const processStartFlags = async (argv: string[], setDefaults = true) => { continue; } + if (arg === '--running-tests') { + // Used by the desktop app to indicate that the app is running end-to-end tests. + argv.splice(0, 1); + continue; + } + if (arg.length && arg[0] === '-') { throw new JoplinError(_('Unknown flag: %s', arg), 'flagError'); } else { diff --git a/packages/lib/versionInfo.ts b/packages/lib/versionInfo.ts index 037e340853..7c111ebaf1 100644 --- a/packages/lib/versionInfo.ts +++ b/packages/lib/versionInfo.ts @@ -85,10 +85,12 @@ export default function versionInfo(packageInfo: PackageInfo, plugins: Plugins) const body = [ _('%s %s (%s, %s)', p.name, p.version, Setting.value('env'), shim.platformName()), '', + _('Device: %s', shim.deviceString()), _('Client ID: %s', Setting.value('clientId')), _('Sync Version: %s', Setting.value('syncVersion')), _('Profile Version: %s', reg.db().version()), _('Keychain Supported: %s', keychainSupported ? _('Yes') : _('No')), + _('Alternative instance ID: %s', Setting.value('altInstanceId') || '-'), ]; if (gitInfo) { diff --git a/packages/lib/welcomeAssets.js b/packages/lib/welcomeAssets.js index e802d6c61d..5a2c3c470d 100644 --- a/packages/lib/welcomeAssets.js +++ b/packages/lib/welcomeAssets.js @@ -46,7 +46,7 @@ module.exports = { { "id": "5ec2e7505ec2e7505ec2e7505ec2e750", "title": "5. Joplin Privacy Policy", - "body": "# Joplin Privacy Policy\n\nThe Joplin applications, including the Android, iOS, Windows, macOS and Linux applications, do not send any data to any service without your authorisation. Any data that Joplin saves, such as notes or images, are saved to your own device and you are free to delete this data at any time.\n\nIf you choose to synchronise with a third-party, such as OneDrive or Dropbox, the notes will be sent to that account, in which case the third-party privacy policy applies.\n\nIn order to provide certain features, Joplin may need to connect to third-party services. You can disable most of these features in the application settings:\n\n| Feature | Description | Default | Can be disabled |\n| -------- | ------------- | -------- | --- |\n| Auto-update | Joplin periodically connects to GitHub to check for new releases. | Enabled | Yes |\n| Geo-location | Joplin saves geo-location information in note properties when you create a note. | Enabled | Yes |\n| Synchronisation | Joplin supports synchronisation of your notes across multiple devices. If you choose to synchronise with a third-party, such as OneDrive, the notes will be sent to your OneDrive account, in which case the third-party privacy policy applies. | Disabled | Yes |\n| Wifi connection check | On mobile, Joplin checks for Wifi connectivity to give the option to synchronise data only when Wifi is enabled. | Enabled | No (1) |\n| Spellchecker dictionary | On Linux and Windows, the desktop application downloads the spellchecker dictionary from `redirector.gvt1.com`. | Enabled | Yes (2) |\n| Plugin repository | The desktop application downloads the list of available plugins from the [official GitHub repository](https://github.com/joplin/plugins). If this repository is not accessible (eg. in China) the app will try to get the plugin list from [various mirrors](https://github.com/laurent22/joplin/blob/8ac6017c02017b6efd59f5fcab7e0b07f8d44164/packages/lib/services/plugins/RepositoryApi.ts#L22), in which case the plugin screen [works slightly differently](https://github.com/laurent22/joplin/issues/5161#issuecomment-925226975). | Enabled | No\n| Voice typing | If you use the voice typing feature on Android, the application will download the language files from https://alphacephei.com/vosk/models | Disabled | Yes\n\n(1) https://github.com/laurent22/joplin/issues/5705
\n(2) If the spellchecker is disabled, [it will not download the dictionary](https://discourse.joplinapp.org/t/new-version-of-joplin-contacting-google-servers-on-startup/23000/40?u=laurent).\n\nFor any question about Joplin privacy policy, please leave a message [on the forum](https://discourse.joplinapp.org/).\n", + "body": "# Joplin Privacy Policy\n\nThe Joplin applications, including the Android, iOS, Windows, macOS and Linux applications, do not send any data to any service without your authorisation. Any data that Joplin saves, such as notes or images, are saved to your own device and you are free to delete this data at any time.\n\nIf you choose to synchronise with a third-party, such as OneDrive or Dropbox, the notes will be sent to that account, in which case the third-party privacy policy applies.\n\nIn order to provide certain features, Joplin may need to connect to third-party services. You can disable most of these features in the application settings:\n\n| Feature | Description | Default | Can be disabled |\n| -------- | ------------- | -------- | --- |\n| Auto-update | Joplin periodically connects to GitHub to check for new releases. | Enabled | Yes |\n| Geo-location | Joplin saves geo-location information in note properties when you create a note. | Enabled | Yes |\n| Synchronisation | Joplin supports synchronisation of your notes across multiple devices. If you choose to synchronise with a third-party, such as OneDrive, the notes will be sent to your OneDrive account, in which case the third-party privacy policy applies. | Disabled | Yes |\n| Wifi connection check | On mobile, Joplin checks for Wifi connectivity to give the option to synchronise data only when Wifi is enabled. | Enabled | No (1) |\n| Spellchecker dictionary | On Linux and Windows, the desktop application downloads the spellchecker dictionary from `redirector.gvt1.com`. | Enabled | Yes (2) |\n| Plugin repository | The desktop application downloads the list of available plugins from the [official GitHub repository](https://github.com/joplin/plugins). If this repository is not accessible (eg. in China) the app will try to get the plugin list from [various mirrors](https://github.com/laurent22/joplin/blob/8ac6017c02017b6efd59f5fcab7e0b07f8d44164/packages/lib/services/plugins/RepositoryApi.ts#L22), in which case the plugin screen [works slightly differently](https://github.com/laurent22/joplin/issues/5161#issuecomment-925226975). | Enabled | No\n| Voice typing | If you use the voice typing feature on Android, the application will download the language files from https://github.com/joplin/voice-typing-models/ or https://alphacephei.com/vosk/models. | Disabled | Yes\n\n(1) https://github.com/laurent22/joplin/issues/5705
\n(2) If the spellchecker is disabled, [it will not download the dictionary](https://discourse.joplinapp.org/t/new-version-of-joplin-contacting-google-servers-on-startup/23000/40?u=laurent).\n\nFor any question about Joplin privacy policy, please leave a message [on the forum](https://discourse.joplinapp.org/).\n", "resources": {}, "parent_id": "9bb5d498aba74cc6a047cfdc841e82a1" } diff --git a/packages/onenote-converter/src/page/embedded_file.rs b/packages/onenote-converter/src/page/embedded_file.rs index c625bc1cca..9488423be8 100644 --- a/packages/onenote-converter/src/page/embedded_file.rs +++ b/packages/onenote-converter/src/page/embedded_file.rs @@ -22,9 +22,10 @@ impl<'a> Renderer<'a> { let file_type = Self::guess_type(file); match file_type { - FileType::Audio => content = format!("", filename), + // TODO: we still don't have support for the audio tag on html notes https://github.com/laurent22/joplin/issues/11939 + // FileType::Audio => content = format!("", filename), FileType::Video => content = format!("", filename), - FileType::Unknown => { + FileType::Unknown | FileType::Audio => { content = format!( "

{}

", filename, filename diff --git a/packages/renderer/noteStyle.ts b/packages/renderer/noteStyle.ts index fa33dbb391..34535004b3 100644 --- a/packages/renderer/noteStyle.ts +++ b/packages/renderer/noteStyle.ts @@ -216,7 +216,7 @@ export default function(theme: any, options: Options = null) { } ul, ol { padding-left: 0; - margin-left: 1.7em; + margin-left: ${theme.listTabSize}; } li { margin-bottom: .4em; diff --git a/packages/server/package.json b/packages/server/package.json index 3394003b4d..b1cb378e1f 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@joplin/server", - "version": "3.3.3", + "version": "3.3.13", "private": true, "scripts": { "start-dev": "yarn build && JOPLIN_IS_TESTING=1 nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev", @@ -31,7 +31,6 @@ "@types/uuid": "9.0.7", "bcryptjs": "2.4.3", "bulma": "1.0.2", - "bulma-prefers-dark": "0.1.0-beta.1", "compare-versions": "6.1.1", "dayjs": "1.11.12", "formidable": "2.1.2", diff --git a/packages/server/src/routes/default.ts b/packages/server/src/routes/default.ts index ab8c568bd3..11b71884eb 100644 --- a/packages/server/src/routes/default.ts +++ b/packages/server/src/routes/default.ts @@ -19,7 +19,6 @@ interface PathToFileMap { // example if they are in node_modules, use the map below. const pathToFileMap: PathToFileMap = { 'css/bulma.min.css': 'node_modules/bulma/css/bulma.min.css', - 'css/bulma-prefers-dark.min.css': 'node_modules/bulma-prefers-dark/css/bulma-prefers-dark.min.css', 'css/fontawesome/css/all.min.css': 'node_modules/@fortawesome/fontawesome-free/css/all.min.css', 'js/zxcvbn.js': 'node_modules/zxcvbn/dist/zxcvbn.js', 'js/zxcvbn.js.map': 'node_modules/zxcvbn/dist/zxcvbn.js.map', @@ -49,6 +48,23 @@ async function findLocalFile(path: string): Promise { return localPath; } +const patchFile = (path: string, fileContent: Buffer): Buffer => { + const patches: Record Buffer> = { + 'css/bulma.min.css': fileContent => { + // We apply the patch here rather than with `yarn patch` because that would mean + // modifying a minified file, which is likely to break on each new update of Bulma. Dark + // theme is disabled mostly because we don't want it in published notes, which may have + // their own style inherited from clipped web pages. Having dark theme in the web UI is + // not that useful because it's not frequently accessed by users. + return Buffer.from(fileContent.toString().replace('prefers-color-scheme:dark', 'prefers-color-scheme:dark-disabled-by-patch'), 'utf-8'); + }, + }; + + if (patches[path]) return patches[path](fileContent); + + return fileContent; +}; + const router = new Router(RouteType.Web); router.public = true; @@ -70,7 +86,8 @@ router.get('', async (path: SubPath, ctx: AppContext) => { let mimeType: string = mime.fromFilename(localPath); if (!mimeType) mimeType = 'application/octet-stream'; - const fileContent: Buffer = await fs.readFile(localPath); + let fileContent: Buffer = await fs.readFile(localPath); + fileContent = patchFile(path.raw, fileContent); const koaResponse = ctx.response; koaResponse.body = fileContent; diff --git a/packages/server/src/routes/index/home.ts b/packages/server/src/routes/index/home.ts index edba94f713..2a7e3df99d 100644 --- a/packages/server/src/routes/index/home.ts +++ b/packages/server/src/routes/index/home.ts @@ -22,6 +22,44 @@ function setupMessageHtml() { } } +// Note: Take the list from `packages/doc-builder/docusaurus.config.js` +const socialFeeds = () => { + return [ + { + label: 'Bluesky', + href: 'https://bsky.app/profile/joplinapp.bsky.social', + }, + { + label: 'Patreon', + href: 'https://www.patreon.com/joplin', + }, + { + label: 'YouTube', + href: 'https://www.youtube.com/@joplinapp', + }, + { + label: 'LinkedIn', + href: 'https://www.linkedin.com/company/joplin', + }, + { + label: 'Discord', + href: 'https://discord.gg/VSj7AFHvpq', + }, + { + label: 'Mastodon', + href: 'https://mastodon.social/@joplinapp', + }, + { + label: 'Lemmy', + href: 'https://sopuli.xyz/c/joplinapp', + }, + { + label: 'GitHub', + href: 'https://github.com/laurent22/joplin/', + }, + ]; +}; + router.get('home', async (_path: SubPath, ctx: AppContext) => { contextSessionId(ctx); @@ -79,6 +117,7 @@ router.get('home', async (_path: SubPath, ctx: AppContext) => { betaExpiredDays: betaUserTrialPeriodDays(user.created_time, 0, 0), betaStartSubUrl: betaStartSubUrl(user.email, user.account_type), setupMessageHtml: setupMessageHtml(), + socialFeeds: socialFeeds(), }; view.cssFiles = ['index/home']; diff --git a/packages/server/src/utils/joplinUtils.test.ts b/packages/server/src/utils/joplinUtils.test.ts index 8e5c155b45..07fc93eb5f 100644 --- a/packages/server/src/utils/joplinUtils.test.ts +++ b/packages/server/src/utils/joplinUtils.test.ts @@ -1,5 +1,6 @@ +import { basename } from '@joplin/utils/path'; import { Item } from '../services/database/types'; -import { itemIsEncrypted } from './joplinUtils'; +import { itemIsEncrypted, localFileFromUrl } from './joplinUtils'; import { expectThrow } from './testing/testUtils'; describe('joplinUtils', () => { @@ -21,4 +22,21 @@ describe('joplinUtils', () => { await expectThrow(async () => itemIsEncrypted({ name: 'missing props' })); }); + it.each([ + 'css/pluginAssets/../../../test', + 'css/pluginAssets/../../test', + 'js/pluginAssets/./../../test', + ])('localFileFromUrl should prevent access to paths outside the assets directory', async (url) => { + await expect(localFileFromUrl(url)).rejects.toThrow('Disallowed access:'); + }); + + it.each([ + 'css/pluginAssets/test.css', + 'js/pluginAssets/subfolder/test.js', + 'css/pluginAssets/testing/this-is-a-test.css', + ])('localFileFromUrl should allow access to paths inside the assets directory', async (url) => { + const resolvedPath = await localFileFromUrl(url); + // Should resolve to the same file + expect(basename(resolvedPath)).toBe(basename(url)); + }); }); diff --git a/packages/server/src/utils/joplinUtils.ts b/packages/server/src/utils/joplinUtils.ts index 9b649d1d27..178080c8e1 100644 --- a/packages/server/src/utils/joplinUtils.ts +++ b/packages/server/src/utils/joplinUtils.ts @@ -26,6 +26,7 @@ import MustacheService from '../services/MustacheService'; import Logger from '@joplin/utils/Logger'; import config from '../config'; import { TreeItem } from '../models/ItemResourceModel'; +import resolvePathWithinDir from '@joplin/lib/utils/resolvePathWithinDir'; const { substrWithEllipsis } = require('@joplin/lib/string-utils'); const logger = Logger.create('JoplinUtils'); @@ -116,11 +117,26 @@ export function isJoplinResourceBlobPath(path: string): boolean { return path.indexOf(resourceDirName) === 0; } -export async function localFileFromUrl(url: string): Promise { +const resolveUnsafeAssetPath = (relativeAssetPath: string) => { + const resolvedPath = resolvePathWithinDir(pluginAssetRootDir_, relativeAssetPath); + if (resolvedPath === null) { + throw new ErrorForbidden('Disallowed access: Item is not in the plugin asset directory'); + } + return resolvedPath; +}; + +export async function localFileFromUrl(urlPath: string): Promise { const cssPluginAssets = 'css/pluginAssets/'; const jsPluginAssets = 'js/pluginAssets/'; - if (url.indexOf(cssPluginAssets) === 0) return `${pluginAssetRootDir_}/${url.substr(cssPluginAssets.length)}`; - if (url.indexOf(jsPluginAssets) === 0) return `${pluginAssetRootDir_}/${url.substr(jsPluginAssets.length)}`; + const baseUrls = [cssPluginAssets, jsPluginAssets]; + + for (const baseUrl of baseUrls) { + if (urlPath.startsWith(baseUrl)) { + const pluginAssetPath = urlPath.substring(baseUrl.length); + return resolveUnsafeAssetPath(pluginAssetPath); + } + } + return null; } diff --git a/packages/server/src/views/index/home.mustache b/packages/server/src/views/index/home.mustache index d8698ac82f..9e6343314e 100644 --- a/packages/server/src/views/index/home.mustache +++ b/packages/server/src/views/index/home.mustache @@ -38,3 +38,19 @@ Upgrade to a Pro account to collaborate on notebooks with other people, to increase the max note size, or the max total size.

{{/showUpgradeProButton}} + +

Follow us on social media

+ +

Get the latest updates about {{global.appName}} on our social feeds!

+ + + + {{#socialFeeds}} + + + + {{/socialFeeds}} + +
+ {{label}} +
diff --git a/packages/tools/buildServerDocker.test.ts b/packages/tools/buildServerDocker.test.ts index 332d23423b..01b84b3811 100644 --- a/packages/tools/buildServerDocker.test.ts +++ b/packages/tools/buildServerDocker.test.ts @@ -6,8 +6,15 @@ describe('buildServerDocker', () => { type TestCase = [string, boolean, string]; const testCases: TestCase[] = [ - ['server-v1.1.2-beta', true, '1.1.2-beta'], - ['server-v1.1.2', false, '1.1.2'], + ['server-v1.2.3-beta', true, '1.2.3-beta'], + ['server-v1.2.3-beta', false, '1.2.3'], + ['server-v1.2.3', false, '1.2.3'], + ['server-v1.2.3-zxc', true, '1.2.3-beta.zxc'], + ['server-v1.2.3-zxc', false, '1.2.3'], + ['server-v1.2.3-4-zxc', true, '1.2.3-beta.4.zxc'], + ['server-v1.2.3-4-zxc', false, '1.2.3'], + ['server-1.2.3-4-zxc', true, '1.2.3-beta.4.zxc'], + ['server-1.2.3-4-zxc', false, '1.2.3'], ]; for (const testCase of testCases) { @@ -21,8 +28,8 @@ describe('buildServerDocker', () => { type TestCase = [string, boolean]; const testCases: TestCase[] = [ - ['server-v1.1.2-beta', true], - ['server-v1.1.2', true], // For now, always returns true + ['server-v1.1.2-beta', false], // For now, always returns false + ['server-v1.1.2', false], ]; for (const testCase of testCases) { diff --git a/packages/tools/buildServerDocker.ts b/packages/tools/buildServerDocker.ts index 479bb49b26..2cabc5db55 100644 --- a/packages/tools/buildServerDocker.ts +++ b/packages/tools/buildServerDocker.ts @@ -7,30 +7,88 @@ interface Argv { pushImages?: boolean; repository?: string; tagName?: string; + platform?: string; + source?: string; + addLatestTag?: boolean; +} + +function parseArgv(): Argv { + return require('yargs') + .scriptName('yarn buildServerDocker') + .usage('$0 --repository OWNER/IMAGE [args]') + .option('r', { + alias: 'repository', + describe: 'Target image repository. Usually in format `OWNER/NAME`', + demandOption: true, + type: 'string', + }) + .option('t', { + alias: 'tagName', + describe: 'Base image tag. Usually should be in format `server-v1.2.3` or `server-v1.2.3-beta`. The latest `server-v*` git tag will be used by default.', + type: 'string', + }) + .option('l', { + alias: 'addLatestTag', + describe: 'Add `latest` tag even for pre-release images.', + type: 'boolean', + default: false, + }) + .option('platform', { + describe: 'Comma separated list of target image platforms. E.g. `linux/amd64` or `linux/amd64,linux/arm64`', + type: 'string', + default: 'linux/amd64', + }) + .option('source', { + describe: 'Source Git repository for the images.', + type: 'string', + default: 'https://github.com/laurent22/joplin.git', + }) + .option('p', { + alias: 'pushImages', + describe: 'Publish images to target repository.', + type: 'boolean', + default: false, + }) + .option('dryRun', { + alias: 'dryRun', + describe: 'Do not call docker, just show command instead.', + type: 'boolean', + default: false, + }) + .help() + .argv as Argv; } export function getVersionFromTag(tagName: string, isPreRelease: boolean): string { const s = tagName.split('-'); - const suffix = isPreRelease ? '-beta' : ''; - return s[1].substr(1) + suffix; + const mainVersion = s[1].replace(/^(v)/, ''); + const metaComponents = s.slice(2).filter(item => item !== 'beta'); + + // Append `git describe` components for pre release images. Mostly for case without `tagName` arg + const suffix = isPreRelease ? `-beta${metaComponents.length > 0 ? `.${metaComponents.join('.')}` : ''}` : ''; + return mainVersion + suffix; } export function getIsPreRelease(_tagName: string): boolean { // For now we only create pre-releases from CI. It's after, once the release // has been proven stable, that it is tagged as "latest". - return true; + return false; // return tagName.indexOf('-beta') > 0; } async function main() { - const argv = require('yargs').argv as Argv; - if (!argv.tagName) throw new Error('--tag-name not provided'); - if (!argv.repository) throw new Error('--repository not provided'); + const argv = parseArgv(); + if (!argv.tagName) console.info('No `--tag-name` was specified. A latest git tag will be used instead.'); - const dryRun = !!argv.dryRun; - const pushImages = !!argv.pushImages; + const dryRun = argv.dryRun; + const addLatestTag = argv.addLatestTag; + const pushImages = argv.pushImages; const repository = argv.repository; - const tagName = argv.tagName; + const tagName = argv.tagName || `server-${await execCommand('git describe --tags --match v*', { showStdout: false })}`; + const platform = argv.platform; + const source = argv.source; + const architecture = argv.platform.split('/')[1]; + const isPreRelease = getIsPreRelease(tagName); const imageVersion = getVersionFromTag(tagName, isPreRelease); const buildDate = moment(new Date().getTime()).format('YYYY-MM-DDTHH:mm:ssZ'); @@ -40,35 +98,57 @@ async function main() { } catch (error) { console.info('Could not get git commit: metadata revision field will be empty'); } - const buildArgs = `--build-arg BUILD_DATE="${buildDate}" --build-arg REVISION="${revision}" --build-arg VERSION="${imageVersion}"`; + + const buildArgs = []; + buildArgs.push(`BUILD_DATE="${buildDate}"`); + buildArgs.push(`REVISION="${revision}"`); + buildArgs.push(`VERSION="${imageVersion}"`); + buildArgs.push(`SOURCE="${source}"`); + const dockerTags: string[] = []; - const versionPart = imageVersion.split('.'); - dockerTags.push(isPreRelease ? 'beta' : 'latest'); - dockerTags.push(versionPart[0] + (isPreRelease ? '-beta' : '')); - dockerTags.push(`${versionPart[0]}.${versionPart[1]}${isPreRelease ? '-beta' : ''}`); - dockerTags.push(imageVersion); + const versionParts = imageVersion.split('.'); + const patchVersionPart = versionParts[2].split('-')[0]; + dockerTags.push(isPreRelease ? 'latest-beta' : 'latest'); + dockerTags.push(versionParts[0] + (isPreRelease ? '-beta' : '')); + dockerTags.push(`${versionParts[0]}.${versionParts[1]}${isPreRelease ? '-beta' : ''}`); + dockerTags.push(`${versionParts[0]}.${versionParts[1]}.${patchVersionPart}${isPreRelease ? '-beta' : ''}`); + if (dockerTags.indexOf(imageVersion) < 0) { + dockerTags.push(imageVersion); + } + if (addLatestTag && dockerTags.indexOf('latest') < 0) { + dockerTags.push('latest'); + } + process.chdir(rootDir); console.info(`Running from: ${process.cwd()}`); + console.info('repository:', repository); console.info('tagName:', tagName); + console.info('platform:', platform); console.info('pushImages:', pushImages); console.info('imageVersion:', imageVersion); console.info('isPreRelease:', isPreRelease); console.info('Docker tags:', dockerTags.join(', ')); - const dockerCommand = `docker build --progress=plain -t "${repository}:${imageVersion}" ${buildArgs} -f Dockerfile.server .`; + const cliArgs = ['--progress=plain']; + cliArgs.push(`--platform ${platform}`); + cliArgs.push(...dockerTags.map(tag => `--tag "${repository}:${architecture}-${tag}"`)); + cliArgs.push(...buildArgs.map(arg => `--build-arg ${arg}`)); + if (pushImages) { + cliArgs.push('--push'); + } + cliArgs.push('-f Dockerfile.server'); + cliArgs.push('.'); + + const dockerCommand = `docker buildx build ${cliArgs.join(' ')}`; + + console.info('exec:', dockerCommand); if (dryRun) { - console.info(dockerCommand); return; } await execCommand(dockerCommand); - - for (const tag of dockerTags) { - await execCommand(`docker tag "${repository}:${imageVersion}" "${repository}:${tag}"`); - if (pushImages) await execCommand(`docker push ${repository}:${tag}`); - } } if (require.main === module) { diff --git a/packages/tools/cspell/dictionary4.txt b/packages/tools/cspell/dictionary4.txt index 3b25d44dd4..a37891ce7e 100644 --- a/packages/tools/cspell/dictionary4.txt +++ b/packages/tools/cspell/dictionary4.txt @@ -90,8 +90,6 @@ signup activatable Prec titlewrapper -notyf -Notyf unresponded activeline Prec @@ -161,6 +159,7 @@ unwatcher pedr Slotozilla keyshortcuts +buildx atag HMAC Siri @@ -171,3 +170,9 @@ millis sideloading ggml Minidump +collapseall +newfolder +unfocusable +unlocker +Tiktok +topagency \ No newline at end of file diff --git a/packages/tools/generate-images.json b/packages/tools/generate-images.json index 7110c37781..44fb9e08e9 100644 --- a/packages/tools/generate-images.json +++ b/packages/tools/generate-images.json @@ -1,23 +1,5 @@ { "done": { - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_marketing1024x1024.png_1024_1024____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76.png_76_76____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76@2x.png_152_152____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20.png_20_20____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20@2x.png_40_40____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_pro_app83.5x83.5@2x.png_167_167____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29.png_29_29____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29@2x.png_58_58____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40.png_40_40____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40@2x.png_80_80____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@2x.png_120_120____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@3x.png_180_180____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@2x.png_40_40____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@3x.png_60_60____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@2x.png_58_58____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@3x.png_87_87____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@2x.png_80_80____": true, - "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@3x.png_120_120____": true, "2_980ab814dfce30eb15adf0a90d85bb1a_Assets/macOs.iconset/icon_16x16.png_16_16____": true, "3_a7fed49fa153dc9f4e58f038011bce4d_Assets/macOs.iconset/icon_16x16@2x.png_32_32____": true, "3_a7fed49fa153dc9f4e58f038011bce4d_Assets/macOs.iconset/icon_32x32.png_32_32____": true, @@ -71,6 +53,38 @@ "12_c1ecc4672fe806dda0c25ec58ddf498a_packages/server/public/images/icons/cloud/icon-32.png_32_32___joplinCloud32_": true, "12_c1ecc4672fe806dda0c25ec58ddf498a_packages/server/public/images/icons/cloud/icon.svg______": true, "12_c1ecc4672fe806dda0c25ec58ddf498a_packages/server/public/images/icons/cloud/favicon.ico______joplinCloud32": true, - "icns_to_icon_set_9f922e3fd5465dd99eabaa24a3dd3c1d_12cd001124be423c65c1b36deccad273_717fde633fffdca1ae13cca5e4d05143_a8f4a3f97ceaefa3151416a40cfab0af_12cd001124be423c65c1b36deccad273_0bf543ae51a3980ce194156b14a77eee_a8f4a3f97ceaefa3151416a40cfab0af_35d1c48c7d56a96cafd8fb898432f25d_0bf543ae51a3980ce194156b14a77eee_b38e4ecf1acfdc684712c8f7d9b9305d": true + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios20x20@2x.png_40_40____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark20x20@2x.png_40_40____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios20x20@3x.png_60_60____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark20x20@3x.png_60_60____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios29x29@2x.png_58_58____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark29x29@2x.png_58_58____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios29x29@3x.png_87_87____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark29x29@3x.png_87_87____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios38x38@2x.png_76_76____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark38x38@2x.png_76_76____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios38x38@3x.png_114_114____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark38x38@3x.png_114_114____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios40x40@2x.png_80_80____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark40x40@2x.png_80_80____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios40x40@3x.png_120_120____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark40x40@3x.png_120_120____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios60x60@2x.png_120_120____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark60x60@2x.png_120_120____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios60x60@3x.png_180_180____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark60x60@3x.png_180_180____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios64x64@2x.png_128_128____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark64x64@2x.png_128_128____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios64x64@3x.png_192_192____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark64x64@3x.png_192_192____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios68x68@2x.png_136_136____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark68x68@2x.png_136_136____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios76x76@2x.png_152_152____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark76x76@2x.png_152_152____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios83.5x83.5@2x.png_167_167____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark83.5x83.5@2x.png_167_167____": true, + "1_80abab89a0e638b5a3f12f96ab5c8792_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios1024x1024.png_1024_1024____": true, + "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_dark1024x1024.png_1024_1024____": true, + "icns_to_icon_set_89ddfe84307b49fa96580655b5d7c045_216bb492f34224f24aabacb5f98c3620_fe652082bfb7427cd5c74566ecc24322_ebf1ccaf3f5b77b01ff690b763a411f9_216bb492f34224f24aabacb5f98c3620_950b970a784b14c329e09e78af827a77_ebf1ccaf3f5b77b01ff690b763a411f9_d33dafc8081155149dd1d8c1713bf03f_950b970a784b14c329e09e78af827a77_94949c497e46ed0c67082175f5bb22f8": true } } \ No newline at end of file diff --git a/packages/tools/generate-images.ts b/packages/tools/generate-images.ts index ea457bb12d..a549b30f53 100644 --- a/packages/tools/generate-images.ts +++ b/packages/tools/generate-images.ts @@ -1,7 +1,7 @@ -import { pathExists, readFile, writeFile, copyFile, readdir } from 'fs-extra'; +import { fileExtension } from '@joplin/lib/path-utils'; +import { copyFile, pathExists, readdir, readFile, writeFile } from 'fs-extra'; import { dirname } from 'path'; import { execCommand } from './tool-utils'; -import { fileExtension } from '@joplin/lib/path-utils'; const md5File = require('md5-file'); const sharp = require('sharp'); @@ -33,6 +33,22 @@ interface Results { done: Record; } +interface iOSAppIconContents { + images: { + appearances?: [ + { + appearance: 'luminosity'; + value: 'dark' | 'tinted'; + }, + ]; + filename?: string; + idiom: 'universal'; + platform: 'ios'; + scale?: '2x' | '3x'; + size: string; + }[]; +} + const sources: Source[] = [ { id: 1, @@ -82,6 +98,10 @@ const sources: Source[] = [ id: 12, name: 'JoplinCloudIcon2.svg', }, + { + id: 13, + name: '../JoplinLetterBlue.svg', + }, ]; function sourceById(id: number) { @@ -91,475 +111,396 @@ function sourceById(id: number) { throw new Error(`Invalid source ID: ${id}`); } -const operations: Operation[] = [ +const iOSReadAppIcon = async (filePath: string): Promise => { + if (!(await pathExists(filePath))) return { images: [] }; + const content = await readFile(filePath, 'utf8'); + return JSON.parse(content) as iOSAppIconContents; +}; - // ============================================================================ - // iOS icons - // ============================================================================ +const getOperations = async (rootDir: string): Promise => { + const iOSAppIconSet = 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset'; + const iOSAppIconSetContents = await iOSReadAppIcon(`${rootDir}/${iOSAppIconSet}/Contents.json`); - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_marketing1024x1024.png', - width: 1024, - height: 1024, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76.png', - width: 76, - height: 76, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76@2x.png', - width: 152, - height: 152, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20.png', - width: 20, - height: 20, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20@2x.png', - width: 40, - height: 40, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_pro_app83.5x83.5@2x.png', - width: 167, - height: 167, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29.png', - width: 29, - height: 29, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29@2x.png', - width: 58, - height: 58, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40.png', - width: 40, - height: 40, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40@2x.png', - width: 80, - height: 80, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@2x.png', - width: 120, - height: 120, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@3x.png', - width: 180, - height: 180, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@2x.png', - width: 40, - height: 40, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@3x.png', - width: 60, - height: 60, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@2x.png', - width: 58, - height: 58, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@3x.png', - width: 87, - height: 87, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@2x.png', - width: 80, - height: 80, - }, - { - source: 1, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@3x.png', - width: 120, - height: 120, - }, + return [ + // ============================================================================ + // iOS icons + // ============================================================================ - // ============================================================================ - // macOS icons - // ============================================================================ + ...(iOSAppIconSetContents.images.map(icon => { + const size = icon.size.split('x').map(Number), + scale = parseInt(icon.scale, 10) || 1; + if (!size || !icon.filename) return null; + return [ + { + source: 1, + dest: `${iOSAppIconSet}/ios${icon.size}${icon.scale ? `@${icon.scale}` : ''}.png`, + width: size[0] * scale, + height: size[1] * scale, + }, + { + source: 13, + dest: `${iOSAppIconSet}/ios_dark${icon.size}${icon.scale ? `@${icon.scale}` : ''}.png`, + width: size[0] * scale, + height: size[1] * scale, + }, + ]; + }).filter(ico => ico !== null)).flat(1), - { - source: 2, - dest: 'Assets/macOs.iconset/icon_16x16.png', - width: 16, - height: 16, - }, - { - source: 3, - dest: 'Assets/macOs.iconset/icon_16x16@2x.png', - width: 32, - height: 32, - }, - { - source: 3, - dest: 'Assets/macOs.iconset/icon_32x32.png', - width: 32, - height: 32, - }, - { - source: 3, - dest: 'Assets/macOs.iconset/icon_32x32@2x.png', - width: 64, - height: 64, - }, - { - source: 7, - dest: 'Assets/macOs.iconset/icon_128x128.png', - width: 128, - height: 128, - }, - { - source: 7, - dest: 'Assets/macOs.iconset/icon_128x128@2x.png', - width: 256, - height: 256, - }, - { - source: 7, - dest: 'Assets/macOs.iconset/icon_256x256.png', - width: 256, - height: 256, - }, - { - source: 7, - dest: 'Assets/macOs.iconset/icon_256x256@2x.png', - width: 512, - height: 512, - }, - { - source: 7, - dest: 'Assets/macOs.iconset/icon_512x512.png', - width: 512, - height: 512, - }, - { - source: 7, - dest: 'Assets/macOs.iconset/icon_512x512@2x.png', - width: 1024, - height: 1024, - }, + // ============================================================================ + // macOS icons + // ============================================================================ - // ============================================================================ - // Linux icons - // ============================================================================ + { + source: 2, + dest: 'Assets/macOs.iconset/icon_16x16.png', + width: 16, + height: 16, + }, + { + source: 3, + dest: 'Assets/macOs.iconset/icon_16x16@2x.png', + width: 32, + height: 32, + }, + { + source: 3, + dest: 'Assets/macOs.iconset/icon_32x32.png', + width: 32, + height: 32, + }, + { + source: 3, + dest: 'Assets/macOs.iconset/icon_32x32@2x.png', + width: 64, + height: 64, + }, + { + source: 7, + dest: 'Assets/macOs.iconset/icon_128x128.png', + width: 128, + height: 128, + }, + { + source: 7, + dest: 'Assets/macOs.iconset/icon_128x128@2x.png', + width: 256, + height: 256, + }, + { + source: 7, + dest: 'Assets/macOs.iconset/icon_256x256.png', + width: 256, + height: 256, + }, + { + source: 7, + dest: 'Assets/macOs.iconset/icon_256x256@2x.png', + width: 512, + height: 512, + }, + { + source: 7, + dest: 'Assets/macOs.iconset/icon_512x512.png', + width: 512, + height: 512, + }, + { + source: 7, + dest: 'Assets/macOs.iconset/icon_512x512@2x.png', + width: 1024, + height: 1024, + }, - { - source: 2, - dest: 'Assets/LinuxIcons/16x16.png', - width: 16, - height: 16, - }, - { - source: 3, - dest: 'Assets/LinuxIcons/24x24.png', - width: 24, - height: 24, - }, - { - source: 3, - dest: 'Assets/LinuxIcons/32x32.png', - width: 32, - height: 32, - }, - { - source: 7, - dest: 'Assets/LinuxIcons/48x48.png', - width: 48, - height: 48, - }, - { - source: 7, - dest: 'Assets/LinuxIcons/72x72.png', - width: 72, - height: 72, - }, - { - source: 7, - dest: 'Assets/LinuxIcons/96x96.png', - width: 96, - height: 96, - }, - { - source: 7, - dest: 'Assets/LinuxIcons/128x128.png', - width: 128, - height: 128, - }, - { - source: 7, - dest: 'Assets/LinuxIcons/144x144.png', - width: 144, - height: 144, - }, - { - source: 7, - dest: 'Assets/LinuxIcons/256x256.png', - width: 256, - height: 256, - }, - { - source: 7, - dest: 'Assets/LinuxIcons/512x512.png', - width: 512, - height: 512, - }, - { - source: 7, - dest: 'Assets/LinuxIcons/1024x1024.png', - width: 1024, - height: 1024, - }, + // ============================================================================ + // Linux icons + // ============================================================================ - // ============================================================================ - // PortableApps launcher - // ============================================================================ + { + source: 2, + dest: 'Assets/LinuxIcons/16x16.png', + width: 16, + height: 16, + }, + { + source: 3, + dest: 'Assets/LinuxIcons/24x24.png', + width: 24, + height: 24, + }, + { + source: 3, + dest: 'Assets/LinuxIcons/32x32.png', + width: 32, + height: 32, + }, + { + source: 7, + dest: 'Assets/LinuxIcons/48x48.png', + width: 48, + height: 48, + }, + { + source: 7, + dest: 'Assets/LinuxIcons/72x72.png', + width: 72, + height: 72, + }, + { + source: 7, + dest: 'Assets/LinuxIcons/96x96.png', + width: 96, + height: 96, + }, + { + source: 7, + dest: 'Assets/LinuxIcons/128x128.png', + width: 128, + height: 128, + }, + { + source: 7, + dest: 'Assets/LinuxIcons/144x144.png', + width: 144, + height: 144, + }, + { + source: 7, + dest: 'Assets/LinuxIcons/256x256.png', + width: 256, + height: 256, + }, + { + source: 7, + dest: 'Assets/LinuxIcons/512x512.png', + width: 512, + height: 512, + }, + { + source: 7, + dest: 'Assets/LinuxIcons/1024x1024.png', + width: 1024, + height: 1024, + }, - { - source: 5, - dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/appicon.ico', - }, - { - source: 2, - dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/appicon_16.png', - }, - { - source: 3, - dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/appicon_32.png', - width: 32, - height: 32, - }, - { - source: 4, - dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/appicon_75.png', - width: 75, - height: 75, - }, - { - source: 4, - dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/appicon_128.png', - width: 128, - height: 128, - }, - { - source: 4, - dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/Launcher/splash.jpg', - width: 144, - height: 144, - }, + // ============================================================================ + // PortableApps launcher + // ============================================================================ - // ============================================================================ - // Windows tiles - // ============================================================================ + { + source: 5, + dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/appicon.ico', + }, + { + source: 2, + dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/appicon_16.png', + }, + { + source: 3, + dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/appicon_32.png', + width: 32, + height: 32, + }, + { + source: 4, + dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/appicon_75.png', + width: 75, + height: 75, + }, + { + source: 4, + dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/appicon_128.png', + width: 128, + height: 128, + }, + { + source: 4, + dest: 'packages/tools/PortableAppsLauncher/App/AppInfo/Launcher/splash.jpg', + width: 144, + height: 144, + }, - { - source: 6, - dest: 'packages/app-desktop/build-win/icons/Square150x150Logo.png', - width: 150, - height: 150, - iconWidth: 99, - iconHeight: 75, - }, - { - source: 6, - dest: 'packages/app-desktop/build-win/icons/SmallTile.png', - width: 70, - height: 70, - iconWidth: 46, - iconHeight: 46, - }, + // ============================================================================ + // Windows tiles + // ============================================================================ - // ============================================================================ - // Website images - // ============================================================================ + { + source: 6, + dest: 'packages/app-desktop/build-win/icons/Square150x150Logo.png', + width: 150, + height: 150, + iconWidth: 99, + iconHeight: 75, + }, + { + source: 6, + dest: 'packages/app-desktop/build-win/icons/SmallTile.png', + width: 70, + height: 70, + iconWidth: 46, + iconHeight: 46, + }, - { - source: 8, - dest: 'Assets/WebsiteAssets/images/home-top-img-4x.webp', - width: 4820, - height: 2938, - }, - { - source: 8, - dest: 'Assets/WebsiteAssets/images/home-top-img-2x.png', - width: 2388, - height: 1456, - }, - { - source: 8, - dest: 'Assets/WebsiteAssets/images/home-top-img-2x.webp', - width: 2388, - height: 1456, - }, - { - source: 8, - dest: 'Assets/WebsiteAssets/images/home-top-img.png', - width: 1205, - height: 734, - }, - { - source: 8, - dest: 'Assets/WebsiteAssets/images/home-top-img.webp', - width: 1205, - height: 734, - }, + // ============================================================================ + // Website images + // ============================================================================ - // ============================================================================ - // Website images CN - // ============================================================================ + { + source: 8, + dest: 'Assets/WebsiteAssets/images/home-top-img-4x.webp', + width: 4820, + height: 2938, + }, + { + source: 8, + dest: 'Assets/WebsiteAssets/images/home-top-img-2x.png', + width: 2388, + height: 1456, + }, + { + source: 8, + dest: 'Assets/WebsiteAssets/images/home-top-img-2x.webp', + width: 2388, + height: 1456, + }, + { + source: 8, + dest: 'Assets/WebsiteAssets/images/home-top-img.png', + width: 1205, + height: 734, + }, + { + source: 8, + dest: 'Assets/WebsiteAssets/images/home-top-img.webp', + width: 1205, + height: 734, + }, - { - source: 9, - dest: 'Assets/WebsiteAssets/images/home-top-img-cn-4x.webp', - width: 4820, - height: 2938, - }, - { - source: 9, - dest: 'Assets/WebsiteAssets/images/home-top-img-cn-2x.png', - width: 2388, - height: 1456, - }, - { - source: 9, - dest: 'Assets/WebsiteAssets/images/home-top-img-cn-2x.webp', - width: 2388, - height: 1456, - }, - { - source: 9, - dest: 'Assets/WebsiteAssets/images/home-top-img-cn.png', - width: 1205, - height: 734, - }, - { - source: 9, - dest: 'Assets/WebsiteAssets/images/home-top-img-cn.webp', - width: 1205, - height: 734, - }, + // ============================================================================ + // Website images CN + // ============================================================================ - // ============================================================================ - // Joplin Server Icons - // ============================================================================ + { + source: 9, + dest: 'Assets/WebsiteAssets/images/home-top-img-cn-4x.webp', + width: 4820, + height: 2938, + }, + { + source: 9, + dest: 'Assets/WebsiteAssets/images/home-top-img-cn-2x.png', + width: 2388, + height: 1456, + }, + { + source: 9, + dest: 'Assets/WebsiteAssets/images/home-top-img-cn-2x.webp', + width: 2388, + height: 1456, + }, + { + source: 9, + dest: 'Assets/WebsiteAssets/images/home-top-img-cn.png', + width: 1205, + height: 734, + }, + { + source: 9, + dest: 'Assets/WebsiteAssets/images/home-top-img-cn.webp', + width: 1205, + height: 734, + }, - { - source: 10, - dest: 'packages/server/public/images/icons/server/icon-512.png', - width: 512, - height: 512, - }, - { - source: 10, - dest: 'packages/server/public/images/icons/server/icon-192.png', - width: 192, - height: 192, - }, - { - source: 10, - dest: 'packages/server/public/images/icons/server/icon-180.png', - width: 180, - height: 180, - }, - { - source: 10, - dest: 'packages/server/public/images/server_logo.png', - width: 512, - height: 512, - }, - { - source: 10, - dest: 'packages/server/public/images/icons/server/icon-32.png', - width: 32, - height: 32, - imageName: 'joplinServer32', - }, - { - source: 10, - dest: 'packages/server/public/images/icons/server/icon.svg', - }, - { - source: 10, - dest: 'packages/server/public/images/icons/server/favicon.ico', - images: ['joplinServer32'], - }, + // ============================================================================ + // Joplin Server Icons + // ============================================================================ - // ============================================================================ - // Joplin Cloud Icons - // ============================================================================ + { + source: 10, + dest: 'packages/server/public/images/icons/server/icon-512.png', + width: 512, + height: 512, + }, + { + source: 10, + dest: 'packages/server/public/images/icons/server/icon-192.png', + width: 192, + height: 192, + }, + { + source: 10, + dest: 'packages/server/public/images/icons/server/icon-180.png', + width: 180, + height: 180, + }, + { + source: 10, + dest: 'packages/server/public/images/server_logo.png', + width: 512, + height: 512, + }, + { + source: 10, + dest: 'packages/server/public/images/icons/server/icon-32.png', + width: 32, + height: 32, + imageName: 'joplinServer32', + }, + { + source: 10, + dest: 'packages/server/public/images/icons/server/icon.svg', + }, + { + source: 10, + dest: 'packages/server/public/images/icons/server/favicon.ico', + images: ['joplinServer32'], + }, - { - source: 11, - dest: 'packages/server/public/images/icons/cloud/icon-512.png', - width: 512, - height: 512, - }, - { - source: 11, - dest: 'packages/server/public/images/icons/cloud/icon-192.png', - width: 192, - height: 192, - }, - { - source: 11, - dest: 'packages/server/public/images/icons/cloud/icon-180.png', - width: 180, - height: 180, - }, - { - source: 11, - dest: 'packages/server/public/images/cloud_logo.png', - width: 512, - height: 512, - }, - { - source: 12, - dest: 'packages/server/public/images/icons/cloud/icon-32.png', - width: 32, - height: 32, - imageName: 'joplinCloud32', - }, - { - source: 12, - dest: 'packages/server/public/images/icons/cloud/icon.svg', - }, - { - source: 12, - dest: 'packages/server/public/images/icons/cloud/favicon.ico', - images: ['joplinCloud32'], - }, -]; + // ============================================================================ + // Joplin Cloud Icons + // ============================================================================ + + { + source: 11, + dest: 'packages/server/public/images/icons/cloud/icon-512.png', + width: 512, + height: 512, + }, + { + source: 11, + dest: 'packages/server/public/images/icons/cloud/icon-192.png', + width: 192, + height: 192, + }, + { + source: 11, + dest: 'packages/server/public/images/icons/cloud/icon-180.png', + width: 180, + height: 180, + }, + { + source: 11, + dest: 'packages/server/public/images/cloud_logo.png', + width: 512, + height: 512, + }, + { + source: 12, + dest: 'packages/server/public/images/icons/cloud/icon-32.png', + width: 32, + height: 32, + imageName: 'joplinCloud32', + }, + { + source: 12, + dest: 'packages/server/public/images/icons/cloud/icon.svg', + }, + { + source: 12, + dest: 'packages/server/public/images/icons/cloud/favicon.ico', + images: ['joplinCloud32'], + }, + ]; +}; const md5Dir = async (dirPath: string): Promise => { const files = await readdir(dirPath); @@ -601,6 +542,7 @@ async function main() { const sourceImageDir = `${rootDir}/Assets/ImageSources`; const resultFilePath = `${__dirname}/generate-images.json`; const results: Results = await readResults(resultFilePath); + const operations = await getOperations(rootDir); for (const operation of operations) { const source = sourceById(operation.source); diff --git a/packages/tools/locales/es_ES.po b/packages/tools/locales/es_ES.po index d5c1a99598..e6c8522d06 100644 --- a/packages/tools/locales/es_ES.po +++ b/packages/tools/locales/es_ES.po @@ -15,14 +15,16 @@ msgid "" msgstr "" "Project-Id-Version: Joplin-CLI 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Francisco Villaverde \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: Éric Duarte \n" "Language-Team: Spanish \n" "Language: es_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.2.2\n" +"X-Generator: Poedit 3.5\n" "X-Poedit-SourceCharset: UTF-8\n" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:593 @@ -57,7 +59,7 @@ msgstr "(En plugin: %s)" #: packages/app-mobile/components/side-menu-content.tsx:265 msgid "(level %d)" -msgstr "" +msgstr "(Nivel %d)" #: packages/lib/SyncTargetNone.ts:16 msgid "(None)" @@ -71,7 +73,7 @@ msgstr "(wysiwyg: %s)" #: packages/app-mobile/components/screens/Note/Note.tsx:712 #: packages/lib/shim-init-node.ts:264 msgid "(You may disable this prompt in the options)" -msgstr "" +msgstr "(Puede desactivar este aviso en las opciones)" #: packages/app-desktop/gui/MenuBar.tsx:1036 #: packages/app-desktop/gui/MenuBar.tsx:737 @@ -105,9 +107,8 @@ msgid "&View" msgstr "&Ver" #: packages/app-desktop/gui/MenuBar.tsx:903 -#, fuzzy msgid "&Window" -msgstr "Cerrar Ventana" +msgstr "&Ventana" #: packages/lib/models/settings/builtInMetadata.ts:1483 #: packages/lib/models/settings/builtInMetadata.ts:1735 @@ -158,15 +159,13 @@ msgid "%d notes match this pattern. Delete them?" msgstr "%d notas coinciden con el patrón. ¿Eliminarlas?" #: packages/app-cli/app/command-rmnote.ts:40 -#, fuzzy msgid "%d notes will be permanently deleted. Continue?" -msgstr "Borrar notas seleccionadas" +msgstr "%d notas se eliminarán de forma permanente. ¿Continuar?" #: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:228 #: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:238 -#, fuzzy msgid "%s" -msgstr "(%s)" +msgstr "%s" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/duplicateNote.ts:18 msgid "%s - Copy" @@ -216,10 +215,12 @@ msgid "" "%s is not optimised for synchronising many small files so your initial " "synchronisation will be slow." msgstr "" +"%s no está optimizado para sincronizar muchos archivos pequeños, por lo que " +"la sincronización inicial será lenta." #: packages/app-mobile/components/plugins/dialogs/PluginPanelViewer.tsx:75 msgid "%s tab opened" -msgstr "" +msgstr "%s pestaña abierta" #: packages/lib/services/ReportService.ts:286 #: packages/lib/services/ReportService.ts:287 @@ -243,9 +244,8 @@ msgstr "%s: %s" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:224 #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:265 -#, fuzzy msgid "%s: Missing password." -msgstr "Contraseña maestra" +msgstr "%s: Falta la contraseña." #: packages/app-cli/app/command-tag.js:14 msgid "" @@ -273,9 +273,8 @@ msgstr "" "tarea a una nota normal." #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:62 -#, fuzzy msgid "A new update (%s) is available" -msgstr "Actualizar perfil" +msgstr "Una nueva actualización (%s) está disponible" #: packages/lib/models/settings/builtInMetadata.ts:1253 msgid "A3" @@ -292,7 +291,7 @@ msgstr "A5" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.tsx:75 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:240 msgid "About" -msgstr "" +msgstr "Acerca de" #: packages/app-desktop/gui/MenuBar.tsx:620 #: packages/app-desktop/gui/MenuBar.tsx:955 @@ -322,17 +321,19 @@ msgid "Accept" msgstr "Aceptar" #: packages/app-mobile/components/screens/ShareManager/index.tsx:98 -#, fuzzy msgid "Accepted invitations" -msgstr "El destinatario ha aceptado la invitación" +msgstr "Invitaciones aceptadas" #: packages/lib/WebDavApi.js:451 msgid "Access denied: Please check your username and password" msgstr "" +"Acceso denegado: Por favor, compruebe su nombre de usuario y contraseña" #: packages/lib/WebDavApi.js:449 msgid "Access denied: Please re-enter your password and/or username" msgstr "" +"Acceso denegado: Por favor, vuelva a introducir su contraseña y/o nombre de " +"usuario" #: packages/server/src/routes/admin/users.ts:144 msgid "Account" @@ -340,15 +341,13 @@ msgstr "Cuenta" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:31 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:48 -#, fuzzy msgid "Account information" -msgstr "Más información" +msgstr "Información de la Cuenta" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:35 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:51 -#, fuzzy msgid "Account type" -msgstr "Cuenta" +msgstr "Tipo de Cuenta" #: packages/app-desktop/gui/ResourceScreen.tsx:113 msgid "Action" @@ -386,7 +385,7 @@ msgstr "Agregar destinatario:" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:94 msgid "Add tag %s to note" -msgstr "" +msgstr "Añadir etiqueta %s a la nota" #: packages/app-mobile/components/screens/Note/Note.tsx:1574 msgid "Add title" @@ -397,9 +396,8 @@ msgid "Add to dictionary" msgstr "Agregar al diccionario" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:98 -#, fuzzy msgid "Add to note" -msgstr "Añadir título" +msgstr "Añadir a la nota" #: packages/server/src/services/MustacheService.ts:162 #: packages/server/src/services/MustacheService.ts:286 @@ -415,18 +413,16 @@ msgid "Advanced options" msgstr "Opciones avanzadas" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:653 -#, fuzzy msgid "Advanced settings" -msgstr "Mostrar Opciones Avanzadas" +msgstr "Ajustes avanzados" #: packages/app-desktop/gui/StatusScreen/StatusScreen.tsx:196 msgid "Advanced tools" msgstr "Herramientas avanzadas" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:18 -#, fuzzy msgid "all" -msgstr "Instalar" +msgstr "todo" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:109 msgid "" @@ -437,6 +433,8 @@ msgstr "" #: packages/lib/services/ReportService.ts:227 msgid "All item sync failures have been marked as \"ignored\"." msgstr "" +"Todos los errores de sincronización de elementos se han marcado como " +"\"ignorados\"." #: packages/app-desktop/gui/Sidebar/listItemComponents/AllNotesItem.tsx:71 #: packages/app-mobile/components/screens/Notes.tsx:208 @@ -453,6 +451,7 @@ msgstr "" #: packages/lib/models/settings/builtInMetadata.ts:910 msgid "Allows debugging mobile plugins. See %s for details." msgstr "" +"Permite depurar plugins móviles. Consulte %s para obtener más detalles." #: packages/app-cli/app/command-config.ts:19 msgid "Also displays unset and hidden config variables." @@ -467,18 +466,16 @@ msgid "Always" msgstr "Siempre" #: packages/lib/models/settings/builtInMetadata.ts:866 -#, fuzzy msgid "Always ask" -msgstr "Siempre" +msgstr "Pregunte siempre" #: packages/app-desktop/bridge.ts:450 msgid "Always open %s files without asking." -msgstr "" +msgstr "Abrir siempre los archivos %s sin preguntar." #: packages/lib/models/settings/builtInMetadata.ts:867 -#, fuzzy msgid "Always resize" -msgstr "Siempre" +msgstr "Redimensionar siempre" #: packages/app-cli/app/command-mv.ts:37 msgid "" @@ -500,10 +497,11 @@ msgstr "" #: packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.ts:13 msgid "An autosaved drawing was found. Attach a copy of it to the note?" msgstr "" +"Se ha encontrado un dibujo autoguardado. ¿Adjuntar una copia a la nota?" #: packages/app-desktop/ElectronAppWrapper.ts:133 msgid "An error occurred: %s" -msgstr "" +msgstr "Se ha producido un error: %s" #: packages/app-desktop/checkForUpdates.ts:107 msgid "An update is available, do you want to download it now?" @@ -511,7 +509,7 @@ msgstr "Está disponible una actualización. ¿Quiere descargarla ahora?" #: packages/app-mobile/utils/getVersionInfoText.ts:22 msgid "Android API level: %d" -msgstr "" +msgstr "Nivel de API de Android: %d" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:48 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:24 @@ -519,6 +517,9 @@ msgid "" "Any email sent to this address will be converted into a note and added to " "your collection. The note will be saved into the Inbox notebook" msgstr "" +"Cualquier correo electrónico enviado a esta dirección se convertirá en una " +"nota y se añadirá a tu colección. La nota se guardará en la libreta Bandeja " +"de entrada." #: packages/lib/models/Setting.ts:1174 msgid "Appearance" @@ -533,11 +534,13 @@ msgid "Apply" msgstr "Aplicar" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:146 -#, fuzzy msgid "" "Are you sure that you want to restore the default toolbar layout?\n" "This cannot be undone." -msgstr "¿Está seguro de que desea renovar el token de autorización?" +msgstr "" +"¿Está seguro de que desea restaurar el diseño predeterminado de la barra de " +"herramientas?\n" +"Esto no se puede deshacer." #: packages/app-desktop/gui/ClipperConfigScreen.tsx:38 msgid "Are you sure you want to renew the authorisation token?" @@ -548,6 +551,8 @@ msgid "" "Are you sure you want to return to the default layout? The current layout " "configuration will be lost." msgstr "" +"¿Estás seguro de que quieres volver al diseño predeterminado? La " +"configuración de diseño actual se perderá." #: packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.tsx:235 msgid "Arguments:" @@ -562,6 +567,8 @@ msgid "" "At present, Joplin Web can only be open in one tab at a time. Please close " "the other instance of Joplin." msgstr "" +"En la actualidad, Joplin Web solo se puede abrir en una pestaña a la vez. " +"Por favor, cierre la otra instancia de Joplin." #: packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.ts:25 msgid "Attach" @@ -630,9 +637,8 @@ msgstr "Token de autorización:" #: packages/app-desktop/gui/JoplinCloudLoginScreen.tsx:88 #: packages/app-mobile/components/screens/JoplinCloudLoginScreen.tsx:162 -#, fuzzy msgid "Authorise" -msgstr "Token de autorización:" +msgstr "Autorizar" #: packages/lib/models/settings/builtInMetadata.ts:398 msgid "Auto" @@ -648,7 +654,7 @@ msgstr "Autoemparejar llaves, paréntesis, comillas, etc." #: packages/lib/models/settings/builtInMetadata.ts:656 msgid "Autocomplete Markdown and HTML" -msgstr "" +msgstr "Autocompletar Markdown y HTML" #: packages/lib/models/settings/builtInMetadata.ts:1198 msgid "Automatically check for updates" @@ -656,7 +662,7 @@ msgstr "Comprobar actualizaciones" #: packages/lib/models/settings/builtInMetadata.ts:1722 msgid "Automatically delete notes in the trash after a number of days" -msgstr "" +msgstr "Eliminar automáticamente notas en la papelera después de varios días" #: packages/lib/models/settings/builtInMetadata.ts:556 msgid "Automatically switch theme to match system theme" @@ -680,7 +686,7 @@ msgstr "Básico" #: packages/app-mobile/components/ScreenHeader/WebBetaButton.tsx:37 #: packages/app-mobile/components/ScreenHeader/WebBetaButton.tsx:45 msgid "Beta" -msgstr "" +msgstr "Beta" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:55 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:54 @@ -699,7 +705,7 @@ msgstr "Explorar..." #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:223 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx:54 msgid "Built-in" -msgstr "" +msgstr "Integrado" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:85 msgid "Bulleted List" @@ -707,11 +713,11 @@ msgstr "Lista con Viñetas" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:129 msgid "by %s" -msgstr "" +msgstr "por %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:20 msgid "by word" -msgstr "" +msgstr "por palabra" #: packages/server/src/routes/admin/users.ts:160 msgid "Can Share" @@ -719,11 +725,11 @@ msgstr "Puede compartir" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:120 msgid "Can view" -msgstr "" +msgstr "Puede ver" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:121 msgid "Can view and edit" -msgstr "" +msgstr "Puede ver y editar" #: packages/app-desktop/bridge.ts:373 packages/app-desktop/bridge.ts:389 #: packages/app-desktop/bridge.ts:452 @@ -782,9 +788,8 @@ msgid "Cannot copy note to \"%s\" notebook" msgstr "No se ha podido copiar la nota a la libreta «%s»" #: packages/app-mobile/components/screens/Notes.tsx:195 -#, fuzzy msgid "Cannot create a new note: %s" -msgstr "Crea una nueva nota." +msgstr "No se puede crear una nueva nota: %s" #: packages/app-cli/app/command-attach.ts:22 #: packages/app-cli/app/command-cat.ts:26 packages/app-cli/app/command-cp.ts:25 @@ -816,9 +821,8 @@ msgid "Cannot find \"%s\"." msgstr "No se puede encontrar «%s»." #: packages/app-cli/app/command-mkbook.ts:28 -#, fuzzy msgid "Cannot find: \"%s\"" -msgstr "No se puede encontrar «%s»." +msgstr "No se puede encontrar: «%s»" #: packages/app-cli/app/command-sync.ts:202 msgid "Cannot initialise synchroniser." @@ -888,13 +892,12 @@ msgid "Change language" msgstr "Cambiar idioma" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:124 -#, fuzzy msgid "Change ratio" -msgstr "Configuración" +msgstr "Relación de cambio" #: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:98 msgid "Change shortcut for \"%s\"" -msgstr "" +msgstr "Cambiar el atajo para \"%s\"" #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:106 msgid "Characters" @@ -907,6 +910,7 @@ msgstr "Caracteres excluyendo espacios" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:168 msgid "Check elements to display in the toolbar" msgstr "" +"Verifica los elementos que se van a mostrar en la barra de herramientas" #: packages/app-desktop/gui/MenuBar.tsx:634 #: packages/app-desktop/gui/MenuBar.tsx:929 @@ -951,9 +955,8 @@ msgstr "Quitar alarma" #: packages/app-desktop/gui/lib/SearchInput/SearchInput.tsx:62 #: packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.tsx:140 -#, fuzzy msgid "Clear search" -msgstr "Quitar alarma" +msgstr "Borrar búsqueda" #: packages/app-desktop/gui/NoteRevisionViewer.tsx:205 msgid "" @@ -972,9 +975,8 @@ msgid "Client ID: %s" msgstr "ID del Cliente: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:23 -#, fuzzy msgid "close" -msgstr "Cerrar" +msgstr "cerrar" #: packages/app-desktop/gui/MenuBar.tsx:359 #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:175 @@ -994,13 +996,12 @@ msgid "Close dropdown" msgstr "Cerrar menú" #: packages/app-mobile/components/accessibility/AccessibleModalMenu.tsx:46 -#, fuzzy msgid "Close menu" -msgstr "Cerrar" +msgstr "Cerrar menú" #: packages/app-mobile/components/SideMenu.tsx:300 msgid "Close side menu" -msgstr "" +msgstr "Cerrar menú lateral" #: packages/lib/models/settings/settingValidations.ts:33 msgid "" @@ -1008,6 +1009,9 @@ msgid "" "application again. Make sure you create a backup first by exporting all your " "notes as JEX." msgstr "" +"Cierre la aplicación, luego elimine su perfil en \"%s\" y vuelva a iniciar " +"la aplicación. Asegúrate de crear una copia de seguridad primero exportando " +"todas tus notas como JEX." #: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:26 #: packages/app-desktop/gui/MenuBar.tsx:699 @@ -1016,11 +1020,11 @@ msgstr "Cerrar Ventana" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.ts:39 msgid "Cmd-click to open" -msgstr "" +msgstr "Cmd-haga clic para abrir" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.ts:45 msgid "Cmd-click to open: %s" -msgstr "" +msgstr "Cmd-clic para abrir: %s" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:70 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:65 @@ -1036,9 +1040,8 @@ msgid "Code View" msgstr "Código" #: packages/lib/utils/joplinCloud/index.ts:173 -#, fuzzy msgid "Collaborate on a notebook with others" -msgstr "Colaborar en los cuadernos con otros" +msgstr "Colaborar en un cuaderno con otros usuarios" #: packages/app-desktop/gui/SyncWizard/Dialog.tsx:169 msgid "Collaborate on notebooks with others" @@ -1046,7 +1049,7 @@ msgstr "Colaborar en los cuadernos con otros" #: packages/app-desktop/gui/Sidebar/listItemComponents/ExpandIcon.tsx:28 msgid "Collapsed, press space to expand." -msgstr "" +msgstr "Contraído, presione espacio para expandir." #: packages/lib/services/ReportService.ts:351 msgid "Coming alarms" @@ -1055,9 +1058,9 @@ msgstr "Alarmas próximas" #: packages/lib/models/settings/builtInMetadata.ts:1379 msgid "" "Comma-separated list of paths to directories to load the certificates from, " -"or path to individual cert files. For example: /my/cert_dir, /other/" -"custom.pem. Note that if you make changes to the TLS settings, you must save " -"your changes before clicking on \"Check synchronisation configuration\"." +"or path to individual cert files. For example: /my/cert_dir, /other/custom." +"pem. Note that if you make changes to the TLS settings, you must save your " +"changes before clicking on \"Check synchronisation configuration\"." msgstr "" "Lista de rutas de directorios separados por comas de dónde cargar los " "certificados, o ruta a certificados individuales. Por ejemplo: /mi/" @@ -1082,19 +1085,16 @@ msgid "Command palette..." msgstr "Paleta de comandos..." #: packages/lib/services/noteList/defaultListRenderer.ts:24 -#, fuzzy msgid "Compact" -msgstr "Completada" +msgstr "Compacto" #: packages/app-desktop/gui/NoteList/utils/useOnKeyDown.ts:153 -#, fuzzy msgid "Complete" -msgstr "Completada" +msgstr "Completo" #: packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.ts:40 -#, fuzzy msgid "Complete to-do" -msgstr "Completada" +msgstr "Tarea completas" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:12 #: packages/app-desktop/gui/NotePropertiesDialog.tsx:70 @@ -1110,6 +1110,8 @@ msgid "" "Completed with warnings:\n" "%s" msgstr "" +"Completado con advertencias:\n" +"%s" #: packages/lib/Synchronizer.ts:207 msgid "Completed: %s (%s)" @@ -1117,7 +1119,7 @@ msgstr "Completado: %s (%s)" #: packages/lib/models/Note.ts:66 msgid "completion date" -msgstr "" +msgstr "fecha de compleción" #: packages/server/src/services/TaskService.ts:28 msgid "Compress old changes" @@ -1157,38 +1159,34 @@ msgstr "Conflictos (adjuntos)" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:253 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:462 -#, fuzzy msgid "Connect to Joplin Cloud" -msgstr "Joplin Cloud" +msgstr "Conéctese a Joplin Cloud" #: packages/lib/utils/joplinCloud/index.ts:208 msgid "Consolidated billing" msgstr "Facturación consolidada" #: packages/app-desktop/gui/Sidebar/listItemComponents/NoteCount.tsx:11 -#, fuzzy msgid "Contains %d note" msgid_plural "Contains %d notes" -msgstr[0] "Convertir en nota" -msgstr[1] "Convertir en nota" +msgstr[0] "Contiene %d nota" +msgstr[1] "Contiene %d notas" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx:106 msgid "Content provided by %s" msgstr "Contenido provisto por %s" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:102 -#, fuzzy msgid "Content provided by: %s" -msgstr "Contenido provisto por %s" +msgstr "Contenido proporcionado por: %s" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:74 msgid "Continue" msgstr "Continuar" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:6 -#, fuzzy msgid "Control character" -msgstr "Caracteres" +msgstr "Carácter de control" #: packages/app-mobile/components/screens/Note/Note.tsx:1221 msgid "Convert to note" @@ -1199,9 +1197,8 @@ msgid "Convert to todo" msgstr "Convertir a tarea" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:181 -#, fuzzy msgid "Converting speech to text..." -msgstr "Convertir en nota" +msgstr "Convertir voz en texto..." #: packages/app-desktop/gui/MenuBar.tsx:601 #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:35 @@ -1232,9 +1229,8 @@ msgstr "Copiar Enlace" #: packages/app-desktop/gui/JoplinCloudLoginScreen.tsx:94 #: packages/app-mobile/components/screens/JoplinCloudLoginScreen.tsx:169 -#, fuzzy msgid "Copy link to website" -msgstr "Sitio web de Joplin" +msgstr "Copiar enlace al sitio web" #: packages/app-desktop/gui/utils/NoteListUtils.ts:112 #: packages/app-mobile/components/screens/Note/Note.tsx:1230 @@ -1253,18 +1249,16 @@ msgstr[1] "Copiar Enlaces Compartible" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:52 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:75 -#, fuzzy msgid "Copy to clipboard" -msgstr "Copiar la ruta al portapapeles" +msgstr "Copiar al portapapeles" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:144 msgid "Copy token" msgstr "Copiar token" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:611 -#, fuzzy msgid "Copy version info" -msgstr "Muestra información de la versión" +msgstr "Copiar información de la versión" #: packages/lib/components/shared/dropbox-login-shared.js:43 msgid "" @@ -1333,18 +1327,16 @@ msgstr "" "abortando. Por favor, inténtelo de nuevo cuando esté conectado a Internet." #: packages/app-mobile/components/biometrics/biometricAuthenticate.ts:30 -#, fuzzy msgid "Could not verify your identity: %s" -msgstr "No se pudo verificar su identidad" +msgstr "No se ha podido verificar su identidad: %s" #: packages/app-desktop/gui/PromptDialog.tsx:277 msgid "Create" msgstr "Crear" #: packages/app-cli/app/command-mkbook.ts:19 -#, fuzzy msgid "Create a new notebook under a parent notebook." -msgstr "Crea una nueva libreta." +msgstr "Cree una nueva libreta en una libreta principal." #: packages/app-mobile/components/NoteList.tsx:102 msgid "Create a notebook" @@ -1411,14 +1403,12 @@ msgid "Creates a new to-do." msgstr "Crea una nueva tarea." #: packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx:123 -#, fuzzy msgid "Creating new note..." -msgstr "Creando nuevo %s..." +msgstr "Creando una nueva nota..." #: packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx:123 -#, fuzzy msgid "Creating new to-do..." -msgstr "Creando nuevo %s..." +msgstr "Creando nueva tarea..." #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.tsx:29 msgid "Creating report..." @@ -1426,21 +1416,19 @@ msgstr "Creando reporte..." #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.ts:41 msgid "Ctrl-click to open" -msgstr "" +msgstr "Ctrl-clic para abrir" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.ts:47 msgid "Ctrl-click to open: %s" -msgstr "" +msgstr "Ctrl-clic para abrir: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:24 -#, fuzzy msgid "current match" -msgstr "Siguiente coincidencia" +msgstr "Partido actual" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:153 -#, fuzzy msgid "Current password" -msgstr "Contraseña maestra" +msgstr "Contraseña actual" #: packages/app-desktop/checkForUpdates.ts:90 msgid "Current version is up-to-date." @@ -1468,7 +1456,7 @@ msgstr "Certificados TLS personalizados" #: packages/lib/utils/joplinCloud/index.ts:194 msgid "Customise the note publishing banner" -msgstr "" +msgstr "Personaliza el banner de publicación de notas" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:40 #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.ts:85 @@ -1564,16 +1552,16 @@ msgid "" "Delete model and re-download?\n" "This cannot be undone." msgstr "" +"¿Eliminar el modelo y volver a descargarlo?\n" +"Esto no se puede deshacer." #: packages/lib/commands/deleteNote.ts:7 -#, fuzzy msgid "Delete note" -msgstr "¿Borrar nota?" +msgstr "Eliminar nota" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/deleteFolder.ts:9 -#, fuzzy msgid "Delete notebook" -msgstr "Eliminar libreta?" +msgstr "Eliminar libreta" #: packages/lib/components/shared/config/plugins/useOnDeleteHandler.ts:16 msgid "Delete plugin \"%s\"?" @@ -1595,6 +1583,10 @@ msgid "" "If you delete the inbox notebook, any email that's recently been sent to it " "may be lost." msgstr "" +"¿Eliminar la libreta de la bandeja de entrada?\n" +"\n" +"Si eliminas la libreta de la bandeja de entrada, es posible que se pierda " +"cualquier correo electrónico que se haya enviado recientemente." #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:246 msgid "" @@ -1610,9 +1602,8 @@ msgstr "¿Borrar este perfil?" #: packages/app-desktop/gui/NotePropertiesDialog.tsx:69 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:207 -#, fuzzy msgid "Deleted" -msgstr "Borrar" +msgstr "Eliminado" #: packages/lib/Synchronizer.ts:203 msgid "Deleted local items: %d." @@ -1624,7 +1615,7 @@ msgstr "Elementos remotos borrados: %d." #: packages/app-cli/app/command-rmnote.ts:20 msgid "Deletes notes permanently, skipping the trash." -msgstr "" +msgstr "Elimina notas de forma permanente, saltándose la papelera." #: packages/app-cli/app/command-rmbook.ts:14 msgid "Deletes the given notebook." @@ -1643,14 +1634,12 @@ msgid "Deletes the notes without asking for confirmation." msgstr "Elimina las notas sin pedir confirmación." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:551 -#, fuzzy msgid "Deletion log" -msgstr "Borrar línea" +msgstr "Registro de eliminación" #: packages/app-mobile/components/NoteItem.tsx:144 -#, fuzzy msgid "Deselect" -msgstr "Seleccione" +msgstr "Deseleccionar" #: packages/app-cli/app/command-export.ts:24 msgid "Destination format: %s" @@ -1659,11 +1648,11 @@ msgstr "Formato de destino: %s" #: packages/lib/services/noteList/defaultLeftToRightListRenderer.ts:25 #: packages/lib/services/noteList/defaultMultiColumnsRenderer.ts:8 msgid "Detailed" -msgstr "" +msgstr "Detallado" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx:99 msgid "Dev" -msgstr "" +msgstr "Desarrollador" #: packages/lib/services/interop/Module.ts:62 msgid "Directory" @@ -1700,9 +1689,8 @@ msgid "Disabled" msgstr "Deshabilitado" #: packages/app-mobile/components/screens/encryption-config.tsx:323 -#, fuzzy msgid "Disabled keys" -msgstr "Ocultar las claves desactivadas" +msgstr "Teclas deshabilitadas" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:200 #: packages/app-mobile/components/screens/encryption-config.tsx:251 @@ -1716,9 +1704,8 @@ msgstr "" "continuar?" #: packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.ts:19 -#, fuzzy msgid "Discard" -msgstr "Descartar cambios" +msgstr "Descartar" #: packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.tsx:105 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:269 @@ -1796,9 +1783,8 @@ msgstr "" "introduzca su contraseña a continuación." #: packages/lib/models/Setting.ts:1220 -#, fuzzy msgid "Donate, website" -msgstr "Sitio web de Joplin" +msgstr "Donar, sitio web" #: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:151 #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:245 @@ -1815,9 +1801,8 @@ msgid "Download and install the relevant extension for your browser:" msgstr "Descargue e instale la extensión apropiada para su navegador:" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:206 -#, fuzzy msgid "Download updated model" -msgstr "Descargado" +msgstr "Descargar modelo actualizado" #: packages/lib/models/Resource.ts:409 msgid "Downloaded" @@ -1836,9 +1821,8 @@ msgid "Downloading" msgstr "Descargando" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:182 -#, fuzzy msgid "Downloading %s language files..." -msgstr "Descargando recursos..." +msgstr "Descarga de archivos de idioma %s..." #: packages/app-cli/app/command-sync.ts:256 msgid "Downloading resources..." @@ -1850,11 +1834,11 @@ msgstr "Drácula" #: packages/app-mobile/components/screens/Note/Note.tsx:1162 msgid "Draw picture" -msgstr "" +msgstr "Dibujo" #: packages/app-mobile/components/screens/Note/Note.tsx:872 msgid "Drawing" -msgstr "" +msgstr "Dibujar" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:1444 msgid "Drop notes or files here" @@ -1870,12 +1854,11 @@ msgstr "Inicio de sesión de Dropbox" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:13 msgid "Due" -msgstr "" +msgstr "Vencido" #: packages/lib/models/Note.ts:65 -#, fuzzy msgid "due date" -msgstr "fecha de actualización" +msgstr "fecha límite" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/duplicateNote.ts:7 #: packages/app-mobile/components/ScreenHeader/index.tsx:470 @@ -1916,12 +1899,11 @@ msgstr "Editar con un editor externo" #: packages/app-desktop/commands/openNoteInNewWindow.ts:10 msgid "Edit in new window" -msgstr "" +msgstr "Editar en ventana nueva" #: packages/app-desktop/gui/utils/NoteListUtils.ts:70 -#, fuzzy msgid "Edit in..." -msgstr "Editar enlace" +msgstr "Editar en..." #: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:143 msgid "Edit link" @@ -1952,9 +1934,8 @@ msgid "Editor" msgstr "Editor" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Toolbar.tsx:38 -#, fuzzy msgid "Editor actions" -msgstr "Fuente del editor" +msgstr "Acciones del editor" #: packages/lib/models/settings/builtInMetadata.ts:1074 msgid "Editor font" @@ -1998,18 +1979,16 @@ msgstr "Email" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:47 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:23 -#, fuzzy msgid "Email to note" -msgstr "Editar nota." +msgstr "Correo electrónico a la nota" #: packages/lib/utils/joplinCloud/index.ts:187 msgid "Email to Note" -msgstr "" +msgstr "Enviar por correo electrónico a la nota" #: packages/lib/models/Setting.ts:1213 -#, fuzzy msgid "Email To Note, login information" -msgstr "Muestra información de la versión" +msgstr "Correo electrónico a la nota, información de inicio de sesión" #: packages/server/src/routes/admin/emails.ts:111 #: packages/server/src/services/MustacheService.ts:133 @@ -2025,7 +2004,7 @@ msgstr "texto en cursiva" #: packages/app-mobile/components/side-menu-content.tsx:338 #: packages/app-mobile/components/side-menu-content.tsx:342 msgid "Empty trash" -msgstr "" +msgstr "Vaciar papelera" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:144 #: packages/app-mobile/components/biometrics/BiometricPopup.tsx:86 @@ -2072,7 +2051,7 @@ msgstr "Habilitar cifrado" #: packages/lib/models/settings/builtInMetadata.ts:994 msgid "Enable file:// URLs for images and videos" -msgstr "" +msgstr "Habilitar URL de file:// para imágenes y videos" #: packages/lib/models/settings/builtInMetadata.ts:975 msgid "Enable footnotes" @@ -2108,12 +2087,11 @@ msgstr "Habilitar historial de notas" #: packages/lib/models/settings/builtInMetadata.ts:500 msgid "Enable optical character recognition (OCR)" -msgstr "" +msgstr "Habilitar el reconocimiento óptico de caracteres (OCR)" #: packages/lib/models/Setting.ts:1219 -#, fuzzy msgid "Enable or disable plugins" -msgstr "Administre sus plugins" +msgstr "Activar o desactivar plugins" #: packages/lib/models/settings/builtInMetadata.ts:973 msgid "Enable PDF viewer" @@ -2121,31 +2099,28 @@ msgstr "Activar visor de PDF" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:119 #: packages/lib/models/settings/builtInMetadata.ts:921 -#, fuzzy msgid "Enable plugin support" -msgstr "Activar el soporte de tipógrafo" +msgstr "Habilitar la compatibilidad con complementos" #: packages/lib/models/settings/builtInMetadata.ts:963 msgid "Enable soft breaks" msgstr "Activar saltos de línea" #: packages/lib/models/settings/builtInMetadata.ts:1303 -#, fuzzy msgid "Enable spell checking in Markdown editor" -msgstr "Activar sintaxis de emojis markdown" +msgstr "Habilitar la revisión ortográfica en el editor de Markdown" #: packages/lib/models/settings/builtInMetadata.ts:789 msgid "Enable spellcheck in the text editor" -msgstr "" +msgstr "Habilitar el corrector ortográfico en el editor de texto" #: packages/lib/models/settings/builtInMetadata.ts:976 msgid "Enable table of contents extension" msgstr "Activar extensión de tabla de contenidos" #: packages/lib/models/settings/builtInMetadata.ts:800 -#, fuzzy msgid "Enable the Markdown toolbar" -msgstr "Activar sintaxis de emojis markdown" +msgstr "Habilitar la barra de herramientas Markdown" #: packages/lib/models/settings/builtInMetadata.ts:964 msgid "Enable typographer support" @@ -2172,6 +2147,8 @@ msgid "" "Enables Markdown list continuation, auto-closing HTML tags, and other markup " "autocompletions." msgstr "" +"Activa la continuación de listas Markdown, el cierre automático de etiquetas " +"HTML y otros autocompletados de marcado." #: packages/lib/components/EncryptionConfigScreen/utils.ts:50 msgid "" @@ -2218,7 +2195,7 @@ msgstr "Cifrado de Extremo a Extremo" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:244 msgid "Ends voice typing" -msgstr "" +msgstr "Finaliza la escritura por voz" #: packages/app-mobile/components/screens/dropbox-login.tsx:70 msgid "Enter code here" @@ -2234,13 +2211,12 @@ msgid "Enter notebook title" msgstr "Introduzca el título de la libreta" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:122 -#, fuzzy msgid "Enter password" -msgstr "Contraseña maestra" +msgstr "Introducir la contraseña" #: packages/app-mobile/components/NoteItem.tsx:120 msgid "Entering selection mode" -msgstr "" +msgstr "Entrando en el modo de selección" #: packages/app-cli/app/help-utils.js:56 msgid "Enum" @@ -2291,27 +2267,24 @@ msgid "Evernote Export File (as Markdown)" msgstr "Exportar como Archivo de Ever(como Markdown)" #: packages/lib/services/interop/InteropService.ts:94 -#, fuzzy msgid "Evernote Export Files (Directory, as HTML)" -msgstr "Exportar como Archivo de Ever(como HTML)" +msgstr "Archivos de exportación de Evernote (directorio, como HTML)" #: packages/lib/services/interop/InteropService.ts:103 -#, fuzzy msgid "Evernote Export Files (Directory, as Markdown)" -msgstr "Exportar como Archivo de Ever(como Markdown)" +msgstr "Archivos de exportación de Evernote (directorio, como Markdown)" #: packages/app-cli/app/command-exit.ts:11 msgid "Exits the application." msgstr "Sale de la aplicación." #: packages/app-mobile/components/side-menu-content.tsx:212 -#, fuzzy msgid "Expand %s" -msgstr "Expandir" +msgstr "Expandir %s" #: packages/app-desktop/gui/Sidebar/listItemComponents/ExpandIcon.tsx:26 msgid "Expanded, press space to collapse." -msgstr "" +msgstr "Expandido, presione espacio para contraer." #: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:182 #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:213 @@ -2325,9 +2298,8 @@ msgid "Export all" msgstr "Exportar todo" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.tsx:20 -#, fuzzy msgid "Export all notes as JEX" -msgstr "Exportar todo" +msgstr "Exportar todas las notas como JEX" #: packages/app-desktop/gui/StatusScreen/StatusScreen.tsx:199 msgid "Export debug report" @@ -2338,9 +2310,8 @@ msgid "Export Debug Report" msgstr "Exportar Informe de Depuración" #: packages/app-desktop/commands/exportDeletionLog.ts:11 -#, fuzzy msgid "Export deletion log" -msgstr "Exportar todo" +msgstr "Exportar registro de eliminación" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:16 msgid "Export profile" @@ -2348,7 +2319,7 @@ msgstr "Exportar perfil" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.tsx:70 msgid "Exported successfully!" -msgstr "" +msgstr "¡Exportado con éxito!" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:36 msgid "Exporting profile..." @@ -2359,9 +2330,8 @@ msgid "Exporting to \"%s\" as \"%s\" format. Please wait..." msgstr "Exportando a «%s» como formato «%s». Por favor espere..." #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.tsx:25 -#, fuzzy msgid "Exporting..." -msgstr "Exportando perfil..." +msgstr "Exportando..." #: packages/app-cli/app/command-export.ts:14 msgid "" @@ -2420,18 +2390,16 @@ msgstr "Sistema de archivos" #: packages/app-mobile/components/screens/LogScreen.tsx:207 #: packages/app-mobile/components/screens/LogScreen.tsx:208 -#, fuzzy msgid "Filter" -msgstr "Filtrar etiquetas" +msgstr "Filtro" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:227 msgid "Filter tags" msgstr "Filtrar etiquetas" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:14 -#, fuzzy msgid "Find" -msgstr "Buscar: " +msgstr "Encontrar" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:253 msgid "Find: " @@ -2469,7 +2437,7 @@ msgstr "Enfocar título" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:97 msgid "Follow link" -msgstr "" +msgstr "Seguir enlace" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:38 msgid "For debugging purpose only: export your profile to an external SD card." @@ -2541,6 +2509,8 @@ msgstr[1] "Creando enlaces..." #: packages/lib/models/Setting.ts:1215 msgid "Geolocation, spellcheck, editor toolbar, image resize" msgstr "" +"Geolocalización, corrector ortográfico, barra de herramientas del editor, " +"cambio de tamaño de imagen" #: packages/app-desktop/gui/ExtensionBadge.tsx:93 msgid "Get it now:" @@ -2562,21 +2532,20 @@ msgstr "" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:13 msgid "go" -msgstr "" +msgstr "ir" #: packages/app-mobile/components/CameraView/CameraView.tsx:165 msgid "Go back" -msgstr "" +msgstr "Volver" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:44 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:59 -#, fuzzy msgid "Go to Joplin Cloud profile" -msgstr "Joplin Cloud" +msgstr "Ir al perfil de Joplin Cloud" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:12 msgid "Go to line" -msgstr "" +msgstr "Ir a la línea" #: packages/app-mobile/components/screens/Note/Note.tsx:1051 msgid "Go to source URL" @@ -2594,6 +2563,7 @@ msgstr "Conceder la autorización" #: packages/app-cli/app/command-sync.ts:116 msgid "Have you authorised the application login in the above URL?" msgstr "" +"¿Ha autorizado el inicio de sesión de la aplicación en la URL anterior?" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:19 msgid "Header %d" @@ -2612,12 +2582,11 @@ msgstr "Ayuda" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:113 msgid "Here's what we do to make plugins safer:" -msgstr "" +msgstr "Esto es lo que hacemos para que los plugins sean más seguros:" #: packages/app-mobile/utils/getVersionInfoText.ts:49 -#, fuzzy msgid "Hermes enabled: %d" -msgstr "FTS activado: %d" +msgstr "Hermes habilitado: %d" #: packages/app-desktop/gui/MenuBar.tsx:670 msgid "Hide %s" @@ -2644,9 +2613,8 @@ msgid "Hide keyboard" msgstr "Ocultar teclado" #: packages/app-desktop/gui/PasswordInput/PasswordInput.tsx:21 -#, fuzzy msgid "Hide password" -msgstr "Contraseña inválida" +msgstr "Ocultar la contraseña" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:14 msgid "Highlight" @@ -2667,7 +2635,7 @@ msgstr "Directorio HTML" #: packages/lib/services/interop/InteropService.ts:112 msgid "HTML document" -msgstr "" +msgstr "Documento HTML" #: packages/lib/services/interop/InteropService.ts:179 msgid "HTML File" @@ -2698,6 +2666,8 @@ msgid "" "If you have already authorised, please wait for the application to sync to " "Joplin Cloud." msgstr "" +"Si ya lo ha autorizado, espere a que la aplicación se sincronice con Joplin " +"Cloud." #: packages/app-desktop/ElectronAppWrapper.ts:127 #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:360 @@ -2711,9 +2681,8 @@ msgid "Ignore TLS certificate errors" msgstr "Ignorar errores en certificados TLS" #: packages/lib/services/ReportService.ts:236 -#, fuzzy msgid "Ignored items that cannot be synchronised" -msgstr "Elementos que no pueden ser sincronizados" +msgstr "Elementos ignorados que no se pueden sincronizar" #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:106 msgid "Images" @@ -2727,32 +2696,32 @@ msgid "Import" msgstr "Importar" #: packages/lib/models/Setting.ts:1186 -#, fuzzy msgid "Import and Export" -msgstr "Exportar Informe de depuración" +msgstr "Importación / exportación .po" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:66 msgid "" "Import failed. Make sure a JEX file was selected.\n" "Details: %s" msgstr "" +"Error de importación. Asegúrese de que se haya seleccionado un archivo JEX.\n" +"Detalles: %s" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:21 msgid "Import from JEX" -msgstr "" +msgstr "Importar desde JEX" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:22 msgid "Import notes from a JEX (Joplin Export) file." -msgstr "" +msgstr "Importar notas de un archivo JEX (Joplin Export)." #: packages/lib/models/Setting.ts:1218 -#, fuzzy msgid "Import or export your data" -msgstr "Exportar todo" +msgstr "Importa o exporta tus datos" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:76 msgid "Imported successfully!" -msgstr "" +msgstr "¡Importado exitosamente!" #: packages/app-desktop/gui/MenuBar.tsx:319 msgid "Importing from \"%s\" as \"%s\" format. Please wait..." @@ -2763,9 +2732,8 @@ msgid "Importing notes..." msgstr "Importando notas..." #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:26 -#, fuzzy msgid "Importing..." -msgstr "Exportando perfil..." +msgstr "Importando..." #: packages/app-cli/app/command-import.ts:16 msgid "Imports data into Joplin." @@ -2839,7 +2807,7 @@ msgstr "" #: packages/lib/services/synchronizer/syncInfoUtils.ts:466 msgid "In order to synchronise, please upgrade your application to version %s+" -msgstr "" +msgstr "Para sincronizar, actualice su aplicación a la versión %s+" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:122 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:224 @@ -2864,17 +2832,15 @@ msgstr "En: %s" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx:69 msgid "Incompatible" -msgstr "" +msgstr "Incompatible" #: packages/app-desktop/gui/NoteList/utils/useOnKeyDown.ts:153 -#, fuzzy msgid "Incomplete" -msgstr "Completada" +msgstr "Incompleto" #: packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.ts:40 -#, fuzzy msgid "Incomplete to-do" -msgstr "Mostrar tareas completadas" +msgstr "Tareas incompletas" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:97 msgid "Increase indent level" @@ -2890,7 +2856,7 @@ msgstr "Aumentar sangría" #: packages/app-mobile/components/DialogManager/hooks/useDialogControl.ts:21 msgid "Info" -msgstr "" +msgstr "Información" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:223 msgid "Information" @@ -2932,9 +2898,8 @@ msgid "Installed" msgstr "Instalado" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:202 -#, fuzzy msgid "Installed (%d):" -msgstr "Instalado" +msgstr "Instalado (%d):" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:192 #: packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.tsx:17 @@ -2967,9 +2932,8 @@ msgid "Invalid password" msgstr "Contraseña inválida" #: packages/app-mobile/utils/getVersionInfoText.ts:24 -#, fuzzy msgid "iOS version: %s" -msgstr "Nueva versión: %s" +msgstr "versión de iOS: %s" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:60 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:59 @@ -2995,7 +2959,7 @@ msgstr "Elementos que no pueden ser sincronizados" #: packages/app-desktop/gui/MenuBar.tsx:923 msgid "Join us on %s" -msgstr "" +msgstr "Únete a nosotros en %s" #: packages/app-desktop/gui/SyncWizard/Dialog.tsx:267 msgid "" @@ -3011,20 +2975,20 @@ msgstr "Joplin Cloud" #: packages/app-desktop/gui/Root.tsx:190 #: packages/app-mobile/components/screens/JoplinCloudLoginScreen.tsx:148 -#, fuzzy msgid "Joplin Cloud Login" -msgstr "Joplin Cloud" +msgstr "Inicio de sesión en Joplin Cloud" #: packages/lib/services/plugins/PluginService.ts:525 -#, fuzzy msgid "Joplin Desktop" -msgstr "Sitio web de Joplin" +msgstr "Escritorio Joplin" #: packages/app-desktop/bridge.ts:448 msgid "" "Joplin doesn't recognise the %s extension. Opening this file could be " "dangerous. What would you like to do?" msgstr "" +"Joplin no reconoce la extensión %s. Abrir este archivo podría ser peligroso. " +"¿Qué te gustaría hacer?" #: packages/lib/services/interop/InteropService.ts:159 #: packages/lib/services/interop/InteropService.ts:68 @@ -3051,14 +3015,12 @@ msgid "Joplin Forum" msgstr "Foro de Joplin" #: packages/app-mobile/utils/lockToSingleInstance.ts:16 -#, fuzzy msgid "Joplin is already running." -msgstr "El servidor ya esta ejecutándose en el puerto %d" +msgstr "Joplin ya se está ejecutando." #: packages/lib/services/plugins/PluginService.ts:523 -#, fuzzy msgid "Joplin Mobile" -msgstr "Sitio web de Joplin" +msgstr "Joplin Móvil" #: packages/lib/SyncTargetJoplinServer.ts:61 msgid "Joplin Server" @@ -3095,16 +3057,15 @@ msgid "" msgstr "" "El servicio de sincronización propio de Joplin. También da acceso a " "funciones específicas de Joplin, como la publicación de notas o la " -"colaboración en cuadernos con otras personas." +"colaboración en libretas con otras personas." #: packages/lib/models/settings/builtInMetadata.ts:1485 msgid "Keep note history for" msgstr "Mantener historial de la nota durante" #: packages/lib/models/settings/builtInMetadata.ts:1739 -#, fuzzy msgid "Keep notes in the trash for" -msgstr "Mantener historial de la nota durante" +msgstr "Guarda las notas en la papelera durante" #: packages/lib/models/settings/builtInMetadata.ts:1285 msgid "Keyboard Mode" @@ -3135,9 +3096,8 @@ msgid "Language" msgstr "Idioma" #: packages/lib/models/Setting.ts:1210 -#, fuzzy msgid "Language, date format" -msgstr "Formato de fecha" +msgstr "Idioma, formato de fecha" #: packages/lib/Synchronizer.ts:208 msgid "Last error: %s" @@ -3149,7 +3109,7 @@ msgstr "Luego" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:7 msgid "Latitude" -msgstr "" +msgstr "Latitud" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx:638 #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/CodeMirror.tsx:237 @@ -3163,18 +3123,18 @@ msgstr "Secuencia del botón de diseño" #: packages/app-desktop/bridge.ts:453 #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:118 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx:52 -#, fuzzy msgid "Learn more" -msgstr "Aumentar sangría" +msgstr "Saber más" #: packages/lib/models/settings/builtInMetadata.ts:1692 msgid "Leave it blank to download the language files from the default website" msgstr "" +"Déjelo en blanco para descargar los archivos de idioma del sitio web " +"predeterminado" #: packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.tsx:90 -#, fuzzy msgid "Leave notebook" -msgstr "Abandonar libreta..." +msgstr "Dejar una libreta" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.ts:11 msgid "Leave notebook..." @@ -3197,6 +3157,8 @@ msgid "" "Like any software you install, plugins can potentially cause security issues " "or data loss." msgstr "" +"Al igual que cualquier software que instale, los complementos pueden causar " +"problemas de seguridad o pérdida de datos." #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:108 msgid "Lines" @@ -3234,9 +3196,8 @@ msgstr "Cargado" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:117 #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:179 #: packages/app-mobile/root.tsx:1247 -#, fuzzy msgid "Loading..." -msgstr "Actualizando..." +msgstr "Cargando..." #: packages/app-desktop/gui/NotePropertiesDialog.tsx:71 msgid "Location" @@ -3259,9 +3220,8 @@ msgid "Log" msgstr "Registro" #: packages/app-desktop/gui/MainScreen.tsx:563 -#, fuzzy msgid "Login to Joplin Cloud." -msgstr "Joplin Cloud" +msgstr "Acceder con Joplin Cloud." #: packages/app-mobile/components/screens/dropbox-login.tsx:59 msgid "Login with Dropbox" @@ -3277,11 +3237,11 @@ msgstr "Cerrar sesión" #: packages/lib/models/Setting.ts:1217 msgid "Logs, profiles, sync status" -msgstr "" +msgstr "Registros, perfiles, estado de sincronización" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:8 msgid "Longitude" -msgstr "" +msgstr "Longitud" #: packages/app-desktop/gui/MenuBar.tsx:926 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:604 @@ -3305,14 +3265,12 @@ msgid "Manage profiles" msgstr "Administrar perfiles" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:555 -#, fuzzy msgid "Manage shared notebooks" -msgstr "Administrar perfiles" +msgstr "Administrar libretas compartidas" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:165 -#, fuzzy msgid "Manage toolbar options" -msgstr "Administre sus plugins" +msgstr "Administrar opciones de la barra de herramientas" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:331 msgid "Manage your plugins" @@ -3345,9 +3303,8 @@ msgstr "Markdown + Front Matter" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/CodeMirror.tsx:375 #: packages/app-mobile/components/NoteEditor/NoteEditor.tsx:352 -#, fuzzy msgid "Markdown editor" -msgstr "Markdown" +msgstr "Editor de Markdown" #: packages/app-cli/app/command-done.ts:15 msgid "Marks a to-do as done." @@ -3378,11 +3335,11 @@ msgstr "Contraseña maestra:" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:19 msgid "match case" -msgstr "" +msgstr "Caso de coincidencia" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:72 msgid "Math" -msgstr "" +msgstr "Matemáticas" #: packages/lib/models/settings/builtInMetadata.ts:421 msgid "Max concurrent connections" @@ -3402,16 +3359,15 @@ msgstr "Tamaño original" #: packages/lib/models/Setting.ts:1214 msgid "Media player, math, diagrams, table of contents" -msgstr "" +msgstr "Reproductor multimedia, matemáticas, diagramas, tabla de contenido" #: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:24 msgid "Minimise" -msgstr "" +msgstr "Minimizar" #: packages/app-mobile/components/CameraView/CameraView.tsx:163 -#, fuzzy msgid "Missing camera permission" -msgstr "Claves Maestras Faltantes" +msgstr "Falta el permiso de la cámara" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:320 msgid "Missing keys" @@ -3426,9 +3382,8 @@ msgid "Missing required argument: %s" msgstr "Falta un argumento requerido: %s" #: packages/app-cli/app/cli-utils.js:135 -#, fuzzy msgid "Missing required flag value: %s" -msgstr "Falta un argumento requerido: %s" +msgstr "Falta el valor de indicador obligatorio: %s" #: packages/app-mobile/components/side-menu-content.tsx:663 msgid "Mobile data - auto-sync disabled" @@ -3451,6 +3406,8 @@ msgstr "" msgid "" "Most plugins have source code available for review on the plugin website." msgstr "" +"La mayoría de los plugins tienen código fuente disponible para su revisión " +"en el sitio web del plugin." #: packages/app-mobile/components/ScreenHeader/index.tsx:546 msgid "Move %d notes to notebook \"%s\"?" @@ -3459,16 +3416,16 @@ msgstr "¿Desea mover %d notas a libreta «%s»?" #: packages/app-cli/app/command-rmbook.ts:38 #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/deleteFolder.ts:20 #: packages/app-mobile/components/side-menu-content.tsx:410 -#, fuzzy msgid "" "Move notebook \"%s\" to the trash?\n" "\n" "All notes and sub-notebooks within this notebook will also be moved to the " "trash." msgstr "" -"¿Borrar libreta «%s»?\n" +"¿Mover la libreta \"%s\" a la papelera?\n" "\n" -"Todas las notas y sublibretas de esta libreta serán borradas." +"Todas las notas y sublibretas dentro de esta libreta también se moverán a la " +"papelera." #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.ts:14 msgid "Move to notebook" @@ -3497,22 +3454,19 @@ msgstr "N" #: packages/lib/models/settings/builtInMetadata.ts:868 msgid "Never resize" -msgstr "" +msgstr "Nunca cambiar el tamaño" #: packages/app-mobile/setupQuickActions.ts:33 -#, fuzzy msgid "New attachment" -msgstr "Adjuntos de las notas" +msgstr "Nuevo apego" #: packages/app-mobile/setupQuickActions.ts:34 -#, fuzzy msgid "New drawing" -msgstr "Nuevas etiquetas:" +msgstr "Nuevo dibujo" #: packages/app-mobile/components/screens/ShareManager/index.tsx:120 -#, fuzzy msgid "New invitations" -msgstr "Nueva versión: %s" +msgstr "Nuevas invitaciones" #: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:111 #: packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.tsx:72 @@ -3537,9 +3491,8 @@ msgid "" msgstr "Se creará la libreta nueva «%s» y se importará en ella el archivo «%s»" #: packages/app-mobile/setupQuickActions.ts:32 -#, fuzzy msgid "New photo" -msgstr "Tomar una foto" +msgstr "Nueva foto" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newSubFolder.ts:6 msgid "New sub-notebook" @@ -3563,7 +3516,7 @@ msgstr "Nueva versión: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:16 msgid "next" -msgstr "" +msgstr "siguiente" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:271 msgid "Next match" @@ -3601,7 +3554,7 @@ msgstr "No hay libreta activa." #: packages/app-mobile/components/screens/ShareManager/index.tsx:92 msgid "No new invitations" -msgstr "" +msgstr "No hay nuevas invitaciones" #: packages/app-cli/app/app.ts:104 msgid "No notebook has been specified." @@ -3617,7 +3570,7 @@ msgstr "No hay ninguna nota. Cree una haciendo clic en «Nota nueva»." #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:202 msgid "No plugins are installed." -msgstr "" +msgstr "No se instalan plugins." #: packages/app-desktop/gui/ResourceScreen.tsx:305 msgid "No resources!" @@ -3636,9 +3589,8 @@ msgid "No suggestions" msgstr "No hay sugerencias" #: packages/app-mobile/components/plugins/dialogs/PluginPanelViewer.tsx:120 -#, fuzzy msgid "No tab selected" -msgstr "Ninguna libreta ha sido seleccionada." +msgstr "No hay ninguna pestaña seleccionada" #: packages/app-cli/app/command-edit.ts:31 msgid "" @@ -3648,14 +3600,12 @@ msgstr "" "editor `" #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:93 -#, fuzzy msgid "No updates available" -msgstr "Actualizar perfil" +msgstr "No hay actualizaciones disponibles" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.ts:44 -#, fuzzy msgid "None" -msgstr "(Ninguno)" +msgstr "Ninguno" #: packages/lib/models/settings/builtInMetadata.ts:47 msgid "Nord" @@ -3738,9 +3688,8 @@ msgid "Note list growth factor" msgstr "Factor de crecimiento de la lista de notas" #: packages/app-desktop/gui/MenuBar.tsx:793 -#, fuzzy msgid "Note list style" -msgstr "Lista de notas" +msgstr "Estilo de lista de notas" #: packages/app-desktop/gui/NotePropertiesDialog.tsx:451 #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteProperties.ts:7 @@ -3764,7 +3713,7 @@ msgstr "" #: packages/app-desktop/gui/MenuBar.tsx:872 msgid "Note&book" -msgstr "Libreta" +msgstr "Li&breta" #: packages/app-desktop/plugins/GotoAnything.tsx:570 #: packages/lib/models/Setting.ts:1176 @@ -3778,12 +3727,11 @@ msgstr "Factor de crecimiento de la lista de libretas" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:5 #: packages/app-mobile/components/side-menu-content.tsx:441 msgid "Notebook: %s" -msgstr "Libretas: %s" +msgstr "Libreta: %s" #: packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.tsx:81 -#, fuzzy msgid "Notebook: %s (%s)" -msgstr "Libretas: %s" +msgstr "Cibreta: %s (%s)" #: packages/app-desktop/gui/Sidebar/hooks/useSidebarListData.ts:50 #: packages/app-mobile/components/side-menu-content.tsx:686 @@ -3817,12 +3765,11 @@ msgstr "Lista Numerada" #: packages/lib/models/settings/builtInMetadata.ts:531 msgid "OCR: Clear cache and re-download language data files" -msgstr "" +msgstr "OCR: Borrar caché y volver a descargar archivos de datos de idioma" #: packages/lib/models/settings/builtInMetadata.ts:512 -#, fuzzy msgid "OCR: Language data URL or path" -msgstr "Formato de fecha" +msgstr "OCR: URL o ruta de datos de idioma" #: packages/app-desktop/bridge.ts:360 packages/app-desktop/bridge.ts:373 #: packages/app-desktop/bridge.ts:387 packages/app-desktop/bridge.ts:403 @@ -3858,7 +3805,7 @@ msgstr "En %s: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:27 msgid "on line" -msgstr "" +msgstr "En línea" #: packages/app-desktop/gui/MainScreen.tsx:525 msgid "One of your master keys use an obsolete encryption method." @@ -3889,9 +3836,8 @@ msgid "OneDrive Login" msgstr "Inicio de sesión de OneDrive" #: packages/lib/services/interop/InteropService.ts:144 -#, fuzzy msgid "OneNote Notebook" -msgstr "Nueva Libreta" +msgstr "Libreta de OneNote" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/print.ts:18 msgid "Only one note can be printed at a time." @@ -3906,9 +3852,8 @@ msgid "Open %s" msgstr "Abrir %s" #: packages/app-desktop/bridge.ts:454 -#, fuzzy msgid "Open it" -msgstr "Abrir" +msgstr "Ábrela" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/openPdfViewer.ts:7 msgid "Open PDF viewer" @@ -3919,14 +3864,12 @@ msgid "Open profile directory" msgstr "Abrir directorio de perfiles" #: packages/app-mobile/components/CameraView/CameraView.tsx:164 -#, fuzzy msgid "Open settings" -msgstr "Mostrar Opciones Avanzadas" +msgstr "Abrir configuración" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:115 -#, fuzzy msgid "Open Source" -msgstr "Origen" +msgstr "Open Source" #: packages/lib/models/settings/builtInMetadata.ts:73 msgid "Open Sync Wizard..." @@ -3938,22 +3881,19 @@ msgstr "Abrir..." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:206 msgid "Opening section %s" -msgstr "" +msgstr "Apertura de la sección %s" #: packages/app-mobile/components/Dropdown.tsx:215 -#, fuzzy msgid "Opens dropdown" -msgstr "Cerrar menú" +msgstr "Abre el menú desplegable " #: packages/app-mobile/components/NoteItem.tsx:162 -#, fuzzy msgid "Opens note" -msgstr "Abrir" +msgstr "Abre la nota" #: packages/app-mobile/components/side-menu-content.tsx:273 -#, fuzzy msgid "Opens notebook" -msgstr "Nueva libreta" +msgstr "Abre la libreta" #: packages/app-cli/app/command-e2ee.ts:41 #: packages/app-cli/app/command-e2ee.ts:87 @@ -4014,7 +3954,7 @@ msgstr "Pegar" #: packages/app-desktop/gui/NoteEditor/commands/pasteAsText.ts:6 #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:211 msgid "Paste as text" -msgstr "" +msgstr "Pegar como texto" #: packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.tsx:261 #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:54 @@ -4031,34 +3971,31 @@ msgid "Per user. Minimum of %d users." msgstr "Por usuario. Mínimo %d usuarios." #: packages/lib/commands/permanentlyDeleteNote.ts:8 -#, fuzzy msgid "Permanently delete note" -msgstr "Borrar notas seleccionadas" +msgstr "Eliminar nota de forma permanente" #: packages/lib/models/Note.ts:943 -#, fuzzy msgid "Permanently delete note \"%s\"?" -msgstr "¿Borrar nota «%s»?" +msgstr "¿Eliminar permanentemente la nota \"%s\"?" #: packages/app-cli/app/command-rmbook.ts:36 -#, fuzzy msgid "" "Permanently delete notebook \"%s\"?\n" "\n" "All notes and sub-notebooks within this notebook will be permanently deleted." msgstr "" -"¿Borrar libreta? Todas las notas y sublibretas dentro de esta libreta " -"también serán eliminadas." +"¿Eliminar permanentemente la libreta %s\"?\n" +"\n" +"Todas las notas y sublibretas de esta libreta se eliminarán de forma " +"permanente." #: packages/lib/models/Note.ts:945 -#, fuzzy msgid "Permanently delete these %d notes?" -msgstr "¿Borrar estas %d notas?" +msgstr "¿Eliminar permanentemente estas notas %d?" #: packages/app-cli/app/command-rmbook.ts:20 -#, fuzzy msgid "Permanently deletes the notebook, skipping the trash." -msgstr "¿Borrar estas %d notas?" +msgstr "Elimina permanentemente la libreta, omitiendo la papelera." #: packages/app-mobile/components/screens/Note/Note.tsx:504 msgid "Permission needed" @@ -4111,7 +4048,7 @@ msgstr "" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:180 msgid "Please record your voice..." -msgstr "" +msgstr "Por favor, graba tu voz..." #: packages/app-cli/app/command-ls.ts:66 msgid "Please select a notebook first." @@ -4134,9 +4071,9 @@ msgid "Please specify the notebook where the notes should be imported to." msgstr "Por favor especifique la libreta donde las notas deben ser importadas." #: packages/lib/services/plugins/PluginService.ts:519 -#, fuzzy msgid "Please upgrade Joplin to version %s or later to use this plugin." -msgstr "Por favor, actualice Joplin para usar este plugin" +msgstr "" +"Actualice Joplin a la versión %s o posterior para usar este complemento." #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:1444 msgid "" @@ -4152,28 +4089,24 @@ msgid "Please wait..." msgstr "Por favor espere..." #: packages/app-mobile/services/plugins/PlatformImplementation.ts:51 -#, fuzzy msgid "Plugin message" -msgstr "Plugins" +msgstr "Mensaje del plugin" #: packages/app-mobile/components/ScreenHeader/index.tsx:400 -#, fuzzy msgid "Plugin panels" -msgstr "Herramientas de Plugin" +msgstr "Paneles de plugins" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:111 msgid "Plugin repository failed to load" -msgstr "" +msgstr "El repositorio de complementos no se pudo cargar" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:29 -#, fuzzy msgid "Plugin search" -msgstr "Plugins" +msgstr "Búsqueda de plugins" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:107 -#, fuzzy msgid "Plugin security" -msgstr "Plugins" +msgstr "Seguridad de los plugins" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:329 msgid "Plugin tools" @@ -4181,7 +4114,7 @@ msgstr "Herramientas de Plugin" #: packages/lib/models/settings/builtInMetadata.ts:909 msgid "Plugin WebView debugging" -msgstr "" +msgstr "Depuración de Plugin WebView" #: packages/app-desktop/gui/ConfigScreen/Sidebar.tsx:148 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:523 @@ -4195,6 +4128,9 @@ msgid "" "Plugins extend Joplin with features that are not present by default. Plugins " "can extend Joplin's editor, viewer, and more." msgstr "" +"Los plugins amplían Joplin con características que no están presentes de " +"forma predeterminada. Los plugins pueden ampliar el editor, el visor y mucho " +"más de Joplin." #: packages/lib/models/settings/builtInMetadata.ts:1261 msgid "Portrait" @@ -4226,11 +4162,11 @@ msgstr "Tema claro preferido" #: packages/lib/models/settings/builtInMetadata.ts:1704 msgid "Preferred voice typing provider" -msgstr "" +msgstr "Proveedor de mecanografía por voz preferido" #: packages/lib/models/settings/builtInMetadata.ts:667 msgid "Preserve colours when pasting text in Rich Text Editor" -msgstr "" +msgstr "Conservar los colores al pegar texto en el Editor de texto enriquecido" #: packages/app-cli/app/app-gui.js:758 msgid "Press Ctrl+D or type \"exit\" to exit the application" @@ -4252,9 +4188,8 @@ msgid "Press to set the decryption password." msgstr "Pulse para establecer la contraseña de descifrado." #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:17 -#, fuzzy msgid "previous" -msgstr "Anterior coincidencia" +msgstr "anterior" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:281 msgid "Previous match" @@ -4294,12 +4229,11 @@ msgstr "Procesar eliminaciones de usuarios" #: packages/lib/models/Resource.ts:32 msgid "Processing" -msgstr "" +msgstr "Procesando" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:111 -#, fuzzy msgid "Processing photo..." -msgstr "Creando reporte..." +msgstr "Procesando foto..." #: packages/server/src/routes/admin/users.ts:254 msgid "Profile" @@ -4355,9 +4289,8 @@ msgid "Publish notes to the internet" msgstr "Publicar notas en Internet" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:78 -#, fuzzy msgid "QR Code" -msgstr "Código" +msgstr "Código QR" #: packages/app-desktop/app.ts:219 #: packages/app-desktop/ElectronAppWrapper.ts:123 @@ -4368,7 +4301,7 @@ msgstr "Salir" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:206 msgid "Re-download model" -msgstr "" +msgstr "Volver a descargar el modelo" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:342 msgid "Re-encrypt data" @@ -4379,9 +4312,8 @@ msgid "Re-encryption" msgstr "Recifrado" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:181 -#, fuzzy msgid "Re-enter password" -msgstr "Contraseña maestra" +msgstr "Escriba la contraseña otra vez" #: packages/lib/models/settings/builtInMetadata.ts:1180 msgid "Re-upload local data to sync target" @@ -4412,14 +4344,12 @@ msgid "Recipients:" msgstr "Destinatarios:" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx:72 -#, fuzzy msgid "Recommended" -msgstr "comando" +msgstr "Recomendado" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:114 -#, fuzzy msgid "Recommended plugins" -msgstr "comando" +msgstr "Plugins recomendados" #: packages/app-desktop/gui/MenuBar.tsx:754 #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:122 @@ -4449,9 +4379,8 @@ msgid "Remove" msgstr "Eliminar" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:330 -#, fuzzy msgid "Remove %s from share" -msgstr "¿Desea eliminar la etiqueta «%s» de todas las notas?" +msgstr "Eliminar %s del recurso compartido" #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:111 msgid "Remove tag \"%s\" from all notes?" @@ -4483,9 +4412,8 @@ msgid "Renew token" msgstr "Renovar token" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:21 -#, fuzzy msgid "replace" -msgstr "Reemplazar" +msgstr "reemplazar" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:15 #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:291 @@ -4493,7 +4421,6 @@ msgid "Replace" msgstr "Reemplazar" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:22 -#, fuzzy msgid "replace all" msgstr "Reemplazar todo" @@ -4511,38 +4438,35 @@ msgstr "Reemplazar: " #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:25 msgid "replaced $ matches" -msgstr "" +msgstr "Coincidencias de $ reemplazadas" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:26 msgid "replaced match on line $" -msgstr "" +msgstr "Coincidencia reemplazada en línea $" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:162 msgid "Report an issue" -msgstr "" +msgstr "Informar de un problema" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:219 msgid "Report any issues concerning the plugin." -msgstr "" +msgstr "Informe cualquier problema relacionado con el complemento." #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:224 msgid "Report fraudulent plugin" -msgstr "" +msgstr "Denunciar un plugin fraudulento" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:116 -#, fuzzy msgid "Report system" -msgstr "Sistema de archivos" +msgstr "Sistema de informes" #: packages/server/src/services/MustacheService.ts:137 -#, fuzzy msgid "Reports" -msgstr "Sistema de archivos" +msgstr "Informes" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/resetLayout.ts:7 -#, fuzzy msgid "Reset application layout" -msgstr "Cambiar el diseño de la aplicación" +msgstr "Restablecer el diseño de la aplicación" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:221 #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:222 @@ -4551,7 +4475,7 @@ msgstr "Restablecer contraseña maestra" #: packages/lib/models/settings/builtInMetadata.ts:862 msgid "Resize large images:" -msgstr "" +msgstr "Cambiar el tamaño de las imágenes grandes:" #: packages/app-cli/app/command-import.ts:54 #: packages/app-desktop/gui/ImportScreen.tsx:93 @@ -4563,9 +4487,8 @@ msgid "Restart and upgrade" msgstr "Reiniciar y actualizar" #: packages/app-desktop/ElectronAppWrapper.ts:130 -#, fuzzy msgid "Restart in safe mode" -msgstr "Reiniciar y actualizar" +msgstr "Reiniciar en modo seguro" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:405 #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:64 @@ -4582,29 +4505,25 @@ msgid "Restore" msgstr "Restaurar" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:156 -#, fuzzy msgid "Restore defaults" -msgstr "Notas Restauradas" +msgstr "Restaurar valores predeterminados" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/restoreNote.ts:10 -#, fuzzy msgid "Restore note" msgstr "Notas Restauradas" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/restoreFolder.ts:9 -#, fuzzy msgid "Restore notebook" -msgstr "Crea libreta" +msgstr "Restaurar libreta" #: packages/app-cli/app/command-restore.ts:12 -#, fuzzy msgid "Restore the items matching from the trash." -msgstr "Elimina las notas que coincidan con ." +msgstr "" +"Restaure los elementos que coincidan desde la papelera." #: packages/lib/services/trash/index.ts:88 -#, fuzzy msgid "Restored items" -msgstr "Notas Restauradas" +msgstr "Objetos restaurados" #: packages/lib/services/RevisionService.ts:248 msgid "Restored Notes" @@ -4612,7 +4531,7 @@ msgstr "Notas Restauradas" #: packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.tsx:162 msgid "Results (%d):" -msgstr "" +msgstr "Resultados (%d):" #: packages/app-desktop/gui/StatusScreen/StatusScreen.tsx:133 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:112 @@ -4646,11 +4565,13 @@ msgstr "Revisión: %s (%s)" #: packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.tsx:28 msgid "Rich Text" -msgstr "" +msgstr "Texto enriquecido" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:686 msgid "Rich Text editor. Press Escape then Tab to escape focus." msgstr "" +"Editor de texto enriquecido. Presione Escape y luego Tab para escapar del " +"enfoque." #: packages/app-cli/app/command-batch.js:10 msgid "" @@ -4723,9 +4644,8 @@ msgid "Save changes" msgstr "Guardar cambios" #: packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.tsx:103 -#, fuzzy msgid "Save changes?" -msgstr "Guardar cambios" +msgstr "¿Guardar Cambios?" #: packages/lib/models/settings/builtInMetadata.ts:768 msgid "Save geo-location with notes" @@ -4733,7 +4653,7 @@ msgstr "Guardar geolocalización en las notas" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:89 msgid "Scanned code" -msgstr "" +msgstr "Código escaneado" #: packages/app-desktop/gui/lib/SearchInput/SearchInput.tsx:62 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:102 @@ -4754,9 +4674,8 @@ msgid "Search for..." msgstr "Buscar..." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:170 -#, fuzzy msgid "Search hidden" -msgstr "Búsquedas" +msgstr "Búsqueda oculta" #: packages/app-desktop/gui/NoteListControls/commands/focusSearch.ts:6 msgid "Search in all the notes" @@ -4767,14 +4686,12 @@ msgid "Search in current note" msgstr "Buscar en la nota actual" #: packages/app-desktop/plugins/GotoAnything.tsx:665 -#, fuzzy msgid "Search results" -msgstr "Sin resultados" +msgstr "Resultados de la búsqueda" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:170 -#, fuzzy msgid "Search shown" -msgstr "Búsquedas" +msgstr "Se muestra la búsqueda" #: packages/app-cli/app/gui/FolderListWidget.ts:56 msgid "Search:" @@ -4793,9 +4710,8 @@ msgid "Searches for the given in all the notes." msgstr "Busca el dado en todas las notas." #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:63 -#, fuzzy msgid "See changelog" -msgstr "Registro de cambios completo" +msgstr "Historial de cambios" #: packages/lib/models/settings/builtInMetadata.ts:1199 msgid "See the pre-release page for more details: %s" @@ -4820,24 +4736,20 @@ msgid "Select file..." msgstr "Seleccionar archivo..." #: packages/app-mobile/components/screens/folder.js:109 -#, fuzzy msgid "Select parent notebook" -msgstr "Borrar libreta" +msgstr "Seleccionar libreta principal" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:9 -#, fuzzy msgid "Selection deleted" -msgstr "Por borrar: %d" +msgstr "Selección eliminada" #: packages/app-mobile/components/FolderPicker.tsx:68 -#, fuzzy msgid "Selects a notebook" -msgstr "Borrar libreta" +msgstr "Selecciona una libreta" #: packages/app-desktop/gui/MenuBar.tsx:359 -#, fuzzy msgid "Send bug report" -msgstr "Exportar Informe de depuración" +msgstr "Enviar informe de error" #: packages/app-cli/app/command-server.js:38 msgid "Server is already running on port %d" @@ -4889,7 +4801,7 @@ msgstr "" #: packages/app-mobile/components/EditorToolbar/EditorToolbar.tsx:47 msgid "Settings" -msgstr "" +msgstr "Ajustes" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:281 #: packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.ts:44 @@ -4903,17 +4815,17 @@ msgid "" "Share a copy of all notes in a file format that can be imported by Joplin on " "a computer." msgstr "" +"Comparta una copia de todas las notas en un formato de archivo que Joplin " +"pueda importar en un ordenador." #: packages/lib/utils/joplinCloud/index.ts:125 -#, fuzzy msgid "Share a notebook with others" -msgstr "Colaborar en los cuadernos con otros" +msgstr "Compartir una libreta con otros usuarios" #: packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.tsx:82 #: packages/app-mobile/components/screens/ShareManager/IncomingShareItem.tsx:33 -#, fuzzy msgid "Share from %s (%s)" -msgstr "%s = %s (%s)" +msgstr "Compartir de %s (%s)" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:400 msgid "Share Notebook" @@ -4925,17 +4837,15 @@ msgstr "Compartir libreta..." #: packages/lib/utils/joplinCloud/index.ts:215 msgid "Share permissions" -msgstr "" +msgstr "Permisos de uso compartido" #: packages/app-desktop/gui/Sidebar/listItemComponents/FolderItem.tsx:56 -#, fuzzy msgid "Shared" -msgstr "Compartir" +msgstr "Compartido" #: packages/app-mobile/components/screens/ShareManager/index.tsx:107 -#, fuzzy msgid "Shares" -msgstr "Compartir" +msgstr "Compartido" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:364 msgid "Sharing notebook..." @@ -4970,23 +4880,20 @@ msgid "Show disabled keys" msgstr "Mostrar claves desactivadas" #: packages/app-desktop/gui/ConfigScreen/controls/FontSearch.tsx:132 -#, fuzzy msgid "Show monospace fonts only." -msgstr "Familia de fuente monoespaciada del editor" +msgstr "Mostrar solo fuentes monoespaciadas." #: packages/lib/models/settings/builtInMetadata.ts:599 msgid "Show note counts" msgstr "Mostrar número de notas" #: packages/app-mobile/components/side-menu-content.tsx:258 -#, fuzzy msgid "Show notebook options" -msgstr "Mostrar número de notas" +msgstr "Mostrar opciones de libreta" #: packages/app-desktop/gui/PasswordInput/PasswordInput.tsx:21 -#, fuzzy msgid "Show password" -msgstr "Establecer la contraseña" +msgstr "Mostrar contraseña" #: packages/lib/models/settings/builtInMetadata.ts:698 msgid "Show sort order buttons" @@ -5001,22 +4908,20 @@ msgid "Show/hide the sidebar" msgstr "Mostrar/ocultar barra lateral" #: packages/app-mobile/components/screens/tags.tsx:76 -#, fuzzy msgid "Shows notes for tag" -msgstr "Mostrar número de notas" +msgstr "Muestra notas para la etiqueta" #: packages/lib/models/settings/builtInMetadata.ts:863 msgid "Shrink large images before adding them to notes." -msgstr "" +msgstr "Reduzca las imágenes grandes antes de agregarlas a las notas." #: packages/app-mobile/components/SideMenu.tsx:258 -#, fuzzy msgid "Side menu closed" -msgstr "Ocultar más acciones" +msgstr "Menú lateral cerrado" #: packages/app-mobile/components/SideMenu.tsx:258 msgid "Side menu opened" -msgstr "" +msgstr "Menú lateral abierto" #: packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.ts:10 #: packages/app-desktop/gui/Sidebar/Sidebar.tsx:77 @@ -5055,12 +4960,16 @@ msgstr "Claro Solarizado" msgid "" "Some attachments could not be downloaded. Please try to download them again." msgstr "" +"No se han podido descargar algunos archivos adjuntos. Por favor, intente " +"descargarlos de nuevo." #: packages/lib/models/settings/settingValidations.ts:18 msgid "" "Some attachments need to be downloaded. Set the attachment download mode to " "\"always\" and try again." msgstr "" +"Algunos archivos adjuntos deben descargarse. Establezca el modo de descarga " +"de archivos adjuntos en \"siempre\" e inténtelo de nuevo." #: packages/app-desktop/gui/MainScreen.tsx:519 #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:52 @@ -5078,20 +4987,19 @@ msgstr "" "información." #: packages/lib/models/settings/settingValidations.ts:24 -#, fuzzy msgid "" "Some items could not be synchronised. Please try to synchronise them first." msgstr "" -"No se han podido sincronizar algunos de los elementos. Pulsa para más " -"información." +"Algunos elementos no se han podido sincronizar. Por favor, intente " +"sincronizarlos primero." #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in ascending order" -msgstr "" +msgstr "Ordene \"%s\" en orden ascendente" #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in descending order" -msgstr "" +msgstr "Ordene \"%s\" en orden descendente" #: packages/lib/models/settings/builtInMetadata.ts:754 msgid "Sort notebooks by" @@ -5126,7 +5034,7 @@ msgstr "Origen: " #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:435 msgid "Spacer" -msgstr "" +msgstr "Espaciador" #: packages/lib/models/settings/builtInMetadata.ts:1463 msgid "" @@ -5276,12 +5184,11 @@ msgstr "Cambiar perfil" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:101 msgid "Switch to back-facing camera" -msgstr "" +msgstr "Cambiar a la cámara trasera" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:101 -#, fuzzy msgid "Switch to front-facing camera" -msgstr "Cambiar al perfil %d" +msgstr "Cambiar a la cámara frontal" #: packages/app-desktop/gui/utils/NoteListUtils.ts:93 msgid "Switch to note type" @@ -5294,14 +5201,12 @@ msgid "Switch to profile %d" msgstr "Cambiar al perfil %d" #: packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.tsx:28 -#, fuzzy msgid "Switch to the %s Editor" -msgstr "Cambiar a nota" +msgstr "Cambiar al editor de %s" #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:78 -#, fuzzy msgid "Switch to the legacy editor" -msgstr "Cambiar a nota" +msgstr "Cambiar al editor heredado" #: packages/app-desktop/gui/utils/NoteListUtils.ts:102 msgid "Switch to to-do type" @@ -5353,9 +5258,8 @@ msgid "Sync your notes" msgstr "Sincroniza tus notas" #: packages/lib/models/Setting.ts:1212 -#, fuzzy msgid "Sync, encryption, proxy" -msgstr "Habilitar cifrado" +msgstr "Sincronización, cifrado, proxy" #: packages/lib/models/Setting.ts:1173 msgid "Synchronisation" @@ -5433,7 +5337,7 @@ msgstr "Tomar una foto" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/TaskButton.tsx:73 msgid "Task \"%s\" failed with error: %s" -msgstr "" +msgstr "Error en la tarea \"%s\" con error: %s" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:87 msgid "Task list" @@ -5449,22 +5353,20 @@ msgid "Teams" msgstr "Equipos" #: packages/lib/services/interop/InteropService.ts:136 -#, fuzzy msgid "Text document" -msgstr "Comando de editor de texto" +msgstr "Documento de texto" #: packages/lib/models/settings/builtInMetadata.ts:1248 msgid "Text editor command" msgstr "Comando de editor de texto" #: packages/lib/utils/joplinCloud/index.ts:167 -#, fuzzy msgid "" "The [Web Clipper](%s) is a browser extension that allows you to save web " "pages and screenshots from your browser." msgstr "" -"El Web Clipper de Joplin le permite guardar páginas web y capturas de " -"pantalla desde su navegador a la aplicación." +"El [Web Clipper](%s) es una extensión del navegador que le permite guardar " +"páginas web y capturas de pantalla desde su navegador." #: packages/lib/services/profileConfig/index.ts:106 msgid "" @@ -5569,11 +5471,11 @@ msgstr "" "los cambios." #: packages/app-desktop/gui/NoteEditor/NoteEditor.tsx:612 -#, fuzzy msgid "The following attachment matches your search query:" msgid_plural "The following attachments match your search query:" -msgstr[0] "Los adjuntos siguientes están siendo vigilados en busca de cambios:" -msgstr[1] "Los adjuntos siguientes están siendo vigilados en busca de cambios:" +msgstr[0] "El siguiente archivo adjunto coincide con su consulta de búsqueda:" +msgstr[1] "" +"Los siguientes archivos adjuntos coinciden con su consulta de búsqueda:" #: packages/app-desktop/gui/NoteEditor/NoteEditor.tsx:596 msgid "The following attachments are being watched for changes:" @@ -5592,6 +5494,8 @@ msgstr "" #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:83 msgid "The following plugins may not support the current markdown editor:" msgstr "" +"Es posible que los siguientes complementos no sean compatibles con el editor " +"de markdown actual:" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:257 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx:49 @@ -5631,16 +5535,14 @@ msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"." msgstr "La nota «%s» se ha restaurado exitosamente a la libreta «%s»." #: packages/app-desktop/gui/TrashNotification/TrashNotification.tsx:68 -#, fuzzy msgid "The note was successfully moved to the trash." msgid_plural "The notes were successfully moved to the trash." -msgstr[0] "La nota «%s» se ha restaurado exitosamente a la libreta «%s»." -msgstr[1] "La nota «%s» se ha restaurado exitosamente a la libreta «%s»." +msgstr[0] "La nota se movió con éxito a la papelera." +msgstr[1] "Las notas se movieron con éxito a la papelera." #: packages/app-desktop/gui/TrashNotification/TrashNotification.tsx:66 -#, fuzzy msgid "The notebook and its content was successfully moved to the trash." -msgstr "La nota «%s» se ha restaurado exitosamente a la libreta «%s»." +msgstr "La libreta y su contenido se han movido correctamente a la papelera." #: packages/app-mobile/components/screens/folder.js:76 msgid "The notebook could not be saved: %s" @@ -5675,6 +5577,13 @@ msgid "" "\n" "%s" msgstr "" +"El destino de sincronización no se puede cambiar por el siguiente motivo: " +"%s\n" +"\n" +"Si el problema no se puede resolver, es posible que primero deba borrar sus " +"datos siguiendo estas instrucciones:\n" +"\n" +"%s" #: packages/app-desktop/gui/MainScreen.tsx:513 msgid "" @@ -5694,9 +5603,8 @@ msgstr "" "proceder." #: packages/app-desktop/gui/MainScreen.tsx:507 -#, fuzzy msgid "The synchronisation password is missing." -msgstr "Comprobar configuración de la sincronización" +msgstr "Falta la contraseña de sincronización." #: packages/lib/models/Tag.ts:233 msgid "The tag \"%s\" already exists. Please choose a different name." @@ -5718,6 +5626,11 @@ msgid "" "\n" "Error: \"%s\"" msgstr "" +"El cliente web no admite la aceptación de libretas compartidas cifradas. " +"Cambie a la aplicación de escritorio o móvil antes de aceptar el uso " +"compartido.\n" +"\n" +"Error: \"%s\"" #: packages/app-desktop/gui/Root.tsx:150 msgid "The Web Clipper needs your authorisation to access your data." @@ -5736,24 +5649,24 @@ msgid "" "The WebDAV implementation of %s is incompatible with Joplin, and as such is " "no longer supported. Please use a different sync method." msgstr "" +"La implementación de WebDAV de %s es incompatible con Joplin y, como tal, ya " +"no es compatible. Utilice un método de sincronización diferente." #: packages/lib/models/settings/builtInMetadata.ts:543 msgid "Theme" msgstr "Tema" #: packages/lib/models/Setting.ts:1211 -#, fuzzy msgid "Themes, editor font" -msgstr "Fuente del editor" +msgstr "Temas, fuente del editor" #: packages/lib/components/shared/NoteList/getEmptyFolderMessage.ts:17 msgid "There are currently no notes. Create one by clicking on the (+) button." msgstr "Actualmente, no hay notas. Cree una pulsando en el botón (+)." #: packages/lib/components/shared/NoteList/getEmptyFolderMessage.ts:9 -#, fuzzy msgid "There are no notes in the trash folder." -msgstr "Mantener historial de la nota durante" +msgstr "No hay notas en la papelera." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:268 msgid "There are unsaved changes." @@ -5783,6 +5696,10 @@ msgid "" "cause the sync warning to appear, but still aren't synced. To unignore, " "click \"retry\"." msgstr "" +"Estos elementos no se han podido sincronizar, pero se han marcado como " +"\"ignorados\". No harán que aparezca la advertencia de sincronización, pero " +"aún así no se sincronizan. Para anular la ignoración, haga clic en " +"\"reintentar\"." #: packages/lib/services/ReportService.ts:187 msgid "" @@ -5818,10 +5735,13 @@ msgid "" "collaborate on it. It does not however allow you to share a notebook with " "someone else, unless you have the feature \"%s\"." msgstr "" +"Esto permite que otro usuario comparta una libreta con usted y, a " +"continuación, ambos pueden colaborar en él. Sin embargo, no le permite " +"compartir una libreta con otra persona, a menos que tenga la función \"%s\"." #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:150 msgid "This attachment does not have OCR data (Status: %s)" -msgstr "" +msgstr "Este archivo adjunto no tiene datos de OCR (Estado: %s)" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:54 #: packages/lib/services/ResourceEditWatcher/index.ts:241 @@ -5837,9 +5757,8 @@ msgstr "" "aplicaciones de terceros acceder a Joplin." #: packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.tsx:103 -#, fuzzy msgid "This drawing may have unsaved changes." -msgstr "Hay cambios sin guardar." +msgstr "Es posible que este dibujo tenga cambios no guardados." #: packages/app-desktop/gui/ResourceScreen.tsx:291 msgid "" @@ -5852,25 +5771,22 @@ msgstr "" "pueden ser recuperados después." #: packages/app-mobile/components/ScreenHeader/index.tsx:237 -#, fuzzy msgid "This note could not be deleted: %s" msgid_plural "These notes could not be deleted: %s" -msgstr[0] "No se ha podido abrir este archivo: %s" -msgstr[1] "No se ha podido abrir este archivo: %s" +msgstr[0] "Esta nota no pudo ser eliminada: %s" +msgstr[1] "Estas notas no pudieron ser eliminadas: %s" #: packages/app-mobile/components/ScreenHeader/index.tsx:224 -#, fuzzy msgid "This note could not be duplicated: %s" msgid_plural "These notes could not be duplicated: %s" -msgstr[0] "No se ha podido guardar la libreta: %s" -msgstr[1] "No se ha podido guardar la libreta: %s" +msgstr[0] "Esta nota no se ha podido duplicar: %s" +msgstr[1] "Estas notas no se han podido duplicar: %s" #: packages/app-mobile/components/ScreenHeader/index.tsx:563 -#, fuzzy msgid "This note could not be moved: %s" msgid_plural "These notes could not be moved: %s" -msgstr[0] "No se ha podido guardar la libreta: %s" -msgstr[1] "No se ha podido guardar la libreta: %s" +msgstr[0] "Esta nota no se pudo mover: %s" +msgstr[1] "Estas notas no se han podido mover: %s" #: packages/lib/models/Note.ts:130 msgid "This note does not have geolocation information." @@ -5895,7 +5811,7 @@ msgstr "Esta nota no tiene historial" #: packages/lib/services/plugins/PluginService.ts:527 msgid "This plugin doesn't support %s." -msgstr "" +msgstr "Este plugin no es compatible con %s." #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:52 msgid "" @@ -5917,7 +5833,7 @@ msgstr "" #: packages/lib/components/shared/NoteList/getEmptyFolderMessage.ts:11 msgid "This subfolder of the trash has no notes." -msgstr "" +msgstr "Esta subcarpeta de la papelera no tiene notas." #: packages/lib/models/settings/builtInMetadata.ts:1009 msgid "" @@ -5937,11 +5853,12 @@ msgstr "Esto abrirá una nueva pantalla. ¿Desea guardar los cambios actuales?" #: packages/app-mobile/components/side-menu-content.tsx:340 msgid "This will permanently delete all items in the trash. Continue?" msgstr "" +"Esto eliminará permanentemente todos los elementos de la papelera. " +"¿Continuar?" #: packages/app-cli/app/command-rmnote.ts:40 -#, fuzzy msgid "This will permanently delete the note \"%s\". Continue?" -msgstr "¿Borrar nota «%s»?" +msgstr "Esto eliminará permanentemente la nota \"%s\". ¿Continuar?" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.ts:17 #: packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.tsx:60 @@ -5979,13 +5896,12 @@ msgstr "" #: packages/app-cli/app/command-sync.ts:110 #: packages/app-desktop/gui/JoplinCloudLoginScreen.tsx:84 #: packages/app-mobile/components/screens/JoplinCloudLoginScreen.tsx:153 -#, fuzzy msgid "" "To allow Joplin to synchronise with Joplin Cloud, please login using this " "URL:" msgstr "" -"Para permitir que Joplin se sincronice con Dropbox, por favor siga estos " -"pasos:" +"Para permitir que Joplin se sincronice con Joplin Cloud, inicie sesión con " +"esta URL:" #: packages/lib/components/EncryptionConfigScreen/utils.ts:53 msgid "To continue, please enter your master password below." @@ -6053,9 +5969,8 @@ msgid "to-do" msgstr "tarea" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:6 -#, fuzzy msgid "To-do" -msgstr "tarea" +msgstr "Tareas" #: packages/app-mobile/components/NoteItem.tsx:172 msgid "to-do: %s" @@ -6074,7 +5989,6 @@ msgid "Toggle editor layout" msgstr "Alternar el diseño del editor" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditorPlugin.ts:11 -#, fuzzy msgid "Toggle editor plugin" msgstr "Alternar el diseño del editor" @@ -6087,14 +6001,12 @@ msgid "Toggle external editing" msgstr "Alternar edición externa" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleMenuBar.ts:7 -#, fuzzy msgid "Toggle menu bar" msgstr "Alternar la barra lateral" #: packages/lib/models/Setting.ts:1216 -#, fuzzy msgid "Toggle note history, keep notes for" -msgstr "Mantener historial de la nota durante" +msgstr "Alternar el historial de notas, guardar notas para" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleNoteList.ts:9 msgid "Toggle note list" @@ -6134,7 +6046,7 @@ msgstr "Total: %d/%d" #: packages/lib/services/trash/index.ts:44 msgid "Trash" -msgstr "" +msgstr "Papelera" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:319 #: packages/app-mobile/components/biometrics/BiometricPopup.tsx:123 @@ -6180,11 +6092,11 @@ msgstr "Tipo: %s." #: packages/app-mobile/components/screens/Note/Note.tsx:946 msgid "Unable to edit resource of type %s" -msgstr "" +msgstr "No se puede editar el recurso de tipo %s" #: packages/app-mobile/components/screens/LogScreen.tsx:108 msgid "Unable to share log data. Reason: %s" -msgstr "" +msgstr "No se pueden compartir los datos de registro. Motivo: %s" #: packages/lib/models/settings/builtInMetadata.ts:616 msgid "Uncompleted to-dos on top" @@ -6202,6 +6114,9 @@ msgid "" "Uninstall and reinstall the application. Make sure you create a backup first " "by exporting all your notes as JEX from the desktop application." msgstr "" +"Desinstale y vuelva a instalar la aplicación. Asegúrate de crear una copia " +"de seguridad primero exportando todas tus notas como JEX desde la aplicación " +"de escritorio." #: packages/app-mobile/utils/getVersionInfoText.ts:13 #: packages/app-mobile/utils/getVersionInfoText.ts:14 @@ -6209,9 +6124,8 @@ msgid "Unknown" msgstr "Desconocido" #: packages/app-desktop/bridge.ts:446 -#, fuzzy msgid "Unknown file type" -msgstr "Flag desconocida: %s" +msgstr "Tipo de archivo desconocido" #: packages/lib/utils/processStartFlags.ts:185 msgid "Unknown flag: %s" @@ -6225,9 +6139,8 @@ msgstr "" "Joplin a la última versión" #: packages/app-mobile/utils/getVersionInfoText.ts:28 -#, fuzzy msgid "Unknown platform" -msgstr "Flag desconocida: %s" +msgstr "Plataforma desconocida" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:82 msgid "Unordered list" @@ -6259,11 +6172,12 @@ msgid "Unsupported link or message: %s" msgstr "Enlace o mensaje no soportado: %s" #: packages/app-mobile/commands/openItem.ts:60 -#, fuzzy msgid "" "Unsupported link or message: %s.\n" "Error: %s" -msgstr "Enlace o mensaje no soportado: %s" +msgstr "" +"Enlace o mensaje no compatible: %s.\n" +"Error: %s" #: packages/app-desktop/gui/ResourceScreen.tsx:123 #: packages/lib/models/BaseItem.ts:921 packages/lib/path-utils.ts:27 @@ -6277,14 +6191,12 @@ msgid "Update" msgstr "Actualizar" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx:77 -#, fuzzy msgid "Update available" -msgstr "Actualizar perfil" +msgstr "Actualización disponible" #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:65 -#, fuzzy msgid "Update later" -msgstr "fecha de actualización" +msgstr "Actualizar más tarde" #: packages/server/src/routes/admin/users.ts:257 #: packages/server/src/routes/index/users.ts:91 @@ -6384,9 +6296,8 @@ msgstr "" "salir." #: packages/lib/models/settings/builtInMetadata.ts:1347 -#, fuzzy msgid "Use the legacy Markdown editor" -msgstr "Activar sintaxis de emojis markdown" +msgstr "Usar el editor Markdown heredado" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:552 msgid "" @@ -6448,7 +6359,7 @@ msgstr "Ver" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:141 msgid "View OCR text" -msgstr "" +msgstr "Ver texto de OCR" #: packages/app-mobile/components/screens/Note/Note.tsx:1044 msgid "View on map" @@ -6473,21 +6384,20 @@ msgstr "Vim" #: packages/lib/models/settings/builtInMetadata.ts:1693 msgid "Voice typing language files (URL)" -msgstr "" +msgstr "Archivos de idioma de escritura por voz (URL)" #: packages/app-mobile/components/screens/Note/Note.tsx:1191 #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:225 msgid "Voice typing..." -msgstr "" +msgstr "Escritura por voz..." #: packages/lib/models/settings/builtInMetadata.ts:1712 msgid "Vosk" -msgstr "" +msgstr "Vosk" #: packages/lib/services/joplinCloudUtils.ts:27 -#, fuzzy msgid "Waiting for authorisation..." -msgstr "Conceder la autorización" +msgstr "A la espera de la autorización..." #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:224 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:122 @@ -6503,12 +6413,15 @@ msgstr "" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:116 msgid "We have a system for reporting and removing problematic plugins." msgstr "" +"Tenemos un sistema para informar y eliminar complementos problemáticos." #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:114 msgid "" "We mark plugins developed by trusted Joplin community members as " "\"recommended\"." msgstr "" +"Marcamos los plugins desarrollados por miembros de confianza de la comunidad " +"de Joplin como \"recomendados\"." #: packages/lib/models/Setting.ts:1182 #: packages/lib/utils/joplinCloud/index.ts:166 @@ -6538,12 +6451,11 @@ msgstr "Sitio web y documentación" #: packages/app-mobile/utils/getVersionInfoText.ts:14 msgid "WebView package: %s" -msgstr "" +msgstr "Paquete WebView: %s" #: packages/app-mobile/utils/getVersionInfoText.ts:13 -#, fuzzy msgid "WebView version: %s" -msgstr "Nueva versión: %s" +msgstr "Versión de WebView: %s" #: packages/app-cli/app/gui/NoteWidget.js:36 msgid "" @@ -6563,14 +6475,12 @@ msgstr "" "`mn`." #: packages/lib/WelcomeUtils.ts:63 -#, fuzzy msgid "Welcome!" -msgstr "Bienvenido" +msgstr "¡Bienvenido!" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:100 -#, fuzzy msgid "What are plugins?" -msgstr "¿Borrar plugin «%s»?" +msgstr "¿Qué son los plugins?" #: packages/lib/models/settings/builtInMetadata.ts:845 msgid "When creating a new note:" @@ -6585,14 +6495,17 @@ msgid "" "When enabled, the application will scan your attachments and extract the " "text from it. This will allow you to search for text in these attachments." msgstr "" +"Cuando está habilitado, la aplicación escaneará sus archivos adjuntos y " +"extraerá el texto de ellos. Esto le permitirá buscar texto en estos archivos " +"adjuntos." #: packages/lib/models/settings/builtInMetadata.ts:1713 msgid "Whisper" -msgstr "" +msgstr "Susurro" #: packages/app-desktop/ElectronAppWrapper.ts:222 msgid "Window unresponsive." -msgstr "" +msgstr "La ventana no responde." #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:105 msgid "Words" @@ -6629,6 +6542,7 @@ msgstr "" #: packages/lib/services/joplinCloudUtils.ts:45 msgid "You are logged in into Joplin Cloud, you can leave this screen now." msgstr "" +"Ha iniciado sesión en Joplin Cloud, puede salir de esta pantalla ahora." #: packages/app-mobile/components/NoteList.tsx:98 msgid "You currently have no notebooks." @@ -6652,21 +6566,17 @@ msgstr "" "cifrado obsoleto." #: packages/lib/services/joplinCloudUtils.ts:53 -#, fuzzy msgid "" "You were unable to connect to Joplin Cloud. Please check your credentials " "and try again. Error:" msgstr "" -"Se ha producido un error al configurar su cuenta de Joplin Cloud. Por favor, " -"verifique su correo electrónico y contraseña e inténtelo de nuevo. El error " -"fue:\n" -"\n" -"%s" +"No ha podido conectarse a Joplin Cloud. Compruebe sus credenciales y vuelva " +"a intentarlo. Error:" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:55 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:70 msgid "Your account doesn't have access to this feature" -msgstr "" +msgstr "Tu cuenta no tiene acceso a esta función" #: packages/app-cli/app/cli-utils.js:160 msgid "Your choice: " @@ -6679,7 +6589,7 @@ msgstr "Sus datos van a ser cifrados y sincronizados nuevamente." #: packages/app-desktop/gui/MainScreen.tsx:562 #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:55 msgid "Your Joplin Cloud credentials are invalid, please login." -msgstr "" +msgstr "Sus credenciales de Joplin Cloud no son válidas, inicie sesión." #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:259 msgid "Your password is needed to decrypt some of your data." @@ -6690,8 +6600,8 @@ msgid "" "Your password is needed to decrypt some of your data. Type `:e2ee decrypt` " "to set it." msgstr "" -"Su contraseña es necesaria para descrifrar algunos de sus datos. Escriba " -"`:e2ee decrypt` para establecerla." +"Su contraseña es necesaria para descrifrar algunos de sus datos. Escriba `:" +"e2ee decrypt` para establecerla." #: packages/app-desktop/checkForUpdates.ts:108 msgid "Your version: %s" diff --git a/packages/tools/locales/hu_HU.po b/packages/tools/locales/hu_HU.po index ca8920392e..04691a22fc 100644 --- a/packages/tools/locales/hu_HU.po +++ b/packages/tools/locales/hu_HU.po @@ -358,7 +358,7 @@ msgstr "Aktív" #: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:18 #: packages/app-desktop/gui/MenuBar.tsx:828 msgid "Actual Size" -msgstr "Aktív méret" +msgstr "Tényleges méret" #: packages/app-mobile/components/screens/Note/Note.tsx:1501 msgid "Add body" @@ -1721,7 +1721,7 @@ msgid "" "to-dos, while `-tnt` would display notes and to-dos." msgstr "" "Csak a megadott típusú elemeket jeleníti meg. Lehet `n` a jegyzetekhez, `t` " -"a teendőkhöz, vagy `nt` a jegyzetekhez és teendőkhöz (pl. `-tt` csak a " +"a teendőkhöz, vagy `nt` a jegyzetekhez és teendőkhöz (például: `-tt` csak a " "teendőket jeleníti meg, míg a `-tnt` a jegyzeteket és teendőket is)." #: packages/app-cli/app/command-status.js:13 @@ -5017,7 +5017,7 @@ msgstr "A kijelölt sorok rendezése" #: packages/app-cli/app/command-ls.ts:29 msgid "Sorts the item by (eg. title, updated_time, created_time)." msgstr "" -"Elem rendezése a(z) minta szerint (pl. title, updated_time, " +"Elem rendezése a(z) minta szerint (például: title, updated_time, " "created_time)" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:9 @@ -6330,8 +6330,8 @@ msgid "" "font is used." msgstr "" "Ott használatos, ahol a szöveg olvasható elrendezéséhez fix szélességű " -"betűtípusra van szükség (pl. táblázatok, jelölőnégyzetek, kódok). Ha nem " -"található, akkor egy általános monospace (fix szélességű) betűtípus lesz " +"betűtípusra van szükség (például: táblázatok, jelölőnégyzetek, kódok). Ha " +"nem található, akkor egy általános monospace (fix szélességű) betűtípus lesz " "használva." #: packages/server/src/services/MustacheService.ts:125 diff --git a/packages/tools/locales/nl_BE.po b/packages/tools/locales/nl_BE.po index 2619127492..3b7bcf221e 100644 --- a/packages/tools/locales/nl_BE.po +++ b/packages/tools/locales/nl_BE.po @@ -7,37 +7,39 @@ msgid "" msgstr "" "Project-Id-Version: Joplin-CLI 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: \n" -"Language-Team: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: MietVdh\n" +"Language-Team: Dutch (Belgium) / Nederlands (België)\n" "Language: nl_BE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.4.2\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.6\n" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:593 msgid "- Camera: to allow taking a picture and attaching it to a note." msgstr "" -"- Camera: om toe te laten een foto te maken en deze als bijlage te voegen " -"bij een notitie." +"- Camera: zodat je een foto kan maken en deze als bijlage kan toevoegen bij " +"een notitie." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:596 msgid "- Location: to allow attaching geo-location information to a note." -msgstr "- Locatie: om toe te laten geo-locatie toe te voegen aan een notitie." +msgstr "- Locatie: zodat je geo-locatie-informatie kan toevoegen aan een notitie." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:590 msgid "" "- Storage: to allow attaching files to notes and to enable filesystem " "synchronisation." msgstr "" -"- Opslag: om toe te laten bestanden als bijlage te voegen bij notities en om " -"filesystem-synchronisatie mogelijk te maken." +"- Opslag: zodat je bestanden als bijlage kan toevoegen bij notities en om " +"synchronisatie met het bestandsysteem mogelijk te maken." #: packages/lib/services/KeymapService.ts:321 #: packages/lib/services/KeymapService.ts:327 msgid "\"%s\" is missing the required \"%s\" property." -msgstr "\"%s\" ontbreekt de vereiste \"%s\" eigenschap." +msgstr "\"%s\" mist de vereiste \"%s\" eigenschap." #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:267 msgid "(%s)" @@ -45,25 +47,25 @@ msgstr "(%s)" #: packages/lib/services/plugins/api/JoplinViewsDialogs.ts:76 msgid "(In plugin: %s)" -msgstr "" +msgstr "(In plugin: %s)" #: packages/app-mobile/components/side-menu-content.tsx:265 msgid "(level %d)" -msgstr "" +msgstr "(niveau %d)" #: packages/lib/SyncTargetNone.ts:16 msgid "(None)" -msgstr "(None)" +msgstr "(Geen)" #: packages/lib/models/settings/builtInMetadata.ts:29 #: packages/lib/models/settings/builtInMetadata.ts:30 msgid "(wysiwyg: %s)" -msgstr "(wysiwyg: %s)" +msgstr "(wuziwuk: %s)" #: packages/app-mobile/components/screens/Note/Note.tsx:712 #: packages/lib/shim-init-node.ts:264 msgid "(You may disable this prompt in the options)" -msgstr "" +msgstr "(Je kan deze prompt uitschakelen in de opties)" # remarkt that this is the second &B in the menu. #: packages/app-desktop/gui/MenuBar.tsx:1036 @@ -98,9 +100,8 @@ msgid "&View" msgstr "&Weergave" #: packages/app-desktop/gui/MenuBar.tsx:903 -#, fuzzy msgid "&Window" -msgstr "Venster sluiten" +msgstr "&Venster" #: packages/lib/models/settings/builtInMetadata.ts:1483 #: packages/lib/models/settings/builtInMetadata.ts:1735 @@ -111,13 +112,13 @@ msgstr "%d dagen" #: packages/lib/utils/joplinCloud/index.ts:149 #: packages/lib/utils/joplinCloud/index.ts:150 msgid "%d GB" -msgstr "" +msgstr "%d GB" #: packages/lib/utils/joplinCloud/index.ts:145 #: packages/lib/utils/joplinCloud/index.ts:146 #: packages/lib/utils/joplinCloud/index.ts:147 msgid "%d GB storage space" -msgstr "" +msgstr "%d GB opslagruimte" #: packages/lib/models/settings/builtInMetadata.ts:1227 msgid "%d hour" @@ -132,14 +133,13 @@ msgstr "%d uren" #: packages/lib/utils/joplinCloud/index.ts:137 #: packages/lib/utils/joplinCloud/index.ts:138 msgid "%d MB" -msgstr "" +msgstr "%d MB" #: packages/lib/utils/joplinCloud/index.ts:133 #: packages/lib/utils/joplinCloud/index.ts:134 #: packages/lib/utils/joplinCloud/index.ts:135 -#, fuzzy msgid "%d MB per note or attachment" -msgstr "Notitiebijlagen" +msgstr "%d MB per notitie of bijlage" #: packages/lib/models/settings/builtInMetadata.ts:1224 #: packages/lib/models/settings/builtInMetadata.ts:1225 @@ -149,18 +149,16 @@ msgstr "%d minuten" #: packages/app-cli/app/command-rmnote.ts:34 msgid "%d notes match this pattern. Delete them?" -msgstr "%d notities voldoen aan het patroon. Deze verwijderen?" +msgstr "%d notities komen overeen met het patroon. Deze verwijderen?" #: packages/app-cli/app/command-rmnote.ts:40 -#, fuzzy msgid "%d notes will be permanently deleted. Continue?" -msgstr "Deze notities verwijderen?" +msgstr "%d notities zullen permanent verwijderd worden. Doorgaan?" #: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:228 #: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:238 -#, fuzzy msgid "%s" -msgstr "(%s)" +msgstr "%s" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/duplicateNote.ts:18 msgid "%s - Copy" @@ -168,7 +166,7 @@ msgstr "%s - Kopieer" #: packages/lib/services/ReportService.ts:192 msgid "%s (%s) could not be uploaded: %s" -msgstr "%s (%s) kon niet opgeladen worden: %s" +msgstr "%s (%s) kon niet worden geüpload: %s" #: packages/app-desktop/gui/MainScreen.tsx:540 #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:68 @@ -210,10 +208,12 @@ msgid "" "%s is not optimised for synchronising many small files so your initial " "synchronisation will be slow." msgstr "" +"%s is niet geoptimaliseerd om veel kleine bestanden te synchroniseren. " +"Daarom zal je initiële synchronisatie traag zijn." #: packages/app-mobile/components/plugins/dialogs/PluginPanelViewer.tsx:75 msgid "%s tab opened" -msgstr "" +msgstr "%s tab geopend" #: packages/lib/services/ReportService.ts:286 #: packages/lib/services/ReportService.ts:287 @@ -237,9 +237,8 @@ msgstr "%s: %s" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:224 #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:265 -#, fuzzy msgid "%s: Missing password." -msgstr "Geef hoofdpaswoord in:" +msgstr "%s: Wachtwoord ontbreekt." #: packages/app-cli/app/command-tag.js:14 msgid "" @@ -249,10 +248,10 @@ msgid "" "list all the tags (use -l for long option)." msgstr "" " kan \"add\", \"remove\", \"list\" of \"notetags\" zijn om een " -"[tag] toe te voegen aan of te verwijderen van een [note], om alle notities " -"geassocieerd met de [tag] op te lijsten of om alle tags van de [note] op te " -"lijsten. Het commando `tag list` kan gebruikt worden om alle tags op te " -"lijsten (gebruik -l voor lange optie)." +"label [tag] toe te voegen aan of te verwijderen van een notitie [note], om " +"alle notities geassocieerd met het label [tag] op te lijsten of om alle " +"labels van de notitie [note] op te lijsten. Het commando `tag list` kan " +"gebruikt worden om alle labels op te lijsten (gebruik -l voor lange optie)." #: packages/app-cli/app/command-todo.js:14 msgid "" @@ -267,9 +266,8 @@ msgstr "" "\"clear\" om terug te wisselen naar een standaard notitie." #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:62 -#, fuzzy msgid "A new update (%s) is available" -msgstr "Exporteer profiel" +msgstr "Een nieuwe update (%s) is beschikbaar" #: packages/lib/models/settings/builtInMetadata.ts:1253 msgid "A3" @@ -286,7 +284,7 @@ msgstr "A5" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.tsx:75 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:240 msgid "About" -msgstr "" +msgstr "Over" #: packages/app-desktop/gui/MenuBar.tsx:620 #: packages/app-desktop/gui/MenuBar.tsx:955 @@ -319,32 +317,30 @@ msgid "Accept" msgstr "Aanvaarden" #: packages/app-mobile/components/screens/ShareManager/index.tsx:98 -#, fuzzy msgid "Accepted invitations" -msgstr "Ontvanger heeft de uitnodiging aanvaard" +msgstr "Aanvaarde uitnodigingen" #: packages/lib/WebDavApi.js:451 msgid "Access denied: Please check your username and password" -msgstr "" +msgstr "Toegang geweigerd: Controleer je gebruikersnaam en wachtwoord" #: packages/lib/WebDavApi.js:449 msgid "Access denied: Please re-enter your password and/or username" -msgstr "" +msgstr "Toegang geweigerd: Geef je wachtwoord en/of gebruikersnaam opnieuw in" #: packages/server/src/routes/admin/users.ts:144 msgid "Account" -msgstr "" +msgstr "Account" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:31 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:48 -#, fuzzy msgid "Account information" -msgstr "Meer informatie" +msgstr "Accountinformatie" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:35 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:51 msgid "Account type" -msgstr "" +msgstr "Accounttype" #: packages/app-desktop/gui/ResourceScreen.tsx:113 msgid "Action" @@ -352,7 +348,6 @@ msgstr "Actie" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:172 #: packages/app-mobile/components/ScreenHeader/index.tsx:628 -#, fuzzy msgid "Actions" msgstr "Acties" @@ -363,21 +358,20 @@ msgstr "Actief" #: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:18 #: packages/app-desktop/gui/MenuBar.tsx:828 msgid "Actual Size" -msgstr "Huidige grootte" +msgstr "Ware Grootte" # Context needed #: packages/app-mobile/components/screens/Note/Note.tsx:1501 msgid "Add body" -msgstr "Body toevoegen" +msgstr "Inhoud toevoegen" #: packages/app-mobile/components/buttons/FloatingActionButton.tsx:91 -#, fuzzy msgid "Add new" -msgstr "Titel toevoegen" +msgstr "Nieuw toevoegen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/setTags.ts:43 msgid "Add or remove tags:" -msgstr "Tags toevoegen of verwijderen:" +msgstr "Labels toevoegen of verwijderen:" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:277 msgid "Add recipient:" @@ -385,7 +379,7 @@ msgstr "Ontvanger toevoegen:" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:94 msgid "Add tag %s to note" -msgstr "" +msgstr "Label %s toevoegen aan notitie" #: packages/app-mobile/components/screens/Note/Note.tsx:1574 msgid "Add title" @@ -396,46 +390,44 @@ msgid "Add to dictionary" msgstr "Toevoegen aan woordenboek" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:98 -#, fuzzy msgid "Add to note" -msgstr "Titel toevoegen" +msgstr "Toevoegen aan notitie" #: packages/server/src/services/MustacheService.ts:162 #: packages/server/src/services/MustacheService.ts:286 msgid "Admin" -msgstr "" +msgstr "Admin" #: packages/server/src/routes/admin/dashboard.ts:10 msgid "Admin dashboard" -msgstr "" +msgstr "Admin dashboard" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:139 msgid "Advanced options" msgstr "Geavanceerde opties" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:653 -#, fuzzy msgid "Advanced settings" -msgstr "Toon geavanceerde opties" +msgstr "Geavanceerde instellingen" #: packages/app-desktop/gui/StatusScreen/StatusScreen.tsx:196 -#, fuzzy msgid "Advanced tools" msgstr "Geavanceerde hulpmiddelen" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:18 -#, fuzzy msgid "all" -msgstr "Installeren" +msgstr "alle" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:109 msgid "" "All data, including notes, notebooks and tags will be permanently deleted." msgstr "" +"Alle data, inclusief notities, notitieboeken en labels zullen permanent " +"verwijderd worden." #: packages/lib/services/ReportService.ts:227 msgid "All item sync failures have been marked as \"ignored\"." -msgstr "" +msgstr "Alle gefaalde item-synchronisaties zijn gemarkeerd als \"genegeerd\"." #: packages/app-desktop/gui/Sidebar/listItemComponents/AllNotesItem.tsx:71 #: packages/app-mobile/components/screens/Notes.tsx:208 @@ -445,45 +437,44 @@ msgstr "Alle notities" #: packages/lib/onedrive-api-node-utils.js:46 msgid "All potential ports are in use - please report the issue at %s" -msgstr "" -"Alle potentiële poorten zijn in gebruik - gelieve dit te rapporteren aan %s" +msgstr "Alle potentiële poorten zijn in gebruik - meld het problem op %s" #: packages/lib/models/settings/builtInMetadata.ts:910 msgid "Allows debugging mobile plugins. See %s for details." -msgstr "" +msgstr "Laat toe mobiele plugins te debuggen. Zie %s voor details." #: packages/app-cli/app/command-config.ts:19 msgid "Also displays unset and hidden config variables." msgstr "Toont ook niet-ingestelde en verborgen configuratie-opties." #: packages/app-desktop/gui/ShareNoteDialog.tsx:205 -#, fuzzy msgid "Also publish linked notes" -msgstr "Delen" +msgstr "Publiceer ook gelinkte notities" #: packages/lib/models/settings/builtInMetadata.ts:396 msgid "Always" msgstr "Altijd" #: packages/lib/models/settings/builtInMetadata.ts:866 -#, fuzzy msgid "Always ask" -msgstr "Altijd" +msgstr "Altijd vragen" #: packages/app-desktop/bridge.ts:450 msgid "Always open %s files without asking." -msgstr "" +msgstr "%s bestanden altijd openen zonder te vragen." #: packages/lib/models/settings/builtInMetadata.ts:867 -#, fuzzy msgid "Always resize" -msgstr "Altijd" +msgstr "Formaat altijd aanpassen" #: packages/app-cli/app/command-mv.ts:37 msgid "" "Ambiguous notebook \"%s\". Please use notebook id instead - press \"ti\" to " "see the short notebook id or use $b for current selected notebook" msgstr "" +"Meerduidig notitieboek \"%s\". Gebruik het id van het notitieboek - druk " +"\"ti\" om het korte notitieboek-id te zien, of gebruik $b voor het huidige " +"notitieboek" #: packages/app-cli/app/command-mkbook.ts:33 #: packages/app-cli/app/command-mv.ts:30 @@ -491,22 +482,26 @@ msgid "" "Ambiguous notebook \"%s\". Please use short notebook id instead - press " "\"ti\" to see the short notebook id" msgstr "" +"Meerduidig notitieboek \"%s\". Gebruik het korte id van het notitieboek - " +"druk \"ti\" om het korte notitieboek-id te zien" #: packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.ts:13 msgid "An autosaved drawing was found. Attach a copy of it to the note?" msgstr "" +"We hebben een automatisch opgeslagen tekening gevonden. Wil je een kopie " +"ervan toevoegen aan de notitie?" #: packages/app-desktop/ElectronAppWrapper.ts:133 msgid "An error occurred: %s" -msgstr "" +msgstr "Er ging iets fout: %s" #: packages/app-desktop/checkForUpdates.ts:107 msgid "An update is available, do you want to download it now?" -msgstr "Er is een update beschikbaar; wil je dit nu downloaden?" +msgstr "Er is een update beschikbaar; wil je die nu downloaden?" #: packages/app-mobile/utils/getVersionInfoText.ts:22 msgid "Android API level: %d" -msgstr "" +msgstr "Android API nummer: %d" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:48 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:24 @@ -514,6 +509,9 @@ msgid "" "Any email sent to this address will be converted into a note and added to " "your collection. The note will be saved into the Inbox notebook" msgstr "" +"Elke email die naar dit adres verstuurd wordt, zal worden omgezet in een " +"notitie en toegevoegd worden aan je collectie . De notitie zal opgeslagen " +"worden in het Inbox notitieboek" #: packages/lib/models/Setting.ts:1174 msgid "Appearance" @@ -521,18 +519,19 @@ msgstr "Weergave" #: packages/lib/models/Setting.ts:1179 msgid "Application" -msgstr "Applicatie" +msgstr "Toepassing" #: packages/app-desktop/gui/ConfigScreen/ButtonBar.tsx:39 msgid "Apply" msgstr "Toepassen" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:146 -#, fuzzy msgid "" "Are you sure that you want to restore the default toolbar layout?\n" "This cannot be undone." -msgstr "Ben je zeker dat je de authorisatietoken wil vernieuwen?" +msgstr "" +"Ben je zeker dat je de terug wil naar de standaardlayout voor de werkbalk?\n" +"Dit kan niet ongedaan worden." #: packages/app-desktop/gui/ClipperConfigScreen.tsx:38 msgid "Are you sure you want to renew the authorisation token?" @@ -543,6 +542,8 @@ msgid "" "Are you sure you want to return to the default layout? The current layout " "configuration will be lost." msgstr "" +"Ben je zeker dat je de terug wil naar de standaardlayout voor de werkbalk? " +"De huidige layout-configuratie zal verloren gaan." #: packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.tsx:235 msgid "Arguments:" @@ -550,18 +551,19 @@ msgstr "Argumenten:" #: packages/lib/models/settings/builtInMetadata.ts:48 msgid "Aritim Dark" -msgstr "Aritim donker" +msgstr "Aritim Donker" #: packages/app-mobile/utils/lockToSingleInstance.ts:15 msgid "" "At present, Joplin Web can only be open in one tab at a time. Please close " "the other instance of Joplin." msgstr "" +"Momenteel kan Joplin Web slechts in één tab tegelijk open zijn. Gelieve de " +"andere instantie van Joplin te sluiten." #: packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.ts:25 -#, fuzzy msgid "Attach" -msgstr "Bijvoegen ..." +msgstr "Toevoegen" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:75 #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:729 @@ -576,17 +578,16 @@ msgstr "Voeg foto toe" #: packages/app-mobile/components/screens/Note/Note.tsx:1155 msgid "Attach..." -msgstr "Bijvoegen ..." +msgstr "Toevoegen..." #: packages/app-cli/app/command-attach.ts:13 msgid "Attaches the given file to the note." -msgstr "Voegt het bestand toe aan de notitie." +msgstr "Voegt het gegeven bestand toe aan de notitie." #: packages/server/src/models/UserModel.ts:247 #: packages/server/src/models/UserModel.ts:264 -#, fuzzy msgid "attachment" -msgstr "Bijlagen" +msgstr "bijlage" #: packages/lib/models/Resource.ts:509 msgid "Attachment conflict: \"%s\"" @@ -594,7 +595,7 @@ msgstr "Bijlageconflict: \"%s\"" #: packages/lib/models/settings/builtInMetadata.ts:392 msgid "Attachment download behaviour" -msgstr "Modus voor ophalen van bijlages" +msgstr "Downloadgedrag voor bijlagen" #: packages/lib/services/ReportService.ts:277 msgid "Attachments" @@ -602,7 +603,7 @@ msgstr "Bijlagen" #: packages/lib/services/ReportService.ts:301 msgid "Attachments that could not be downloaded" -msgstr "Bijlagen die niet opgehaald konden worden" +msgstr "Bijlagen die niet gedownload konden worden" #: packages/lib/models/settings/builtInMetadata.ts:33 msgid "" @@ -627,9 +628,8 @@ msgstr "Autorisatietoken:" #: packages/app-desktop/gui/JoplinCloudLoginScreen.tsx:88 #: packages/app-mobile/components/screens/JoplinCloudLoginScreen.tsx:162 -#, fuzzy msgid "Authorise" -msgstr "Autorisatietoken:" +msgstr "Autoriseer" #: packages/lib/models/settings/builtInMetadata.ts:398 msgid "Auto" @@ -637,25 +637,24 @@ msgstr "Auto" #: packages/server/src/services/TaskService.ts:30 msgid "Auto-add disabled accounts for deletion" -msgstr "" +msgstr "Uitgeschakelde accounts automatisch toevoegen voor verwijdering" # The "." at the end of the original text is not the period at the end of a sentence but merely the period to indicate that etc. is an abbreviation (of etcetera). #: packages/lib/models/settings/builtInMetadata.ts:645 msgid "Auto-pair braces, parentheses, quotations, etc." -msgstr "Haakjes, aanhalingstekens, etc. automatisch aanvullen" +msgstr "Haakjes, aanhalingstekens, etc. automatisch aanvullen." #: packages/lib/models/settings/builtInMetadata.ts:656 msgid "Autocomplete Markdown and HTML" -msgstr "" +msgstr "Markdown en HTML automatisch aanvullen" #: packages/lib/models/settings/builtInMetadata.ts:1198 -#, fuzzy msgid "Automatically check for updates" -msgstr "Controleren op updates ..." +msgstr "Automtisch controleren op updates" #: packages/lib/models/settings/builtInMetadata.ts:1722 msgid "Automatically delete notes in the trash after a number of days" -msgstr "" +msgstr "Notities in de prullenmand automatisch verwijderen na een aantal dagen" #: packages/lib/models/settings/builtInMetadata.ts:556 msgid "Automatically switch theme to match system theme" @@ -672,13 +671,13 @@ msgstr "Terug" #: packages/lib/utils/joplinCloud/index.ts:347 msgid "Basic" -msgstr "" +msgstr "Standaard" #: packages/app-mobile/components/BetaChip.tsx:37 #: packages/app-mobile/components/ScreenHeader/WebBetaButton.tsx:37 #: packages/app-mobile/components/ScreenHeader/WebBetaButton.tsx:45 msgid "Beta" -msgstr "" +msgstr "Beta" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:55 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:54 @@ -688,41 +687,41 @@ msgstr "Vet" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:222 #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:319 msgid "Browse all plugins" -msgstr "Plugin doorbladeren" +msgstr "Alle plugins doorbladeren" #: packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.tsx:275 msgid "Browse..." -msgstr "Bladeren ..." +msgstr "Bladeren..." #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:223 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx:54 msgid "Built-in" -msgstr "" +msgstr "Ingebouwd" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:85 msgid "Bulleted List" -msgstr "Opsommingstekens" +msgstr "Opsommingslijst" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:129 msgid "by %s" -msgstr "" +msgstr "volgens %s" +# Context unclear #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:20 msgid "by word" -msgstr "" +msgstr "per woord" #: packages/server/src/routes/admin/users.ts:160 -#, fuzzy msgid "Can Share" -msgstr "Delen" +msgstr "Kan Delen" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:120 msgid "Can view" -msgstr "" +msgstr "Kan bekijken" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:121 msgid "Can view and edit" -msgstr "" +msgstr "Kan bekijken en bewerken" #: packages/app-desktop/bridge.ts:373 packages/app-desktop/bridge.ts:389 #: packages/app-desktop/bridge.ts:452 @@ -762,11 +761,11 @@ msgstr "Achtergrond-synchronisatie wordt geannuleerd... Even geduld." #: packages/lib/Synchronizer.ts:206 msgid "Cancelling..." -msgstr "Annuleren..." +msgstr "Bezig met annuleren..." #: packages/app-cli/app/command-sync.ts:289 msgid "Cancelling... Please wait." -msgstr "Annuleren.. Even geduld." +msgstr "Bezig met annuleren.. Even geduld." #: packages/lib/shim-init-node.ts:325 msgid "Cannot access %s" @@ -781,9 +780,8 @@ msgid "Cannot copy note to \"%s\" notebook" msgstr "Kan notitie niet naar notitieboek \"%s\" kopiëren" #: packages/app-mobile/components/screens/Notes.tsx:195 -#, fuzzy msgid "Cannot create a new note: %s" -msgstr "Maakt een nieuwe notitie aan." +msgstr "Kan geen nieuwe notitie maken: %s" #: packages/app-cli/app/command-attach.ts:22 #: packages/app-cli/app/command-cat.ts:26 packages/app-cli/app/command-cp.ts:25 @@ -815,13 +813,12 @@ msgid "Cannot find \"%s\"." msgstr "Kan \"%s\" niet vinden." #: packages/app-cli/app/command-mkbook.ts:28 -#, fuzzy msgid "Cannot find: \"%s\"" msgstr "Kan \"%s\" niet vinden." #: packages/app-cli/app/command-sync.ts:202 msgid "Cannot initialise synchroniser." -msgstr "Kan de synchronisatie niet starten." +msgstr "Synchronisatie kan niet worden geïnitialiseerd." # Context needed #: packages/lib/services/interop/InteropService.ts:263 @@ -862,7 +859,7 @@ msgid "" "Cannot save %s \"%s\" because it would go over the total allowed size (%s) " "for this account" msgstr "" -"Kan %s \"%s\" niet opslaan omdat het groter is dan de maximum toegestane " +"Kan %s \"%s\" niet opslaan omdat het groter is dan de resterende toegestane " "bestandsgrootte voor dit account" #: packages/lib/services/share/ShareService.ts:337 @@ -871,30 +868,29 @@ msgid "" "enabled end-to-end encryption. They may do so from the screen Configuration " "> Encryption." msgstr "" -"Kan het versleutelde notitieblok niet delen met ontvanger %s omdat ze de end-" -"to-end versleuteling niet hebben ingesteld. Dit kan via Configuratie > " -"Encryptie." +"Kan het versleutelde notitieboek niet delen met ontvanger %s omdat ze de end-" +"to-end-versleuteling niet hebben ingesteld. Dit kunnen ze doen via " +"Configuratie > Encryptie." #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:331 msgid "Case sensitive" -msgstr "" +msgstr "Hoofdlettergevoelig" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleLayoutMoveMode.ts:7 msgid "Change application layout" -msgstr "Layout veranderen" +msgstr "Layout van de toepassing wijzigen" #: packages/lib/services/spellChecker/SpellCheckerService.ts:210 msgid "Change language" msgstr "Taal wijzigen" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:124 -#, fuzzy msgid "Change ratio" -msgstr "Configuratie" +msgstr "Beeldverhouding wijzigen" #: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:98 msgid "Change shortcut for \"%s\"" -msgstr "" +msgstr "Snelkoppeling voor \"%s\" wijzigen" #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:106 msgid "Characters" @@ -906,12 +902,12 @@ msgstr "Tekens zonder spaties" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:168 msgid "Check elements to display in the toolbar" -msgstr "" +msgstr "Vink de elementen aan die je in de werkbalk wil zien" #: packages/app-desktop/gui/MenuBar.tsx:634 #: packages/app-desktop/gui/MenuBar.tsx:929 msgid "Check for updates..." -msgstr "Controleren op updates ..." +msgstr "Controleren op updates..." #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:264 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:465 @@ -943,7 +939,7 @@ msgstr "Chrome Webwinkel" #: packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.tsx:101 #: packages/app-mobile/components/screens/SearchScreen/index.tsx:111 msgid "Clear" -msgstr "Vrijmaken" +msgstr "Wissen" #: packages/app-mobile/components/SelectDateTimeDialog.tsx:169 msgid "Clear alarm" @@ -951,9 +947,8 @@ msgstr "Wis melding" #: packages/app-desktop/gui/lib/SearchInput/SearchInput.tsx:62 #: packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.tsx:140 -#, fuzzy msgid "Clear search" -msgstr "Wis melding" +msgstr "Zoekopdracht wissen" #: packages/app-desktop/gui/NoteRevisionViewer.tsx:205 msgid "" @@ -966,16 +961,15 @@ msgstr "" #: packages/app-desktop/gui/NoteEditor/NoteEditor.tsx:453 msgid "Click to add tags..." -msgstr "Klik om tags toe te voegen ..." +msgstr "Klik om labels toe te voegen ..." #: packages/lib/versionInfo.ts:88 msgid "Client ID: %s" msgstr "Client ID: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:23 -#, fuzzy msgid "close" -msgstr "Sluit" +msgstr "sluit" #: packages/app-desktop/gui/MenuBar.tsx:359 #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:175 @@ -991,18 +985,16 @@ msgid "Close" msgstr "Sluit" #: packages/app-mobile/components/Dropdown.tsx:199 -#, fuzzy msgid "Close dropdown" -msgstr "Venster sluiten" +msgstr "Lijst sluiten" #: packages/app-mobile/components/accessibility/AccessibleModalMenu.tsx:46 -#, fuzzy msgid "Close menu" -msgstr "Sluit" +msgstr "Menu sluiten" #: packages/app-mobile/components/SideMenu.tsx:300 msgid "Close side menu" -msgstr "" +msgstr "Menu aan zijkant sluiten" #: packages/lib/models/settings/settingValidations.ts:33 msgid "" @@ -1010,6 +1002,9 @@ msgid "" "application again. Make sure you create a backup first by exporting all your " "notes as JEX." msgstr "" +"Sluit de toepassing af. Verwijder dan je profile in \"%s\", en start de " +"toepassing opnieuw. Zorg dat je eerst een back-up maakt door al je notities " +"als JEX te exporteren." #: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:26 #: packages/app-desktop/gui/MenuBar.tsx:699 @@ -1018,11 +1013,11 @@ msgstr "Venster sluiten" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.ts:39 msgid "Cmd-click to open" -msgstr "" +msgstr "Cmd-klik om te openen" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.ts:45 msgid "Cmd-click to open: %s" -msgstr "" +msgstr "Cmd-klik om te openen: %s" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:70 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:65 @@ -1038,22 +1033,20 @@ msgid "Code View" msgstr "Code-weergave" #: packages/lib/utils/joplinCloud/index.ts:173 -#, fuzzy msgid "Collaborate on a notebook with others" -msgstr "Maak eerst een notitieboek aan" +msgstr "Werk samen met anderen aan een notitieboek" #: packages/app-desktop/gui/SyncWizard/Dialog.tsx:169 -#, fuzzy msgid "Collaborate on notebooks with others" -msgstr "Maak eerst een notitieboek aan" +msgstr "Werk samen met anderen aan notitieboeken" #: packages/app-desktop/gui/Sidebar/listItemComponents/ExpandIcon.tsx:28 msgid "Collapsed, press space to expand." -msgstr "" +msgstr "Ingeklapt, druk spatie om uit te klappen." #: packages/lib/services/ReportService.ts:351 msgid "Coming alarms" -msgstr "Komende meldingen" +msgstr "Komende alarmen" #: packages/lib/models/settings/builtInMetadata.ts:1379 msgid "" @@ -1064,8 +1057,8 @@ msgid "" msgstr "" "Komma-gescheiden lijst van paden naar mappen om de certificaten van te " "laden, of pad naar individuele cert-bestanden. Bijvoorbeeld: /my/cert_dir, /" -"other/custom.pem. Merk op dat als u wijzigingen aanbrengt in de TLS-" -"instellingen, u uw wijzigingen moet opslaan voordat u klikt op \"Verifieer " +"other/custom.pem. Merk op dat als je wijzigingen aanbrengt in de TLS-" +"instellingen, je je wijzigingen moet opslaan voordat je klikt op \"Verifieer " "de configuratie van de synchronisatie\"." #: packages/lib/services/KeymapService.ts:321 @@ -1082,24 +1075,20 @@ msgid "Command palette" msgstr "Commandopalet" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/commandPalette.ts:7 -#, fuzzy msgid "Command palette..." -msgstr "Commandopalet" +msgstr "Commandopalet..." #: packages/lib/services/noteList/defaultListRenderer.ts:24 -#, fuzzy msgid "Compact" -msgstr "Voltooid" +msgstr "Compact" #: packages/app-desktop/gui/NoteList/utils/useOnKeyDown.ts:153 -#, fuzzy msgid "Complete" msgstr "Voltooid" #: packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.ts:40 -#, fuzzy msgid "Complete to-do" -msgstr "Voltooid" +msgstr "To-do voltooien" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:12 #: packages/app-desktop/gui/NotePropertiesDialog.tsx:70 @@ -1114,20 +1103,19 @@ msgstr "Decryptie voltooid." msgid "" "Completed with warnings:\n" "%s" -msgstr "" +msgstr "Voltooid met waarschuwingen: %s" #: packages/lib/Synchronizer.ts:207 -#, fuzzy msgid "Completed: %s (%s)" -msgstr "Voltooid: %s" +msgstr "Voltooid: %s (%s)" #: packages/lib/models/Note.ts:66 msgid "completion date" -msgstr "" +msgstr "datum voltooiing" #: packages/server/src/services/TaskService.ts:28 msgid "Compress old changes" -msgstr "" +msgstr "Comprimeer oude wijzigingen" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:132 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:819 @@ -1142,13 +1130,12 @@ msgstr "Bevestiging wachtwoord kan niet leeg zijn" #: packages/app-cli/app/command-e2ee.ts:95 #: packages/app-mobile/components/screens/encryption-config.tsx:175 msgid "Confirm password:" -msgstr "Bevestig paswoord:" +msgstr "Bevestig wachtwoord:" #: packages/app-desktop/gui/Root.tsx:126 #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:70 -#, fuzzy msgid "Confirmation" -msgstr "Configuratie" +msgstr "Bevestiging" #: packages/lib/services/ReportService.ts:330 msgid "Conflicted: %d" @@ -1159,45 +1146,39 @@ msgid "Conflicts" msgstr "Conflicten" #: packages/lib/models/Resource.ts:478 -#, fuzzy msgid "Conflicts (attachments)" -msgstr "Notitiebijlagen" +msgstr "Conflicten (bijlagen)" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:253 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:462 -#, fuzzy msgid "Connect to Joplin Cloud" -msgstr "Joplin Forum" +msgstr "Verbinding maken met Joplin Cloud" #: packages/lib/utils/joplinCloud/index.ts:208 msgid "Consolidated billing" -msgstr "" +msgstr "Geconsolideerde facturatie" #: packages/app-desktop/gui/Sidebar/listItemComponents/NoteCount.tsx:11 -#, fuzzy msgid "Contains %d note" msgid_plural "Contains %d notes" -msgstr[0] "Converteer naar notitie" -msgstr[1] "Converteer naar notitie" +msgstr[0] "Bevat %d notitie" +msgstr[1] "Bevat %d notities" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx:106 -#, fuzzy msgid "Content provided by %s" -msgstr "Notitieboek titel:" +msgstr "Inhoud geleverd door %s" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:102 -#, fuzzy msgid "Content provided by: %s" -msgstr "Notitieboek titel:" +msgstr "Inhoud geleverd door: %s" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:74 msgid "Continue" -msgstr "" +msgstr "Doorgaan" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:6 -#, fuzzy msgid "Control character" -msgstr "Tekens" +msgstr "Controleteken" #: packages/app-mobile/components/screens/Note/Note.tsx:1221 msgid "Convert to note" @@ -1208,9 +1189,8 @@ msgid "Convert to todo" msgstr "Converteer naar to-do" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:181 -#, fuzzy msgid "Converting speech to text..." -msgstr "Converteer naar notitie" +msgstr "Bezig met omzetten van spraak naar tekst..." #: packages/app-desktop/gui/MenuBar.tsx:601 #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:35 @@ -1218,7 +1198,7 @@ msgstr "Converteer naar notitie" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:190 #: packages/app-desktop/gui/NotePropertiesDialog.tsx:387 msgid "Copy" -msgstr "Kopieer" +msgstr "Kopiëren" #: packages/app-desktop/commands/copyDevCommand.ts:9 msgid "Copy dev mode command to clipboard" @@ -1228,56 +1208,51 @@ msgstr "Kopieer dev mode commando naar klembord" #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:243 #: packages/app-desktop/gui/utils/NoteListUtils.ts:127 #: packages/app-mobile/components/screens/Note/Note.tsx:1239 -#, fuzzy msgid "Copy external link" -msgstr "Beëindig externe bijwerking" +msgstr "Kopieer externe link" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:173 -#, fuzzy msgid "Copy image" -msgstr "Kopieer token" +msgstr "Kopieer afbeelding" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:218 msgid "Copy Link Address" -msgstr "Kopieer koppelingslocatie" +msgstr "Kopieer linkadres" #: packages/app-desktop/gui/JoplinCloudLoginScreen.tsx:94 #: packages/app-mobile/components/screens/JoplinCloudLoginScreen.tsx:169 -#, fuzzy msgid "Copy link to website" -msgstr "Joplin website" +msgstr "Kopieer link naar website" #: packages/app-desktop/gui/utils/NoteListUtils.ts:112 #: packages/app-mobile/components/screens/Note/Note.tsx:1230 msgid "Copy Markdown link" -msgstr "Kopieer Markdown koppeling" +msgstr "Kopieer Markdownlink" # Why mention 'to clipboard'. Isn't that where something that is copied is always placed? #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:158 msgid "Copy path to clipboard" -msgstr "Kopieer pad" +msgstr "Kopieer pad naar klembord" #: packages/app-desktop/gui/ShareNoteDialog.tsx:216 msgid "Copy Shareable Link" msgid_plural "Copy Shareable Links" -msgstr[0] "Kopieer deelbare koppeling" -msgstr[1] "Kopieer deelbare koppelingen" +msgstr[0] "Kopieer deelbare link" +msgstr[1] "Kopieer deelbare links" # Why mention 'to clipboard'. Isn't that where something that is copied is always placed? #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:52 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:75 -#, fuzzy msgid "Copy to clipboard" -msgstr "Kopieer pad" +msgstr "Kopieer naar klembord" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:144 msgid "Copy token" msgstr "Kopieer token" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:611 -#, fuzzy msgid "Copy version info" -msgstr "Weergave versie informatie" +msgstr "Kopieer versie-informatie" #: packages/lib/components/shared/dropbox-login-shared.js:43 msgid "" @@ -1301,14 +1276,13 @@ msgid "" "%s" msgstr "" "Kon geen verbinding maken met de Joplin Server. Controleer de Synchronisatie " -"opties in het configuratie scherm. Volledige fout was:\n" +"opties in het configuratiescherm. Volledige foutmelding was:\n" "\n" "%s" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:319 -#, fuzzy msgid "Could not connect to plugin repository." -msgstr "Kon plugin niet installeren: %s" +msgstr "Kon geen verbinding maken met de plugin-bibliotheek." #: packages/app-desktop/InteropServiceHelper.ts:220 msgid "Could not export notes: %s" @@ -1331,9 +1305,8 @@ msgstr "" "De foutmelding was: \"%s\"" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:59 -#, fuzzy msgid "Could not switch profile: %s" -msgstr "Kon plugin niet installeren: %s" +msgstr "Kon profiel niet wisselen: %s" #: packages/lib/components/EncryptionConfigScreen/utils.ts:224 msgid "Could not upgrade master key: %s" @@ -1344,23 +1317,20 @@ msgid "" "Could not verify the share status of this notebook - aborting. Please try " "again when you are connected to the internet." msgstr "" -"Kon de deelstatus van dit notitieblok niet nagaan - annuleren. Probeer " +"Kon de deelstatus van dit notitieboek niet nagaan - annuleren. Probeer " "opnieuw wanneer je terug een netwerkverbinding hebt." #: packages/app-mobile/components/biometrics/biometricAuthenticate.ts:30 -#, fuzzy msgid "Could not verify your identity: %s" -msgstr "Kon notities niet exporteren: %s" +msgstr "Kon je identiteit niet verifiëren: %s" #: packages/app-desktop/gui/PromptDialog.tsx:277 -#, fuzzy msgid "Create" -msgstr "Aangemaakt" +msgstr "Aanmaken" #: packages/app-cli/app/command-mkbook.ts:19 -#, fuzzy msgid "Create a new notebook under a parent notebook." -msgstr "Maakt een nieuw notitieboek aan." +msgstr "Maak een nieuw notitieboek aan onder een bestaand notitieboek." #: packages/app-mobile/components/NoteList.tsx:102 msgid "Create a notebook" @@ -1368,19 +1338,16 @@ msgstr "Maak nieuw notitieboek aan" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/addProfile.ts:9 #: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:88 -#, fuzzy msgid "Create new profile..." -msgstr "Maakt een nieuwe notitie aan." +msgstr "Nieuw profiel aanmaken..." #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:166 -#, fuzzy msgid "Create notebook" msgstr "Maak nieuw notitieboek aan" #: packages/server/src/routes/admin/users.ts:257 -#, fuzzy msgid "Create user" -msgstr "Aangemaakt: %s" +msgstr "Gebruiker aanmaken" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:14 #: packages/app-desktop/gui/NotePropertiesDialog.tsx:67 @@ -1398,16 +1365,15 @@ msgstr "Aangemaakte lokale items: %d." #: packages/lib/services/ReportService.ts:288 msgid "Created locally" -msgstr "Lokaal toegevoegd" +msgstr "Lokaal aangemaakt" #: packages/lib/Synchronizer.ts:201 msgid "Created remote items: %d." -msgstr "Aangemaakte remote items: %d." +msgstr "Aangemaakte externe items: %d." #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:140 -#, fuzzy msgid "Created: " -msgstr "Aangemaakt: %s" +msgstr "Aangemaakt: " #: packages/app-cli/app/command-import.ts:51 #: packages/app-desktop/gui/ImportScreen.tsx:90 @@ -1432,58 +1398,55 @@ msgid "Creates a new to-do." msgstr "Maakt nieuwe to-do aan." #: packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx:123 -#, fuzzy msgid "Creating new note..." -msgstr "Nieuwe %s aanmaken ..." +msgstr "Nieuwe notitie aanmaken..." #: packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx:123 -#, fuzzy msgid "Creating new to-do..." -msgstr "Nieuwe %s aanmaken ..." +msgstr "Nieuwe to-do aanmaken..." #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.tsx:29 msgid "Creating report..." -msgstr "Rapport aanmaken ..." +msgstr "Rapport aanmaken..." #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.ts:41 msgid "Ctrl-click to open" -msgstr "" +msgstr "Ctrl-klik om te openen" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.ts:47 msgid "Ctrl-click to open: %s" -msgstr "" +msgstr "Ctrl-klik om te openen: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:24 msgid "current match" -msgstr "" +msgstr "huidige overeenkomst" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:153 -#, fuzzy msgid "Current password" -msgstr "Geef hoofdpaswoord in:" +msgstr "Huidig wachtwoord" #: packages/app-desktop/checkForUpdates.ts:90 msgid "Current version is up-to-date." -msgstr "Actuele versie is up-to-date." +msgstr "Huidige versie is up-to-date." #: packages/lib/models/Note.ts:64 msgid "custom order" -msgstr "aangepaste rangschikking" +msgstr "aangepaste volgorde" #: packages/app-desktop/gui/NoteList/utils/canManuallySortNotes.ts:10 msgid "Custom order" -msgstr "Aangepaste rangschikking" +msgstr "Aangepaste volgorde" # Contexed needed # What is a 'app style'? #: packages/lib/models/settings/builtInMetadata.ts:1167 msgid "Custom stylesheet for Joplin-wide app styles" -msgstr "Aangepaste stijlweergave voor algemene stijl" +msgstr "Aangepast stijlblad voor Joplin-brede appstijlen" # Need clarification. Should it not be '... for rendering Markdown'? #: packages/lib/models/settings/builtInMetadata.ts:1150 msgid "Custom stylesheet for rendered Markdown" -msgstr "Aangepaste stijlweergave voor gerenderde Markdown" +msgstr "Aangepast stijlblad voor gerenderde Markdown" #: packages/lib/models/settings/builtInMetadata.ts:1378 msgid "Custom TLS certificates" @@ -1491,13 +1454,13 @@ msgstr "Aangepaste TLS-certificaten" #: packages/lib/utils/joplinCloud/index.ts:194 msgid "Customise the note publishing banner" -msgstr "" +msgstr "De banner voor het publiceren van notities aanpassen" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:40 #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.ts:85 #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:182 msgid "Cut" -msgstr "Knip" +msgstr "Knippen" #: packages/lib/models/settings/builtInMetadata.ts:43 msgid "Dark" @@ -1505,7 +1468,7 @@ msgstr "Donker" #: packages/server/src/services/MustacheService.ts:117 msgid "Dashboard" -msgstr "" +msgstr "Dashboard" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:169 msgid "Date" @@ -1522,20 +1485,20 @@ msgstr "dagen" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:92 msgid "Decrease indent level" -msgstr "" +msgstr "Inspringniveau verlagen" #: packages/app-cli/app/command-e2ee.ts:65 msgid "Decrypted items: %d" -msgstr "Gedecrypteerde items: %d" +msgstr "Ontsleutelde items: %d" #: packages/lib/components/EncryptionConfigScreen/utils.ts:45 msgid "Decrypted items: %s / %s" -msgstr "Gedecrypteerde items: %s / %s" +msgstr "Ontsleutelde items: %s / %s" #: packages/app-desktop/gui/Sidebar/Sidebar.tsx:48 #: packages/app-mobile/components/side-menu-content.tsx:637 msgid "Decrypting items: %d/%d" -msgstr "Items decrypteren: %d/%d" +msgstr "Items ontsleutelen: %d/%d" #: packages/lib/models/settings/builtInMetadata.ts:1081 #: packages/lib/models/settings/builtInMetadata.ts:1088 @@ -1565,53 +1528,49 @@ msgid "Delete attachment \"%s\"?" msgstr "Bijlage \"%s\" verwijderen?" #: packages/server/src/services/TaskService.ts:27 -#, fuzzy msgid "Delete expired sessions" -msgstr "Schakel wiskundige formules in" +msgstr "Verlopen sessies verwijderen" #: packages/server/src/services/TaskService.ts:22 -#, fuzzy msgid "Delete expired tokens" -msgstr "Deze notities %d verwijderen?" +msgstr "Verlopen tokens verwijderen" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:110 msgid "Delete line" -msgstr "Lijn verwijderen" +msgstr "Regel verwijderen" #: packages/lib/models/settings/builtInMetadata.ts:1191 msgid "Delete local data and re-download from sync target" msgstr "" -"Verwijder lokale gegevens en download opnieuw van synchronisatie locatie" +"Verwijder lokale gegevens en download opnieuw van synchronisatie-locatie" #: packages/app-mobile/services/voiceTyping/VoiceTyping.ts:90 msgid "" "Delete model and re-download?\n" "This cannot be undone." msgstr "" +"Model verwijderen en opnieuw downloaden?\n" +"Dit kan niet ongedaan gemaakt worden." #: packages/lib/commands/deleteNote.ts:7 -#, fuzzy msgid "Delete note" -msgstr "Notitie verwijderen?" +msgstr "Notitie verwijderen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/deleteFolder.ts:9 -#, fuzzy msgid "Delete notebook" -msgstr "Notitie verwijderen?" +msgstr "Notitieboek verwijderen" #: packages/lib/components/shared/config/plugins/useOnDeleteHandler.ts:16 msgid "Delete plugin \"%s\"?" msgstr "Plugin \"%s\" verwijderen?" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:112 -#, fuzzy msgid "Delete profile \"%s\"" -msgstr "Notities \"%s\" verwijderen?" +msgstr "Profiel \"%s\" verwijderen" #: packages/app-mobile/components/ScreenHeader/index.tsx:432 -#, fuzzy msgid "Delete selected notes" -msgstr "Deze notities verwijderen?" +msgstr "Geselecteerde notities verwijderen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/deleteFolder.ts:22 #: packages/app-mobile/components/side-menu-content.tsx:407 @@ -1621,25 +1580,27 @@ msgid "" "If you delete the inbox notebook, any email that's recently been sent to it " "may be lost." msgstr "" +"Het Inbox notitieboek verwijderen?\n" +"\n" +"Als je het Inbox notitieboek verwijdert, gaan emails die er recent naar " +"verzonden zijn mogelijk verloren." #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:246 msgid "" "Delete this invitation? The recipient will no longer have access to this " "shared notebook." msgstr "" -"Deze uitnodiging verwijderen? De ontvanger heeft niet langer toegang tot dit " -"gedeelde notitieblok." +"Deze uitnodiging verwijderen? De ontvanger zal niet langer toegang hebben " +"tot dit gedeelde notitieboek." #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:108 -#, fuzzy msgid "Delete this profile?" -msgstr "Deze notities %d verwijderen?" +msgstr "Dit profiel verwijderen?" #: packages/app-desktop/gui/NotePropertiesDialog.tsx:69 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:207 -#, fuzzy msgid "Deleted" -msgstr "Verwijderen" +msgstr "Verwijderd" #: packages/lib/Synchronizer.ts:203 msgid "Deleted local items: %d." @@ -1647,11 +1608,11 @@ msgstr "Verwijderde lokale items: %d." #: packages/lib/Synchronizer.ts:204 msgid "Deleted remote items: %d." -msgstr "Verwijderde remote items: %d." +msgstr "Verwijderde externe items: %d." #: packages/app-cli/app/command-rmnote.ts:20 msgid "Deletes notes permanently, skipping the trash." -msgstr "" +msgstr "Verwijder notities permanent, zonder ze naar de prullenmand te sturen." #: packages/app-cli/app/command-rmbook.ts:14 msgid "Deletes the given notebook." @@ -1670,14 +1631,12 @@ msgid "Deletes the notes without asking for confirmation." msgstr "Verwijdert de notities zonder om bevestiging te vragen." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:551 -#, fuzzy msgid "Deletion log" -msgstr "Lijn verwijderen" +msgstr "Verwijderingslog" #: packages/app-mobile/components/NoteItem.tsx:144 -#, fuzzy msgid "Deselect" -msgstr "Selecteer alles" +msgstr "Deselecteren" #: packages/app-cli/app/command-export.ts:24 msgid "Destination format: %s" @@ -1686,11 +1645,11 @@ msgstr "Doelformaat: %s" #: packages/lib/services/noteList/defaultLeftToRightListRenderer.ts:25 #: packages/lib/services/noteList/defaultMultiColumnsRenderer.ts:8 msgid "Detailed" -msgstr "" +msgstr "Gedetailleerd" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx:99 msgid "Dev" -msgstr "" +msgstr "Dev" #: packages/lib/services/interop/Module.ts:62 msgid "Directory" @@ -1701,18 +1660,17 @@ msgid "Directory to synchronise with (absolute path)" msgstr "Map om mee te synchroniseren (absolute pad)" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:144 -#, fuzzy msgid "Disable" -msgstr "Uitgeschakeld" +msgstr "Uitschakelen" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:226 #: packages/app-mobile/components/screens/encryption-config.tsx:292 msgid "Disable encryption" -msgstr "Schakel encryptie uit" +msgstr "Encryptie uitschakelen" #: packages/app-desktop/gui/MainScreen.tsx:502 msgid "Disable safe mode and restart" -msgstr "Veilige modues uitschakelen en opnieuw opstarten" +msgstr "Veilige modus uitschakelen en opnieuw opstarten" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:94 msgid "Disable Web Clipper Service" @@ -1728,9 +1686,8 @@ msgid "Disabled" msgstr "Uitgeschakeld" #: packages/app-mobile/components/screens/encryption-config.tsx:323 -#, fuzzy msgid "Disabled keys" -msgstr "Uitgeschakelde toetsen verbergen" +msgstr "Uitgeschakelde toetsen" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:200 #: packages/app-mobile/components/screens/encryption-config.tsx:251 @@ -1744,24 +1701,23 @@ msgstr "" "zullen gestuurd worden. Wil je verdergaan?" #: packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.ts:19 -#, fuzzy msgid "Discard" -msgstr "Wijzigingen annuleren" +msgstr "Verwerpen" #: packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.tsx:105 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:269 #: packages/app-mobile/components/screens/Note/Note.tsx:209 msgid "Discard changes" -msgstr "Wijzigingen annuleren" +msgstr "Wijzigingen verwerpen" # Context needed. What is being dismissed? #: packages/app-desktop/gui/NoteEditor/WarningBanner/BannerContent.tsx:20 msgid "Dismiss" -msgstr "Verwerpen" +msgstr "Afwijzen" #: packages/app-cli/app/command-geoloc.ts:13 msgid "Displays a geolocation URL for the note." -msgstr "Geeft een geo-locatie-URL voor de notitie weer." +msgstr "Toont een geo-locatie-URL voor de notitie." #: packages/app-cli/app/command-ls.ts:28 msgid "Displays only the first top notes." @@ -1780,31 +1736,31 @@ msgstr "" # Suggestion for English: 'Displays a summary of all notes and notebooks.' #: packages/app-cli/app/command-status.js:13 msgid "Displays summary about the notes and notebooks." -msgstr "Geeft een overzicht van alle notities en notitieboeken weer." +msgstr "Toont een samenvatting over alle notities en notitieboeken." #: packages/app-cli/app/command-cat.ts:18 msgid "Displays the complete information about note." -msgstr "Geeft de volledige informatie van een notitie weer." +msgstr "Toont de volledige informatie over notitie." #: packages/app-cli/app/command-cat.ts:14 msgid "Displays the given note." -msgstr "Geeft de opgegeven notitie weer." +msgstr "Toont de opgegeven notitie." #: packages/app-cli/app/command-ls.ts:19 msgid "" "Displays the notes in the current notebook. Use `ls /` to display the list " "of notebooks." msgstr "" -"Geeft notities in het huidige notitieboek weer. Gebruik `ls /` om een lijst " +"Toont de notities in het huidige notitieboek. Gebruik `ls /` om een lijst " "van notitieboeken weer te geven." #: packages/app-cli/app/command-help.ts:13 msgid "Displays usage information." -msgstr "Geeft informatie over het verbruik weer." +msgstr "Toont gebruiksinformatie." #: packages/app-cli/app/command-version.ts:11 msgid "Displays version information" -msgstr "Weergave versie informatie" +msgstr "Toont versie-informatie" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:338 #: packages/app-desktop/gui/NoteList/utils/canManuallySortNotes.ts:11 @@ -1816,26 +1772,24 @@ msgid "Do not ask for confirmation." msgstr "Vraag niet om bevestiging." #: packages/lib/components/EncryptionConfigScreen/utils.ts:55 -#, fuzzy msgid "" "Do not lose the password as, for security purposes, this will be the *only* " "way to decrypt the data! To enable encryption, please enter your password " "below." msgstr "" -"Voor beveiligingsredenen, bewaar het wachtwoord zorgvuldig. Dit is de " -"*enige* manier om je data te ontsleutelen! Gelieve je wachtwoord hieronder " -"in te geven om encrypte te activeren." +"Bewaar het wachtwoord zorgvuldig. Om beveiligingsredenen is dit de *enige* " +"manier om je data te ontsleutelen! Gelieve je wachtwoord hieronder in te " +"geven om encryptie in te schakelen." #: packages/lib/models/Setting.ts:1220 -#, fuzzy msgid "Donate, website" -msgstr "Joplin website" +msgstr "Doneren, website" #: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:151 #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:245 #: packages/lib/models/Resource.ts:34 msgid "Done" -msgstr "" +msgstr "Klaar" #: packages/app-desktop/checkForUpdates.ts:109 msgid "Download" @@ -1843,13 +1797,11 @@ msgstr "Downloaden" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:131 msgid "Download and install the relevant extension for your browser:" -msgstr "" -"Downloaden en installeren van de overeenkomstige extensie voor je browser:" +msgstr "Download en installeer de relevante extensie voor je browser:" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:206 -#, fuzzy msgid "Download updated model" -msgstr "Gedownload" +msgstr "Download bijgewerkt model" #: packages/lib/models/Resource.ts:409 msgid "Downloaded" @@ -1857,25 +1809,24 @@ msgstr "Gedownload" #: packages/lib/services/ReportService.ts:286 msgid "Downloaded and decrypted" -msgstr "Gedownload en gedecrypteerd" +msgstr "Gedownload en ontsleuteld" #: packages/lib/services/ReportService.ts:287 msgid "Downloaded and encrypted" -msgstr "Gedownload en geëncrypteerd" +msgstr "Gedownload en versleuteld" # This text does not end with a period, but with three dots to indicate an ongoing action. #: packages/lib/models/Resource.ts:408 msgid "Downloading" -msgstr "Aan het downloaded ..." +msgstr "Bezig met downloaden" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:182 -#, fuzzy msgid "Downloading %s language files..." -msgstr "Bijlagen downloaden ..." +msgstr "Bezig met downloaden van taalbestanden voor %s..." #: packages/app-cli/app/command-sync.ts:256 msgid "Downloading resources..." -msgstr "Bijlagen downloaden ..." +msgstr "Bezig met downloaden van hulpmiddelen..." #: packages/lib/models/settings/builtInMetadata.ts:44 msgid "Dracula" @@ -1883,11 +1834,11 @@ msgstr "Dracula" #: packages/app-mobile/components/screens/Note/Note.tsx:1162 msgid "Draw picture" -msgstr "" +msgstr "Tekening maken" #: packages/app-mobile/components/screens/Note/Note.tsx:872 msgid "Drawing" -msgstr "" +msgstr "Tekening" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:1444 msgid "Drop notes or files here" @@ -1901,15 +1852,15 @@ msgstr "Dropbox" msgid "Dropbox Login" msgstr "Dropbox Login" +# Context unclear #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:13 msgid "Due" -msgstr "" +msgstr "Uiterste datum" # Context needed #: packages/lib/models/Note.ts:65 -#, fuzzy msgid "due date" -msgstr "datum bijwerking" +msgstr "uiterste datum" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/duplicateNote.ts:7 #: packages/app-mobile/components/ScreenHeader/index.tsx:470 @@ -1918,22 +1869,20 @@ msgid "Duplicate" msgstr "Dupliceer" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:114 -#, fuzzy msgid "Duplicate line" -msgstr "Dupliceer" +msgstr "Dupliceer regel" #: packages/app-mobile/components/ScreenHeader/index.tsx:472 -#, fuzzy msgid "Duplicate selected notes" -msgstr "Dupliceer" +msgstr "Dupliceer geselecteerde notities" #: packages/app-cli/app/command-cp.ts:13 msgid "" "Duplicates the notes matching to [notebook]. If no notebook is " "specified the note is duplicated in the current notebook." msgstr "" -"Dupliceert de notities die voldoen aan in [notitieboek]. Als er geen " -"notitieboek is gespecifieerd, zal de notitie gedupliceerd worden in het " +"Dupliceert de notities die voldoen aan naar [notitieboek]. Als er " +"geen notitieboek is gespecifieerd, zal de notitie gedupliceerd worden in het " "huidige notitieboek." #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.ts:89 @@ -1945,7 +1894,7 @@ msgstr "" #: packages/app-mobile/components/screens/Note/Note.tsx:1540 #: packages/app-mobile/components/side-menu-content.tsx:414 msgid "Edit" -msgstr "Bewerk" +msgstr "Bewerken" #: packages/app-desktop/commands/startExternalEditing.ts:10 msgid "Edit in external editor" @@ -1953,35 +1902,32 @@ msgstr "Bewerken in externe editor" #: packages/app-desktop/commands/openNoteInNewWindow.ts:10 msgid "Edit in new window" -msgstr "" +msgstr "Bewerken in nieuw venster" #: packages/app-desktop/gui/utils/NoteListUtils.ts:70 -#, fuzzy msgid "Edit in..." -msgstr "Bewerk notitieboek" +msgstr "Bewerken in..." #: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:143 -#, fuzzy msgid "Edit link" -msgstr "Bewerk notitieboek" +msgstr "Link bewerken" #: packages/app-cli/app/command-edit.ts:17 msgid "Edit note." -msgstr "Bewerk notitie." +msgstr "Notitie bewerken." #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:166 #: packages/app-mobile/components/screens/folder.js:97 msgid "Edit notebook" -msgstr "Bewerk notitieboek" +msgstr "Notitieboek bewerken" #: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:88 -#, fuzzy msgid "Edit profile" -msgstr "Exporteer profiel" +msgstr "Profiel bewerken" #: packages/app-desktop/commands/editProfileConfig.ts:9 msgid "Edit profile configuration..." -msgstr "" +msgstr "Profielconfiguratie bewerken..." #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:144 #: packages/lib/models/settings/builtInMetadata.ts:608 @@ -1991,36 +1937,33 @@ msgid "Editor" msgstr "Editor" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Toolbar.tsx:38 -#, fuzzy msgid "Editor actions" -msgstr "Lettertype editor" +msgstr "Editor acties" #: packages/lib/models/settings/builtInMetadata.ts:1074 msgid "Editor font" -msgstr "Lettertype editor" +msgstr "Lettertype van editor" #: packages/lib/models/settings/builtInMetadata.ts:1100 msgid "Editor font family" -msgstr "Lettertype editor" +msgstr "Lettertypefamilie van editor" #: packages/lib/models/settings/builtInMetadata.ts:1062 msgid "Editor font size" -msgstr "Lettergrootte editor" +msgstr "Lettergrootte van editor" #: packages/lib/models/settings/builtInMetadata.ts:1121 msgid "Editor maximum width" msgstr "Maximum breedte editor" #: packages/lib/models/settings/builtInMetadata.ts:1113 -#, fuzzy msgid "Editor monospace font family" -msgstr "Lettertype editor" +msgstr "Monospace lettertypefamilie van editor" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:118 #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:122 -#, fuzzy msgid "Editor: %s" -msgstr "Editor" +msgstr "Editor: %s" #: packages/app-cli/app/command-ls.ts:32 msgid "Either \"text\" or \"json\"" @@ -2035,65 +1978,63 @@ msgstr "Emacs" #: packages/server/src/routes/admin/emails.ts:127 #: packages/server/src/routes/admin/users.ts:140 msgid "Email" -msgstr "" +msgstr "E-mail" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:47 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:23 -#, fuzzy msgid "Email to note" -msgstr "Bewerk notitie." +msgstr "E-mail naar notitie" #: packages/lib/utils/joplinCloud/index.ts:187 msgid "Email to Note" -msgstr "" +msgstr "E-mail naar Notitie" #: packages/lib/models/Setting.ts:1213 -#, fuzzy msgid "Email To Note, login information" -msgstr "Weergave versie informatie" +msgstr "E-mail Naar Notitie, login-informatie" #: packages/server/src/routes/admin/emails.ts:111 #: packages/server/src/services/MustacheService.ts:133 msgid "Emails" -msgstr "" +msgstr "E-mails" # Unclear to me what 'emphasised' means. Bold? And what is the difference with 'strong'? #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx:195 msgid "emphasised text" -msgstr "gemarkeerde tekst" +msgstr "benadrukte tekst" #: packages/app-desktop/commands/emptyTrash.ts:16 #: packages/app-desktop/commands/emptyTrash.ts:8 #: packages/app-mobile/components/side-menu-content.tsx:338 #: packages/app-mobile/components/side-menu-content.tsx:342 msgid "Empty trash" -msgstr "" +msgstr "Prullenmand leegmaken" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:144 #: packages/app-mobile/components/biometrics/BiometricPopup.tsx:86 #: packages/app-mobile/components/screens/encryption-config.tsx:189 msgid "Enable" -msgstr "Activeer" +msgstr "Inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:978 msgid "Enable ^sup^ syntax" -msgstr "Activeer ^sup^ syntax" +msgstr "^sup^ syntaxis inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:982 msgid "Enable ++insert++ syntax" -msgstr "Activeer ++insert++ syntax" +msgstr "++insert++ syntaxis inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:974 msgid "Enable ==mark== syntax" -msgstr "Activeer ==mark== syntax" +msgstr "==mark== syntaxis inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:977 msgid "Enable ~sub~ syntax" -msgstr "Activeer ~sub~ syntax" +msgstr "~sub~ syntaxis inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:980 msgid "Enable abbreviation syntax" -msgstr "Activeer afkorting syntax" +msgstr "Afkortingensyntaxis inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:971 msgid "Enable audio player" @@ -2101,20 +2042,20 @@ msgstr "Audiospeler inschakelen" #: packages/app-mobile/components/biometrics/BiometricPopup.tsx:82 msgid "Enable biometrics authentication?" -msgstr "" +msgstr "Authenticatie met biometrie inschakelen?" #: packages/lib/models/settings/builtInMetadata.ts:979 msgid "Enable deflist syntax" -msgstr "Activeer definitielijst syntax" +msgstr "Definitielijst syntaxis inschakelen" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:226 #: packages/app-mobile/components/screens/encryption-config.tsx:292 msgid "Enable encryption" -msgstr "Schakel encryptie in" +msgstr "Encryptie inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:994 msgid "Enable file:// URLs for images and videos" -msgstr "" +msgstr "file:// URL's inschakelen voor afbeeldingen en video's" #: packages/lib/models/settings/builtInMetadata.ts:975 msgid "Enable footnotes" @@ -2122,11 +2063,11 @@ msgstr "Voetnoten inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:968 msgid "Enable Fountain syntax support" -msgstr "Ondersteuning van Fountain syntax inschakelen" +msgstr "Ondersteuning van Fountain syntaxis inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:965 msgid "Enable Linkify" -msgstr "Schakel Linkify in" +msgstr "Linkify inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:981 msgid "Enable markdown emoji" @@ -2134,7 +2075,7 @@ msgstr "Markdown emoji inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:967 msgid "Enable math expressions" -msgstr "Schakel wiskundige formules in" +msgstr "Wiskundige expressies inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:969 msgid "Enable Mermaid diagrams support" @@ -2142,60 +2083,57 @@ msgstr "Ondersteuning van Mermaid diagrammen inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:983 msgid "Enable multimarkdown table extension" -msgstr "Activeer multimarkdown tabel extensie" +msgstr "Multimarkdown tabel extensie inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:1473 msgid "Enable note history" -msgstr "Schakel notitiehistoriek in" +msgstr "Notitiehistoriek inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:500 msgid "Enable optical character recognition (OCR)" msgstr "" +"Optische tekenherkenning (optical character recognition - OCR) inschakelen" #: packages/lib/models/Setting.ts:1219 -#, fuzzy msgid "Enable or disable plugins" -msgstr "Beheer uw plugins" +msgstr "Plugins in- of uitschakelen" #: packages/lib/models/settings/builtInMetadata.ts:973 msgid "Enable PDF viewer" -msgstr "Schakel PDF weergave in" +msgstr "PDF-weergave inschakelen" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:119 #: packages/lib/models/settings/builtInMetadata.ts:921 -#, fuzzy msgid "Enable plugin support" -msgstr "Schakel ondersteuning voor typograaf in" +msgstr "Ondersteuning voor plugins inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:963 msgid "Enable soft breaks" msgstr "Soft breaks inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:1303 -#, fuzzy msgid "Enable spell checking in Markdown editor" -msgstr "Markdown emoji inschakelen" +msgstr "Spellingscontrole in de Markdown editor inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:789 msgid "Enable spellcheck in the text editor" -msgstr "" +msgstr "Spellingscontrole in de teksteditor inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:976 msgid "Enable table of contents extension" msgstr "Extensie voor inhoudstafel inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:800 -#, fuzzy msgid "Enable the Markdown toolbar" -msgstr "Markdown emoji inschakelen" +msgstr "Markdown werkbalk inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:964 msgid "Enable typographer support" -msgstr "Schakel ondersteuning voor typograaf in" +msgstr "Ondersteuning voor typografie inschakelen" #: packages/lib/models/settings/builtInMetadata.ts:972 msgid "Enable video player" -msgstr "Schakel videospeler in" +msgstr "Videospeler inschakelen" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:105 msgid "Enable Web Clipper Service" @@ -2214,16 +2152,17 @@ msgid "" "Enables Markdown list continuation, auto-closing HTML tags, and other markup " "autocompletions." msgstr "" +"Schakelt automatisch aanvullen van opmaak in, o.a. Markdown-" +"lijstvoortzetting, afluitende HTML-tags, etc." #: packages/lib/components/EncryptionConfigScreen/utils.ts:50 -#, fuzzy msgid "" "Enabling encryption means *all* your notes and attachments are going to be " "re-synchronised and sent encrypted to the sync target." msgstr "" -"Encryptie uitschakelen betekent dat *al* je notities en toevoegingen opnieuw " -"gesynchroniseerd zullen worden en ontsleuteld naar het synchronisatiedoel " -"zullen gestuurd worden. Wil je verder gaan?" +"Encryptie inschakelen betekent dat *al* je notities en toevoegingen opnieuw " +"gesynchroniseerd zullen worden en versleuteld naar het synchronisatiedoel " +"zullen gestuurd worden." #: packages/lib/models/BaseItem.ts:920 msgid "Encrypted" @@ -2240,32 +2179,29 @@ msgstr "Encryptie" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:530 #: packages/app-mobile/components/screens/encryption-config.tsx:298 msgid "Encryption Config" -msgstr "Encryptie configuratie" +msgstr "Encryptie-configuratie" #: packages/app-cli/app/command-e2ee.ts:128 #: packages/app-mobile/components/screens/encryption-config.tsx:313 msgid "Encryption is: %s" -msgstr "Encryptie is: %s" +msgstr "Gebruikte encryptie is: %s" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:160 #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:261 -#, fuzzy msgid "Encryption keys" -msgstr "Versleuteling is:" +msgstr "Encryptie-sleutels" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:237 -#, fuzzy msgid "Encryption:" -msgstr "Encryptie" +msgstr "Encryptie:" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:235 -#, fuzzy msgid "End-to-end encryption" -msgstr "Schakel encryptie in" +msgstr "End-to-end encryptie" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:244 msgid "Ends voice typing" -msgstr "" +msgstr "Beëindigt steminvoer" # Context needed #: packages/app-mobile/components/screens/dropbox-login.tsx:70 @@ -2275,20 +2211,19 @@ msgstr "Code hier ingeven" #: packages/app-cli/app/command-e2ee.ts:39 #: packages/app-cli/app/command-e2ee.ts:85 msgid "Enter master password:" -msgstr "Geef hoofdpaswoord in:" +msgstr "Geef hoofdwachtwoord in:" #: packages/app-mobile/components/screens/folder.js:100 msgid "Enter notebook title" msgstr "Geef titel van notitieboek in" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:122 -#, fuzzy msgid "Enter password" -msgstr "Geef hoofdpaswoord in:" +msgstr "Geef wachtwoord in" #: packages/app-mobile/components/NoteItem.tsx:120 msgid "Entering selection mode" -msgstr "" +msgstr "Bezig met selectiemodus openen" #: packages/app-cli/app/help-utils.js:56 msgid "Enum" @@ -2322,13 +2257,12 @@ msgid "" "Error. Please check that URL, username, password, etc. are correct and that " "the sync target is accessible. The reported error was:" msgstr "" -"Fout. Gelieve te verifiëren of URL, gebruikersnaam, paswoord, etc. correct " -"zijn en of het synchronisatiedoel bereikbaar is. De error was:" +"Fout. Gelieve te verifiëren of URL, gebruikersnaam, wachtwoord, etc. correct " +"zijn en of het synchronisatiedoel bereikbaar is. De gemelde fout was:" #: packages/app-mobile/components/screens/LogScreen.tsx:237 -#, fuzzy msgid "Errors only" -msgstr "Fout" +msgstr "Alleen fouten" #: packages/lib/services/interop/InteropService.ts:76 msgid "Evernote Export File (as HTML)" @@ -2339,26 +2273,24 @@ msgid "Evernote Export File (as Markdown)" msgstr "Evernote Exportbestand (als Markdown)" #: packages/lib/services/interop/InteropService.ts:94 -#, fuzzy msgid "Evernote Export Files (Directory, as HTML)" -msgstr "Evernote Exportbestand (als HTML)" +msgstr "Evernote Exportbestanden (Map, als HTML)" #: packages/lib/services/interop/InteropService.ts:103 -#, fuzzy msgid "Evernote Export Files (Directory, as Markdown)" -msgstr "Evernote Exportbestand (als Markdown)" +msgstr "Evernote Exportbestanden (Map, als Markdown)" #: packages/app-cli/app/command-exit.ts:11 msgid "Exits the application." -msgstr "Sluit de applicatie." +msgstr "Sluit de applicatie af." #: packages/app-mobile/components/side-menu-content.tsx:212 msgid "Expand %s" -msgstr "" +msgstr "%s uitklappen" #: packages/app-desktop/gui/Sidebar/listItemComponents/ExpandIcon.tsx:26 msgid "Expanded, press space to collapse." -msgstr "" +msgstr "Uitgeklapt, druk spatie om in te klappen." #: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:182 #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:213 @@ -2369,56 +2301,51 @@ msgstr "Exporteren" #: packages/app-desktop/gui/MenuBar.tsx:653 #: packages/app-desktop/gui/MenuBar.tsx:709 msgid "Export all" -msgstr "Exporteer alle" +msgstr "Alles exporteren" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.tsx:20 -#, fuzzy msgid "Export all notes as JEX" -msgstr "Exporteer alle" +msgstr "Alle notities exporteren als JEX" #: packages/app-desktop/gui/StatusScreen/StatusScreen.tsx:199 -#, fuzzy msgid "Export debug report" -msgstr "Exporteer debug rapport" +msgstr "Foutopsporingsrapport exporteren" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.tsx:14 msgid "Export Debug Report" -msgstr "Exporteer debug rapport" +msgstr "Foutopsporingsrapport Exporteren" #: packages/app-desktop/commands/exportDeletionLog.ts:11 -#, fuzzy msgid "Export deletion log" -msgstr "Exporteer alle" +msgstr "Verwijderingslog exporteren" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:16 msgid "Export profile" -msgstr "Exporteer profiel" +msgstr "Profiel exporteren" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.tsx:70 msgid "Exported successfully!" -msgstr "" +msgstr "Succesvol geëxporteerd!" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:36 msgid "Exporting profile..." -msgstr "Profiel exporteren ..." +msgstr "Profiel exporteren..." #: packages/app-desktop/InteropServiceHelper.ts:199 msgid "Exporting to \"%s\" as \"%s\" format. Please wait..." -msgstr "Exporteren naar \"%s\" in \"%s\" formaat. Even geduld ..." +msgstr "Exporteren naar \"%s\" in \"%s\" formaat. Even geduld..." #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.tsx:25 -#, fuzzy msgid "Exporting..." -msgstr "Profiel exporteren ..." +msgstr "Bezig met exporteren..." #: packages/app-cli/app/command-export.ts:14 msgid "" "Exports Joplin data to the given path. By default, it will export the " "complete database including notebooks, notes, tags and resources." msgstr "" -"Exporteert Joplin-gegevens naar de opgegeven map. Standaard zal dit de " -"volledige database exporteren, inclusief notitieboeken, notities, tags en " -"bijlagen." +"Exporteert Joplin-gegevens naar het opgegeven pad. Standaard exporteert dit " +"de volledige database,, inclusief notitieboeken, notities,labels en bijlagen." #: packages/app-cli/app/command-export.ts:24 msgid "Exports only the given note." @@ -2430,16 +2357,15 @@ msgstr "Exporteert alleen het opgegeven notitieboek." #: packages/lib/models/settings/builtInMetadata.ts:1444 msgid "Fail-safe" -msgstr "Beveiligd tegen falen" +msgstr "Faalbeveiliging" #: packages/lib/models/settings/builtInMetadata.ts:1445 msgid "" "Fail-safe: Do not wipe out local data when sync target is empty (often the " "result of a misconfiguration or bug)" msgstr "" -"Beveiligd tegen falen: Wis de lokale data niet wanneer de " -"synchronisatiebestemming leeg is (vaak het resultaat van een misconfiguratie " -"of bug)" +"Faalbeveiliging: Wis de lokale data niet wanneer de synchronisatiebestemming " +"leeg is (vaak het resultaat van een misconfiguratie of softwarefout)" #: packages/app-cli/app/main.js:107 msgid "Fatal error:" @@ -2452,7 +2378,7 @@ msgstr "Feature vlaggen" #: packages/lib/Synchronizer.ts:205 msgid "Fetched items: %d/%d." -msgstr "Opgehaalde items: %d/%d." +msgstr "Items opgehaald: %d/%d." #: packages/app-desktop/gui/Sidebar/Sidebar.tsx:53 #: packages/app-mobile/components/side-menu-content.tsx:642 @@ -2469,28 +2395,24 @@ msgstr "Bestandssysteem" #: packages/app-mobile/components/screens/LogScreen.tsx:207 #: packages/app-mobile/components/screens/LogScreen.tsx:208 -#, fuzzy msgid "Filter" -msgstr "Nieuwe tags:" +msgstr "Filter" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:227 -#, fuzzy msgid "Filter tags" -msgstr "Nieuwe tags:" +msgstr "Filter labels" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:14 -#, fuzzy msgid "Find" -msgstr "Gevonden: %d." +msgstr "Vinden" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:253 -#, fuzzy msgid "Find: " -msgstr "Gevonden: %d." +msgstr "Vinden: " #: packages/app-desktop/gui/ExtensionBadge.tsx:65 msgid "Firefox Extension" -msgstr "Firefox extensie" +msgstr "Firefox-extensie" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:552 msgid "Fix search index" @@ -2498,7 +2420,7 @@ msgstr "Zoekindex herstellen" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:552 msgid "Fixing search index..." -msgstr "Zoekindex herstellen ..." +msgstr "Zoekindex herstellen..." #: packages/app-desktop/gui/MenuBar.tsx:866 #: packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.ts:8 @@ -2511,37 +2433,35 @@ msgstr "Focus" #: packages/lib/models/settings/builtInMetadata.ts:832 #: packages/lib/models/settings/builtInMetadata.ts:849 msgid "Focus body" -msgstr "Focus tekst" +msgstr "Tekst focussen" #: packages/lib/models/settings/builtInMetadata.ts:831 #: packages/lib/models/settings/builtInMetadata.ts:848 msgid "Focus title" -msgstr "Focus titel" +msgstr "Titel focussen" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:97 msgid "Follow link" -msgstr "" +msgstr "Volg link" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:38 msgid "For debugging purpose only: export your profile to an external SD card." -msgstr "" -"Enkel met het oog op debugging: exporteer je profiel naar een externe SD " -"card." +msgstr "Alleen voor debugging: exporteer je profiel naar een externe SD-kaart." #: packages/lib/models/settings/builtInMetadata.ts:1424 msgid "For example \"%s\"" -msgstr "" +msgstr "Bijvoorbeeld \"%s\"" #: packages/app-cli/app/command-help.ts:37 msgid "For information on how to customise the shortcuts please visit %s" -msgstr "Voor informatie over hoe de snelkoppeling aan te passen, bezoek %s" +msgstr "Voor informatie over hoe de snelkoppelingen aan te passen, bezoek %s" #: packages/app-mobile/components/screens/encryption-config.tsx:302 msgid "" "For more information about End-To-End Encryption (E2EE) and advice on how to " "enable it please check the documentation:" msgstr "" -"Voor meer informatie over End-To-End Encryptie (E2EE) en advies over hoe u " +"Voor meer informatie over End-To-End Encryptie (E2EE) en advies over hoe je " "dit kunt inschakelen, gelieve de documentatie te raadplegen:" #: packages/app-cli/app/command-help.ts:85 @@ -2556,7 +2476,7 @@ msgstr "Forceer stijl van pad" #: packages/lib/commands/historyForward.ts:6 msgid "Forward" -msgstr "Verder" +msgstr "Vooruit" #: packages/app-cli/app/command-import.ts:50 #: packages/app-desktop/gui/ImportScreen.tsx:89 @@ -2564,18 +2484,16 @@ msgid "Found: %d." msgstr "Gevonden: %d." #: packages/app-mobile/utils/getVersionInfoText.ts:47 -#, fuzzy msgid "FTS enabled: %d" -msgstr "Verwijderen: %d" +msgstr "FTS ingeschakeld: %d" #: packages/app-desktop/checkForUpdates.ts:109 msgid "Full changelog" msgstr "Volledige log van wijzigingen" #: packages/server/src/routes/admin/users.ts:136 -#, fuzzy msgid "Full name" -msgstr "Volledige log van wijzigingen" +msgstr "Volledige naam" #: packages/lib/models/Setting.ts:1172 #: packages/server/src/services/MustacheService.ts:114 @@ -2583,25 +2501,26 @@ msgid "General" msgstr "Algemeen" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:240 -#, fuzzy msgid "Generated" -msgstr "Algemeen" +msgstr "Gegenereerd" #: packages/app-desktop/gui/ShareNoteDialog.tsx:186 msgid "Generating link..." msgid_plural "Generating links..." -msgstr[0] "Generatie koppeling ..." -msgstr[1] "Generatie koppelingen ..." +msgstr[0] "Link genereren..." +msgstr[1] "Links genereren..." #: packages/lib/models/Setting.ts:1215 msgid "Geolocation, spellcheck, editor toolbar, image resize" msgstr "" +"Geo-locatie, spellingscontrole, editor werkbalk, afmetingen afbeelding " +"aanpassen" # Context needed # Is 'get' a bit like download? #: packages/app-desktop/gui/ExtensionBadge.tsx:93 msgid "Get it now:" -msgstr "Nu ophalen:" +msgstr "Download nu:" #: packages/lib/models/settings/builtInMetadata.ts:1199 msgid "Get pre-releases when checking for updates" @@ -2614,26 +2533,25 @@ msgid "" "current configuration." msgstr "" "Haalt een configuratie waarde op of stelt een waarde in. Als [value] niet " -"opgegeven is, zal de waarde van [name] getoond worden. Als noch de [name] of " -"[waarde] opgegeven zijn, zal de huidige configuratie opgelijst worden." +"opgegeven is, zal de waarde van [name] getoond worden. Als noch de [name] " +"noch [waarde] opgegeven zijn, zal de huidige configuratie opgelijst worden." #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:13 msgid "go" -msgstr "" +msgstr "ga" #: packages/app-mobile/components/CameraView/CameraView.tsx:165 msgid "Go back" -msgstr "" +msgstr "Ga terug" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:44 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:59 -#, fuzzy msgid "Go to Joplin Cloud profile" -msgstr "Joplin Forum" +msgstr "Ga naar Joplin Cloud-profiel" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:12 msgid "Go to line" -msgstr "" +msgstr "Ga naar regel" #: packages/app-mobile/components/screens/Note/Note.tsx:1051 msgid "Go to source URL" @@ -2642,55 +2560,51 @@ msgstr "Ga naar bron-URL" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/gotoAnything.ts:13 #: packages/app-desktop/plugins/GotoAnything.tsx:737 msgid "Goto Anything..." -msgstr "Ga naar om het even wat ..." +msgstr "Ga naar om het even wat..." #: packages/app-desktop/gui/Root.tsx:151 -#, fuzzy msgid "Grant authorisation" -msgstr "Autorisatietoken:" +msgstr "Autorisatie verlenen" +# Context unclear #: packages/app-cli/app/command-sync.ts:116 msgid "Have you authorised the application login in the above URL?" -msgstr "" +msgstr "Heb je de applicatie-login geautoriseerd op de URL hierboven?" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:19 msgid "Header %d" -msgstr "" +msgstr "Kop %d" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:95 msgid "Heading" -msgstr "Hoofding" +msgstr "Kop" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:228 #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:265 #: packages/app-desktop/gui/HelpButton.tsx:49 #: packages/server/src/services/MustacheService.ts:284 -#, fuzzy msgid "Help" msgstr "Help" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:113 msgid "Here's what we do to make plugins safer:" -msgstr "" +msgstr "Dit is wat wij doen om plugins veiliger te maken:" #: packages/app-mobile/utils/getVersionInfoText.ts:49 -#, fuzzy msgid "Hermes enabled: %d" -msgstr "Verwijderen: %d" +msgstr "Hermes ingeschakeld: %d" #: packages/app-desktop/gui/MenuBar.tsx:670 msgid "Hide %s" -msgstr "Verberg %s" +msgstr "%s verbergen" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:221 -#, fuzzy msgid "Hide advanced" -msgstr "Verberg metadata" +msgstr "Geavanceerd verbergen" #: packages/server/src/routes/admin/users.ts:206 -#, fuzzy msgid "Hide disabled" -msgstr "Uitgeschakelde toetsen verbergen" +msgstr "Uitgeschakeld verbergen" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:160 msgid "Hide disabled keys" @@ -2701,14 +2615,12 @@ msgid "Hide Joplin" msgstr "Verberg Joplin" #: packages/app-mobile/components/screens/Note/commands/hideKeyboard.ts:7 -#, fuzzy msgid "Hide keyboard" -msgstr "Verberg metadata" +msgstr "Toetsenbord verbergen" #: packages/app-desktop/gui/PasswordInput/PasswordInput.tsx:21 -#, fuzzy msgid "Hide password" -msgstr "Ongeldig antwoord: %s" +msgstr "Wachtwoord verbergen" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:14 msgid "Highlight" @@ -2717,27 +2629,27 @@ msgstr "Markeren" #: packages/server/src/services/MustacheService.ts:150 #: packages/server/src/services/MustacheService.ts:279 msgid "Home" -msgstr "Thuis" +msgstr "Start" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:100 msgid "Horizontal Rule" -msgstr "Horizontale lijn" +msgstr "Horizontale Lijn" #: packages/lib/services/interop/InteropService.ts:186 msgid "HTML Directory" -msgstr "HTML Map" +msgstr "HTML-Map" #: packages/lib/services/interop/InteropService.ts:112 msgid "HTML document" -msgstr "" +msgstr "HTML-document" #: packages/lib/services/interop/InteropService.ts:179 msgid "HTML File" -msgstr "HTML Bestand" +msgstr "HTML-Bestand" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:65 msgid "Hyperlink" -msgstr "Koppeling" +msgstr "Hyperlink" #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:141 msgid "Icon" @@ -2760,74 +2672,74 @@ msgid "" "If you have already authorised, please wait for the application to sync to " "Joplin Cloud." msgstr "" +"Als je al geautoriseerd hebt, gelieve te wachten terwijl de toepassing " +"synchroniseert met Joplin Cloud." #: packages/app-desktop/ElectronAppWrapper.ts:127 #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:360 #: packages/app-desktop/gui/StatusScreen/StatusScreen.tsx:121 #: packages/app-mobile/components/screens/status.tsx:147 msgid "Ignore" -msgstr "Negeer" +msgstr "Negeren" #: packages/lib/models/settings/builtInMetadata.ts:1403 msgid "Ignore TLS certificate errors" msgstr "Negeer TLS-certificaatfouten" #: packages/lib/services/ReportService.ts:236 -#, fuzzy msgid "Ignored items that cannot be synchronised" -msgstr "Items die niet gesynchroniseerd kunnen worden" +msgstr "Genegeerde items die niet gesynchroniseerd kunnen worden" #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:106 msgid "Images" -msgstr "" +msgstr "Afbeeldingen" #: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:181 #: packages/app-desktop/gui/MenuBar.tsx:649 #: packages/app-desktop/gui/MenuBar.tsx:706 #: packages/app-desktop/gui/Root.tsx:191 msgid "Import" -msgstr "Importeer" +msgstr "Importeren" #: packages/lib/models/Setting.ts:1186 -#, fuzzy msgid "Import and Export" -msgstr "Exporteer debug rapport" +msgstr "Importeren en Exporteren" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:66 msgid "" "Import failed. Make sure a JEX file was selected.\n" "Details: %s" msgstr "" +"Importeren gefaald. Controleer of er een JEX-bestand geselecteerd was.\n" +"Details: %s" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:21 msgid "Import from JEX" -msgstr "" +msgstr "Importeren van JEX" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:22 msgid "Import notes from a JEX (Joplin Export) file." -msgstr "" +msgstr "Importeer notities van een JEX (Joplin Export) bestand." #: packages/lib/models/Setting.ts:1218 -#, fuzzy msgid "Import or export your data" -msgstr "Exporteer alle" +msgstr "Importeer of exporteer je gegevens" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:76 msgid "Imported successfully!" -msgstr "" +msgstr "Succesvol geïmporteerd!" #: packages/app-desktop/gui/MenuBar.tsx:319 msgid "Importing from \"%s\" as \"%s\" format. Please wait..." -msgstr "Importeren van \"%s\" in \"%s\" formaat. Even geduld ..." +msgstr "Importeren van \"%s\" in \"%s\" formaat. Even geduld..." #: packages/app-cli/app/command-import.ts:68 msgid "Importing notes..." -msgstr "Notities importeren ..." +msgstr "Notities importeren..." #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:26 -#, fuzzy msgid "Importing..." -msgstr "Profiel exporteren ..." +msgstr "Importeren..." #: packages/app-cli/app/command-import.ts:16 msgid "Imports data into Joplin." @@ -2851,8 +2763,8 @@ msgid "" "note or notebook. `$c` can be used to refer to the currently selected item." msgstr "" "Bij ieder commando kan er naar een notitie of een notitieboek verwezen " -"worden door de titel, het ID of de snelkoppelingen `$n` of `$b` te " -"gebruiken, respectievelijk, het huidig geselecteerde notitieboek of de " +"worden door de titel of het ID, of door de snelkoppelingen `$n` of `$b` te " +"gebruiken voor, respectievelijk, het huidig geselecteerde notitieboek of de " "huidige geselecteerde notitie. `$c` kan gebruikt worden om te refereren naar " "het huidige geselecteerde item." @@ -2863,10 +2775,10 @@ msgid "" "\n" "You may turn off this option at any time in the Configuration screen." msgstr "" -"Om een geo-locatie aan de notitie te koppelen, heeft de app uw toestemming " -"nodig om toegang te krijgen tot uw locatie.\n" +"Om een geo-locatie aan de notitie te koppelen, heeft de app je toestemming " +"nodig om toegang te krijgen tot je locatie.\n" "\n" -"U kunt deze optie op elk moment uitschakelen in de instellingen." +"Je kan deze optie op elk moment uitschakelen in het 'Configuratie'-scherm." #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:346 msgid "" @@ -2884,23 +2796,24 @@ msgid "" "\n" "Important: you only need to run this ONCE on one device." msgstr "" -"Om dit te doen, moet uw volledige dataset worden versleuteld en " -"gesynchroniseerd, dus het is het beste om het 's nachts uit te voeren.\n" +"Om dit te doen, moet je volledige dataset worden versleuteld en " +"gesynchroniseerd, dus het is het best om dit 's nachts uit te voeren.\n" "\n" "Om te beginnen, gelieve deze instructies te volgen:\n" "\n" -"1. Synchroniseer al uw apparaten.\n" +"1. Synchroniseer al je apparaten.\n" "2. Klik op \"%s\".\n" -"3. Laat het tot het einde lopen. Terwijl het loopt, vermijd het veranderen " -"van enige notitie op uw andere apparaten, om conflicten te vermijden.\n" -"4. Zodra de synchronisatie op dit apparaat is voltooid, synchroniseert u " -"alle andere apparaten en laat u de synchronisatie tot het einde uitvoeren.\n" +"3. Laat het tot het einde lopen. Zorg ervoor dat je ondertussen geen " +"notities wijzigt op je andere apparaten, om conflicten te vermijden.\n" +"4. Zodra de synchronisatie op dit apparaat is voltooid, synchroniseer je " +"alle andere apparaten en laat je de synchronisatie tot het einde uitvoeren.\n" "\n" -"Belangrijk: u hoeft dit maar EEN keer op één apparaat te doen." +"Belangrijk: je hoeft dit maar ÉÉN keer op één apparaat te doen." #: packages/lib/services/synchronizer/syncInfoUtils.ts:466 msgid "In order to synchronise, please upgrade your application to version %s+" msgstr "" +"Om te synchroniseren, gelieve je toepassing te upgraden naar versie %s+" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:122 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:224 @@ -2908,16 +2821,16 @@ msgid "" "In order to use file system synchronisation your permission to write to " "external storage is required." msgstr "" -"Om bestandssysteemsynchronisatie te kunnen gebruiken, is uw toestemming " +"Om bestandssysteemsynchronisatie te kunnen gebruiken, is je toestemming " "vereist om naar externe opslag te schrijven." #: packages/app-desktop/gui/ClipperConfigScreen.tsx:121 msgid "In order to use the web clipper, you need to do the following:" -msgstr "Om de web clipper te gebruiken, moet u het volgende doen:" +msgstr "Om de web clipper te gebruiken, moet je het volgende doen:" #: packages/lib/Synchronizer.ts:325 msgid "In progress" -msgstr "In uitvoering" +msgstr "Bezig" #: packages/app-desktop/gui/NoteEditor/NoteEditor.tsx:628 msgid "In: %s" @@ -2925,21 +2838,19 @@ msgstr "In: %s" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx:69 msgid "Incompatible" -msgstr "" +msgstr "Incompatibel" #: packages/app-desktop/gui/NoteList/utils/useOnKeyDown.ts:153 -#, fuzzy msgid "Incomplete" -msgstr "Voltooid" +msgstr "Niet voltooid" #: packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.ts:40 -#, fuzzy msgid "Incomplete to-do" -msgstr "Toon voltooide to-do's" +msgstr "Niet-voltooide taak" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:97 msgid "Increase indent level" -msgstr "" +msgstr "Niveau van inspringing vergroten" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:126 msgid "Indent less" @@ -2951,7 +2862,7 @@ msgstr "Insprong vergroten" #: packages/app-mobile/components/DialogManager/hooks/useDialogControl.ts:21 msgid "Info" -msgstr "" +msgstr "Info" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:223 msgid "Information" @@ -2968,14 +2879,13 @@ msgstr "Invoegen" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx:197 #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.ts:84 msgid "Insert Hyperlink" -msgstr "Koppeling inlassen" +msgstr "Hyperlink invoegen" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:105 #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:779 #: packages/app-mobile/components/screens/Note/commands/insertDateTime.ts:8 -#, fuzzy msgid "Insert time" -msgstr "Datum en tijd inlassen" +msgstr "Tijd invoegen" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:191 #: packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.tsx:18 @@ -2994,9 +2904,8 @@ msgid "Installed" msgstr "Geïnstalleerd" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:202 -#, fuzzy msgid "Installed (%d):" -msgstr "Geïnstalleerd" +msgstr "Geïnstalleerd (%d):" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:192 #: packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.tsx:17 @@ -3025,14 +2934,12 @@ msgid "Invalid option value: \"%s\". Possible values are: %s." msgstr "Ongeldige optie: \"%s\". Geldige waarden zijn: %s." #: packages/app-cli/app/command-e2ee.ts:47 -#, fuzzy msgid "Invalid password" -msgstr "Ongeldig antwoord: %s" +msgstr "Ongeldig wachtwoord" #: packages/app-mobile/utils/getVersionInfoText.ts:24 -#, fuzzy msgid "iOS version: %s" -msgstr "Nieuwe versie: %s" +msgstr "iOS versie: %s" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:60 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:59 @@ -3041,7 +2948,7 @@ msgstr "Cursief" #: packages/lib/services/ReportService.ts:194 msgid "Item \"%s\" could not be downloaded: %s" -msgstr "Item \"%s\" kon niet opgehaald worden: %s" +msgstr "Item \"%s\" kon niet gedownload worden: %s" #: packages/server/src/services/MustacheService.ts:158 #: packages/server/src/services/MustacheService.ts:281 @@ -3050,15 +2957,16 @@ msgstr "Items" #: packages/lib/services/ReportService.ts:252 msgid "Items that cannot be decrypted" -msgstr "Items die niet gedecrypteerd kunnen worden" +msgstr "Items die niet ontsleuteld kunnen worden" #: packages/lib/services/ReportService.ts:185 msgid "Items that cannot be synchronised" msgstr "Items die niet gesynchroniseerd kunnen worden" +# Context onduidelijk #: packages/app-desktop/gui/MenuBar.tsx:923 msgid "Join us on %s" -msgstr "" +msgstr "Volg ons op %s" #: packages/app-desktop/gui/SyncWizard/Dialog.tsx:267 msgid "" @@ -3069,26 +2977,25 @@ msgstr "" "een provider van onderstaande lijst." #: packages/lib/models/Setting.ts:1184 packages/lib/SyncTargetJoplinCloud.ts:30 -#, fuzzy msgid "Joplin Cloud" -msgstr "Joplin Forum" +msgstr "Joplin Cloud" #: packages/app-desktop/gui/Root.tsx:190 #: packages/app-mobile/components/screens/JoplinCloudLoginScreen.tsx:148 -#, fuzzy msgid "Joplin Cloud Login" -msgstr "Joplin Forum" +msgstr "Joplin Cloud Login" #: packages/lib/services/plugins/PluginService.ts:525 -#, fuzzy msgid "Joplin Desktop" -msgstr "Joplin website" +msgstr "Joplin Desktop" #: packages/app-desktop/bridge.ts:448 msgid "" "Joplin doesn't recognise the %s extension. Opening this file could be " "dangerous. What would you like to do?" msgstr "" +"Joplin herkent de %s extensie niet. Dit bestand openen kan gevaarlijk zijn. " +"Wat wil je doen?" #: packages/lib/services/interop/InteropService.ts:159 #: packages/lib/services/interop/InteropService.ts:68 @@ -3106,36 +3013,33 @@ msgid "" "are corrupted or too large. These items will remain on the device but Joplin " "will no longer attempt to decrypt them." msgstr "" -"Joplin is er meerdere malen niet in geslaagd deze items te decrypteren, " -"mogelijk omdat ze corrupt of te groot zijn. Deze items blijven op het " -"apparaat staan, maar Joplin zal niet langer proberen ze te decrypteren." +"Joplin is er meerdere malen niet in geslaagd deze items te ontsleutelen, " +"mogelijk omdat ze beschadigd of te groot zijn. Deze items blijven op het " +"apparaat staan, maar Joplin zal niet langer proberen ze te ontsleutelen." #: packages/app-desktop/gui/MenuBar.tsx:920 msgid "Joplin Forum" msgstr "Joplin Forum" #: packages/app-mobile/utils/lockToSingleInstance.ts:16 -#, fuzzy msgid "Joplin is already running." -msgstr "Server is reeds actief op poort %d" +msgstr "Joplin is al gestart." #: packages/lib/services/plugins/PluginService.ts:523 -#, fuzzy msgid "Joplin Mobile" -msgstr "Joplin website" +msgstr "Joplin Mobile" #: packages/lib/SyncTargetJoplinServer.ts:61 msgid "Joplin Server" msgstr "Joplin Server" #: packages/lib/models/settings/builtInMetadata.ts:321 -#, fuzzy msgid "Joplin Server email" -msgstr "Joplin Server" +msgstr "Joplin Server e-mail" #: packages/lib/models/settings/builtInMetadata.ts:333 msgid "Joplin Server password" -msgstr "Joplin Server paswoord" +msgstr "Joplin Server wachtwoord" #: packages/lib/models/settings/builtInMetadata.ts:302 msgid "Joplin Server URL" @@ -3151,14 +3055,14 @@ msgstr "" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:605 msgid "Joplin website" -msgstr "Joplin website" +msgstr "Joplin-website" #: packages/lib/SyncTargetJoplinCloud.ts:34 msgid "" "Joplin's own sync service. Also gives access to Joplin-specific features " "such as publishing notes or collaborating on notebooks with others." msgstr "" -"Joplins eigen synchronisatie service. Geeft ook toegang tot specifieke " +"Joplins eigen synchronisatieservice. Geeft ook toegang tot Joplin-specifieke " "features zoals het publiceren van notities of samenwerken aan notitieboeken " "met anderen." @@ -3167,9 +3071,8 @@ msgid "Keep note history for" msgstr "Bewaar notitiehistoriek gedurende" #: packages/lib/models/settings/builtInMetadata.ts:1739 -#, fuzzy msgid "Keep notes in the trash for" -msgstr "Bewaar notitiehistoriek gedurende" +msgstr "Bewaar notities in de prullenmand gedurende" #: packages/lib/models/settings/builtInMetadata.ts:1285 msgid "Keyboard Mode" @@ -3188,9 +3091,8 @@ msgid "Keychain Supported: %s" msgstr "Keychain ondersteund: %s" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:74 -#, fuzzy msgid "Keys that need upgrading" -msgstr "Hoofdsleutels hebben upgrade nodig" +msgstr "Sleutels die upgrade nodig hebben" #: packages/lib/models/settings/builtInMetadata.ts:1262 msgid "Landscape" @@ -3201,9 +3103,8 @@ msgid "Language" msgstr "Taal" #: packages/lib/models/Setting.ts:1210 -#, fuzzy msgid "Language, date format" -msgstr "Datumformaat" +msgstr "Taal, datumformaat" #: packages/lib/Synchronizer.ts:208 msgid "Last error: %s" @@ -3215,7 +3116,7 @@ msgstr "Later" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:7 msgid "Latitude" -msgstr "" +msgstr "Breedtegraad" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx:638 #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/CodeMirror.tsx:237 @@ -3224,28 +3125,25 @@ msgstr "Layout" #: packages/app-desktop/gui/MenuBar.tsx:789 msgid "Layout button sequence" -msgstr "Volgorde layout-knop" +msgstr "Volgorde layout-knoppen" #: packages/app-desktop/bridge.ts:453 #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:118 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx:52 -#, fuzzy msgid "Learn more" -msgstr "Insprong vergroten" +msgstr "Meer leren" #: packages/lib/models/settings/builtInMetadata.ts:1692 msgid "Leave it blank to download the language files from the default website" -msgstr "" +msgstr "Laat leeg om de taalbestanden van de standard website te downloaden" #: packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.tsx:90 -#, fuzzy msgid "Leave notebook" -msgstr "Notitie delen ..." +msgstr "Verlaat notitieboek" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.ts:11 -#, fuzzy msgid "Leave notebook..." -msgstr "Notitie delen ..." +msgstr "Verlaat notitieboek..." #: packages/lib/models/settings/builtInMetadata.ts:1256 msgid "Legal" @@ -3264,55 +3162,49 @@ msgid "" "Like any software you install, plugins can potentially cause security issues " "or data loss." msgstr "" +"Zoals elke software die je installeert, kunnen plugins mogelijk " +"beveiligingsproblemen of gegevensverlies veroorzaken." #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:108 msgid "Lines" msgstr "Regels" -# Unclear to me what 'strong' means. Bold? And what is the difference with 'emphasised'? #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:107 -#, fuzzy msgid "Link" -msgstr "benadrukte text" +msgstr "Link" #: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:97 -#, fuzzy msgid "Link description" -msgstr "Encryptie" +msgstr "Beschrijving van link" #: packages/app-desktop/gui/ShareNoteDialog.tsx:187 msgid "Link has been copied to clipboard!" msgid_plural "Links have been copied to clipboard!" -msgstr[0] "Koppeling is gekopieerd naar klembord!" -msgstr[1] "Koppelingen zijn gekopieerd naar klembord!" +msgstr[0] "Link is gekopieerd naar klembord!" +msgstr[1] "Links zijn gekopieerd naar klembord!" -# Unclear to me what 'strong' means. Bold? And what is the difference with 'emphasised'? #: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:94 -#, fuzzy msgid "Link text" -msgstr "benadrukte text" +msgstr "Tekst van link" # Unclear. Context needed. #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx:231 #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx:233 #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx:234 msgid "List item" -msgstr "Lijst item" +msgstr "Lijstitem" #: packages/app-mobile/components/screens/encryption-config.tsx:220 -#, fuzzy msgid "Loaded" -msgstr "Opgehaald" +msgstr "Geladen" #: packages/app-desktop/gui/ConfigScreen/controls/FontSearch.tsx:123 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:117 #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:179 #: packages/app-mobile/root.tsx:1247 -#, fuzzy msgid "Loading..." -msgstr "Updaten ..." +msgstr "Laden..." -# Context needed #: packages/app-desktop/gui/NotePropertiesDialog.tsx:71 msgid "Location" msgstr "Locatie" @@ -3324,8 +3216,8 @@ msgid "" "taking place, you may delete the lock file at \"%s\" and resume the " "operation." msgstr "" -"Vergrendelingsbestand is al bezet. Als u zeker bent dat er geen " -"synchronisatie bezig is, mag u het vergrendelingsbestand verwijderen op " +"Vergrendelingsbestand is al bezet. Als je zeker bent dat er geen " +"synchronisatie bezig is, mag je het vergrendelingsbestand verwijderen op " "\"%s\" en de synchronisatie hervatten." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:550 @@ -3335,17 +3227,16 @@ msgid "Log" msgstr "Log" #: packages/app-desktop/gui/MainScreen.tsx:563 -#, fuzzy msgid "Login to Joplin Cloud." -msgstr "Joplin Forum" +msgstr "Inloggen op Joplin Cloud." #: packages/app-mobile/components/screens/dropbox-login.tsx:59 msgid "Login with Dropbox" -msgstr "Login with Dropbox" +msgstr "Inloggen met Dropbox" #: packages/app-mobile/components/screens/onedrive-login.js:110 msgid "Login with OneDrive" -msgstr "Log in met OneDrive" +msgstr "Inloggen met OneDrive" #: packages/server/src/services/MustacheService.ts:285 msgid "Logout" @@ -3353,11 +3244,11 @@ msgstr "Afmelden" #: packages/lib/models/Setting.ts:1217 msgid "Logs, profiles, sync status" -msgstr "" +msgstr "Logs, profielen, synchronisatie-status" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:8 msgid "Longitude" -msgstr "" +msgstr "Lengtegraad" #: packages/app-desktop/gui/MenuBar.tsx:926 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:604 @@ -3365,48 +3256,41 @@ msgid "Make a donation" msgstr "Doe een gift" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:221 -#, fuzzy msgid "Manage master password" -msgstr "Geef hoofdpaswoord in:" +msgstr "Beheer hoofdwachtwoord" #: packages/lib/commands/openMasterPasswordDialog.ts:6 -#, fuzzy msgid "Manage master password..." -msgstr "Geef hoofdpaswoord in:" +msgstr "Beheer hoofdwachtwoord..." #: packages/lib/utils/joplinCloud/index.ts:201 -#, fuzzy msgid "Manage multiple users" -msgstr "Geef hoofdpaswoord in:" +msgstr "Beheer meedere gebruikers" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:548 -#, fuzzy msgid "Manage profiles" -msgstr "Exporteer profiel" +msgstr "Beheer profielen" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:555 -#, fuzzy msgid "Manage shared notebooks" -msgstr "Exporteer profiel" +msgstr "Beheer gedeelde notitieboeken" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:165 -#, fuzzy msgid "Manage toolbar options" -msgstr "Beheer uw plugins" +msgstr "Beheer werkbalk-opties" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:331 msgid "Manage your plugins" -msgstr "Beheer uw plugins" +msgstr "Beheer je plugins" #. `generate-ppk` #: packages/app-cli/app/command-e2ee.ts:19 -#, fuzzy msgid "" "Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, " "`status`, `decrypt-file`, and `target-status`." msgstr "" -"Beheert E2EE configuratie. Commando's zijn `enable`, `disable`, `decrypt`, " -"`status` and `target-status`." +"Beheert E2EE-configuratie. Commando's zijn `enable`, `disable`, `decrypt`, " +"`status`, `decrypt-file`, en `target-status`." #: packages/lib/models/settings/builtInMetadata.ts:397 msgid "Manual" @@ -3426,17 +3310,16 @@ msgstr "Markdown + Front Matter" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/CodeMirror.tsx:375 #: packages/app-mobile/components/NoteEditor/NoteEditor.tsx:352 -#, fuzzy msgid "Markdown editor" -msgstr "Markdown" +msgstr "Markdown editor" #: packages/app-cli/app/command-done.ts:15 msgid "Marks a to-do as done." -msgstr "Markeert een to-do als voltooid." +msgstr "Markeert een taak als voltooid." #: packages/app-cli/app/command-undone.js:12 msgid "Marks a to-do as non-completed." -msgstr "Markeert een to-do als onvoltooid." +msgstr "Markeert een taak als onvoltooid." #: packages/app-desktop/gui/NotePropertiesDialog.tsx:74 msgid "Markup" @@ -3444,28 +3327,26 @@ msgstr "Opmaak" #: packages/app-mobile/components/screens/encryption-config.tsx:124 msgid "Master Key %s" -msgstr "Hoofdsleutel: %s" +msgstr "Hoofdsleutel %s" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:114 #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:273 #: packages/app-mobile/components/screens/encryption-config.tsx:109 -#, fuzzy msgid "Master password" -msgstr "Geef hoofdpaswoord in:" +msgstr "Hoofdwachtwoord" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:274 #: packages/app-mobile/components/screens/encryption-config.tsx:219 -#, fuzzy msgid "Master password:" -msgstr "Geef hoofdpaswoord in:" +msgstr "Hoofdwachtwoord:" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:19 msgid "match case" -msgstr "" +msgstr "hoofdlettergebruik evenaren" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:72 msgid "Math" -msgstr "" +msgstr "Wiskunde" #: packages/lib/models/settings/builtInMetadata.ts:421 msgid "Max concurrent connections" @@ -3473,35 +3354,31 @@ msgstr "Maximum aantal gelijktijdige connecties" #: packages/server/src/routes/admin/users.ts:148 msgid "Max Item Size" -msgstr "" +msgstr "Maximale grootte van items" #: packages/lib/utils/joplinCloud/index.ts:129 -#, fuzzy msgid "Max note or attachment size" -msgstr "Notitiebijlagen" +msgstr "Maximale grootte van notities of bijlagen" #: packages/server/src/routes/admin/users.ts:156 -#, fuzzy msgid "Max Total Size" -msgstr "Huidige grootte" +msgstr "Maximale totale grootte" #: packages/lib/models/Setting.ts:1214 msgid "Media player, math, diagrams, table of contents" -msgstr "" +msgstr "Mediaspeler, wiskunde, diagrammen, inhoudstafel" #: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:24 msgid "Minimise" -msgstr "" +msgstr "Minimaliseren" #: packages/app-mobile/components/CameraView/CameraView.tsx:163 -#, fuzzy msgid "Missing camera permission" -msgstr "Ontbrekende Hoofdsleutels" +msgstr "Toestemming voor camera ontbreekt" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:320 -#, fuzzy msgid "Missing keys" -msgstr "Ontbrekende Hoofdsleutels" +msgstr "Ontbrekende sleutels" #: packages/app-mobile/components/screens/encryption-config.tsx:282 msgid "Missing Master Keys" @@ -3509,12 +3386,11 @@ msgstr "Ontbrekende Hoofdsleutels" #: packages/app-cli/app/cli-utils.js:112 msgid "Missing required argument: %s" -msgstr "Benodigde argumenten niet voorzien: %s" +msgstr "Vereist argument ontbreekt: %s" #: packages/app-cli/app/cli-utils.js:135 -#, fuzzy msgid "Missing required flag value: %s" -msgstr "Benodigde argumenten niet voorzien: %s" +msgstr "Vereiste argumentwaarde ontbreekt: %s" #: packages/app-mobile/components/side-menu-content.tsx:663 msgid "Mobile data - auto-sync disabled" @@ -3531,12 +3407,14 @@ msgstr "Meer informatie" #: packages/app-cli/app/app.ts:67 msgid "More than one item match \"%s\". Please narrow down your query." msgstr "" -"Meer dan één item voldoet aan de zoekterm \"%s\". Verfijn uw zoekterm a.u.b." +"Meer dan één item voldoet aan de zoekterm \"%s\". Verfijn je zoekopdracht." #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:115 msgid "" "Most plugins have source code available for review on the plugin website." msgstr "" +"De meeste plugins hebben broncode die je kunt bekijken op de website van de " +"plugin." #: packages/app-mobile/components/ScreenHeader/index.tsx:546 msgid "Move %d notes to notebook \"%s\"?" @@ -3545,17 +3423,16 @@ msgstr "Verplaats %d notities naar notitieboek \"%s\"?" #: packages/app-cli/app/command-rmbook.ts:38 #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/deleteFolder.ts:20 #: packages/app-mobile/components/side-menu-content.tsx:410 -#, fuzzy msgid "" "Move notebook \"%s\" to the trash?\n" "\n" "All notes and sub-notebooks within this notebook will also be moved to the " "trash." msgstr "" -"Notitieboek \"%s\" verwijderen?\n" +"Notitieboek \"%s\" naar de prullenmand verplaatsen?\n" "\n" -"Alle notities en sub-notitieboeken in dit notitieboek zullen ook verwijderd " -"worden." +"Alle notities en sub-notitieboeken in dit notitieboek zullen ook naar de " +"prullenmand verplaatst worden." #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.ts:14 msgid "Move to notebook" @@ -3570,9 +3447,8 @@ msgid "Move to notebook..." msgstr "Verplaats naar notitieboek..." #: packages/app-cli/app/command-mv.ts:14 -#, fuzzy msgid "Moves the given to [notebook]" -msgstr "Verplaatst de notities die voldoen aan naar [notitieboek]." +msgstr "Verplaatst het gegeven naar [notitieboek]" #: packages/app-cli/app/cli-utils.js:177 #: packages/app-cli/app/setupCommand.ts:23 @@ -3585,22 +3461,19 @@ msgstr "N" #: packages/lib/models/settings/builtInMetadata.ts:868 msgid "Never resize" -msgstr "" +msgstr "Nooit formaat wijzigen" #: packages/app-mobile/setupQuickActions.ts:33 -#, fuzzy msgid "New attachment" -msgstr "Notitiebijlagen" +msgstr "Nieuwe bijlage" #: packages/app-mobile/setupQuickActions.ts:34 -#, fuzzy msgid "New drawing" -msgstr "Nieuwe tags:" +msgstr "Nieuwe tekening" #: packages/app-mobile/components/screens/ShareManager/index.tsx:120 -#, fuzzy msgid "New invitations" -msgstr "Nieuwe versie: %s" +msgstr "Nieuwe uitnodigingen" #: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:111 #: packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.tsx:72 @@ -3623,13 +3496,12 @@ msgstr "Nieuw Notitieboek" msgid "" "New notebook \"%s\" will be created and file \"%s\" will be imported into it" msgstr "" -"Nieuw notitieboek \"%s\" zal aangemaakt worden en bestand \"%s\" wordt eraan " -"toegevoegd" +"Nieuw notitieboek \"%s\" zal aangemaakt worden en bestand \"%s\" wordt " +"ernaar geïmporteerd" #: packages/app-mobile/setupQuickActions.ts:32 -#, fuzzy msgid "New photo" -msgstr "Maak en foto" +msgstr "Nieuwe foto" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newSubFolder.ts:6 msgid "New sub-notebook" @@ -3637,7 +3509,7 @@ msgstr "Nieuw sub-notitieboek" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:206 msgid "New tags:" -msgstr "Nieuwe tags:" +msgstr "Nieuwe labels:" #: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:121 #: packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.tsx:72 @@ -3645,7 +3517,7 @@ msgstr "Nieuwe tags:" #: packages/app-mobile/components/screens/Notes.tsx:257 #: packages/app-mobile/setupQuickActions.ts:31 msgid "New to-do" -msgstr "Nieuwe to-do" +msgstr "Nieuwe taak" #: packages/app-desktop/checkForUpdates.ts:108 msgid "New version: %s" @@ -3653,11 +3525,11 @@ msgstr "Nieuwe versie: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:16 msgid "next" -msgstr "" +msgstr "volgende" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:271 msgid "Next match" -msgstr "" +msgstr "Volgende overeenkomst" #: packages/lib/SyncTargetNextcloud.js:25 msgid "Nextcloud" @@ -3665,25 +3537,25 @@ msgstr "Nextcloud" #: packages/lib/models/settings/builtInMetadata.ts:165 msgid "Nextcloud password" -msgstr "Nextcloud paswoord" +msgstr "Nextcloud-wachtwoord" #: packages/lib/models/settings/builtInMetadata.ts:153 msgid "Nextcloud username" -msgstr "Nextcloud username" +msgstr "Nextcloud-gebruikersnaam" #: packages/lib/models/settings/builtInMetadata.ts:140 msgid "Nextcloud WebDAV URL" -msgstr "Nextcloud WebDAV URL" +msgstr "Nextcloud WebDAV-URL" #: packages/lib/models/settings/builtInMetadata.ts:30 msgid "no" -msgstr "neen" +msgstr "nee" #: packages/app-desktop/services/plugins/UserWebviewDialogButtonBar.tsx:22 #: packages/app-mobile/components/screens/Note/Note.tsx:714 #: packages/lib/shim-init-node.ts:265 packages/lib/versionInfo.ts:91 msgid "No" -msgstr "Neen" +msgstr "Nee" #: packages/app-cli/app/command-edit.ts:41 msgid "No active notebook." @@ -3691,7 +3563,7 @@ msgstr "Geen actief notitieboek." #: packages/app-mobile/components/screens/ShareManager/index.tsx:92 msgid "No new invitations" -msgstr "" +msgstr "Geen nieuwe uitnodigingen" #: packages/app-cli/app/app.ts:104 msgid "No notebook has been specified." @@ -3703,11 +3575,12 @@ msgstr "Geen notitieboek geselecteerd." #: packages/lib/components/shared/NoteList/getEmptyFolderMessage.ts:15 msgid "No notes in here. Create one by clicking on \"New note\"." -msgstr "Geen notities. Maak een notitie door op \"Nieuwe notitie\" te klikken." +msgstr "" +"Geen notities hier. Maak een notitie door op \"Nieuwe notitie\" te klikken." #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:202 msgid "No plugins are installed." -msgstr "" +msgstr "Er zijn geen plugins geïnstalleerd." #: packages/app-desktop/gui/ResourceScreen.tsx:305 msgid "No resources!" @@ -3726,9 +3599,8 @@ msgid "No suggestions" msgstr "Geen suggesties" #: packages/app-mobile/components/plugins/dialogs/PluginPanelViewer.tsx:120 -#, fuzzy msgid "No tab selected" -msgstr "Geen notitieboek geselecteerd." +msgstr "Geen tab geselecteerd" #: packages/app-cli/app/command-edit.ts:31 msgid "" @@ -3736,14 +3608,12 @@ msgid "" msgstr "Geen editor gedefinieerd. Stel in met `config editor `" #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:93 -#, fuzzy msgid "No updates available" -msgstr "Exporteer profiel" +msgstr "Geen updates beschikbaar" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.ts:44 -#, fuzzy msgid "None" -msgstr "(None)" +msgstr "Geen" #: packages/lib/models/settings/builtInMetadata.ts:47 msgid "Nord" @@ -3751,21 +3621,19 @@ msgstr "Nord" #: packages/app-cli/app/command-sync.ts:123 msgid "Not authenticated with %s. Please provide any missing credentials." -msgstr "" -"Niet geverifieerd voor %s. Gelieve ontbrekende referenties in te vullen." +msgstr "Niet geauthenticeerd met %s. Voer ontbrekende inloggegevens in." #: packages/lib/models/Resource.ts:407 msgid "Not downloaded" -msgstr "Niet opgehaald" +msgstr "Niet gedownload" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:240 msgid "Not generated" msgstr "Niet gegenereerd" #: packages/app-mobile/components/biometrics/BiometricPopup.tsx:91 -#, fuzzy msgid "Not now" -msgstr "Nu doen" +msgstr "Niet nu" #: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:109 #: packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.tsx:71 @@ -3782,7 +3650,7 @@ msgstr "Notitie" #: packages/lib/models/settings/builtInMetadata.ts:1549 msgid "Note area growth factor" -msgstr "Groeifactor notitiegrootte" +msgstr "Groeifactor notitiegebied" #: packages/app-desktop/gui/Root.tsx:193 msgid "Note attachments" @@ -3802,9 +3670,8 @@ msgstr "Notitie bestaat niet: \"%s\". Aanmaken?" #: packages/app-desktop/gui/NoteTextViewer.tsx:228 #: packages/app-mobile/components/NoteEditor/NoteEditor.tsx:139 -#, fuzzy msgid "Note editor" -msgstr "Notitiehistoriek" +msgstr "Notitie-editor" #: packages/app-cli/app/command-edit.ts:98 msgid "Note has been saved." @@ -3817,7 +3684,7 @@ msgstr "Notitiehistoriek" #: packages/app-cli/app/command-done.ts:23 msgid "Note is not a to-do: \"%s\"" -msgstr "Notitie is geen to-do: \"%s\"" +msgstr "Notitie is geen taak: \"%s\"" #: packages/app-desktop/gui/NoteList/commands/focusElementNoteList.ts:9 #: packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.tsx:181 @@ -3829,19 +3696,18 @@ msgid "Note list growth factor" msgstr "Groeifactor notitielijst" #: packages/app-desktop/gui/MenuBar.tsx:793 -#, fuzzy msgid "Note list style" -msgstr "Notitielijst" +msgstr "Notitielijst-stijl" #: packages/app-desktop/gui/NotePropertiesDialog.tsx:451 #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteProperties.ts:7 msgid "Note properties" -msgstr "Eigenschappen" +msgstr "Eigenschappen van notitie" #: packages/app-desktop/gui/NoteEditor/commands/focusElementNoteTitle.ts:7 #: packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx:124 msgid "Note title" -msgstr "Notitietitel" +msgstr "Titel van notitie" # Clarification needed. # Why not '... desktop OS'? @@ -3853,19 +3719,17 @@ msgstr "Opmerking: werkt niet in alle desktop-omgevingen." msgid "" "Note: When a note is shared, it will no longer be encrypted on the server." msgstr "" -"Opmerking: wanneer een notitie gedeeld wordt, is ze niet langer " -"geëncrypteerd op de server." +"Opmerking: wanneer een notitie gedeeld wordt, is ze niet langer versleuteld " +"op de server." #: packages/app-desktop/gui/MenuBar.tsx:872 -#, fuzzy msgid "Note&book" -msgstr "Notitieboeken" +msgstr "Notitie&boek" #: packages/app-desktop/plugins/GotoAnything.tsx:570 #: packages/lib/models/Setting.ts:1176 -#, fuzzy msgid "Notebook" -msgstr "Notitieboeken" +msgstr "Notitieboek" #: packages/lib/models/settings/builtInMetadata.ts:1519 msgid "Notebook list growth factor" @@ -3877,9 +3741,8 @@ msgid "Notebook: %s" msgstr "Notitieboek: %s" #: packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.tsx:81 -#, fuzzy msgid "Notebook: %s (%s)" -msgstr "Notitieboek: %s" +msgstr "Notitieboek: %s (%s)" #: packages/app-desktop/gui/Sidebar/hooks/useSidebarListData.ts:50 #: packages/app-mobile/components/side-menu-content.tsx:686 @@ -3890,15 +3753,14 @@ msgstr "Notitieboeken" #: packages/lib/models/Folder.ts:906 msgid "Notebooks cannot be named \"%s\", which is a reserved title." msgstr "" -"Notitieboeken kunnen niet \"%s\" genoemd worden, dit is een gereserveerd " -"woord." +"Notitieboeken kunnen niet \"%s\" genoemd worden, dit is een gereserveerde " +"titel." #: packages/app-desktop/gui/NoteList/NoteList2.tsx:289 #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleNotesSortOrderField.ts:8 #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleNotesSortOrderReverse.ts:9 -#, fuzzy msgid "Notes" -msgstr "Notitie" +msgstr "Notities" #: packages/lib/models/Setting.ts:1199 msgid "Notes and settings are stored in: %s" @@ -3911,16 +3773,15 @@ msgstr "Notities kunnen enkel in een notitieboek aangemaakt worden." #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:80 msgid "Numbered List" -msgstr "Nummering" +msgstr "Genummerde Lijst" #: packages/lib/models/settings/builtInMetadata.ts:531 msgid "OCR: Clear cache and re-download language data files" -msgstr "" +msgstr "OCR: Cache wissen en taalgegevensbestanden opnieuw downloaden" #: packages/lib/models/settings/builtInMetadata.ts:512 -#, fuzzy msgid "OCR: Language data URL or path" -msgstr "Datumformaat" +msgstr "OCR: Taalgegevens-URL of -pad" #: packages/app-desktop/bridge.ts:360 packages/app-desktop/bridge.ts:373 #: packages/app-desktop/bridge.ts:387 packages/app-desktop/bridge.ts:403 @@ -3958,11 +3819,11 @@ msgstr "Op %s: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:27 msgid "on line" -msgstr "" +msgstr "op regel" #: packages/app-desktop/gui/MainScreen.tsx:525 msgid "One of your master keys use an obsolete encryption method." -msgstr "Eén van uw hoofdsleutels gebruikt een verouderde encryptiemethode." +msgstr "Eén van je hoofdsleutels gebruikt een verouderde encryptiemethode." #: packages/app-cli/app/gui/NoteWidget.js:48 msgid "" @@ -3971,14 +3832,14 @@ msgid "" "supplied the password, the encrypted items are being decrypted in the " "background and will be available soon." msgstr "" -"Eén of meerdere items zijn momenteel versleuteld en de hoofdsleutel kan " -"gevraagd worden. Om te ontsleutelen, typ `e2ee decrypt`. Als je de " -"hoofdsleutel al ingegeven hebt, worden de versleutelde items ontsleuteld in " -"de achtergrond. Ze zijn binnenkort beschikbaar." +"Eén of meerdere items zijn momenteel versleuteld en het hoofdwachtwoord kan " +"gevraagd worden. Om te ontsleutelen, typ `e2ee decrypt`. Als je het " +"wachtwoord al ingegeven hebt, worden de versleutelde items ontsleuteld in de " +"achtergrond. Ze zijn binnenkort beschikbaar." #: packages/app-desktop/gui/MainScreen.tsx:554 msgid "One or more master keys need a password." -msgstr "Eén of meer hoofdsleutels moeten een paswoord hebben." +msgstr "Eén of meer hoofdsleutels moeten een wachtwoord hebben." #: packages/lib/SyncTargetOneDrive.ts:36 msgid "OneDrive" @@ -3986,12 +3847,11 @@ msgstr "OneDrive" #: packages/app-desktop/gui/Root.tsx:188 msgid "OneDrive Login" -msgstr "OneDrive Login" +msgstr "OneDrive-Login" #: packages/lib/services/interop/InteropService.ts:144 -#, fuzzy msgid "OneNote Notebook" -msgstr "Nieuw Notitieboek" +msgstr "OneNote Notitieboek" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/print.ts:18 msgid "Only one note can be printed at a time." @@ -3999,35 +3859,31 @@ msgstr "Er kan slechts één notitie tegelijk afgedrukt worden." #: packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.ts:40 msgid "Open" -msgstr "Open" +msgstr "Openen" #: packages/app-desktop/app.ts:217 msgid "Open %s" msgstr "Open %s" #: packages/app-desktop/bridge.ts:454 -#, fuzzy msgid "Open it" -msgstr "Open" +msgstr "Openen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/openPdfViewer.ts:7 -#, fuzzy msgid "Open PDF viewer" -msgstr "Schakel PDF weergave in" +msgstr "Open PDF-weergave" #: packages/app-desktop/commands/openProfileDirectory.ts:8 msgid "Open profile directory" msgstr "Open profielmap" #: packages/app-mobile/components/CameraView/CameraView.tsx:164 -#, fuzzy msgid "Open settings" -msgstr "Toon geavanceerde opties" +msgstr "Open instellingen" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:115 -#, fuzzy msgid "Open Source" -msgstr "Bron" +msgstr "Open Bron" #: packages/lib/models/settings/builtInMetadata.ts:73 msgid "Open Sync Wizard..." @@ -4035,26 +3891,23 @@ msgstr "Sync Wizard openen..." #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:87 msgid "Open..." -msgstr "Openen ..." +msgstr "Openen..." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:206 msgid "Opening section %s" -msgstr "" +msgstr "Sectie %s openen" #: packages/app-mobile/components/Dropdown.tsx:215 -#, fuzzy msgid "Opens dropdown" -msgstr "Venster sluiten" +msgstr "Opent keuzelijst" #: packages/app-mobile/components/NoteItem.tsx:162 -#, fuzzy msgid "Opens note" -msgstr "Open" +msgstr "Opent notitie" #: packages/app-mobile/components/side-menu-content.tsx:273 -#, fuzzy msgid "Opens notebook" -msgstr "Nieuw notitieboek" +msgstr "Opent notitieboek" #: packages/app-cli/app/command-e2ee.ts:41 #: packages/app-cli/app/command-e2ee.ts:87 @@ -4069,14 +3922,12 @@ msgid "Options" msgstr "Opties" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:77 -#, fuzzy msgid "Ordered list" -msgstr "Aangemaakt: %s" +msgstr "Geordende lijst" #: packages/app-desktop/gui/MenuBar.tsx:518 -#, fuzzy msgid "Other applications..." -msgstr "Sluit de applicatie." +msgstr "Andere toepassingen..." #: packages/app-cli/app/command-import.ts:29 msgid "Output format: %s" @@ -4112,12 +3963,12 @@ msgstr "Wachtwoorden komen niet overeen!" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.ts:105 #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:197 msgid "Paste" -msgstr "Plak" +msgstr "Plakken" #: packages/app-desktop/gui/NoteEditor/commands/pasteAsText.ts:6 #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:211 msgid "Paste as text" -msgstr "" +msgstr "Plakken als tekst" #: packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.tsx:261 #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:54 @@ -4127,41 +3978,40 @@ msgstr "Pad:" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/exportPdf.ts:11 #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/exportPdf.ts:25 msgid "PDF File" -msgstr "PDF Bestand" +msgstr "PDF-Bestand" #: packages/lib/utils/joplinCloud/index.ts:408 msgid "Per user. Minimum of %d users." -msgstr "" +msgstr "Per gebruiker. Minimum van %d gebruikers." #: packages/lib/commands/permanentlyDeleteNote.ts:8 -#, fuzzy msgid "Permanently delete note" -msgstr "Deze notities verwijderen?" +msgstr "Notitie permanent verwijderen" #: packages/lib/models/Note.ts:943 -#, fuzzy msgid "Permanently delete note \"%s\"?" -msgstr "Notities \"%s\" verwijderen?" +msgstr "Notitie \"%s\" permanent verwijderen?" #: packages/app-cli/app/command-rmbook.ts:36 -#, fuzzy msgid "" "Permanently delete notebook \"%s\"?\n" "\n" "All notes and sub-notebooks within this notebook will be permanently deleted." msgstr "" -"Notitieboek verwijderen? Alle notities en sub-notitieboeken in dit " -"notitieboek zullen ook verwijderd worden." +"Notitieboek \"%s\" permanent verwijderen?\n" +"\n" +"Alle notities en sub-notitieboeken in dit notitieboek zullen ook permanent " +"verwijderd worden." #: packages/lib/models/Note.ts:945 -#, fuzzy msgid "Permanently delete these %d notes?" -msgstr "Deze notities %d verwijderen?" +msgstr "Deze %d notities permanent verwijderen?" #: packages/app-cli/app/command-rmbook.ts:20 -#, fuzzy msgid "Permanently deletes the notebook, skipping the trash." -msgstr "Deze notities %d verwijderen?" +msgstr "" +"Verwijdert het notitieboek permanent, zonder het naar de prullenmand te " +"verplaatsen." #: packages/app-mobile/components/screens/Note/Note.tsx:504 msgid "Permission needed" @@ -4172,18 +4022,21 @@ msgid "" "Please click on \"%s\" to proceed, or set the passwords in the \"%s\" list " "below." msgstr "" +"Klik op \"%s\" om door te gaan, of zet de wachtwoorden in de \"%s\" lijst " +"hieronder." #: packages/lib/components/EncryptionConfigScreen/utils.ts:64 msgid "" "Please confirm that you would like to re-encrypt your complete database." -msgstr "Gelieve te bevestigen dat u uw hele databank opnieuw wil encrypteren." +msgstr "" +"Gelieve te bevestigen dat je je hele databank opnieuw wil versleutelen." #: packages/lib/components/EncryptionConfigScreen/utils.ts:213 msgid "" "Please enter your password in the master key list below before upgrading the " "key." msgstr "" -"Gelieve uw paswoord in te geven in de lijst hoofdsleutels hieronder, " +"Gelieve je wachtwoord in te geven in de lijst hoofdsleutels hieronder, " "alvorens de sleutel te upgraden." #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:380 @@ -4203,15 +4056,15 @@ msgid "" "any files outside this directory nor to any other personal data. No data " "will be shared with any third party." msgstr "" -"Open de volgende URL in uw browser om de toepassing te verifiëren. De " +"Open de volgende URL in je browser om de toepassing te verifiëren. De " "toepassing zal een map aanmaken in \"Apps/Joplin\" en zal enkel in déze map " -"bestanden lezen en schrijven. Het zal geen toegang hebben tot bestanden " +"bestanden lezen en schrijven. Ze zal geen toegang hebben tot bestanden " "buiten deze map, noch tot andere persoonlijke gegevens. Er worden geen " "gegevens gedeeld met derden." #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:180 msgid "Please record your voice..." -msgstr "" +msgstr "Neem je stem op..." #: packages/app-cli/app/command-ls.ts:66 msgid "Please select a notebook first." @@ -4219,11 +4072,11 @@ msgstr "Selecteer eerst een notitieboek." #: packages/app-cli/app/app-gui.js:468 msgid "Please select the note or notebook to be deleted first." -msgstr "Selecteer eerst het notitieboek of de notitie om te verwijderen." +msgstr "Selecteer eerst het notitieboek of de notitie dat je wil verwijderen." #: packages/app-desktop/gui/StatusScreen/StatusScreen.tsx:31 msgid "Please select where the sync status should be exported to" -msgstr "Selecteer waar de synchronisatie status naar geëxporteerd moet worden" +msgstr "Selecteer waar de synchronisatiestatus naar geëxporteerd moet worden" #: packages/lib/services/interop/InteropService.ts:309 msgid "Please specify import format for %s" @@ -4235,46 +4088,43 @@ msgstr "" "Specifieer het notitieboek waarin de notities moeten geïmporteerd worden." #: packages/lib/services/plugins/PluginService.ts:519 -#, fuzzy msgid "Please upgrade Joplin to version %s or later to use this plugin." -msgstr "Gelieve Joplin te upgraden om deze plugin te gebruiken" +msgstr "" +"Gelieve Joplin te upgraden naar versie %s of later om deze plugin te " +"gebruiken." #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:1444 msgid "" "Please wait for all attachments to be downloaded and decrypted. You may also " "switch to %s to edit the note." msgstr "" -"Gelieve te wachten tot alle bijlagen opgehaald en gedecrypteerd zijn. U kan " +"Gelieve te wachten tot alle bijlagen gedownload en ontsleuteld zijn. Je kan " "ook overschakelen naar %s om de notitie te bewerken." #: packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx:118 #: packages/app-desktop/gui/ResourceScreen.tsx:302 msgid "Please wait..." -msgstr "Even geduld ..." +msgstr "Even geduld..." #: packages/app-mobile/services/plugins/PlatformImplementation.ts:51 -#, fuzzy msgid "Plugin message" -msgstr "Plugins" +msgstr "Plugin-bericht" #: packages/app-mobile/components/ScreenHeader/index.tsx:400 -#, fuzzy msgid "Plugin panels" -msgstr "Plugin-hulpmiddelen" +msgstr "Plugin-panelen" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:111 msgid "Plugin repository failed to load" -msgstr "" +msgstr "Plugin-bibliotheek werd niet geladen" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:29 -#, fuzzy msgid "Plugin search" -msgstr "Plugins" +msgstr "Zoek plugins" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:107 -#, fuzzy msgid "Plugin security" -msgstr "Plugins" +msgstr "Plugin-beveiliging" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:329 msgid "Plugin tools" @@ -4282,7 +4132,7 @@ msgstr "Plugin-hulpmiddelen" #: packages/lib/models/settings/builtInMetadata.ts:909 msgid "Plugin WebView debugging" -msgstr "" +msgstr "Plugin WebView foutopsporing" #: packages/app-desktop/gui/ConfigScreen/Sidebar.tsx:148 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:523 @@ -4296,6 +4146,9 @@ msgid "" "Plugins extend Joplin with features that are not present by default. Plugins " "can extend Joplin's editor, viewer, and more." msgstr "" +"Plugins bieden aanvullende functionaliteit, die niet standaard aanwezig is " +"in Joplin. Plugins kunnen extra functionaliteit verlenen aan Joplins editor, " +"viewer, en meer." #: packages/lib/models/settings/builtInMetadata.ts:1261 msgid "Portrait" @@ -4327,11 +4180,11 @@ msgstr "Voorkeurthema licht" #: packages/lib/models/settings/builtInMetadata.ts:1704 msgid "Preferred voice typing provider" -msgstr "" +msgstr "Voorkeursleverancier voor steminvoer" #: packages/lib/models/settings/builtInMetadata.ts:667 msgid "Preserve colours when pasting text in Rich Text Editor" -msgstr "" +msgstr "Behoud kleuren bij het plakken van tekst in de Rich Text Editor" #: packages/app-cli/app/app-gui.js:758 msgid "Press Ctrl+D or type \"exit\" to exit the application" @@ -4351,15 +4204,15 @@ msgstr "" #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:40 msgid "Press to set the decryption password." -msgstr "Klik om het decryptie wachtwoord in te stellen" +msgstr "Druk om het decryptiewachtwoord in te stellen." #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:17 msgid "previous" -msgstr "" +msgstr "vorige" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:281 msgid "Previous match" -msgstr "" +msgstr "Vorige overeenkomst" #: packages/app-desktop/gui/NotePropertiesDialog.tsx:368 msgid "Previous versions of this note" @@ -4371,7 +4224,7 @@ msgstr "Afdrukken" #: packages/lib/utils/joplinCloud/index.ts:222 msgid "Priority support" -msgstr "" +msgstr "Ondersteuning met prioriteit" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:606 msgid "Privacy Policy" @@ -4379,77 +4232,71 @@ msgstr "Privacybeleid" #: packages/lib/utils/joplinCloud/index.ts:369 msgid "Pro" -msgstr "" +msgstr "Pro" #: packages/server/src/services/TaskService.ts:26 msgid "Process failed payment subscriptions" -msgstr "" +msgstr "Mislukte betalingsabonnementen verwerken" #: packages/server/src/services/TaskService.ts:24 msgid "Process oversized accounts" -msgstr "" +msgstr "Verwerk te grote accounts" #: packages/server/src/services/TaskService.ts:29 msgid "Process user deletions" -msgstr "" +msgstr "Verwerk verwijdering van gebruikers" #: packages/lib/models/Resource.ts:32 msgid "Processing" -msgstr "" +msgstr "Verwerken" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:111 -#, fuzzy msgid "Processing photo..." -msgstr "Rapport aanmaken ..." +msgstr "Foto verwerken..." #: packages/server/src/routes/admin/users.ts:254 msgid "Profile" -msgstr "" +msgstr "Profiel" #: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:96 -#, fuzzy msgid "Profile name" -msgstr "Profielversie: %s." +msgstr "Profielnaam" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/addProfile.ts:18 -#, fuzzy msgid "Profile name:" -msgstr "Profielversie: %s." +msgstr "Profielnaam:" #: packages/lib/versionInfo.ts:90 msgid "Profile Version: %s" -msgstr "Profielversie: %s." +msgstr "Profielversie: %s" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:172 -#, fuzzy msgid "Profiles" -msgstr "Profielversie: %s." +msgstr "Profielen" #: packages/app-mobile/components/screens/Note/Note.tsx:1248 msgid "Properties" msgstr "Eigenschappen" #: packages/lib/models/settings/builtInMetadata.ts:1413 -#, fuzzy msgid "Proxy enabled" -msgstr "Ingeschakeld" +msgstr "Proxy ingeschakeld" #: packages/lib/models/settings/builtInMetadata.ts:1435 msgid "Proxy timeout (seconds)" -msgstr "" +msgstr "Proxy timeout (seconden)" #: packages/lib/models/settings/builtInMetadata.ts:1423 msgid "Proxy URL" -msgstr "" +msgstr "Proxy URL" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:240 msgid "Public-private key pair:" -msgstr "Publiek-private sleutelpaar" +msgstr "Publiek-private sleutelpaar:" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareNoteDialog.ts:6 -#, fuzzy msgid "Publish note..." -msgstr "Notitie delen ..." +msgstr "Publiceer notitie..." #: packages/app-desktop/gui/ShareNoteDialog.tsx:213 msgid "Publish Notes" @@ -4461,43 +4308,40 @@ msgid "Publish notes to the internet" msgstr "Notities publiceren op het Internet" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:78 -#, fuzzy msgid "QR Code" -msgstr "Code" +msgstr "QR-Code" #: packages/app-desktop/app.ts:219 #: packages/app-desktop/ElectronAppWrapper.ts:123 #: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:16 #: packages/app-desktop/gui/MenuBar.tsx:424 msgid "Quit" -msgstr "Stop" +msgstr "Afsluiten" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:206 msgid "Re-download model" -msgstr "" +msgstr "Model opnieuw downloaden" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:342 msgid "Re-encrypt data" -msgstr "Herencrypteer de gegevens" +msgstr "Versleutel de gegevens opnieuw" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:354 msgid "Re-encryption" -msgstr "Her-encryptie" +msgstr "Opnieuw versleutelen" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:181 -#, fuzzy msgid "Re-enter password" -msgstr "Geef hoofdpaswoord in:" +msgstr "Geef paswoord opnieuw in" #: packages/lib/models/settings/builtInMetadata.ts:1180 msgid "Re-upload local data to sync target" -msgstr "Lokale gegevens opnieuw uploaden naar synchronisatie doel" +msgstr "Lokale gegevens opnieuw uploaden naar synchronisatiedoel" #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:47 msgid "Read more about it" -msgstr "Meer hierover" +msgstr "Lees meer hierover" -# Context needed #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:159 msgid "Read time: %s min" msgstr "Leestijd: %s min" @@ -4519,14 +4363,12 @@ msgid "Recipients:" msgstr "Ontvangers:" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx:72 -#, fuzzy msgid "Recommended" -msgstr "commando" +msgstr "Aanbevolen" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:114 -#, fuzzy msgid "Recommended plugins" -msgstr "commando" +msgstr "Aanbevolen plugins" #: packages/app-desktop/gui/MenuBar.tsx:754 #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:122 @@ -4539,12 +4381,11 @@ msgstr "Opnieuw" #: packages/app-mobile/components/screens/onedrive-login.js:121 #: packages/app-mobile/components/screens/status.tsx:174 msgid "Refresh" -msgstr "Vernieuwen" +msgstr "Verversen" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:316 -#, fuzzy msgid "Regular expression" -msgstr "Schakel wiskundige formules in" +msgstr "Reguliere expressie" #: packages/app-desktop/gui/MainScreen.tsx:543 #: packages/app-desktop/gui/Root.tsx:152 @@ -4557,17 +4398,16 @@ msgid "Remove" msgstr "Verwijderen" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:330 -#, fuzzy msgid "Remove %s from share" -msgstr "De tag \"%s\" verwijderen van alle notities?" +msgstr "Verwijder %s van delen" #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:111 msgid "Remove tag \"%s\" from all notes?" -msgstr "De tag \"%s\" verwijderen van alle notities?" +msgstr "Het label \"%s\" verwijderen van alle notities?" #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:113 msgid "Remove this search from the sidebar?" -msgstr "Dit item verwijderen van de zijbalk?" +msgstr "Deze zoekopdracht verwijderen uit de zijbalk?" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/renameFolder.ts:8 #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/renameTag.ts:8 @@ -4580,7 +4420,7 @@ msgstr "Hernoem notitieboek:" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/renameTag.ts:22 msgid "Rename tag:" -msgstr "Hernoem tag:" +msgstr "Hernoem label:" #: packages/app-cli/app/command-ren.ts:14 msgid "Renames the given (note or notebook) to ." @@ -4591,91 +4431,83 @@ msgid "Renew token" msgstr "Token vernieuwen" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:21 -#, fuzzy msgid "replace" -msgstr "Selecteer alles" +msgstr "vervang" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:15 #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:291 msgid "Replace" -msgstr "" +msgstr "Vervang" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:22 -#, fuzzy msgid "replace all" -msgstr "Selecteer alles" +msgstr "alles vervangen" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:301 -#, fuzzy msgid "Replace all" -msgstr "Selecteer alles" +msgstr "Alles vervangen" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:239 msgid "Replace with..." -msgstr "" +msgstr "Vervangen door..." #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:260 msgid "Replace: " -msgstr "" +msgstr "Vervang: " #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:25 msgid "replaced $ matches" -msgstr "" +msgstr "$ overeenkomsten vervangen" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:26 msgid "replaced match on line $" -msgstr "" +msgstr "overeenkomst vervangen op regel $" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:162 msgid "Report an issue" -msgstr "" +msgstr "Meld een probleem" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:219 msgid "Report any issues concerning the plugin." -msgstr "" +msgstr "Alle problemen met de plugin rapporteren." #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:224 msgid "Report fraudulent plugin" -msgstr "" +msgstr "Frauduleuze plugin rapporteren" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:116 -#, fuzzy msgid "Report system" -msgstr "Bestandssysteem" +msgstr "Rapporteersysteem" #: packages/server/src/services/MustacheService.ts:137 -#, fuzzy msgid "Reports" -msgstr "Bestandssysteem" +msgstr "Rapporten" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/resetLayout.ts:7 -#, fuzzy msgid "Reset application layout" -msgstr "Layout veranderen" +msgstr "Herstel de lay-out van de applicatie" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:221 #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:222 -#, fuzzy msgid "Reset master password" -msgstr "Geef hoofdpaswoord in:" +msgstr "Hoofdwachtwoord opnieuw instellen" #: packages/lib/models/settings/builtInMetadata.ts:862 msgid "Resize large images:" -msgstr "" +msgstr "Formaat van grote afbeeldingen wijzigen:" #: packages/app-cli/app/command-import.ts:54 #: packages/app-desktop/gui/ImportScreen.tsx:93 msgid "Resources: %d." -msgstr "Bijlagen: %d." +msgstr "Bronnen: %d." #: packages/app-desktop/gui/MainScreen.tsx:514 msgid "Restart and upgrade" msgstr "Herstarten en upgraden" #: packages/app-desktop/ElectronAppWrapper.ts:130 -#, fuzzy msgid "Restart in safe mode" -msgstr "Herstarten en upgraden" +msgstr "Herstarten in veilige modus" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:405 #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:64 @@ -4689,40 +4521,35 @@ msgstr "Nu herstarten" #: packages/app-mobile/components/screens/Note/Note.tsx:1256 #: packages/app-mobile/components/side-menu-content.tsx:359 msgid "Restore" -msgstr "Terugzetten" +msgstr "Herstellen" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:156 -#, fuzzy msgid "Restore defaults" -msgstr "Teruggezette Notities" +msgstr "Standaardinstellingen herstellen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/restoreNote.ts:10 -#, fuzzy msgid "Restore note" -msgstr "Teruggezette Notities" +msgstr "Notitie herstellen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/restoreFolder.ts:9 -#, fuzzy msgid "Restore notebook" -msgstr "Maak nieuw notitieboek aan" +msgstr "Notitieboek herstellen" #: packages/app-cli/app/command-restore.ts:12 -#, fuzzy msgid "Restore the items matching from the trash." -msgstr "Verwijder alle notities die voldoen aan ." +msgstr "Herstel alle notities die voldoen aan uit de prullenmand." #: packages/lib/services/trash/index.ts:88 -#, fuzzy msgid "Restored items" -msgstr "Teruggezette Notities" +msgstr "Herstelde items" #: packages/lib/services/RevisionService.ts:248 msgid "Restored Notes" -msgstr "Teruggezette Notities" +msgstr "Herstelde Notities" #: packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.tsx:162 msgid "Results (%d):" -msgstr "" +msgstr "Resultaten (%d):" #: packages/app-desktop/gui/StatusScreen/StatusScreen.tsx:133 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:112 @@ -4744,11 +4571,11 @@ msgstr "Toon bestand in map" #: packages/lib/models/settings/builtInMetadata.ts:688 #: packages/lib/models/settings/builtInMetadata.ts:767 msgid "Reverse sort order" -msgstr "Draai rangschikking om" +msgstr "Draai sorteervolgorde om" #: packages/app-cli/app/command-ls.ts:30 msgid "Reverses the sorting order." -msgstr "Draait de rangschikking om." +msgstr "Draait de sorteervolgorde om." #: packages/lib/versionInfo.ts:65 msgid "Revision: %s (%s)" @@ -4756,55 +4583,53 @@ msgstr "Revisie: %s (%s)" #: packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.tsx:28 msgid "Rich Text" -msgstr "" +msgstr "Rich Text" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:686 msgid "Rich Text editor. Press Escape then Tab to escape focus." msgstr "" +"Rich Text editor. Druk op Escape en vervolgens op Tab om de focus te " +"verbreken." #: packages/app-cli/app/command-batch.js:10 msgid "" "Runs the commands contained in the text file. There should be one command " "per line." msgstr "" -"Voert de commandos in het tekstbestand uit. Er zou een commando per lijn " -"moeten zijn." +"Voert de commando's in het tekstbestand uit. Er moet één commando per lijn " +"zijn." #: packages/lib/SyncTargetAmazonS3.js:28 msgid "S3" msgstr "S3" #: packages/lib/models/settings/builtInMetadata.ts:266 -#, fuzzy msgid "S3 access key" -msgstr "AWS sleutel" +msgstr "S3 toegangssleutel" #: packages/lib/models/settings/builtInMetadata.ts:223 -#, fuzzy msgid "S3 bucket" -msgstr "AWS S3 bucket" +msgstr "S3 bucket" #: packages/lib/models/settings/builtInMetadata.ts:254 msgid "S3 region" msgstr "S3 regio" #: packages/lib/models/settings/builtInMetadata.ts:278 -#, fuzzy msgid "S3 secret key" -msgstr "AWS secret" +msgstr "S3 geheime sleutel" #: packages/lib/models/settings/builtInMetadata.ts:239 -#, fuzzy msgid "S3 URL" -msgstr "AWS S3 URL" +msgstr "S3 URL" #: packages/app-desktop/gui/MainScreen.tsx:501 msgid "" "Safe mode is currently active. Note rendering and all plugins are " "temporarily disabled." msgstr "" -"Veilige modus is actief. Renderen van notities en alle plugins zijn " -"tijdelijk gedeactiveerd." +"Veilige modus is actief. Notitieweergave en alle plugins zijn tijdelijk " +"gedeactiveerd." #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:129 #: packages/app-desktop/gui/KeymapConfig/ShortcutRecorder.tsx:93 @@ -4817,17 +4642,16 @@ msgstr "Opslaan" #: packages/app-mobile/components/SelectDateTimeDialog.tsx:166 msgid "Save alarm" -msgstr "Melding opslaan" +msgstr "Alarm opslaan" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:107 #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:114 -#, fuzzy msgid "Save as %s" -msgstr "Opslaan als ..." +msgstr "Opslaan als %s" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:94 msgid "Save as..." -msgstr "Opslaan als ..." +msgstr "Opslaan als..." #: packages/app-desktop/gui/NotePropertiesDialog.tsx:325 #: packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.tsx:110 @@ -4838,9 +4662,8 @@ msgid "Save changes" msgstr "Wijzigingen opslaan" #: packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.tsx:103 -#, fuzzy msgid "Save changes?" -msgstr "Wijzigingen opslaan" +msgstr "Wijzigingen opslaan?" #: packages/lib/models/settings/builtInMetadata.ts:768 msgid "Save geo-location with notes" @@ -4848,7 +4671,7 @@ msgstr "Sla geo-locatie op bij notities" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:89 msgid "Scanned code" -msgstr "" +msgstr "Gescande code" #: packages/app-desktop/gui/lib/SearchInput/SearchInput.tsx:62 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:102 @@ -4862,17 +4685,15 @@ msgstr "Zoeken" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx:118 #: packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.tsx:157 msgid "Search for plugins..." -msgstr "Plugins zoeken ..." +msgstr "Plugins zoeken..." #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:226 -#, fuzzy msgid "Search for..." -msgstr "Zoeken ..." +msgstr "Zoeken naar..." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:170 -#, fuzzy msgid "Search hidden" -msgstr "Zoekopdrachten" +msgstr "Zoek verborgen" #: packages/app-desktop/gui/NoteListControls/commands/focusSearch.ts:6 msgid "Search in all the notes" @@ -4880,17 +4701,16 @@ msgstr "Zoek in alle notities" #: packages/app-desktop/gui/NoteEditor/commands/showLocalSearch.ts:7 msgid "Search in current note" -msgstr "Zoeken in actuele notitie" +msgstr "Zoeken in huidige notitie" #: packages/app-desktop/plugins/GotoAnything.tsx:665 -#, fuzzy msgid "Search results" -msgstr "Geen resultaten" +msgstr "Zoekresultaten" +# Context unclear #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:170 -#, fuzzy msgid "Search shown" -msgstr "Zoekopdrachten" +msgstr "Doorzoek getoonde" #: packages/app-cli/app/gui/FolderListWidget.ts:56 msgid "Search:" @@ -4902,16 +4722,15 @@ msgstr "Zoek:" #: packages/app-desktop/gui/ResourceScreen.tsx:299 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:785 msgid "Search..." -msgstr "Zoeken ..." +msgstr "Zoeken..." #: packages/app-cli/app/command-search.js:13 msgid "Searches for the given in all the notes." -msgstr "Zoektermen voor het opgegeven in alle notities." +msgstr "Zoekt het opgegeven in alle notities." #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:63 -#, fuzzy msgid "See changelog" -msgstr "Volledige log van wijzigingen" +msgstr "Log van wijzigingen zien" #: packages/lib/models/settings/builtInMetadata.ts:1199 msgid "See the pre-release page for more details: %s" @@ -4919,44 +4738,37 @@ msgstr "Zie de pre-release pagina voor meer details: %s" #: packages/app-desktop/gui/SyncWizard/Dialog.tsx:197 #: packages/app-mobile/components/NoteItem.tsx:144 -#, fuzzy msgid "Select" -msgstr "Selecteer alles" +msgstr "Selecteren" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:50 #: packages/app-mobile/components/ScreenHeader/index.tsx:364 msgid "Select all" -msgstr "Selecteer alles" +msgstr "Alles selecteren" #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:145 -#, fuzzy msgid "Select emoji..." -msgstr "Selecteer datum" +msgstr "Selecteer emoji..." #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:149 -#, fuzzy msgid "Select file..." -msgstr "Selecteer alles" +msgstr "Selecteer bestand..." #: packages/app-mobile/components/screens/folder.js:109 -#, fuzzy msgid "Select parent notebook" -msgstr "Verwijder notitieboek" +msgstr "Selecteer hoofdnotitieboek" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:9 -#, fuzzy msgid "Selection deleted" -msgstr "Verwijderen: %d" +msgstr "Selectie verwijderd" #: packages/app-mobile/components/FolderPicker.tsx:68 -#, fuzzy msgid "Selects a notebook" -msgstr "Verwijder notitieboek" +msgstr "Selecteert een notitieboek" #: packages/app-desktop/gui/MenuBar.tsx:359 -#, fuzzy msgid "Send bug report" -msgstr "Exporteer debug rapport" +msgstr "Foutopsportingsrapport verzenden" #: packages/app-cli/app/command-server.js:38 msgid "Server is already running on port %d" @@ -4975,18 +4787,19 @@ msgstr "Server is actief op poort %d" #: packages/app-mobile/components/screens/Note/Note.tsx:1169 #: packages/app-mobile/components/SelectDateTimeDialog.tsx:161 msgid "Set alarm" -msgstr "Stel melding in" +msgstr "Alarm instellen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/editAlarm.ts:29 msgid "Set alarm:" -msgstr "Stel melding in:" +msgstr "Alarm instellen:" #: packages/lib/models/settings/builtInMetadata.ts:1121 msgid "" "Set it to 0 to make it take the complete available space. Recommended width " "is 600." msgstr "" -"Zet dit op 0 om de volledige ruimte te benutten. Aanbevolen breedte is 600" +"Stel dit in op 0 om de volledige beschikbare ruimte te benutten. Aanbevolen " +"breedte is 600." #: packages/app-desktop/gui/MainScreen.tsx:508 #: packages/app-desktop/gui/MainScreen.tsx:555 @@ -5007,7 +4820,7 @@ msgstr "" #: packages/app-mobile/components/EditorToolbar/EditorToolbar.tsx:47 msgid "Settings" -msgstr "" +msgstr "Instellingen" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:281 #: packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.ts:44 @@ -5021,59 +4834,55 @@ msgid "" "Share a copy of all notes in a file format that can be imported by Joplin on " "a computer." msgstr "" +"Deel een kopie van alle notities in een format dat geïmporteerd kan worden " +"door Joplin op een computer." #: packages/lib/utils/joplinCloud/index.ts:125 -#, fuzzy msgid "Share a notebook with others" -msgstr "Maak eerst een notitieboek aan" +msgstr "Deel een notitieboek met anderen" #: packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.tsx:82 #: packages/app-mobile/components/screens/ShareManager/IncomingShareItem.tsx:33 -#, fuzzy msgid "Share from %s (%s)" -msgstr "%s = %s (%s)" +msgstr "Deel van %s (%s)" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:400 -#, fuzzy msgid "Share Notebook" -msgstr "Notities delen" +msgstr "Notitieboek Delen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareFolderDialog.ts:6 -#, fuzzy msgid "Share notebook..." -msgstr "Notitie delen ..." +msgstr "Notitieboek delen..." +# Context unclear #: packages/lib/utils/joplinCloud/index.ts:215 msgid "Share permissions" -msgstr "" +msgstr "Deelmachtigingen" #: packages/app-desktop/gui/Sidebar/listItemComponents/FolderItem.tsx:56 -#, fuzzy msgid "Shared" -msgstr "Delen" +msgstr "Gedeeld" +# Context unclear #: packages/app-mobile/components/screens/ShareManager/index.tsx:107 -#, fuzzy msgid "Shares" msgstr "Delen" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:364 -#, fuzzy msgid "Sharing notebook..." -msgstr "Notitie delen ..." +msgstr "Notitieboek delen..." #: packages/app-cli/app/command-help.ts:45 msgid "Shortcuts are not available in CLI mode." msgstr "Snelkoppelingen zijn niet beschikbaar in command line modus." #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:211 -#, fuzzy msgid "Show advanced" msgstr "Toon geavanceerde opties" #: packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.tsx:24 msgid "Show Advanced Settings" -msgstr "Toon geavanceerde opties" +msgstr "Toon Geavanceerde Opties" #: packages/app-mobile/components/screens/LogScreen.tsx:237 msgid "Show all" @@ -5081,67 +4890,59 @@ msgstr "Alles tonen" #: packages/lib/models/settings/builtInMetadata.ts:617 msgid "Show completed to-dos" -msgstr "Toon voltooide to-do's" +msgstr "Toon voltooide taken" #: packages/server/src/routes/admin/users.ts:206 -#, fuzzy msgid "Show disabled" -msgstr "Toon geavanceerde opties" +msgstr "Toon uigeschakelde" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:160 -#, fuzzy msgid "Show disabled keys" -msgstr "Toon geavanceerde opties" +msgstr "Toon uitgeschakelde sleutels" #: packages/app-desktop/gui/ConfigScreen/controls/FontSearch.tsx:132 -#, fuzzy msgid "Show monospace fonts only." -msgstr "Lettertype editor" +msgstr "Toon enkel monospace-lettertypes." #: packages/lib/models/settings/builtInMetadata.ts:599 msgid "Show note counts" msgstr "Toon aantal notities" #: packages/app-mobile/components/side-menu-content.tsx:258 -#, fuzzy msgid "Show notebook options" msgstr "Toon aantal notities" #: packages/app-desktop/gui/PasswordInput/PasswordInput.tsx:21 -#, fuzzy msgid "Show password" -msgstr "Stel het wachtwoord in" +msgstr "Toon wachtwoord" #: packages/lib/models/settings/builtInMetadata.ts:698 -#, fuzzy msgid "Show sort order buttons" -msgstr "Toon aantal notities" +msgstr "Toon sorteervolgorde-knoppen" #: packages/lib/models/settings/builtInMetadata.ts:1007 msgid "Show tray icon" -msgstr "Toon tray icon" +msgstr "Toon systeemvakpictogram" #: packages/app-mobile/components/ScreenHeader/index.tsx:263 msgid "Show/hide the sidebar" -msgstr "" +msgstr "Zijbalk tonen/verbergen" #: packages/app-mobile/components/screens/tags.tsx:76 -#, fuzzy msgid "Shows notes for tag" -msgstr "Toon aantal notities" +msgstr "Toon notities voor label" #: packages/lib/models/settings/builtInMetadata.ts:863 msgid "Shrink large images before adding them to notes." -msgstr "" +msgstr "Grote afbeeldingen verkleinen alvorens ze toe te voegen aan notities." #: packages/app-mobile/components/SideMenu.tsx:258 -#, fuzzy msgid "Side menu closed" -msgstr "Verberg metadata" +msgstr "Zijmenu gesloten" #: packages/app-mobile/components/SideMenu.tsx:258 msgid "Side menu opened" -msgstr "" +msgstr "Zijmenu geopend" #: packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.ts:10 #: packages/app-desktop/gui/Sidebar/Sidebar.tsx:77 @@ -5160,7 +4961,8 @@ msgstr "Deze versie overslaan" #: packages/app-cli/app/command-e2ee.ts:66 msgid "Skipped items: %d (use --retry-failed-items to retry decrypting them)" msgstr "" -"Overgeslagen items: %d (gebruik --retry-failed-items om opnieuw te proberen)" +"Overgeslagen items: %d (gebruik --retry-failed-items om ze opnieuw te " +"proberen te ontsleutelen)" #: packages/app-cli/app/command-import.ts:53 #: packages/app-desktop/gui/ImportScreen.tsx:92 @@ -5179,59 +4981,64 @@ msgstr "Gesolariseerd Licht" msgid "" "Some attachments could not be downloaded. Please try to download them again." msgstr "" +"Sommige bijlagen konden niet gedownload worden. Probeer ze opnieuw te " +"downloaden." #: packages/lib/models/settings/settingValidations.ts:18 msgid "" "Some attachments need to be downloaded. Set the attachment download mode to " "\"always\" and try again." msgstr "" +"Sommige bijlagen moeten gedownload worden. Stel de bijlage-downloadmodus in " +"op \"altijd\" en probeer opnieuw." #: packages/app-desktop/gui/MainScreen.tsx:519 #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:52 msgid "Some items cannot be decrypted." -msgstr "Sommige items kunnen niet gedecrypteerd worden." +msgstr "Sommige items kunnen niet ontsleuteld worden." #: packages/app-desktop/gui/MainScreen.tsx:548 msgid "Some items cannot be synchronised." msgstr "Sommige items kunnen niet gesynchroniseerd worden." #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:43 -#, fuzzy msgid "Some items cannot be synchronised. Press for more info." -msgstr "Sommige items kunnen niet gesynchroniseerd worden." +msgstr "" +"Sommige items kunnen niet gesynchroniseerd worden. Druk voor meer info." #: packages/lib/models/settings/settingValidations.ts:24 -#, fuzzy msgid "" "Some items could not be synchronised. Please try to synchronise them first." -msgstr "Sommige items kunnen niet gesynchroniseerd worden." +msgstr "" +"Sommige items konden niet gesynchroniseerd worden. Probeer ze eerst te " +"synchroniseren." #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in ascending order" -msgstr "" +msgstr "Sorteer \"%s\" in oplopende volgorde" #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in descending order" -msgstr "" +msgstr "Sorteer \"%s\" in aflopende volgorde" #: packages/lib/models/settings/builtInMetadata.ts:754 msgid "Sort notebooks by" -msgstr "Rangschik notitieboeken volgens" +msgstr "Sorteer notitieboeken volgens" #: packages/app-desktop/gui/NoteList/utils/canManuallySortNotes.ts:10 #: packages/app-mobile/components/ScreenHeader/index.tsx:487 #: packages/lib/models/settings/builtInMetadata.ts:625 msgid "Sort notes by" -msgstr "Rangschik notities volgens" +msgstr "Sorteer notities volgens" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:138 msgid "Sort selected lines" -msgstr "Rangschik geselecteerde regels" +msgstr "Sorteer geselecteerde regels" #: packages/app-cli/app/command-ls.ts:29 msgid "Sorts the item by (eg. title, updated_time, created_time)." msgstr "" -"Rangschik de items volgens (bv. title, updated_time, created_time)." +"Sorteert het item volgens (bv. title, updated_time, created_time)." #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:9 msgid "Source" @@ -5242,13 +5049,12 @@ msgid "Source format: %s" msgstr "Bronformaat: %s" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:139 -#, fuzzy msgid "Source: " -msgstr "Bron" +msgstr "Bron: " #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:435 msgid "Spacer" -msgstr "" +msgstr "Tussenstuk" #: packages/lib/models/settings/builtInMetadata.ts:1463 msgid "" @@ -5256,7 +5062,7 @@ msgid "" "default will be used." msgstr "" "Specifieer de poort te gebruiken door de API-server. Indien niet ingesteld " -"wordt een default gebruikt." +"wordt een standaardpoort gebruikt." #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.ts:11 #: packages/lib/services/spellChecker/SpellCheckerService.ts:218 @@ -5267,11 +5073,11 @@ msgstr "Spellingscontrole" #: packages/lib/models/settings/builtInMetadata.ts:610 #: packages/lib/models/settings/builtInMetadata.ts:611 msgid "Split View" -msgstr "Naast elkaar" +msgstr "Gesplitste Weergave" #: packages/lib/models/settings/builtInMetadata.ts:1022 msgid "Start application minimised in the tray icon" -msgstr "Start application minimised in the tray" +msgstr "Start applicatie geminimaliseerd in het systeemvak" #: packages/app-cli/app/command-server.js:14 msgid "" @@ -5301,11 +5107,11 @@ msgstr "" #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:163 msgid "Statistics" -msgstr "Statistiek" +msgstr "Statistieken" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteContentProperties.ts:8 msgid "Statistics..." -msgstr "Statistiek ..." +msgstr "Statistieken..." #: packages/app-mobile/components/screens/encryption-config.tsx:312 #: packages/app-mobile/components/screens/status.tsx:172 @@ -5318,28 +5124,27 @@ msgstr "Status: %s" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:82 msgid "Status: Started on port %d" -msgstr "Status: gestart op poort %d" +msgstr "Status: Gestart op poort %d" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:124 msgid "Step 1: Enable the clipper service" -msgstr "Step 1: de clipper service inschakelen" +msgstr "Stap 1: de clipper-service inschakelen" #: packages/app-cli/app/command-sync.ts:82 #: packages/app-desktop/gui/DropboxLoginScreen.tsx:46 #: packages/app-mobile/components/screens/dropbox-login.tsx:63 msgid "Step 1: Open this URL in your browser to authorise the application:" -msgstr "" -"Stap 1: Open deze koppeling in je browser om de applicatie te autoriseren:" +msgstr "Stap 1: Open deze URL in je browser om de applicatie te autoriseren:" #: packages/app-cli/app/command-sync.ts:84 #: packages/app-desktop/gui/DropboxLoginScreen.tsx:50 #: packages/app-mobile/components/screens/dropbox-login.tsx:69 msgid "Step 2: Enter the code provided by Dropbox:" -msgstr "Step 2: voer de door Dropbox verstrekte code in:" +msgstr "Stap 2: Voer de door Dropbox verstrekte code in:" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:130 msgid "Step 2: Install the extension" -msgstr "Step 2: installeer de extensie" +msgstr "Stap 2: Installeer de extensie" # Context could be useful #: packages/app-desktop/commands/toggleExternalEditing.ts:29 @@ -5348,11 +5153,11 @@ msgstr "Stop" #: packages/app-desktop/commands/stopExternalEditing.ts:8 msgid "Stop external editing" -msgstr "Beëindig externe bijwerking" +msgstr "Stop met extern bewerken" #: packages/lib/utils/joplinCloud/index.ts:141 msgid "Storage space" -msgstr "" +msgstr "Opslagruimte" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:19 msgid "Strikethrough" @@ -5370,79 +5175,74 @@ msgstr "Verzenden" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:36 msgid "Subscript" -msgstr "Subschrift" +msgstr "Subscript" #: packages/lib/components/shared/config/config-shared.ts:99 msgid "Success! Synchronisation configuration appears to be correct." -msgstr "Succes! De configuratie van de synchronisatie is blijkbaar correct." +msgstr "Succes! De configuratie van de synchronisatie lijkt correct te zijn." #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:30 msgid "Superscript" -msgstr "Superschrift" +msgstr "Superscript" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:146 msgid "Swap line down" -msgstr "Een regel omlaag" +msgstr "Verplaats een regel omlaag" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:142 msgid "Swap line up" -msgstr "Een regel omhoog" +msgstr "Verplaats een regel omhoog" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleNoteType.ts:7 msgid "Switch between note and to-do type" -msgstr "Wissel tussen notitie en to-do" +msgstr "Wissel tussen notitie en taak" #: packages/app-desktop/gui/MenuBar.tsx:552 #: packages/app-mobile/components/side-menu-content.tsx:625 -#, fuzzy msgid "Switch profile" -msgstr "Exporteer profiel" +msgstr "Wissel van profiel" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:101 msgid "Switch to back-facing camera" -msgstr "" +msgstr "Overschakelen naar camera aan achterkant" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:101 -#, fuzzy msgid "Switch to front-facing camera" -msgstr "Wissel naar notitie" +msgstr "Overschakelen naar camera aan voorkant" #: packages/app-desktop/gui/utils/NoteListUtils.ts:93 msgid "Switch to note type" -msgstr "Wissel naar notitie" +msgstr "Overschakelen naar notitie-type" #: packages/app-desktop/commands/switchProfile1.ts:7 #: packages/app-desktop/commands/switchProfile2.ts:7 #: packages/app-desktop/commands/switchProfile3.ts:7 -#, fuzzy msgid "Switch to profile %d" -msgstr "Wissel naar notitie" +msgstr "Overschakelen naar profile %d" #: packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.tsx:28 -#, fuzzy msgid "Switch to the %s Editor" -msgstr "Wissel naar notitie" +msgstr "Overschakelen naar de %s Editor" #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:78 -#, fuzzy msgid "Switch to the legacy editor" -msgstr "Wissel naar notitie" +msgstr "Overschakelen naar de oudere editor" #: packages/app-desktop/gui/utils/NoteListUtils.ts:102 msgid "Switch to to-do type" -msgstr "Wissel naar to-do" +msgstr "Wissel naar taak" #: packages/app-cli/app/command-use.ts:12 msgid "" "Switches to [notebook] - all further operations will happen within this " "notebook." msgstr "" -"Wisselt naar [notitieboek] - Alle verdere acties zullen op dit notitieboek " -"toegepast worden." +"Schakelt over naar naar [notebook] - Alle verdere acties zullen op dit " +"notitieboek toegepast worden." #: packages/lib/utils/joplinCloud/index.ts:160 msgid "Sync as many devices as you want" -msgstr "" +msgstr "Synchroniseer met zoveel apparaten als je wil" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:549 msgid "Sync Status" @@ -5455,7 +5255,7 @@ msgstr "Synchronisatiestatus (gesynchroniseerde items / totaal aantal items)" #: packages/app-cli/app/command-sync.ts:271 msgid "Sync target must be upgraded! Run `%s` to proceed." msgstr "" -"Synchronisatiedoel moet upgraded krijgen! Voer `%s` uit om verder te gaan." +"Synchronisatiedoel moet worden geüpgraded! Voer `%s` uit om verder te gaan." #: packages/app-mobile/components/screens/UpgradeSyncTargetScreen.tsx:59 msgid "Sync Target Upgrade" @@ -5464,21 +5264,20 @@ msgstr "Synchronisatiedoel Upgrade" #: packages/app-cli/app/command-sync.ts:41 msgid "Sync to provided target (defaults to sync.target config value)" msgstr "" -"Synchroniseer naar opgegeven doel (standaard sync.target configuratie optie)" +"Synchroniseer naar opgegeven doel (standaard is dit sync.target configuratie " +"optie)" #: packages/lib/versionInfo.ts:89 msgid "Sync Version: %s" msgstr "Synchronisatieversie: %s" #: packages/app-desktop/gui/SyncWizard/Dialog.tsx:167 -#, fuzzy msgid "Sync your notes" -msgstr "Rangschik notities volgens" +msgstr "Synchroniseer je notities" #: packages/lib/models/Setting.ts:1212 -#, fuzzy msgid "Sync, encryption, proxy" -msgstr "Schakel encryptie in" +msgstr "Synchronisatie, encryptie, proxy" #: packages/lib/models/Setting.ts:1173 msgid "Synchronisation" @@ -5521,12 +5320,11 @@ msgstr "Synchroniseert met externe opslag." #: packages/app-desktop/gui/ShareNoteDialog.tsx:185 msgid "Synchronising..." -msgstr "Synchroniseren ..." +msgstr "Synchroniseren..." #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:363 -#, fuzzy msgid "Synchronizing..." -msgstr "Synchroniseren ..." +msgstr "Synchroniseren..." #: packages/lib/models/settings/builtInMetadata.ts:1255 msgid "Tabloid" @@ -5534,12 +5332,12 @@ msgstr "Tabloid" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:215 msgid "tag1, tag2, ..." -msgstr "" +msgstr "label1, label2, ..." #: packages/app-cli/app/command-import.ts:55 #: packages/app-desktop/gui/ImportScreen.tsx:94 msgid "Tagged: %d." -msgstr "Getagd: %d." +msgstr "Gelabeld: %d." #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:10 #: packages/app-desktop/gui/Sidebar/hooks/useSidebarListData.ts:69 @@ -5548,21 +5346,20 @@ msgstr "Getagd: %d." #: packages/app-mobile/components/screens/tags.tsx:87 #: packages/app-mobile/components/side-menu-content.tsx:622 msgid "Tags" -msgstr "Tags" +msgstr "Labels" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:111 #: packages/app-mobile/components/screens/Note/commands/attachFile.ts:83 msgid "Take photo" -msgstr "Maak en foto" +msgstr "Foto maken" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/TaskButton.tsx:73 msgid "Task \"%s\" failed with error: %s" -msgstr "" +msgstr "Taken \"%s\" faalden met fout: %s" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:87 -#, fuzzy msgid "Task list" -msgstr "Taken" +msgstr "Takenlijst" #: packages/server/src/services/MustacheService.ts:129 #: packages/server/src/services/MustacheService.ts:283 @@ -5571,31 +5368,31 @@ msgstr "Taken" #: packages/lib/utils/joplinCloud/index.ts:391 msgid "Teams" -msgstr "" +msgstr "Teams" #: packages/lib/services/interop/InteropService.ts:136 -#, fuzzy msgid "Text document" -msgstr "Teksteditorcommando" +msgstr "Tekstdocument" #: packages/lib/models/settings/builtInMetadata.ts:1248 msgid "Text editor command" msgstr "Teksteditorcommando" #: packages/lib/utils/joplinCloud/index.ts:167 -#, fuzzy msgid "" "The [Web Clipper](%s) is a browser extension that allows you to save web " "pages and screenshots from your browser." msgstr "" -"Joplin Web Clipper laat toe webpagina's en schermafbeeldingen van je browser " -"op te slaan in Joplin." +"De [Web Clipper](%s) is een browserextensie die je toelaat webpagina's en " +"schermafbeeldingen van je browser op te slaan in Joplin." #: packages/lib/services/profileConfig/index.ts:106 msgid "" "The active profile cannot be deleted. Switch to a different profile and try " "again." msgstr "" +"Het actieve profiel kan niet verwijderd worden. Schakel over naar een ander " +"profiel en probeer opieuw." #: packages/app-desktop/bridge.ts:504 msgid "" @@ -5607,15 +5404,16 @@ msgstr "" msgid "" "The application did not close properly. Would you like to start in safe mode?" msgstr "" +"De applicatie is niet correct afgesloten. Wil je starten in veilige modus?" #: packages/lib/onedrive-api-node-utils.js:87 msgid "" "The application has been authorised - you may now close this browser tab." -msgstr "De applicatie werd geautoriseerd - U kan deze tab sluiten." +msgstr "De applicatie werd geautoriseerd - Je kan deze tab nu sluiten." #: packages/lib/components/shared/dropbox-login-shared.js:39 msgid "The application has been authorised!" -msgstr "De applicatie is succesvol geautoriseerd!" +msgstr "De applicatie is geautoriseerd!" #: packages/lib/onedrive-api-node-utils.js:89 msgid "The application has been successfully authorised." @@ -5633,19 +5431,19 @@ msgid "" "The attachments will no longer be watched when you switch to a different " "note." msgstr "" -"De bijlagen worden niet langer opgevolgd als u naar een andere notitie " +"De bijlagen worden niet langer opgevolgd wanneer je naar een andere notitie " "wisselt." #: packages/app-cli/app/app.ts:282 msgid "The command \"%s\" is only available in GUI mode" -msgstr "Het opgegeven commando \"%s\" is alleen beschikbaar in de GUI versie" +msgstr "Het commando \"%s\" is alleen beschikbaar in de GUI-modus" #: packages/server/src/middleware/notificationHandler.ts:25 msgid "" "The default admin password is insecure and has not been changed! [Change it " "now](%s)" msgstr "" -"Het standaard admin paswoord is onveilig en is nog niet gewijzigd! [Wijzig " +"Het standaard admin-wachtwoord is onveilig en is nog niet gewijzigd! [Wijzig " "het nu](%s)" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:344 @@ -5653,28 +5451,28 @@ msgid "" "The default encryption method has been changed to a more secure one and it " "is recommended that you apply it to your data." msgstr "" -"De standaard encryptiemethode is veranderd naar een veiliger methode en het " -"wordt aanbevolen dat u deze toepast op uw gegevens." +"De standaard encryptiemethode is veranderd naar een veiligere methode en het " +"wordt aanbevolen dat je deze toepast op je gegevens." #: packages/app-desktop/gui/MainScreen.tsx:531 msgid "" "The default encryption method has been changed, you should re-encrypt your " "data." msgstr "" -"De standaard encryptiemethode is veranderd, u moet uw data opnieuw " -"encrypteren." +"De standaard encryptiemethode is veranderd, je moet je data opnieuw " +"versleutelen." #: packages/lib/services/profileConfig/index.ts:105 msgid "The default profile cannot be deleted" -msgstr "" +msgstr "Het standaardprofiel kan niet verwijderd worden" #: packages/lib/models/settings/builtInMetadata.ts:1248 msgid "" "The editor command (may include arguments) that will be used to open a note. " "If none is provided it will try to auto-detect the default editor." msgstr "" -"Het commando (mogelijk met parameters) dat zal gebruikt worden om de notitie " -"te openen in de externe editor. Als er geen opgegeven wordt, zal het " +"Het editor-commando (mogelijk met argumenten) dat zal gebruikt worden om de " +"notitie te openen in de externe editor. Als er geen opgegeven wordt, zal het " "programma de standaard editor proberen te detecteren." #: packages/lib/models/settings/builtInMetadata.ts:1521 @@ -5686,37 +5484,36 @@ msgid "" "item with a factor of 2 will take twice as much space as an item with a " "factor of 1.Restart app to see changes." msgstr "" -"De factor-eigenschap bepaalt hoe het item zal groeien of krimpen om de " +"De factor-eigenschap bepaalt hoe het item zal groeien of krimpen om in de " "beschikbare ruimte in zijn container te passen ten opzichte van de andere " "items. Zo zal een item met factor 2 twee keer zoveel ruimte innemen als een " "item met factor 1. Herstart de app om de veranderingen te zien." #: packages/app-desktop/gui/NoteEditor/NoteEditor.tsx:612 -#, fuzzy msgid "The following attachment matches your search query:" msgid_plural "The following attachments match your search query:" -msgstr[0] "Van volgende bijlagen wordt het wijzigen opgevolgd:" -msgstr[1] "Van volgende bijlagen wordt het wijzigen opgevolgd:" +msgstr[0] "De volgende bijlage komt overeen met je zoekopdracht:" +msgstr[1] "De volgende bijlagen komen overeen met je zoekopdracht:" #: packages/app-desktop/gui/NoteEditor/NoteEditor.tsx:596 msgid "The following attachments are being watched for changes:" -msgstr "Van volgende bijlagen wordt het wijzigen opgevolgd:" +msgstr "De volgende bijlagen worden gecontroleerd op wijzigingen:" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:75 -#, fuzzy msgid "" "The following keys use an out-dated encryption algorithm and it is " "recommended to upgrade them. The upgraded key will still be able to decrypt " "and encrypt your data as usual." msgstr "" -"De volgende hoofdsleutels gebruiken een verouderd versleutelingsalgoritme en " -"het wordt aanbevolen om ze te upgraden. De geüpgrade hoofdsleutel zal nog " -"steeds in staat zijn om uw gegevens te decrypteren en te encrypteren zoals " +"De volgende sleutels gebruiken een verouderd versleutelingsalgoritme en het " +"wordt aanbevolen om ze te upgraden. De geüpgrade sleutel zal nog steeds in " +"staat zijn om je gegevens te ontsleutelen en te versleutelen zoals " "gewoonlijk." #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:83 msgid "The following plugins may not support the current markdown editor:" msgstr "" +"De volgende plugins ondersteunen de huidige markdown-editor mogelijk niet:" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:257 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx:49 @@ -5725,23 +5522,22 @@ msgid "" "security and performance." msgstr "" "Het Joplin-team heeft deze plugin geaudit en het voldoet aan de standaarden " -"voor performantie en veiligheid." +"voor veiligheid en prestaties." #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:321 -#, fuzzy msgid "" "The keys with these IDs are used to encrypt some of your items, however the " "application does not currently have access to them. It is likely they will " "eventually be downloaded via synchronisation." msgstr "" -"De hoofdsleutels met deze ID's worden gebruikt om sommige van uw items te " +"De sleutels met deze ID's worden gebruikt om sommige van je items te " "versleutelen, maar de toepassing heeft daar momenteel geen toegang toe. Het " "is waarschijnlijk dat ze uiteindelijk via synchronisatie zullen worden " "opgehaald." #: packages/lib/components/EncryptionConfigScreen/utils.ts:222 msgid "The master key has been upgraded successfully!" -msgstr "De hoofdsleutel is met succes geüpgrade!" +msgstr "De hoofdsleutel is met succes geüpgraded!" #: packages/app-mobile/components/screens/encryption-config.tsx:283 msgid "" @@ -5749,7 +5545,7 @@ msgid "" "however the application does not currently have access to them. It is likely " "they will eventually be downloaded via synchronisation." msgstr "" -"De hoofdsleutels met deze ID's worden gebruikt om sommige van uw items te " +"De hoofdsleutels met deze ID's worden gebruikt om sommige van je items te " "versleutelen, maar de toepassing heeft daar momenteel geen toegang toe. Het " "is waarschijnlijk dat ze uiteindelijk via synchronisatie zullen worden " "opgehaald." @@ -5757,22 +5553,17 @@ msgstr "" #: packages/lib/services/RevisionService.ts:272 msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"." msgstr "" -"De notitie \"%s\" werd met succes teruggezet naar het notitieboek \"%s\"." +"De notitie \"%s\" werd met success hersteld naar het notitieboek \"%s\"." #: packages/app-desktop/gui/TrashNotification/TrashNotification.tsx:68 -#, fuzzy msgid "The note was successfully moved to the trash." msgid_plural "The notes were successfully moved to the trash." -msgstr[0] "" -"De notitie \"%s\" werd met succes teruggezet naar het notitieboek \"%s\"." -msgstr[1] "" -"De notitie \"%s\" werd met succes teruggezet naar het notitieboek \"%s\"." +msgstr[0] "De notitie werd naar de prullenmand verplaatst." +msgstr[1] "De notities werden naar de prullenmand verplaatst." #: packages/app-desktop/gui/TrashNotification/TrashNotification.tsx:66 -#, fuzzy msgid "The notebook and its content was successfully moved to the trash." -msgstr "" -"De notitie \"%s\" werd met succes teruggezet naar het notitieboek \"%s\"." +msgstr "Het notitieboek en alle inhoud warden naar de prullenmand verplaatst." #: packages/app-mobile/components/screens/folder.js:76 msgid "The notebook could not be saved: %s" @@ -5806,6 +5597,12 @@ msgid "" "\n" "%s" msgstr "" +"Het synchronisatiedoel kon niet gewijzigd worden, om de volgende reden: %s\n" +"\n" +"Als het problem niet kan worden opgelost, moet je mogelijk eerst je gegevens " +"wissen door deze instructies te volgen:\n" +"\n" +"%s" #: packages/app-desktop/gui/MainScreen.tsx:513 msgid "" @@ -5815,23 +5612,22 @@ msgid "" msgstr "" "Het synchronisatiedoel moet worden geüpgraded voordat Joplin kan " "synchroniseren. Het kan een paar minuten duren voordat deze operatie " -"voltooid is en de app moet dan opnieuw worden opgestart. Klik op de " -"koppeling om verder te gaan." +"voltooid is en de app moet dan opnieuw worden opgestart. Klik op de link om " +"verder te gaan." #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:46 -#, fuzzy msgid "The sync target needs to be upgraded. Press this banner to proceed." msgstr "" -"Synchronisatiedoel moet upgraded krijgen! Voer `%s` uit om verder te gaan." +"Synchronisatiedoel moet geüpgraded worden. Druk op deze banner om verder te " +"gaan." #: packages/app-desktop/gui/MainScreen.tsx:507 -#, fuzzy msgid "The synchronisation password is missing." -msgstr "Verifieer de configuratie van de synchronisatie" +msgstr "Het synchronisatie-wachtwoord ontbreekt." #: packages/lib/models/Tag.ts:233 msgid "The tag \"%s\" already exists. Please choose a different name." -msgstr "De tag \"%s\" bestaat al. Kies een andere naam." +msgstr "Het label \"%s\" bestaat al. Kies een andere naam." #: packages/lib/models/settings/builtInMetadata.ts:86 msgid "" @@ -5849,35 +5645,41 @@ msgid "" "\n" "Error: \"%s\"" msgstr "" +"De webclient ondersteunt het aanvaarden van versleutelde gedeelde " +"notitieboeken niet. Schakel over naar de desktop- of mobiele applicatie om " +"het delen te aanvaarden.\n" +"\n" +"Foutmelding: \"%s\"" #: packages/app-desktop/gui/Root.tsx:150 msgid "The Web Clipper needs your authorisation to access your data." msgstr "" -"De Web Clipper plugin heeft jouw authorisatie nodig om je gegevens te " -"raadplegen." +"De Web Clipper plugin heeft jouw authorisatie nodig om toegang te krijgen " +"tot je gegevens." #: packages/app-desktop/gui/ClipperConfigScreen.tsx:76 msgid "The web clipper service is enabled and set to auto-start." -msgstr "De web clipper service is ingeschakeld en ingesteld als auto-start." +msgstr "De webclipper-dienst is ingeschakeld en wordt automatisch opgestart." #: packages/app-desktop/gui/ClipperConfigScreen.tsx:100 msgid "The web clipper service is not enabled." -msgstr "De web clipper service is niet ingeschakeld." +msgstr "De webclipper-dienst is niet ingeschakeld." #: packages/lib/utils/webDAVUtils.ts:28 msgid "" "The WebDAV implementation of %s is incompatible with Joplin, and as such is " "no longer supported. Please use a different sync method." msgstr "" +"De WebDAV-implementatie van %s is niet compatible met Joplin, en wordt niet " +"langer ondersteund. Gebruik een andere synchronisatie-methode." #: packages/lib/models/settings/builtInMetadata.ts:543 msgid "Theme" msgstr "Thema" #: packages/lib/models/Setting.ts:1211 -#, fuzzy msgid "Themes, editor font" -msgstr "Lettertype editor" +msgstr "Thema's, lettertype van editor" #: packages/lib/components/shared/NoteList/getEmptyFolderMessage.ts:17 msgid "There are currently no notes. Create one by clicking on the (+) button." @@ -5885,13 +5687,12 @@ msgstr "" "Er zijn momenteel geen notities. Maak een notitie door op (+) te klikken." #: packages/lib/components/shared/NoteList/getEmptyFolderMessage.ts:9 -#, fuzzy msgid "There are no notes in the trash folder." -msgstr "Bewaar notitiehistoriek gedurende" +msgstr "Er zijn geen notities in de prullenmand-map." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:268 msgid "There are unsaved changes." -msgstr "" +msgstr "Er zijn niet-opgeslagen wijzigingen." #: packages/lib/services/interop/InteropService_Exporter_Jex.ts:36 msgid "There is no data to export." @@ -5909,7 +5710,7 @@ msgstr "" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:52 msgid "There was an error downloading this attachment:" -msgstr "Er was een fout bij het ophalen van deze bijlage:" +msgstr "Er was een fout bij het downloaden van deze bijlage:" #: packages/lib/services/ReportService.ts:237 msgid "" @@ -5917,6 +5718,10 @@ msgid "" "cause the sync warning to appear, but still aren't synced. To unignore, " "click \"retry\"." msgstr "" +"Deze items warden niet succesvol gesynchroniseerd, maar zijn gemarkeerd als " +"\"genegeerd\". Ze zullen geen nieuwe synchronisatie-waarschuwing " +"veroorzaken, maar ze zijn nog steeds niet gesynchroniseerd. Om het negeren " +"ongedaan te maken, klik \"opnieuw proberen\"." #: packages/lib/services/ReportService.ts:187 msgid "" @@ -5942,7 +5747,7 @@ msgstr "" "op dat, hoewel deze functies nuttig kunnen zijn, ze geen standaard Markdown " "zijn en dat de meeste dus alleen in Joplin zullen werken. Bovendien zijn " "sommige *incompatibel* met de WYSIWYG editor. Als je een nota opent die een " -"van deze plugins gebruikt in die editor, zal je de opmaak van de plugins " +"van deze plugins gebruikt in die editor, zal je de opmaak van de plugin " "verliezen. Hieronder wordt aangegeven welke plugins al dan niet compatibel " "zijn met de WYSIWYG editor." @@ -5952,15 +5757,18 @@ msgid "" "collaborate on it. It does not however allow you to share a notebook with " "someone else, unless you have the feature \"%s\"." msgstr "" +"Dit laat een andere gebruiker toe om een notitieboek te delen met jou, zodat " +"jullie er samen aan kunnen werken. Het laat jou echter niet toe om een " +"notitieboek met iemand anders te delen, tenzij je de functie \"%s\" hebt." #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:150 msgid "This attachment does not have OCR data (Status: %s)" -msgstr "" +msgstr "Deze bijlage heft geen OCR-gegevens (Status: %s)" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:54 #: packages/lib/services/ResourceEditWatcher/index.ts:241 msgid "This attachment is not downloaded or not decrypted yet" -msgstr "Deze bijlage is nog niet opgehaald of nog niet gedecrypteerd" +msgstr "Deze bijlage is niet gedownload of nog niet ontsleuteld" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:147 msgid "" @@ -5972,7 +5780,7 @@ msgstr "" #: packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.tsx:103 msgid "This drawing may have unsaved changes." -msgstr "" +msgstr "Deze tekening heft mogelijk niet-opgelagen wijzigingen." #: packages/app-desktop/gui/ResourceScreen.tsx:291 msgid "" @@ -5980,30 +5788,27 @@ msgid "" "notes. Please be careful when deleting one of them as they cannot be " "restored afterwards." msgstr "" -"Dit is een geavanceerd hulpmiddel om de bijlagen te tonen die aan uw " -"notities zijn gekoppeld. Wees voorzichtig als u er een verwijdert, want ze " +"Dit is een geavanceerd hulpmiddel om de bijlagen te tonen die aan je " +"notities zijn gekoppeld. Wees voorzichtig als je er een verwijdert, want ze " "kunnen nadien niet meer worden hersteld." #: packages/app-mobile/components/ScreenHeader/index.tsx:237 -#, fuzzy msgid "This note could not be deleted: %s" msgid_plural "These notes could not be deleted: %s" -msgstr[0] "Het notitieboek kon niet opgeslaan worden: %s" -msgstr[1] "Het notitieboek kon niet opgeslaan worden: %s" +msgstr[0] "Deze notitie kon niet verwijderd worden: %s" +msgstr[1] "Deze notities konden niet verwijderd worden: %s" #: packages/app-mobile/components/ScreenHeader/index.tsx:224 -#, fuzzy msgid "This note could not be duplicated: %s" msgid_plural "These notes could not be duplicated: %s" -msgstr[0] "Het notitieboek kon niet opgeslagen worden: %s" -msgstr[1] "Het notitieboek kon niet opgeslagen worden: %s" +msgstr[0] "Deze notitie kon niet gedupliceerd worden: %s" +msgstr[1] "Deze notities konden niet gedupliceerd worden: %s" #: packages/app-mobile/components/ScreenHeader/index.tsx:563 -#, fuzzy msgid "This note could not be moved: %s" msgid_plural "These notes could not be moved: %s" -msgstr[0] "Het notitieboek kon niet opgeslagen worden: %s" -msgstr[1] "Het notitieboek kon niet opgeslagen worden: %s" +msgstr[0] "Deze notitie kon niet verplaatst worden: %s" +msgstr[1] "Deze notities konden niet verplaatst worden: %s" #: packages/lib/models/Note.ts:130 msgid "This note does not have geolocation information." @@ -6018,7 +5823,9 @@ msgstr "Deze notitie werd aangepast:" msgid "" "This note has no content. Click on \"%s\" to toggle the editor and edit the " "note." -msgstr "Deze notitie heeft geen inhoud. Klik op \"%s\" om ze te bewerken." +msgstr "" +"Deze notitie heeft geen inhoud. Klik op \"%s\" om de editor te openen en de " +"notitie te bewerken." #: packages/app-desktop/gui/NoteRevisionViewer.tsx:130 msgid "This note has no history" @@ -6026,15 +5833,15 @@ msgstr "Deze notitie heeft geen historiek" #: packages/lib/services/plugins/PluginService.ts:527 msgid "This plugin doesn't support %s." -msgstr "" +msgstr "Deze plugin ondersteunt %s niet." #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:52 msgid "" "This Rich Text editor has a number of limitations and it is recommended to " "be aware of them before using it." msgstr "" -"Deze Rich Text editor heeft een aantal beperkingen en het is aanbevolen om u " -"daarvan bewust te zijn vooraleer hem te gebruiken." +"Deze Rich Text editor heeft een aantal beperkingen en het is aanbevolen dat " +"je weet wat die zijn vooraleer hem te gebruiken." #: packages/app-desktop/gui/ClipperConfigScreen.tsx:125 msgid "" @@ -6043,12 +5850,12 @@ msgid "" "to a particular port." msgstr "" "Deze dienst laat de browserextensie toe om te communiceren met Joplin. " -"Wanneer u deze inschakelt, kan uw firewall u vragen om Joplin toestemming te " -"geven om op een bepaalde poort te luisteren." +"Wanneer je deze inschakelt, kan je firewall je vragen om Joplin toestemming " +"te geven om op een bepaalde poort te luisteren." #: packages/lib/components/shared/NoteList/getEmptyFolderMessage.ts:11 msgid "This subfolder of the trash has no notes." -msgstr "" +msgstr "Deze submap in de prullenmand bevat geen notities." #: packages/lib/models/settings/builtInMetadata.ts:1009 msgid "" @@ -6056,9 +5863,9 @@ msgid "" "this setting so that your notes are constantly being synchronised, thus " "reducing the number of conflicts." msgstr "" -"Dit laat Joplin toe in de achtergrond actief te blijven. Dit is aanbevolen " -"omdat zo uw notities voortdurend gesynchroniseerd worden en het aantal " -"conflicten verminderd wordt." +"Dit laat Joplin toe in de achtergrond actief te blijven. Het is aanbevolen " +"om deze instlling in te schakelen, zodat je notities voortdurend " +"gesynchroniseerd worden, waardoor het aantal conflicten wordt beperkt." #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:148 msgid "This will open a new screen. Save your current changes?" @@ -6067,12 +5874,11 @@ msgstr "Dit opent een nieuw venster. Lopende wijzigingen opslaan?" #: packages/app-desktop/commands/emptyTrash.ts:14 #: packages/app-mobile/components/side-menu-content.tsx:340 msgid "This will permanently delete all items in the trash. Continue?" -msgstr "" +msgstr "Dit zal alle items in de prullenmand permanent verwijderen. Doorgaan?" #: packages/app-cli/app/command-rmnote.ts:40 -#, fuzzy msgid "This will permanently delete the note \"%s\". Continue?" -msgstr "Notities \"%s\" verwijderen?" +msgstr "Dit zal de notitie \"%s\" permanent verwijderen. Doorgaan?" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.ts:17 #: packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.tsx:60 @@ -6080,8 +5886,8 @@ msgid "" "This will remove the notebook from your collection and you will no longer " "have access to its content. Do you wish to continue?" msgstr "" -"Dit zal de notitieboek van je verzameling verwijderen. Je zal niet langer " -"toegang hebben tot deze inhoud. Wil je verder gaan?" +"Dit zal het notitieboek van je verzameling verwijderen. Je zal niet langer " +"toegang hebben tot de inhoud ervan. Wil je verder gaan?" #: packages/lib/models/settings/builtInMetadata.ts:481 msgid "Time format" @@ -6104,49 +5910,48 @@ msgstr "Titel" msgid "" "To allow Joplin to synchronise with Dropbox, please follow the steps below:" msgstr "" -"Om Joplin toe te staan with Dropbox te synchroniseren, gelieve de " +"Om Joplin toe te staan met Dropbox te synchroniseren, gelieve de " "onderstaande stappen te volgen:" #: packages/app-cli/app/command-sync.ts:110 #: packages/app-desktop/gui/JoplinCloudLoginScreen.tsx:84 #: packages/app-mobile/components/screens/JoplinCloudLoginScreen.tsx:153 -#, fuzzy msgid "" "To allow Joplin to synchronise with Joplin Cloud, please login using this " "URL:" msgstr "" -"Om Joplin toe te staan with Dropbox te synchroniseren, gelieve de " -"onderstaande stappen te volgen:" +"Om Joplin toe te staan met Joplin Cloud te synchroniseren, log in via deze " +"URL:" #: packages/lib/components/EncryptionConfigScreen/utils.ts:53 -#, fuzzy msgid "To continue, please enter your master password below." -msgstr "Geef hoofdpaswoord in:" +msgstr "Geef je hoofdwachtwoord hieronder in om verder te gaan." #: packages/app-cli/app/app-gui.js:458 msgid "To delete a tag, untag the associated notes." msgstr "" -"Om een tag te verwijderen, wis hem eerst bij alle notities die hem hebben." +"Om een label te verwijderen, verwijder het eerst van elke notitie waaraan " +"het is toegevoegd." #: packages/lib/services/ReportService.ts:331 msgid "To delete: %d" -msgstr "Verwijderen: %d" +msgstr "Te verwijderen: %d" #: packages/app-cli/app/command-help.ts:83 msgid "To enter command line mode, press \":\"" -msgstr "Om de command line modus te gebruiken, gebruik \":\"" +msgstr "Om de command line modus te gebruiken, druk \":\"" #: packages/app-cli/app/command-help.ts:84 msgid "To exit command line mode, press ESCAPE" -msgstr "Om de command line modus te verlaten, gebruik ESCAPE" +msgstr "Om de command line modus te verlaten, druk ESCAPE" #: packages/app-desktop/gui/NoteList/utils/canManuallySortNotes.ts:10 msgid "" "To manually sort the notes, the sort order must be changed to \"%s\" in the " "menu \"%s\" > \"%s\"" msgstr "" -"Om de notities manueel te rangschikken, moet de rangschikking veranderd " -"worden in \"%s\" in het menu \"%s\" > \"%s\"" +"Om de notities manueel te sorteren, moet de sorteervolgorde veranderd worden " +"naar \"%s\" in het menu \"%s\" > \"%s\"" #: packages/app-cli/app/command-help.ts:82 msgid "To maximise/minimise the console, press \"tc\"." @@ -6169,90 +5974,85 @@ msgid "" "To switch the profile, the app is going to close and you will need to " "restart it." msgstr "" +"Om van profiel te kunnen wisselen, gaat de applicatie afsluiten, en je zal " +"die zelf opnieuw moeten starten." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:587 msgid "" "To work correctly, the app needs the following permissions. Please enable " "them in your phone settings, in Apps > Joplin > Permissions" msgstr "" -"Om correct te werken, heeft de app de volgende permissies nodig. Schakel ze " -"in uw telefooninstellingen in, in Apps > Joplin > Machtigingen" +"Om correct te werken, heeft de app de volgende machtigingen nodig. Schakel " +"ze in je telefooninstellingen in, in Apps > Joplin > Machtigingen" # Context needed #: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:119 #: packages/app-desktop/gui/NoteListWrapper/NoteListWrapper.tsx:71 msgid "to-do" -msgstr "to-do" +msgstr "taak" # Context needed #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:6 -#, fuzzy msgid "To-do" -msgstr "to-do" +msgstr "Taak" # Context needed #: packages/app-mobile/components/NoteItem.tsx:172 -#, fuzzy msgid "to-do: %s" -msgstr "to-do" +msgstr "taak: %s" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:134 msgid "Toggle comment" -msgstr "In commentaar zetten" +msgstr "Commentaar tonen/verbergen" #: packages/app-desktop/gui/MenuBar.tsx:939 msgid "Toggle development tools" -msgstr "Toon / Verberg ontwikkelingshulpmiddelen" +msgstr "Ontwikkelingshulpmiddelen tonen/verbergen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleVisiblePanes.ts:6 msgid "Toggle editor layout" -msgstr "Wissel editor layout" +msgstr "Schakel tussen editor lay-outs" +# Context unclear #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditorPlugin.ts:11 -#, fuzzy msgid "Toggle editor plugin" -msgstr "Wissel editor layout" +msgstr "Schakel tussen editor-plugins" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditors.ts:8 msgid "Toggle editors" -msgstr "Verwissel van editor" +msgstr "Schakel tussen editors" #: packages/app-desktop/commands/toggleExternalEditing.ts:8 msgid "Toggle external editing" -msgstr "Aan- of uitschakelen externe editor" +msgstr "Extern bewerken in-/uitschakelen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleMenuBar.ts:7 -#, fuzzy msgid "Toggle menu bar" -msgstr "Toon / Verberg zijbalk" +msgstr "Menubalk tonen/verbergen" #: packages/lib/models/Setting.ts:1216 -#, fuzzy msgid "Toggle note history, keep notes for" -msgstr "Bewaar notitiehistoriek gedurende" +msgstr "Notitiehistoriek in-/uitschakelen, bewaar notities gedurende" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleNoteList.ts:9 msgid "Toggle note list" -msgstr "Toon / Verberg notitielijst" +msgstr "Notitielijst tonen/verbergen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/togglePerFolderSortOrder.ts:7 -#, fuzzy msgid "Toggle own sort order" -msgstr "Toon / Verberg zijbalk" +msgstr "Eigen sorteervolgorde in-/uitschakelen" #: packages/app-desktop/commands/toggleSafeMode.ts:8 -#, fuzzy msgid "Toggle safe mode" -msgstr "Toon / Verberg zijbalk" +msgstr "Veilige modus in-/uitschakelen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleSideBar.ts:9 msgid "Toggle sidebar" -msgstr "Toon / Verberg zijbalk" +msgstr "Zijbalk tonen/verbergen" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleNotesSortOrderField.ts:7 -#, fuzzy msgid "Toggle sort order field" -msgstr "Toon / Verberg zijbalk" +msgstr "Sorteervolgordeveld tonen/verbergen" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:34 msgid "Token has been copied to the clipboard!" @@ -6263,9 +6063,8 @@ msgid "Tools" msgstr "Hulpmiddelen" #: packages/server/src/routes/admin/users.ts:152 -#, fuzzy msgid "Total Size" -msgstr "Huidige grootte" +msgstr "Totale Grootte" #: packages/lib/services/ReportService.ts:328 msgid "Total: %d/%d" @@ -6273,7 +6072,7 @@ msgstr "Totaal: %d/%d" #: packages/lib/services/trash/index.ts:44 msgid "Trash" -msgstr "" +msgstr "Prullenmand" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:319 #: packages/app-mobile/components/biometrics/BiometricPopup.tsx:123 @@ -6283,9 +6082,8 @@ msgstr "Probeer opnieuw" #: packages/lib/utils/joplinCloud/index.ts:362 #: packages/lib/utils/joplinCloud/index.ts:384 #: packages/lib/utils/joplinCloud/index.ts:406 -#, fuzzy msgid "Try it now" -msgstr "Nu doen" +msgstr "Probeer het nu" #: packages/app-cli/app/command-help.ts:72 msgid "" @@ -6293,7 +6091,7 @@ msgid "" "all` for the complete usage information." msgstr "" "Typ `help [commando]` voor meer informatie over een commando; of typ `help " -"all` voor de volledige gebruiksaanwijzing." +"all` voor de volledige gebruiksinformatie." #: packages/app-cli/app/main.js:105 msgid "Type `joplin help` for usage information." @@ -6306,12 +6104,12 @@ msgid "" "commands." msgstr "" "Typ de titel van een notitie of een deel van de inhoud om er naartoe te " -"springen. Of typ # gevolgd door de naam van een tag, of @ gevolgd door de " +"springen. Of typ # gevolgd door de naam van een label, of @ gevolgd door de " "naam van een notitieboek. Of typ : om naar commando's te zoeken." #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:235 msgid "Type new tags or select from list" -msgstr "Typ nieuwe tags of selecteer uit lijst" +msgstr "Typ nieuwe labels of selecteer uit lijst" #: packages/app-cli/app/help-utils.js:56 msgid "Type: %s." @@ -6319,15 +6117,15 @@ msgstr "Type: %s." #: packages/app-mobile/components/screens/Note/Note.tsx:946 msgid "Unable to edit resource of type %s" -msgstr "" +msgstr "Bron van het type %s kan niet bewerkt worden" #: packages/app-mobile/components/screens/LogScreen.tsx:108 msgid "Unable to share log data. Reason: %s" -msgstr "" +msgstr "Loggegevens kunnen niet gedeeld worden. Reden: %s" #: packages/lib/models/settings/builtInMetadata.ts:616 msgid "Uncompleted to-dos on top" -msgstr "Toon onvoltooide to-do's bovenaan" +msgstr "Toon onvoltooide taken bovenaan" #: packages/app-desktop/gui/MenuBar.tsx:750 #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:118 @@ -6341,17 +6139,18 @@ msgid "" "Uninstall and reinstall the application. Make sure you create a backup first " "by exporting all your notes as JEX from the desktop application." msgstr "" +"Verwijder de applicatie en installeer ze opnieuw. Zorg dat je eerst een back-" +"up creëert door al je notities als JEX te exporteren van de desktop-" +"applicatie." #: packages/app-mobile/utils/getVersionInfoText.ts:13 #: packages/app-mobile/utils/getVersionInfoText.ts:14 -#, fuzzy msgid "Unknown" -msgstr "Onbekende optie: %s" +msgstr "Onbekend" #: packages/app-desktop/bridge.ts:446 -#, fuzzy msgid "Unknown file type" -msgstr "Onbekende optie: %s" +msgstr "Onbekend bestandstype" #: packages/lib/utils/processStartFlags.ts:185 msgid "Unknown flag: %s" @@ -6361,28 +6160,24 @@ msgstr "Onbekende optie: %s" msgid "" "Unknown item type downloaded - please upgrade Joplin to the latest version" msgstr "" -"Onbekend item type opgehaald - gelieve Joplin te upgraden naar de recentste " -"versie" +"Onbekend itemtype gedownload - gelieve Joplin te upgraden naar de meest " +"recente versie" #: packages/app-mobile/utils/getVersionInfoText.ts:28 -#, fuzzy msgid "Unknown platform" -msgstr "Onbekende optie: %s" +msgstr "Onbekend platform" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:82 -#, fuzzy msgid "Unordered list" -msgstr "Aangemaakt: %s" +msgstr "Ongeordende lijst" #: packages/app-desktop/gui/ShareNoteDialog.tsx:165 -#, fuzzy msgid "Unpublish note" -msgstr "Delen" +msgstr "Publicatie van notitie ongedaan maken" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:168 -#, fuzzy msgid "Unshare" -msgstr "Delen" +msgstr "Stoppen met delen" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:387 msgid "" @@ -6394,25 +6189,26 @@ msgstr "" #: packages/app-mobile/components/screens/Note/Note.tsx:811 msgid "Unsupported image type: %s" -msgstr "Afbeeldingstype %s wordt niet ondersteund" +msgstr "Niet-ondersteund afbeeldingstype: %s" #: packages/app-desktop/gui/NoteRevisionViewer.tsx:173 #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/openItem.ts:47 msgid "Unsupported link or message: %s" -msgstr "Niet-ondersteunde koppeling of bericht: %s" +msgstr "Niet-ondersteunde link of bericht: %s" #: packages/app-mobile/commands/openItem.ts:60 -#, fuzzy msgid "" "Unsupported link or message: %s.\n" "Error: %s" -msgstr "Niet-ondersteunde koppeling of bericht: %s" +msgstr "" +"Niet-ondersteunde link of bericht: %s\n" +"Foutmelding: %s" #: packages/app-desktop/gui/ResourceScreen.tsx:123 #: packages/lib/models/BaseItem.ts:921 packages/lib/path-utils.ts:27 #: packages/lib/path-utils.ts:71 msgid "Untitled" -msgstr "Untitled" +msgstr "Zonder titel" # Context needed. #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:206 @@ -6421,26 +6217,22 @@ msgid "Update" msgstr "Bijwerken" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx:77 -#, fuzzy msgid "Update available" -msgstr "Exporteer profiel" +msgstr "Update beschikbaar" # Context needed #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:65 -#, fuzzy msgid "Update later" -msgstr "datum bijwerking" +msgstr "Later bijwerken" #: packages/server/src/routes/admin/users.ts:257 #: packages/server/src/routes/index/users.ts:91 -#, fuzzy msgid "Update profile" -msgstr "Exporteer profiel" +msgstr "Profiel bijwerken" #: packages/server/src/services/TaskService.ts:23 -#, fuzzy msgid "Update total sizes" -msgstr "Bijgewerkte lokale items: %d." +msgstr "Totale groottes bijwerken" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:208 #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:209 @@ -6461,12 +6253,11 @@ msgstr "Bijgewerkte lokale items: %d." #: packages/lib/Synchronizer.ts:202 msgid "Updated remote items: %d." -msgstr "Bijgewerkte remote items: %d." +msgstr "Bijgewerkte externe items: %d." #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:140 -#, fuzzy msgid "Updated: " -msgstr "Bijgewerkt: %s" +msgstr "Bijgewerkt: " #: packages/app-cli/app/command-import.ts:52 #: packages/app-desktop/gui/ImportScreen.tsx:91 @@ -6480,7 +6271,7 @@ msgstr "Bijgewerkt: %s" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:207 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:170 msgid "Updating..." -msgstr "Updaten ..." +msgstr "Bezig met updaten..." #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:80 msgid "Upgrade" @@ -6488,7 +6279,7 @@ msgstr "Upgraden" #: packages/app-cli/app/command-sync.ts:42 msgid "Upgrade the sync target to the latest version." -msgstr "Upgrade het synchronisatiedoel naar de laatste versie." +msgstr "Upgrade het synchronisatiedoel naar de meest recente versie." #: packages/app-desktop/gui/NotePropertiesDialog.tsx:72 #: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:114 @@ -6504,7 +6295,7 @@ msgstr "Gebruik: %s" #: packages/lib/models/settings/builtInMetadata.ts:1601 msgid "Use biometrics to secure access to the app" -msgstr "" +msgstr "Gebruik biometrie om toegang tot de app te beveiligen" #: packages/app-cli/app/command-ls.ts:33 packages/app-cli/app/command-tag.js:18 msgid "" @@ -6512,11 +6303,11 @@ msgid "" "TODO_CHECKED (for to-dos), TITLE" msgstr "" "Gebruik lange lijstopmaak. Opmaak is ID, NOTE_COUNT (voor notitieboek), " -"DATE, TODO_CHECKED (voor to-do's), TITLE" +"DATE, TODO_CHECKED (voor taken), TITLE" #: packages/lib/services/spellChecker/SpellCheckerService.ts:185 msgid "Use spell checker" -msgstr "Spellingscontrole inschakelen" +msgstr "Spellingscontrole gebruiken" #: packages/app-cli/app/command-help.ts:81 msgid "" @@ -6529,13 +6320,12 @@ msgstr "" #: packages/app-desktop/gui/MainScreen.tsx:750 msgid "Use the arrows to move the layout items. Press \"Escape\" to exit." msgstr "" -"Gebruik de pijlen om de schermonderdelen te verplaatsen. Druk op \"escape\" " -"om te eindigen." +"Gebruik de pijlen om schermonderdelen te verplaatsen. Druk op \"Escape\" om " +"te eindigen." #: packages/lib/models/settings/builtInMetadata.ts:1347 -#, fuzzy msgid "Use the legacy Markdown editor" -msgstr "Markdown emoji inschakelen" +msgstr "Gebruik de oudere Markdown-editor" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:552 msgid "" @@ -6550,14 +6340,16 @@ msgid "" "Use your biometrics to secure access to your application. You can always set " "it up later in Settings." msgstr "" +"Gebruik biometrie om toegang tot je applicatie te beveiligen. Je kan dit " +"altijd later instellen in Instellingen." #: packages/lib/models/settings/builtInMetadata.ts:1102 msgid "" "Used for most text in the markdown editor. If not found, a generic " "proportional (variable width) font is used." msgstr "" -"Wordt gebruikt voor de voornaamste tekst in de markdown bewerken. Wanneer " -"dit niet gevonden wordt, zal een proportioneel (variabele breedte) " +"Wordt gebruikt voor de meeste tekst in de Markdown-editor. Wanneer dit niet " +"gevonden wordt, zal een generiek proportioneel (variabele breedte) " "lettertype gebruikt worden." #: packages/lib/models/settings/builtInMetadata.ts:1115 @@ -6567,13 +6359,14 @@ msgid "" "font is used." msgstr "" "Wordt gebruikt waar een lettertype met vaste breedte nodig is om de tekst " -"leesbaar uit te lijnen (e.g. tabellen, checkboxes, code). Indien niet " -"gevonden zal er een algemeen monoscape lettertype met vaste breedte worden " +"leesbaar uit te lijnen (e.g. tabellen, selectievakjes, code). Indien niet " +"gevonden zal er een generiek monospace lettertype met vaste breedte worden " "gebruikt." +# Context unclear #: packages/server/src/services/MustacheService.ts:125 msgid "User deletions" -msgstr "" +msgstr "Verwijderingen van gebruikers" #: packages/server/src/routes/admin/users.ts:197 #: packages/server/src/services/MustacheService.ts:121 @@ -6583,13 +6376,12 @@ msgstr "Gebruikers" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:171 #: packages/app-desktop/gui/PasswordInput/LabelledPasswordInput.tsx:23 -#, fuzzy msgid "Valid" -msgstr "Ongeldig" +msgstr "Geldig" #: packages/app-mobile/components/biometrics/biometricAuthenticate.ts:10 msgid "Verify your identity" -msgstr "" +msgstr "Verifieer je identiteit" #: packages/app-desktop/gui/NoteList/utils/canManuallySortNotes.ts:10 msgid "View" @@ -6597,7 +6389,7 @@ msgstr "Weergave" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:141 msgid "View OCR text" -msgstr "" +msgstr "OCR-tekst zien" #: packages/app-mobile/components/screens/Note/Note.tsx:1044 msgid "View on map" @@ -6614,7 +6406,7 @@ msgstr "Bekijk ze nu" #: packages/lib/models/settings/builtInMetadata.ts:609 #: packages/lib/models/settings/builtInMetadata.ts:611 msgid "Viewer" -msgstr "Weergave" +msgstr "Viewer" #: packages/lib/models/settings/builtInMetadata.ts:1291 msgid "Vim" @@ -6622,21 +6414,20 @@ msgstr "Vim" #: packages/lib/models/settings/builtInMetadata.ts:1693 msgid "Voice typing language files (URL)" -msgstr "" +msgstr "Taalbestanden voor steminvoer (URL)" #: packages/app-mobile/components/screens/Note/Note.tsx:1191 #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:225 msgid "Voice typing..." -msgstr "" +msgstr "Steminvoer..." #: packages/lib/models/settings/builtInMetadata.ts:1712 msgid "Vosk" -msgstr "" +msgstr "Vosk" #: packages/lib/services/joplinCloudUtils.ts:27 -#, fuzzy msgid "Waiting for authorisation..." -msgstr "Autorisatietoken:" +msgstr "Wachten op autorisatie…." #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:224 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:122 @@ -6646,18 +6437,22 @@ msgstr "Waarschuwing" #: packages/app-desktop/gui/ResourceScreen.tsx:309 msgid "Warning: not all resources shown for performance reasons (limit: %s)." msgstr "" -"Waarschuwing: om redenen van performantie worden niet alle bijlagen getoond " -"(beperking: %s)." +"Waarschuwing: om redenen van performantie worden niet alle bronnen getoond " +"(limiet: %s)." #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:116 msgid "We have a system for reporting and removing problematic plugins." msgstr "" +"We hebben een system om problematische plugins te rapporteren en te " +"verwijderen." #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:114 msgid "" "We mark plugins developed by trusted Joplin community members as " "\"recommended\"." msgstr "" +"We markeren plugins die ontwikkeld zijn door vertrouwde leden van de Joplin-" +"community als \"aanbevolen\"." #: packages/lib/models/Setting.ts:1182 #: packages/lib/utils/joplinCloud/index.ts:166 @@ -6687,12 +6482,11 @@ msgstr "Website en documentatie" #: packages/app-mobile/utils/getVersionInfoText.ts:14 msgid "WebView package: %s" -msgstr "" +msgstr "WebView packet: %s" #: packages/app-mobile/utils/getVersionInfoText.ts:13 -#, fuzzy msgid "WebView version: %s" -msgstr "Nieuwe versie: %s" +msgstr "WebView versie: %s" #: packages/app-cli/app/gui/NoteWidget.js:36 msgid "" @@ -6708,40 +6502,40 @@ msgstr "" "Typ `:help shortcuts` voor een lijst van shortcuts, of `:help` voor " "gebruiksinformatie.\n" "\n" -"Om bijvoorbeeld een notitieboek aan te maken, typ `mb`; om een notitie te " +"Bijvoorbeeld, om een notitieboek aan te maken, typ `mb`; om een notitie te " "maken, typ `mn`." #: packages/lib/WelcomeUtils.ts:63 -#, fuzzy msgid "Welcome!" -msgstr "Welkom" +msgstr "Welkom!" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:100 -#, fuzzy msgid "What are plugins?" -msgstr "Plugin \"%s\" verwijderen?" +msgstr "Wat zijn plugins?" #: packages/lib/models/settings/builtInMetadata.ts:845 msgid "When creating a new note:" -msgstr "Wanneer u een nieuwe notitie aanmaakt:" +msgstr "Wanneer je een nieuwe notitie aanmaakt:" #: packages/lib/models/settings/builtInMetadata.ts:828 msgid "When creating a new to-do:" -msgstr "Wanneer u een nieuwe to-do aanmaakt:" +msgstr "Wanneer je een nieuwe taak aanmaakt:" #: packages/lib/models/settings/builtInMetadata.ts:501 msgid "" "When enabled, the application will scan your attachments and extract the " "text from it. This will allow you to search for text in these attachments." msgstr "" +"Wanneer ingeschakeld, zal de applicatie je bijlagen scannen en de tekst " +"ervan extraheren. Dit zal je toelaten om tekst te zoeken in deze bijlagen." #: packages/lib/models/settings/builtInMetadata.ts:1713 msgid "Whisper" -msgstr "" +msgstr "Fluister" #: packages/app-desktop/ElectronAppWrapper.ts:222 msgid "Window unresponsive." -msgstr "" +msgstr "Venster reageert niet." #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:105 msgid "Words" @@ -6774,69 +6568,63 @@ msgid "" "You are about to attach a large image (%dx%d pixels). Would you like to " "resize it down to %d pixels before attaching it?" msgstr "" -"U staat op het punt een grote afbeelding (%dx%d pixels) bij te voegen. Wil u " -"ze verkleinen tot %d pixels vooraleer ze bij te voegen?" +"Je staat op het punt een grote afbeelding (%dx%d pixels) bij te voegen. Wil " +"je die verkleinen tot %d pixels vooraleer ze bij te voegen?" #: packages/lib/services/joplinCloudUtils.ts:45 msgid "You are logged in into Joplin Cloud, you can leave this screen now." -msgstr "" +msgstr "Je bent ingelogd op Joplin Cloud, je mag dit scherm nu sluiten." #: packages/app-mobile/components/NoteList.tsx:98 msgid "You currently have no notebooks." -msgstr "U heeft momenteel geen notitieboeken." +msgstr "Je hebt momenteel geen notitieboeken." #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:280 msgid "You do not have any installed plugin." -msgstr "U heeft geen enkele geïnstalleerde plugin." +msgstr "Je hebt geen enkele geïnstalleerde plugin." #: packages/app-cli/app/gui/NoteWidget.js:50 msgid "You may also type `status` for more information." -msgstr "U kan ook `status` typen voor meer informatie." +msgstr "Je kan ook `status` typen voor meer informatie." #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:344 msgid "" "You may use the tool below to re-encrypt your data, for example if you know " "that some of your notes are encrypted with an obsolete encryption method." msgstr "" -"U kunt het onderstaande hulpmiddel gebruiken om uw gegevens opnieuw te " -"encrypteren, bijvoorbeeld als u weet dat sommige van uw notities " -"geëncrypteerd zijn met een verouderde encryptiemethode." +"Je kan het onderstaande hulpmiddel gebruiken om je gegevens opnieuw te " +"encrypteren, bijvoorbeeld als je weet dat sommige van je notities " +"versleuteld zijn met een verouderde encryptiemethode." #: packages/lib/services/joplinCloudUtils.ts:53 -#, fuzzy msgid "" "You were unable to connect to Joplin Cloud. Please check your credentials " "and try again. Error:" msgstr "" -"Er is een probleem opgetreden bij het configureren van je Joplin Cloud " -"gebruiker. Gelievee je e-mailadres en wachtwoord te controleren en opnieuw " -"te proberen. Fout was:\n" -"\n" -"%s" +"Je kon geen verbinding maken met Joplin Cloud. Controleer je inloggegevens " +"en probeer opnieuw. Foutmelding:" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:55 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:70 msgid "Your account doesn't have access to this feature" -msgstr "" +msgstr "Je account heeft geen toegang tot deze functie" #: packages/app-cli/app/cli-utils.js:160 msgid "Your choice: " -msgstr "Uw keuze: " +msgstr "Jouw keuze: " #: packages/lib/components/EncryptionConfigScreen/utils.ts:70 msgid "Your data is going to be re-encrypted and synced again." -msgstr "Uw data zullen opnieuw geëncrypteerd en gesynchroniseerd worden." +msgstr "Je gegevens zullen opnieuw verseluteld en gesynchroniseerd worden." #: packages/app-desktop/gui/MainScreen.tsx:562 #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:55 msgid "Your Joplin Cloud credentials are invalid, please login." -msgstr "" +msgstr "Je gegevens voor Joplin Cloud zijn ongeldig, gelieve in te loggen." #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:259 -#, fuzzy msgid "Your password is needed to decrypt some of your data." -msgstr "" -"Je masterwachtwoord is nodig om een deel van je gegevens te decrypteren." +msgstr "Je wachtwoord is nodig om een deel van je gegevens te decrypteren." #: packages/app-cli/app/command-sync.ts:262 msgid "" @@ -6848,7 +6636,7 @@ msgstr "" #: packages/app-desktop/checkForUpdates.ts:108 msgid "Your version: %s" -msgstr "Uw versie: %s" +msgstr "Jouw versie: %s" #: packages/app-desktop/gui/MenuBar.tsx:839 #: packages/app-desktop/gui/MenuBar.tsx:845 diff --git a/packages/tools/locales/ru_RU.po b/packages/tools/locales/ru_RU.po index 12f0a6d59e..09ca7c13e9 100644 --- a/packages/tools/locales/ru_RU.po +++ b/packages/tools/locales/ru_RU.po @@ -6,9 +6,11 @@ # msgid "" msgstr "" -"Project-Id-Version: Joplin-CLI 1.0.0\n" +"Project-Id-Version: Joplin-CLI 3.2.13\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Dmitriy Q \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: Dmitriy Q \n" "Language-Team: Sergey Segeda \n" "Language: ru_RU\n" "MIME-Version: 1.0\n" @@ -16,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 3.4.2\n" +"X-Generator: Poedit 3.5\n" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:593 msgid "- Camera: to allow taking a picture and attaching it to a note." @@ -50,7 +52,7 @@ msgstr "(В плагине: %s)" #: packages/app-mobile/components/side-menu-content.tsx:265 msgid "(level %d)" -msgstr "" +msgstr "(уровень %d)" #: packages/lib/SyncTargetNone.ts:16 msgid "(None)" @@ -264,9 +266,8 @@ msgstr "" "\"clear\" преобразует выбранную задачу в обычную заметку." #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:62 -#, fuzzy msgid "A new update (%s) is available" -msgstr "Доступно обновление" +msgstr "Доступно новое (%s) обновление" #: packages/lib/models/settings/builtInMetadata.ts:1253 msgid "A3" @@ -375,7 +376,7 @@ msgstr "Добавить получателя:" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:94 msgid "Add tag %s to note" -msgstr "" +msgstr "Добавить тег %s к заметке" #: packages/app-mobile/components/screens/Note/Note.tsx:1574 msgid "Add title" @@ -386,9 +387,9 @@ msgid "Add to dictionary" msgstr "Добавить в словарь" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:98 -#, fuzzy msgid "Add to note" -msgstr "Добавить заголовок" +msgstr "Добавить к заметке" + #: packages/server/src/services/MustacheService.ts:162 #: packages/server/src/services/MustacheService.ts:286 @@ -412,9 +413,8 @@ msgid "Advanced tools" msgstr "Расширенные инструменты" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:18 -#, fuzzy msgid "all" -msgstr "Установить" +msgstr "все" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:109 msgid "" @@ -521,13 +521,13 @@ msgid "Apply" msgstr "Применить" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:146 -#, fuzzy msgid "" "Are you sure that you want to restore the default toolbar layout?\n" "This cannot be undone." msgstr "" -"Вы уверены, что хотите вернуться к макету по умолчанию? Текущая конфигурация " -"макета будет потеряна." +"Вы уверены, что хотите восстановить расположение панели инструментов по " +"умолчанию?\n" +"Это нельзя отменить." #: packages/app-desktop/gui/ClipperConfigScreen.tsx:38 msgid "Are you sure you want to renew the authorisation token?" @@ -554,6 +554,8 @@ msgid "" "At present, Joplin Web can only be open in one tab at a time. Please close " "the other instance of Joplin." msgstr "" +"В настоящее время Joplin Web может быть одновременно открыт только в одной " +"вкладке. Пожалуйста, закройте другую вкладку Joplin." #: packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.ts:25 msgid "Attach" @@ -640,7 +642,7 @@ msgstr "Автоматическое закрытие скобок, кавыче #: packages/lib/models/settings/builtInMetadata.ts:656 msgid "Autocomplete Markdown and HTML" -msgstr "" +msgstr "Автозавершение Markdown и HTML" #: packages/lib/models/settings/builtInMetadata.ts:1198 msgid "Automatically check for updates" @@ -704,7 +706,7 @@ msgstr "к %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:20 msgid "by word" -msgstr "" +msgstr "словом" #: packages/server/src/routes/admin/users.ts:160 msgid "Can Share" @@ -877,13 +879,12 @@ msgid "Change language" msgstr "Изменить язык" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:124 -#, fuzzy msgid "Change ratio" -msgstr "Конфигурация" +msgstr "Коэффициент изменений" #: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:98 msgid "Change shortcut for \"%s\"" -msgstr "" +msgstr "Изменить ярлык для \"%s\"" #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:106 msgid "Characters" @@ -895,7 +896,7 @@ msgstr "Символы за исключением пробелов" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:168 msgid "Check elements to display in the toolbar" -msgstr "" +msgstr "Отметьте элементы для отображения на панели инструментов" #: packages/app-desktop/gui/MenuBar.tsx:634 #: packages/app-desktop/gui/MenuBar.tsx:929 @@ -960,9 +961,8 @@ msgid "Client ID: %s" msgstr "Идентификатор клиента: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:23 -#, fuzzy msgid "close" -msgstr "Закрыть" +msgstr "закрыть" #: packages/app-desktop/gui/MenuBar.tsx:359 #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:175 @@ -982,13 +982,12 @@ msgid "Close dropdown" msgstr "Закрыть выпадающий список" #: packages/app-mobile/components/accessibility/AccessibleModalMenu.tsx:46 -#, fuzzy msgid "Close menu" -msgstr "Закрыть" +msgstr "Закрыть меню" #: packages/app-mobile/components/SideMenu.tsx:300 msgid "Close side menu" -msgstr "" +msgstr "Закрыть боковое меню" #: packages/lib/models/settings/settingValidations.ts:33 msgid "" @@ -1036,7 +1035,7 @@ msgstr "Совместная работа над блокнотами с дру #: packages/app-desktop/gui/Sidebar/listItemComponents/ExpandIcon.tsx:28 msgid "Collapsed, press space to expand." -msgstr "" +msgstr "Свернуто, нажмите пробел чтобы развернуть." #: packages/lib/services/ReportService.ts:351 msgid "Coming alarms" @@ -1076,14 +1075,12 @@ msgid "Compact" msgstr "Компактный" #: packages/app-desktop/gui/NoteList/utils/useOnKeyDown.ts:153 -#, fuzzy msgid "Complete" -msgstr "Завершено" +msgstr "Завершенные" #: packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.ts:40 -#, fuzzy msgid "Complete to-do" -msgstr "Завершено" +msgstr "Завершенные дела" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:12 #: packages/app-desktop/gui/NotePropertiesDialog.tsx:70 @@ -1156,12 +1153,11 @@ msgid "Consolidated billing" msgstr "Консолидированный биллинг" #: packages/app-desktop/gui/Sidebar/listItemComponents/NoteCount.tsx:11 -#, fuzzy msgid "Contains %d note" msgid_plural "Contains %d notes" -msgstr[0] "Преобразовать в заметку" -msgstr[1] "Преобразовать в заметку" -msgstr[2] "Преобразовать в заметку" +msgstr[0] "Содержит %d заметку" +msgstr[1] "Содержит %d заметки" +msgstr[2] "Содержит %d заметок" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx:106 msgid "Content provided by %s" @@ -1176,9 +1172,8 @@ msgid "Continue" msgstr "Продолжить" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:6 -#, fuzzy msgid "Control character" -msgstr "Символы" +msgstr "Управляющий символ" #: packages/app-mobile/components/screens/Note/Note.tsx:1221 msgid "Convert to note" @@ -1415,14 +1410,12 @@ msgid "Ctrl-click to open: %s" msgstr "Нажмите Ctrl для открытия: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:24 -#, fuzzy msgid "current match" -msgstr "Следующее совпадение" +msgstr "текущее совпадение" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:153 -#, fuzzy msgid "Current password" -msgstr "Введите пароль" +msgstr "Текущий пароль" #: packages/app-desktop/checkForUpdates.ts:90 msgid "Current version is up-to-date." @@ -1544,6 +1537,8 @@ msgid "" "Delete model and re-download?\n" "This cannot be undone." msgstr "" +"Удалить модель и перезагрузить?\n" +"Это невозможно отменить." #: packages/lib/commands/deleteNote.ts:7 msgid "Delete note" @@ -1624,14 +1619,12 @@ msgid "Deletes the notes without asking for confirmation." msgstr "Удаляет заметки без запроса подтверждения." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:551 -#, fuzzy msgid "Deletion log" -msgstr "Удалить строку" +msgstr "Журнал удаления" #: packages/app-mobile/components/NoteItem.tsx:144 -#, fuzzy msgid "Deselect" -msgstr "Выбрать" +msgstr "Отменить выбор" #: packages/app-cli/app/command-export.ts:24 msgid "Destination format: %s" @@ -1643,9 +1636,8 @@ msgid "Detailed" msgstr "Подробный" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx:99 -#, fuzzy msgid "Dev" -msgstr "Due" +msgstr "Разр" #: packages/lib/services/interop/Module.ts:62 msgid "Directory" @@ -1682,9 +1674,8 @@ msgid "Disabled" msgstr "Отключено" #: packages/app-mobile/components/screens/encryption-config.tsx:323 -#, fuzzy msgid "Disabled keys" -msgstr "Скрыть отключенные ключи" +msgstr "Отключенные клавиши" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:200 #: packages/app-mobile/components/screens/encryption-config.tsx:251 @@ -1795,9 +1786,8 @@ msgid "Download and install the relevant extension for your browser:" msgstr "Скачайте и установите расширение для вашего браузера:" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:206 -#, fuzzy msgid "Download updated model" -msgstr "Загружено" +msgstr "Скачать обновленную модель" #: packages/lib/models/Resource.ts:409 msgid "Downloaded" @@ -1894,12 +1884,11 @@ msgstr "Редактировать во внешнем редакторе" #: packages/app-desktop/commands/openNoteInNewWindow.ts:10 msgid "Edit in new window" -msgstr "" +msgstr "Редактировать в новом окне" #: packages/app-desktop/gui/utils/NoteListUtils.ts:70 -#, fuzzy msgid "Edit in..." -msgstr "Редактирование ссылки" +msgstr "Редактировать в..." #: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:143 msgid "Edit link" @@ -1930,9 +1919,8 @@ msgid "Editor" msgstr "Редактор" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Toolbar.tsx:38 -#, fuzzy msgid "Editor actions" -msgstr "Шрифт редактора" +msgstr "Действия редактора" #: packages/lib/models/settings/builtInMetadata.ts:1074 msgid "Editor font" @@ -2048,7 +2036,7 @@ msgstr "Включить шифрование" #: packages/lib/models/settings/builtInMetadata.ts:994 msgid "Enable file:// URLs for images and videos" -msgstr "" +msgstr "Включить URL-адреса file:// для изображений и видео" #: packages/lib/models/settings/builtInMetadata.ts:975 msgid "Enable footnotes" @@ -2104,9 +2092,8 @@ msgid "Enable soft breaks" msgstr "Включить мягкие отступы" #: packages/lib/models/settings/builtInMetadata.ts:1303 -#, fuzzy msgid "Enable spell checking in Markdown editor" -msgstr "Включить проверку орфографии в текстовом редакторе" +msgstr "Включить проверку орфографии в редакторе Markdown" #: packages/lib/models/settings/builtInMetadata.ts:789 msgid "Enable spellcheck in the text editor" @@ -2145,6 +2132,8 @@ msgid "" "Enables Markdown list continuation, auto-closing HTML tags, and other markup " "autocompletions." msgstr "" +"Включает продолжение списка Markdown, автозакрытие HTML-тегов и другие " +"автодополнения разметки." #: packages/lib/components/EncryptionConfigScreen/utils.ts:50 msgid "" @@ -2191,9 +2180,8 @@ msgid "End-to-end encryption" msgstr "Сквозное шифрование" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:244 -#, fuzzy msgid "Ends voice typing" -msgstr "Голосовой набор" +msgstr "Завершение голосового набора" #: packages/app-mobile/components/screens/dropbox-login.tsx:70 msgid "Enter code here" @@ -2213,9 +2201,8 @@ msgid "Enter password" msgstr "Введите пароль" #: packages/app-mobile/components/NoteItem.tsx:120 -#, fuzzy msgid "Entering selection mode" -msgstr "Открытие раздела %s" +msgstr "Вход в режим выбора" #: packages/app-cli/app/help-utils.js:56 msgid "Enum" @@ -2282,7 +2269,7 @@ msgstr "Расширить %s" #: packages/app-desktop/gui/Sidebar/listItemComponents/ExpandIcon.tsx:26 msgid "Expanded, press space to collapse." -msgstr "" +msgstr "Развернуто, нажмите пробел чтобы свернуть." #: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:182 #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:213 @@ -2308,9 +2295,8 @@ msgid "Export Debug Report" msgstr "Экспортировать отладочный отчет" #: packages/app-desktop/commands/exportDeletionLog.ts:11 -#, fuzzy msgid "Export deletion log" -msgstr "Экспортировать все" +msgstr "Экспорт журнала удаления" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:16 msgid "Export profile" @@ -2396,9 +2382,8 @@ msgid "Filter tags" msgstr "Фильтр тегов" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:14 -#, fuzzy msgid "Find" -msgstr "Найти: " +msgstr "Поиск" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:253 msgid "Find: " @@ -2436,7 +2421,7 @@ msgstr "Фокус на названии" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:97 msgid "Follow link" -msgstr "" +msgstr "Перейти по ссылке" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:38 msgid "For debugging purpose only: export your profile to an external SD card." @@ -2532,11 +2517,11 @@ msgstr "" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:13 msgid "go" -msgstr "" +msgstr "перейти" #: packages/app-mobile/components/CameraView/CameraView.tsx:165 msgid "Go back" -msgstr "" +msgstr "Вернуться" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:44 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:59 @@ -2545,7 +2530,7 @@ msgstr "Перейти в профиль Joplin Cloud" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:12 msgid "Go to line" -msgstr "" +msgstr "Перейти к строке" #: packages/app-mobile/components/screens/Note/Note.tsx:1051 msgid "Go to source URL" @@ -2612,9 +2597,8 @@ msgid "Hide keyboard" msgstr "Скрыть клавиатуру" #: packages/app-desktop/gui/PasswordInput/PasswordInput.tsx:21 -#, fuzzy msgid "Hide password" -msgstr "Неверный пароль" +msgstr "Скрыть пароль" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:14 msgid "Highlight" @@ -2833,14 +2817,12 @@ msgid "Incompatible" msgstr "Несовместимые" #: packages/app-desktop/gui/NoteList/utils/useOnKeyDown.ts:153 -#, fuzzy msgid "Incomplete" -msgstr "Завершено" +msgstr "Незавершенный" #: packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.ts:40 -#, fuzzy msgid "Incomplete to-do" -msgstr "Показать завершенные задачи" +msgstr "Незавершенные задачи" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:97 msgid "Increase indent level" @@ -2856,7 +2838,7 @@ msgstr "Увеличить отступ" #: packages/app-mobile/components/DialogManager/hooks/useDialogControl.ts:21 msgid "Info" -msgstr "" +msgstr "Информация" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:223 msgid "Information" @@ -2958,9 +2940,8 @@ msgid "Items that cannot be synchronised" msgstr "Элементы, которые не могут быть синхронизированы" #: packages/app-desktop/gui/MenuBar.tsx:923 -#, fuzzy msgid "Join us on %s" -msgstr "Присоединяйтесь к нам в Twitter" +msgstr "Присоединяйтесь к нам в %s" #: packages/app-desktop/gui/SyncWizard/Dialog.tsx:267 msgid "" @@ -3016,9 +2997,8 @@ msgid "Joplin Forum" msgstr "Форум Joplin" #: packages/app-mobile/utils/lockToSingleInstance.ts:16 -#, fuzzy msgid "Joplin is already running." -msgstr "Сервер уже запущен. Порт: %d" +msgstr "Сервер уже запущен." #: packages/lib/services/plugins/PluginService.ts:523 msgid "Joplin Mobile" @@ -3272,9 +3252,8 @@ msgid "Manage shared notebooks" msgstr "Управление общими блокнотами" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:165 -#, fuzzy msgid "Manage toolbar options" -msgstr "Управлять плагинами" +msgstr "Управление параметрами панели инструментов" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:331 msgid "Manage your plugins" @@ -3307,9 +3286,8 @@ msgstr "Markdown + Front Matter" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/CodeMirror.tsx:375 #: packages/app-mobile/components/NoteEditor/NoteEditor.tsx:352 -#, fuzzy msgid "Markdown editor" -msgstr "Markdown" +msgstr "Редактор Markdown" #: packages/app-cli/app/command-done.ts:15 msgid "Marks a to-do as done." @@ -3340,11 +3318,11 @@ msgstr "Главный пароль:" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:19 msgid "match case" -msgstr "" +msgstr "подходящий вариант" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:72 msgid "Math" -msgstr "" +msgstr "Математика" #: packages/lib/models/settings/builtInMetadata.ts:421 msgid "Max concurrent connections" @@ -3371,9 +3349,8 @@ msgid "Minimise" msgstr "Минимизировать" #: packages/app-mobile/components/CameraView/CameraView.tsx:163 -#, fuzzy msgid "Missing camera permission" -msgstr "Недостающие мастер-ключи" +msgstr "Отсутствует разрешение для камеры" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:320 msgid "Missing keys" @@ -3463,14 +3440,12 @@ msgid "Never resize" msgstr "Никогда не изменять размер" #: packages/app-mobile/setupQuickActions.ts:33 -#, fuzzy msgid "New attachment" -msgstr "Вложения заметки" +msgstr "Новое вложение" #: packages/app-mobile/setupQuickActions.ts:34 -#, fuzzy msgid "New drawing" -msgstr "Рисунок" +msgstr "Новый рисунок" #: packages/app-mobile/components/screens/ShareManager/index.tsx:120 msgid "New invitations" @@ -3500,9 +3475,8 @@ msgstr "" "Будет создан новый блокнот \"%s\" и файл \"%s\" будет импортирован в него" #: packages/app-mobile/setupQuickActions.ts:32 -#, fuzzy msgid "New photo" -msgstr "Сделать фото" +msgstr "Новое фото" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newSubFolder.ts:6 msgid "New sub-notebook" @@ -3526,7 +3500,7 @@ msgstr "Новая версия: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:16 msgid "next" -msgstr "" +msgstr "далее" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:271 msgid "Next match" @@ -3610,14 +3584,12 @@ msgstr "" "path>`" #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:93 -#, fuzzy msgid "No updates available" -msgstr "Доступно обновление" +msgstr "Нет доступных обновлений" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.ts:44 -#, fuzzy msgid "None" -msgstr "(Нет)" +msgstr "Нет" #: packages/lib/models/settings/builtInMetadata.ts:47 msgid "Nord" @@ -3780,12 +3752,11 @@ msgstr "Нумерованный список" #: packages/lib/models/settings/builtInMetadata.ts:531 msgid "OCR: Clear cache and re-download language data files" -msgstr "" +msgstr "OCR: очистите кэш и заново загрузите языковые файлы" #: packages/lib/models/settings/builtInMetadata.ts:512 -#, fuzzy msgid "OCR: Language data URL or path" -msgstr "Язык, формат даты" +msgstr "OCR: URL-адрес или путь к языковым данным" #: packages/app-desktop/bridge.ts:360 packages/app-desktop/bridge.ts:373 #: packages/app-desktop/bridge.ts:387 packages/app-desktop/bridge.ts:403 @@ -3821,7 +3792,7 @@ msgstr "В %s: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:27 msgid "on line" -msgstr "" +msgstr "он-лайн" #: packages/app-desktop/gui/MainScreen.tsx:525 msgid "One of your master keys use an obsolete encryption method." @@ -3852,9 +3823,8 @@ msgid "OneDrive Login" msgstr "Вход в OneDrive" #: packages/lib/services/interop/InteropService.ts:144 -#, fuzzy msgid "OneNote Notebook" -msgstr "Новый блокнот" +msgstr "Блокнот OneNote" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/print.ts:18 msgid "Only one note can be printed at a time." @@ -3881,9 +3851,8 @@ msgid "Open profile directory" msgstr "Открыть директорию с настройками" #: packages/app-mobile/components/CameraView/CameraView.tsx:164 -#, fuzzy msgid "Open settings" -msgstr "Открытие раздела %s" +msgstr "Открыть настройки" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:115 msgid "Open Source" @@ -3902,19 +3871,16 @@ msgid "Opening section %s" msgstr "Открытие раздела %s" #: packages/app-mobile/components/Dropdown.tsx:215 -#, fuzzy msgid "Opens dropdown" -msgstr "Закрыть выпадающий список" +msgstr "Открыть выпадающий список" #: packages/app-mobile/components/NoteItem.tsx:162 -#, fuzzy msgid "Opens note" -msgstr "Откройте" +msgstr "Открыть заметку" #: packages/app-mobile/components/side-menu-content.tsx:273 -#, fuzzy msgid "Opens notebook" -msgstr "Новый блокнот" +msgstr "Открыть блокнот" #: packages/app-cli/app/command-e2ee.ts:41 #: packages/app-cli/app/command-e2ee.ts:87 @@ -4183,11 +4149,11 @@ msgstr "Предпочитаемая светлая тема" #: packages/lib/models/settings/builtInMetadata.ts:1704 msgid "Preferred voice typing provider" -msgstr "" +msgstr "Предпочитаемый провайдер голосового ввода текста" #: packages/lib/models/settings/builtInMetadata.ts:667 msgid "Preserve colours when pasting text in Rich Text Editor" -msgstr "" +msgstr "Сохранение цветов при вставке текста в редактор свободного текста" #: packages/app-cli/app/app-gui.js:758 msgid "Press Ctrl+D or type \"exit\" to exit the application" @@ -4210,9 +4176,8 @@ msgid "Press to set the decryption password." msgstr "Нажмите, чтобы установить пароль для расшифровки." #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:17 -#, fuzzy msgid "previous" -msgstr "Предыдущее совпадение" +msgstr "предыдущее" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:281 msgid "Previous match" @@ -4255,9 +4220,8 @@ msgid "Processing" msgstr "Обработка" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:111 -#, fuzzy msgid "Processing photo..." -msgstr "Обработка" +msgstr "Обработка фото..." #: packages/server/src/routes/admin/users.ts:254 msgid "Profile" @@ -4313,9 +4277,8 @@ msgid "Publish notes to the internet" msgstr "Опубликовать заметки в Интернет" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:78 -#, fuzzy msgid "QR Code" -msgstr "Код" +msgstr "QR код" #: packages/app-desktop/app.ts:219 #: packages/app-desktop/ElectronAppWrapper.ts:123 @@ -4326,7 +4289,7 @@ msgstr "Выход" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:206 msgid "Re-download model" -msgstr "" +msgstr "Скачать модель заново" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:342 msgid "Re-encrypt data" @@ -4337,9 +4300,8 @@ msgid "Re-encryption" msgstr "Зашифровать заново" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:181 -#, fuzzy msgid "Re-enter password" -msgstr "Введите пароль" +msgstr "Введите пароль повторно" #: packages/lib/models/settings/builtInMetadata.ts:1180 msgid "Re-upload local data to sync target" @@ -4405,9 +4367,8 @@ msgid "Remove" msgstr "Удалить" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:330 -#, fuzzy msgid "Remove %s from share" -msgstr "Удалить метку “%s” из всех заметок?" +msgstr "Удалить %s из общего ресурса" #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:111 msgid "Remove tag \"%s\" from all notes?" @@ -4439,9 +4400,8 @@ msgid "Renew token" msgstr "Обновить токен" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:21 -#, fuzzy msgid "replace" -msgstr "Замена" +msgstr "заменить" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:15 #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:291 @@ -4449,9 +4409,8 @@ msgid "Replace" msgstr "Замена" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:22 -#, fuzzy msgid "replace all" -msgstr "Заменить все" +msgstr "заменить все" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:301 msgid "Replace all" @@ -4467,11 +4426,11 @@ msgstr "Замена: " #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:25 msgid "replaced $ matches" -msgstr "" +msgstr "заменено $ совпадений" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:26 msgid "replaced match on line $" -msgstr "" +msgstr "заменено совпадение в строке $" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:162 msgid "Report an issue" @@ -4534,9 +4493,8 @@ msgid "Restore" msgstr "Восстановить" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:156 -#, fuzzy msgid "Restore defaults" -msgstr "Восстановить объекты" +msgstr "Восстановить настройки по умолчанию" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/restoreNote.ts:10 msgid "Restore note" @@ -4594,11 +4552,12 @@ msgstr "Изменения: %s (%s)" #: packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.tsx:28 msgid "Rich Text" -msgstr "" +msgstr "Свободный текст" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:686 msgid "Rich Text editor. Press Escape then Tab to escape focus." msgstr "" +"Редактор свободного текста. Нажмите Escape, затем Tab, чтобы выйти из фокуса." #: packages/app-cli/app/command-batch.js:10 msgid "" @@ -4679,7 +4638,7 @@ msgstr "Сохранять информацию о географическом #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:89 msgid "Scanned code" -msgstr "" +msgstr "Сканировать код" #: packages/app-desktop/gui/lib/SearchInput/SearchInput.tsx:62 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:102 @@ -4712,9 +4671,8 @@ msgid "Search in current note" msgstr "Поиск в текущей заметке" #: packages/app-desktop/plugins/GotoAnything.tsx:665 -#, fuzzy msgid "Search results" -msgstr "Нет данных" +msgstr "Результаты поиска" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:170 msgid "Search shown" @@ -4737,9 +4695,8 @@ msgid "Searches for the given in all the notes." msgstr "Осуществляет поиск по шаблону во всех заметках." #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:63 -#, fuzzy msgid "See changelog" -msgstr "Полный список изменений" +msgstr "Посмотреть список изменений" #: packages/lib/models/settings/builtInMetadata.ts:1199 msgid "See the pre-release page for more details: %s" @@ -4770,14 +4727,12 @@ msgid "Select parent notebook" msgstr "Выбрать родительский блокнот" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:9 -#, fuzzy msgid "Selection deleted" -msgstr "дата завершения" +msgstr "Выбранное удалено" #: packages/app-mobile/components/FolderPicker.tsx:68 -#, fuzzy msgid "Selects a notebook" -msgstr "Выбрать родительский блокнот" +msgstr "Выбор блокнота" #: packages/app-desktop/gui/MenuBar.tsx:359 msgid "Send bug report" @@ -4833,7 +4788,7 @@ msgstr "" #: packages/app-mobile/components/EditorToolbar/EditorToolbar.tsx:47 msgid "Settings" -msgstr "" +msgstr "Настройки" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:281 #: packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.ts:44 @@ -4872,7 +4827,6 @@ msgid "Share permissions" msgstr "Разрешения для общего доступа" #: packages/app-desktop/gui/Sidebar/listItemComponents/FolderItem.tsx:56 -#, fuzzy msgid "Shared" msgstr "Поделиться" @@ -4921,14 +4875,12 @@ msgid "Show note counts" msgstr "Показывать число заметок" #: packages/app-mobile/components/side-menu-content.tsx:258 -#, fuzzy msgid "Show notebook options" -msgstr "Показывать число заметок" +msgstr "Показать параметры блокнота" #: packages/app-desktop/gui/PasswordInput/PasswordInput.tsx:21 -#, fuzzy msgid "Show password" -msgstr "Установить пароль" +msgstr "Показать пароль" #: packages/lib/models/settings/builtInMetadata.ts:698 msgid "Show sort order buttons" @@ -4943,9 +4895,8 @@ msgid "Show/hide the sidebar" msgstr "Показать/скрыть боковую панель" #: packages/app-mobile/components/screens/tags.tsx:76 -#, fuzzy msgid "Shows notes for tag" -msgstr "Показывать число заметок" +msgstr "Показать заметки для тега" #: packages/lib/models/settings/builtInMetadata.ts:863 msgid "Shrink large images before adding them to notes." @@ -5031,11 +4982,11 @@ msgstr "" #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in ascending order" -msgstr "" +msgstr "Сортировка \"%s\" в порядке возрастания" #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in descending order" -msgstr "" +msgstr "Сортировка \"%s\" в порядке убывания" #: packages/lib/models/settings/builtInMetadata.ts:754 msgid "Sort notebooks by" @@ -5219,12 +5170,11 @@ msgstr "Переключение профиля" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:101 msgid "Switch to back-facing camera" -msgstr "" +msgstr "Переключение на основную камеру" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:101 -#, fuzzy msgid "Switch to front-facing camera" -msgstr "Переключение на профиль %d" +msgstr "Переключение на фронтальную камеру" #: packages/app-desktop/gui/utils/NoteListUtils.ts:93 msgid "Switch to note type" @@ -5237,14 +5187,12 @@ msgid "Switch to profile %d" msgstr "Переключение на профиль %d" #: packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.tsx:28 -#, fuzzy msgid "Switch to the %s Editor" -msgstr "Конвертировать в заметку" +msgstr "Переключение на редактор %s" #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:78 -#, fuzzy msgid "Switch to the legacy editor" -msgstr "Конвертировать в заметку" +msgstr "Переключение на старый редактор" #: packages/app-desktop/gui/utils/NoteListUtils.ts:102 msgid "Switch to to-do type" @@ -5355,7 +5303,7 @@ msgstr "тег1, тег2, ..." #: packages/app-cli/app/command-import.ts:55 #: packages/app-desktop/gui/ImportScreen.tsx:94 msgid "Tagged: %d." -msgstr "С метками: %d." +msgstr "Тегировано: %d." #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:10 #: packages/app-desktop/gui/Sidebar/hooks/useSidebarListData.ts:69 @@ -5527,7 +5475,7 @@ msgstr "" #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:83 msgid "The following plugins may not support the current markdown editor:" -msgstr "" +msgstr "Следующие плагины могут не поддерживать текущий редактор markdown:" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:257 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx:49 @@ -5655,6 +5603,11 @@ msgid "" "\n" "Error: \"%s\"" msgstr "" +"Веб-клиент не поддерживает прием зашифрованных общих блокнотов. Пожалуйста, " +"переключитесь на ПК или мобильное приложение, прежде чем принимать общий " +"доступ.\n" +"\n" +"Ошибка: \"%s\"" #: packages/app-desktop/gui/Root.tsx:150 msgid "The Web Clipper needs your authorisation to access your data." @@ -6013,9 +5966,8 @@ msgid "Toggle editor layout" msgstr "Переключить вид редактора" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditorPlugin.ts:11 -#, fuzzy msgid "Toggle editor plugin" -msgstr "Переключить вид редактора" +msgstr "Переключить плагин редактора" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditors.ts:8 msgid "Toggle editors" @@ -6162,9 +6114,8 @@ msgstr "" "версии" #: packages/app-mobile/utils/getVersionInfoText.ts:28 -#, fuzzy msgid "Unknown platform" -msgstr "Неизвестный флаг: %s" +msgstr "Неизвестная платформа" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:82 msgid "Unordered list" @@ -6196,11 +6147,12 @@ msgid "Unsupported link or message: %s" msgstr "Неподдерживаемая ссылка или сообщение: %s" #: packages/app-mobile/commands/openItem.ts:60 -#, fuzzy msgid "" "Unsupported link or message: %s.\n" "Error: %s" -msgstr "Неподдерживаемая ссылка или сообщение: %s" +msgstr "" +"Неподдерживаемая ссылка или сообщение: %s\n" +"Ошибка: %s" #: packages/app-desktop/gui/ResourceScreen.tsx:123 #: packages/lib/models/BaseItem.ts:921 packages/lib/path-utils.ts:27 @@ -6218,9 +6170,8 @@ msgid "Update available" msgstr "Доступно обновление" #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:65 -#, fuzzy msgid "Update later" -msgstr "дата обновления" +msgstr "Обновить позже" #: packages/server/src/routes/admin/users.ts:257 #: packages/server/src/routes/index/users.ts:91 @@ -6320,9 +6271,8 @@ msgstr "" "выхода." #: packages/lib/models/settings/builtInMetadata.ts:1347 -#, fuzzy msgid "Use the legacy Markdown editor" -msgstr "Включить панель инструментов Markdown" +msgstr "Использовать старый редактор Markdown" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:552 msgid "" @@ -6418,7 +6368,7 @@ msgstr "Голосовой набор..." #: packages/lib/models/settings/builtInMetadata.ts:1712 msgid "Vosk" -msgstr "" +msgstr "Vosk" #: packages/lib/services/joplinCloudUtils.ts:27 msgid "Waiting for authorisation..." @@ -6523,7 +6473,7 @@ msgstr "" #: packages/lib/models/settings/builtInMetadata.ts:1713 msgid "Whisper" -msgstr "" +msgstr "Шепот" #: packages/app-desktop/ElectronAppWrapper.ts:222 msgid "Window unresponsive." diff --git a/packages/tools/locales/sv.po b/packages/tools/locales/sv.po index 4af365700b..3989f52095 100644 --- a/packages/tools/locales/sv.po +++ b/packages/tools/locales/sv.po @@ -1,13 +1,15 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Laurent Cozic # This file is distributed under the same license as the Joplin-CLI package. -# FIRST AUTHOR , YEAR. +# Jonatan Nyberg, 2023, 2024, 2025. # msgid "" msgstr "" "Project-Id-Version: Joplin-CLI 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: Jonatan Nyberg \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: Jonatan Nyberg\n" "Language-Team: \n" "Language: sv\n" "MIME-Version: 1.0\n" @@ -49,7 +51,7 @@ msgstr "(I insticksmodul: %s)" #: packages/app-mobile/components/side-menu-content.tsx:265 msgid "(level %d)" -msgstr "" +msgstr "(nivå %d)" #: packages/lib/SyncTargetNone.ts:16 msgid "(None)" @@ -263,9 +265,8 @@ msgstr "" "att-göra tillbaka till en vanlig anteckning." #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:62 -#, fuzzy msgid "A new update (%s) is available" -msgstr "Uppdatering tillgänglig" +msgstr "En ny uppdatering (%s) är tillgänglig" #: packages/lib/models/settings/builtInMetadata.ts:1253 msgid "A3" @@ -373,7 +374,7 @@ msgstr "Lägg till mottagare:" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:94 msgid "Add tag %s to note" -msgstr "" +msgstr "Lägg till taggen %s till anteckningen" #: packages/app-mobile/components/screens/Note/Note.tsx:1574 msgid "Add title" @@ -384,9 +385,8 @@ msgid "Add to dictionary" msgstr "Lägg till i ordlistan" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:98 -#, fuzzy msgid "Add to note" -msgstr "Lägg till titel" +msgstr "Lägg till i anteckning" #: packages/server/src/services/MustacheService.ts:162 #: packages/server/src/services/MustacheService.ts:286 @@ -410,9 +410,8 @@ msgid "Advanced tools" msgstr "Avancerade verktyg" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:18 -#, fuzzy msgid "all" -msgstr "Installera" +msgstr "alla" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:109 msgid "" @@ -522,13 +521,12 @@ msgid "Apply" msgstr "Tillämpa" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:146 -#, fuzzy msgid "" "Are you sure that you want to restore the default toolbar layout?\n" "This cannot be undone." msgstr "" -"Är du säker på att du vill återgå till standardlayouten? Den nuvarande " -"layoutkonfigurationen kommer att gå förlorad." +"Är du säker på att du vill återställa standardlayouten för verktygsfältet?\n" +"Detta kan inte ångras." #: packages/app-desktop/gui/ClipperConfigScreen.tsx:38 msgid "Are you sure you want to renew the authorisation token?" @@ -555,6 +553,8 @@ msgid "" "At present, Joplin Web can only be open in one tab at a time. Please close " "the other instance of Joplin." msgstr "" +"För närvarande kan Joplin Web endast vara öppen på en flik åt gången. Stäng " +"den andra instansen av Joplin." #: packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.ts:25 msgid "Attach" @@ -640,7 +640,7 @@ msgstr "Para automatiskt hakparenteser, paranteser, situationstecken, etc." #: packages/lib/models/settings/builtInMetadata.ts:656 msgid "Autocomplete Markdown and HTML" -msgstr "" +msgstr "Slutför automatiskt Markdown och HTML" #: packages/lib/models/settings/builtInMetadata.ts:1198 msgid "Automatically check for updates" @@ -702,7 +702,7 @@ msgstr "av %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:20 msgid "by word" -msgstr "" +msgstr "med ord" #: packages/server/src/routes/admin/users.ts:160 msgid "Can Share" @@ -878,13 +878,12 @@ msgid "Change language" msgstr "Ändra språk" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:124 -#, fuzzy msgid "Change ratio" -msgstr "Konfiguration" +msgstr "Ändra förhållande" #: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:98 msgid "Change shortcut for \"%s\"" -msgstr "" +msgstr "Ändra genväg för \"%s\"" #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:106 msgid "Characters" @@ -896,7 +895,7 @@ msgstr "Tecken exklusive mellanslag" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:168 msgid "Check elements to display in the toolbar" -msgstr "" +msgstr "Markera element som ska visas i verktygsfältet" #: packages/app-desktop/gui/MenuBar.tsx:634 #: packages/app-desktop/gui/MenuBar.tsx:929 @@ -962,9 +961,8 @@ msgid "Client ID: %s" msgstr "Klient-ID: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:23 -#, fuzzy msgid "close" -msgstr "Stäng" +msgstr "stäng" #: packages/app-desktop/gui/MenuBar.tsx:359 #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:175 @@ -984,13 +982,12 @@ msgid "Close dropdown" msgstr "Stäng rullgardinsmenyn" #: packages/app-mobile/components/accessibility/AccessibleModalMenu.tsx:46 -#, fuzzy msgid "Close menu" -msgstr "Stäng" +msgstr "Stäng menyn" #: packages/app-mobile/components/SideMenu.tsx:300 msgid "Close side menu" -msgstr "" +msgstr "Stäng sidomenyn" #: packages/lib/models/settings/settingValidations.ts:33 msgid "" @@ -1038,7 +1035,7 @@ msgstr "Samarbeta på anteckningsböcker tillsammans med andra" #: packages/app-desktop/gui/Sidebar/listItemComponents/ExpandIcon.tsx:28 msgid "Collapsed, press space to expand." -msgstr "" +msgstr "Komprimerad, tryck på blanksteg för att expandera." #: packages/lib/services/ReportService.ts:351 msgid "Coming alarms" @@ -1047,9 +1044,9 @@ msgstr "Kommande larm" #: packages/lib/models/settings/builtInMetadata.ts:1379 msgid "" "Comma-separated list of paths to directories to load the certificates from, " -"or path to individual cert files. For example: /my/cert_dir, /other/" -"custom.pem. Note that if you make changes to the TLS settings, you must save " -"your changes before clicking on \"Check synchronisation configuration\"." +"or path to individual cert files. For example: /my/cert_dir, /other/custom." +"pem. Note that if you make changes to the TLS settings, you must save your " +"changes before clicking on \"Check synchronisation configuration\"." msgstr "" "Kommaseparerad lista över sökvägar till kataloger för att läsa certifikaten " "från eller sökvägen till enskilda cert-filer. Till exempel: /my/cert_dir,/" @@ -1078,14 +1075,12 @@ msgid "Compact" msgstr "Kompakt" #: packages/app-desktop/gui/NoteList/utils/useOnKeyDown.ts:153 -#, fuzzy msgid "Complete" -msgstr "Slutförd" +msgstr "Slutför" #: packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.ts:40 -#, fuzzy msgid "Complete to-do" -msgstr "Slutförd" +msgstr "Slutför att-göra" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:12 #: packages/app-desktop/gui/NotePropertiesDialog.tsx:70 @@ -1158,11 +1153,10 @@ msgid "Consolidated billing" msgstr "Konsoliderad fakturering" #: packages/app-desktop/gui/Sidebar/listItemComponents/NoteCount.tsx:11 -#, fuzzy msgid "Contains %d note" msgid_plural "Contains %d notes" -msgstr[0] "Konvertera till anteckning" -msgstr[1] "Konvertera till anteckning" +msgstr[0] "Innehåller %d anteckning" +msgstr[1] "Innehåller %d anteckningar" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx:106 msgid "Content provided by %s" @@ -1177,9 +1171,8 @@ msgid "Continue" msgstr "Fortsätt" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:6 -#, fuzzy msgid "Control character" -msgstr "Tecken" +msgstr "Kontrollera tecken" #: packages/app-mobile/components/screens/Note/Note.tsx:1221 msgid "Convert to note" @@ -1416,14 +1409,12 @@ msgid "Ctrl-click to open: %s" msgstr "Ctrl-klicka för att öppna: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:24 -#, fuzzy msgid "current match" -msgstr "Nästa träff" +msgstr "aktuell matchning" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:153 -#, fuzzy msgid "Current password" -msgstr "Ange lösenord" +msgstr "Aktuellt lösenord" #: packages/app-desktop/checkForUpdates.ts:90 msgid "Current version is up-to-date." @@ -1545,6 +1536,8 @@ msgid "" "Delete model and re-download?\n" "This cannot be undone." msgstr "" +"Ta bort modell och hämta igen?\n" +"Detta kan inte ångras." #: packages/lib/commands/deleteNote.ts:7 msgid "Delete note" @@ -1625,14 +1618,12 @@ msgid "Deletes the notes without asking for confirmation." msgstr "Tar bort anteckningarna utan att be om bekräftelse." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:551 -#, fuzzy msgid "Deletion log" -msgstr "Ta bort rad" +msgstr "Borttagningslogg" #: packages/app-mobile/components/NoteItem.tsx:144 -#, fuzzy msgid "Deselect" -msgstr "Välj" +msgstr "Avmarkera" #: packages/app-cli/app/command-export.ts:24 msgid "Destination format: %s" @@ -1644,9 +1635,8 @@ msgid "Detailed" msgstr "Detaljerad" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx:99 -#, fuzzy msgid "Dev" -msgstr "Förfaller" +msgstr "Dev" #: packages/lib/services/interop/Module.ts:62 msgid "Directory" @@ -1683,9 +1673,8 @@ msgid "Disabled" msgstr "Inaktiverad" #: packages/app-mobile/components/screens/encryption-config.tsx:323 -#, fuzzy msgid "Disabled keys" -msgstr "Dölj inaktiverade nycklar" +msgstr "Inaktiverade nycklar" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:200 #: packages/app-mobile/components/screens/encryption-config.tsx:251 @@ -1727,9 +1716,9 @@ msgid "" "to-dos, while `-tnt` would display notes and to-dos." msgstr "" "Visar endast objekt av den specifika typen/typerna. Kan vara `n` för " -"anteckningar, `t` för att-göra eller `nt` för anteckningar och att-göra " -"(t.ex. `-tt` skulle bara visa att-göra, medan `-tnt` skulle visa " -"anteckningar och att-göra." +"anteckningar, `t` för att-göra eller `nt` för anteckningar och att-göra (t." +"ex. `-tt` skulle bara visa att-göra, medan `-tnt` skulle visa anteckningar " +"och att-göra." #: packages/app-cli/app/command-status.js:13 msgid "Displays summary about the notes and notebooks." @@ -1797,9 +1786,8 @@ msgid "Download and install the relevant extension for your browser:" msgstr "Hämta och installera det relevanta tillägget för din webbläsare:" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:206 -#, fuzzy msgid "Download updated model" -msgstr "Hämtad" +msgstr "Hämta uppdaterad modell" #: packages/lib/models/Resource.ts:409 msgid "Downloaded" @@ -1897,12 +1885,11 @@ msgstr "Redigera i extern redigerare" #: packages/app-desktop/commands/openNoteInNewWindow.ts:10 msgid "Edit in new window" -msgstr "" +msgstr "Redigera i nytt fönster" #: packages/app-desktop/gui/utils/NoteListUtils.ts:70 -#, fuzzy msgid "Edit in..." -msgstr "Redigera länk" +msgstr "Redigera i..." #: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:143 msgid "Edit link" @@ -1933,9 +1920,8 @@ msgid "Editor" msgstr "Redigerare" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Toolbar.tsx:38 -#, fuzzy msgid "Editor actions" -msgstr "Redigerarens teckensnitt" +msgstr "Redigeraråtgärder" #: packages/lib/models/settings/builtInMetadata.ts:1074 msgid "Editor font" @@ -2051,7 +2037,7 @@ msgstr "Aktivera kryptering" #: packages/lib/models/settings/builtInMetadata.ts:994 msgid "Enable file:// URLs for images and videos" -msgstr "" +msgstr "Aktivera file:// URL:er för bilder och videor" #: packages/lib/models/settings/builtInMetadata.ts:975 msgid "Enable footnotes" @@ -2107,9 +2093,8 @@ msgid "Enable soft breaks" msgstr "Aktivera mjuk radbrytning" #: packages/lib/models/settings/builtInMetadata.ts:1303 -#, fuzzy msgid "Enable spell checking in Markdown editor" -msgstr "Aktivera stavningskontroll i textredigeraren" +msgstr "Aktivera stavningskontroll i Markdown-redigeraren" #: packages/lib/models/settings/builtInMetadata.ts:789 msgid "Enable spellcheck in the text editor" @@ -2148,6 +2133,8 @@ msgid "" "Enables Markdown list continuation, auto-closing HTML tags, and other markup " "autocompletions." msgstr "" +"Aktiverar fortsättning av Markdown-lista, automatisk stängning av HTML-" +"taggar och andra automatiska slutföranden av uppmärkning." #: packages/lib/components/EncryptionConfigScreen/utils.ts:50 msgid "" @@ -2193,9 +2180,8 @@ msgid "End-to-end encryption" msgstr "Ände-till-ände-kryptering" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:244 -#, fuzzy msgid "Ends voice typing" -msgstr "Röstskrivning..." +msgstr "Avslutar röstinmatning" #: packages/app-mobile/components/screens/dropbox-login.tsx:70 msgid "Enter code here" @@ -2215,9 +2201,8 @@ msgid "Enter password" msgstr "Ange lösenord" #: packages/app-mobile/components/NoteItem.tsx:120 -#, fuzzy msgid "Entering selection mode" -msgstr "Öppnar avsnitt %s" +msgstr "Går in i urvalsläge" #: packages/app-cli/app/help-utils.js:56 msgid "Enum" @@ -2284,7 +2269,7 @@ msgstr "Fäll ut %s" #: packages/app-desktop/gui/Sidebar/listItemComponents/ExpandIcon.tsx:26 msgid "Expanded, press space to collapse." -msgstr "" +msgstr "Expanderad, tryck på blanksteg för att komprimera." #: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:182 #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:213 @@ -2310,9 +2295,8 @@ msgid "Export Debug Report" msgstr "Exportera felsökningsrapport" #: packages/app-desktop/commands/exportDeletionLog.ts:11 -#, fuzzy msgid "Export deletion log" -msgstr "Exportera alla" +msgstr "Exportera borttagningslogg" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:16 msgid "Export profile" @@ -2399,9 +2383,8 @@ msgid "Filter tags" msgstr "Filtrera taggar" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:14 -#, fuzzy msgid "Find" -msgstr "Hitta: " +msgstr "Hitta" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:253 msgid "Find: " @@ -2439,7 +2422,7 @@ msgstr "Fokus på titel" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:97 msgid "Follow link" -msgstr "" +msgstr "Följ länken" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:38 msgid "For debugging purpose only: export your profile to an external SD card." @@ -2534,11 +2517,11 @@ msgstr "" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:13 msgid "go" -msgstr "" +msgstr "gå" #: packages/app-mobile/components/CameraView/CameraView.tsx:165 msgid "Go back" -msgstr "" +msgstr "Gå tillbaka" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:44 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:59 @@ -2547,7 +2530,7 @@ msgstr "Gå till Joplin Cloud-profil" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:12 msgid "Go to line" -msgstr "" +msgstr "Gå till rad" #: packages/app-mobile/components/screens/Note/Note.tsx:1051 msgid "Go to source URL" @@ -2614,9 +2597,8 @@ msgid "Hide keyboard" msgstr "Dölj tangentbord" #: packages/app-desktop/gui/PasswordInput/PasswordInput.tsx:21 -#, fuzzy msgid "Hide password" -msgstr "Ogiltigt lösenord" +msgstr "Dölj lösenord" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:14 msgid "Highlight" @@ -2835,14 +2817,12 @@ msgid "Incompatible" msgstr "Inkompatibel" #: packages/app-desktop/gui/NoteList/utils/useOnKeyDown.ts:153 -#, fuzzy msgid "Incomplete" -msgstr "Slutförd" +msgstr "Ofullständig" #: packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.ts:40 -#, fuzzy msgid "Incomplete to-do" -msgstr "Visa slutförda att-göra" +msgstr "Ofullständig att-göra" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:97 msgid "Increase indent level" @@ -2858,7 +2838,7 @@ msgstr "Mer indrag" #: packages/app-mobile/components/DialogManager/hooks/useDialogControl.ts:21 msgid "Info" -msgstr "" +msgstr "Info" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:223 msgid "Information" @@ -2960,9 +2940,8 @@ msgid "Items that cannot be synchronised" msgstr "Objekt som inte kan synkroniseras" #: packages/app-desktop/gui/MenuBar.tsx:923 -#, fuzzy msgid "Join us on %s" -msgstr "Följ oss på Twitter" +msgstr "Följ oss ​​på %s" #: packages/app-desktop/gui/SyncWizard/Dialog.tsx:267 msgid "" @@ -3018,9 +2997,8 @@ msgid "Joplin Forum" msgstr "Joplin-forum" #: packages/app-mobile/utils/lockToSingleInstance.ts:16 -#, fuzzy msgid "Joplin is already running." -msgstr "Servern körs redan på port %d" +msgstr "Joplin körs redan." #: packages/lib/services/plugins/PluginService.ts:523 msgid "Joplin Mobile" @@ -3270,9 +3248,8 @@ msgid "Manage shared notebooks" msgstr "Hantera delade anteckningsböcker" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:165 -#, fuzzy msgid "Manage toolbar options" -msgstr "Hantera dina insticksmoduler" +msgstr "Hantera verktygsfältsalternativ" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:331 msgid "Manage your plugins" @@ -3284,8 +3261,8 @@ msgid "" "Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, " "`status`, `decrypt-file`, and `target-status`." msgstr "" -"Hanterar E2EE-konfigurationen. Kommandona är `enable`,` disable`, " -"`decrypt`,` status`, `decrypt-file` och `target-status`." +"Hanterar E2EE-konfigurationen. Kommandona är `enable`,` disable`, `decrypt`," +"` status`, `decrypt-file` och `target-status`." #: packages/lib/models/settings/builtInMetadata.ts:397 msgid "Manual" @@ -3305,9 +3282,8 @@ msgstr "Markdown + Front Matter" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/CodeMirror.tsx:375 #: packages/app-mobile/components/NoteEditor/NoteEditor.tsx:352 -#, fuzzy msgid "Markdown editor" -msgstr "Markdown" +msgstr "Markdown-redigerare" #: packages/app-cli/app/command-done.ts:15 msgid "Marks a to-do as done." @@ -3338,11 +3314,11 @@ msgstr "Huvudlösenord:" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:19 msgid "match case" -msgstr "" +msgstr "matcha skiftläge" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:72 msgid "Math" -msgstr "" +msgstr "Matematik" #: packages/lib/models/settings/builtInMetadata.ts:421 msgid "Max concurrent connections" @@ -3369,9 +3345,8 @@ msgid "Minimise" msgstr "Minimera" #: packages/app-mobile/components/CameraView/CameraView.tsx:163 -#, fuzzy msgid "Missing camera permission" -msgstr "Saknade huvudnycklar" +msgstr "Kameratillstånd saknas" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:320 msgid "Missing keys" @@ -3460,14 +3435,12 @@ msgid "Never resize" msgstr "Ändra aldrig storlek" #: packages/app-mobile/setupQuickActions.ts:33 -#, fuzzy msgid "New attachment" -msgstr "Anteckningsbilagor" +msgstr "Ny bilaga" #: packages/app-mobile/setupQuickActions.ts:34 -#, fuzzy msgid "New drawing" -msgstr "Teckning" +msgstr "Ny ritning" #: packages/app-mobile/components/screens/ShareManager/index.tsx:120 msgid "New invitations" @@ -3498,9 +3471,8 @@ msgstr "" "importeras till den" #: packages/app-mobile/setupQuickActions.ts:32 -#, fuzzy msgid "New photo" -msgstr "Ta ett foto" +msgstr "Nytt foto" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newSubFolder.ts:6 msgid "New sub-notebook" @@ -3524,7 +3496,7 @@ msgstr "Ny version: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:16 msgid "next" -msgstr "" +msgstr "nästa" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:271 msgid "Next match" @@ -3608,14 +3580,12 @@ msgstr "" "path>`" #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:93 -#, fuzzy msgid "No updates available" -msgstr "Uppdatering tillgänglig" +msgstr "Inga uppdateringar tillgängliga" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/moveToFolder.ts:44 -#, fuzzy msgid "None" -msgstr "(Ingen)" +msgstr "Ingen" #: packages/lib/models/settings/builtInMetadata.ts:47 msgid "Nord" @@ -3775,12 +3745,11 @@ msgstr "Numrerad lista" #: packages/lib/models/settings/builtInMetadata.ts:531 msgid "OCR: Clear cache and re-download language data files" -msgstr "" +msgstr "OCR: Rensa cacheminnet och hämta språkdatafiler igen" #: packages/lib/models/settings/builtInMetadata.ts:512 -#, fuzzy msgid "OCR: Language data URL or path" -msgstr "Språk, datumformat" +msgstr "OCR: Språkdata-URL eller -sökväg" #: packages/app-desktop/bridge.ts:360 packages/app-desktop/bridge.ts:373 #: packages/app-desktop/bridge.ts:387 packages/app-desktop/bridge.ts:403 @@ -3816,7 +3785,7 @@ msgstr "På %s: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:27 msgid "on line" -msgstr "" +msgstr "på rad" #: packages/app-desktop/gui/MainScreen.tsx:525 msgid "One of your master keys use an obsolete encryption method." @@ -3847,9 +3816,8 @@ msgid "OneDrive Login" msgstr "OneDrive-inloggning" #: packages/lib/services/interop/InteropService.ts:144 -#, fuzzy msgid "OneNote Notebook" -msgstr "Ny anteckningsbok" +msgstr "OneNote-anteckningsbok" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/print.ts:18 msgid "Only one note can be printed at a time." @@ -3876,9 +3844,8 @@ msgid "Open profile directory" msgstr "Öppna profilmapp" #: packages/app-mobile/components/CameraView/CameraView.tsx:164 -#, fuzzy msgid "Open settings" -msgstr "Öppnar avsnitt %s" +msgstr "Öppna inställningar" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:115 msgid "Open Source" @@ -3897,19 +3864,16 @@ msgid "Opening section %s" msgstr "Öppnar avsnitt %s" #: packages/app-mobile/components/Dropdown.tsx:215 -#, fuzzy msgid "Opens dropdown" -msgstr "Stäng rullgardinsmenyn" +msgstr "Öppnar rullgardinsmenyn" #: packages/app-mobile/components/NoteItem.tsx:162 -#, fuzzy msgid "Opens note" -msgstr "Öppna den" +msgstr "Öppnar anteckning" #: packages/app-mobile/components/side-menu-content.tsx:273 -#, fuzzy msgid "Opens notebook" -msgstr "Ny anteckningsbok" +msgstr "Öppnar anteckningsboken" #: packages/app-cli/app/command-e2ee.ts:41 #: packages/app-cli/app/command-e2ee.ts:87 @@ -4175,11 +4139,11 @@ msgstr "Föredraget ljust tema" #: packages/lib/models/settings/builtInMetadata.ts:1704 msgid "Preferred voice typing provider" -msgstr "" +msgstr "Föredragen leverantör av röstinmatning" #: packages/lib/models/settings/builtInMetadata.ts:667 msgid "Preserve colours when pasting text in Rich Text Editor" -msgstr "" +msgstr "Bevara färger när du klistrar in text i Rich Text Editor" #: packages/app-cli/app/app-gui.js:758 msgid "Press Ctrl+D or type \"exit\" to exit the application" @@ -4202,9 +4166,8 @@ msgid "Press to set the decryption password." msgstr "Tryck för att ställa in dekrypteringslösenordet." #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:17 -#, fuzzy msgid "previous" -msgstr "Föregående träff" +msgstr "föregående" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:281 msgid "Previous match" @@ -4247,9 +4210,8 @@ msgid "Processing" msgstr "Bearbetning" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:111 -#, fuzzy msgid "Processing photo..." -msgstr "Bearbetning" +msgstr "Bearbetar foto..." #: packages/server/src/routes/admin/users.ts:254 msgid "Profile" @@ -4305,9 +4267,8 @@ msgid "Publish notes to the internet" msgstr "Publicera anteckningar till internet" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:78 -#, fuzzy msgid "QR Code" -msgstr "Kod" +msgstr "QR-kod" #: packages/app-desktop/app.ts:219 #: packages/app-desktop/ElectronAppWrapper.ts:123 @@ -4318,7 +4279,7 @@ msgstr "Avsluta" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:206 msgid "Re-download model" -msgstr "" +msgstr "Hämta modellen igen" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:342 msgid "Re-encrypt data" @@ -4329,9 +4290,8 @@ msgid "Re-encryption" msgstr "Omkryptering" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:181 -#, fuzzy msgid "Re-enter password" -msgstr "Ange lösenord" +msgstr "Ange lösenordet igen" #: packages/lib/models/settings/builtInMetadata.ts:1180 msgid "Re-upload local data to sync target" @@ -4397,9 +4357,8 @@ msgid "Remove" msgstr "Ta bort" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:330 -#, fuzzy msgid "Remove %s from share" -msgstr "Ta bort taggen \"%s\" från alla anteckningar?" +msgstr "Ta bort %s från delningen" #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:111 msgid "Remove tag \"%s\" from all notes?" @@ -4433,9 +4392,8 @@ msgid "Renew token" msgstr "Förnya token" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:21 -#, fuzzy msgid "replace" -msgstr "Ersätt" +msgstr "ersätt" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:15 #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:291 @@ -4443,9 +4401,8 @@ msgid "Replace" msgstr "Ersätt" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:22 -#, fuzzy msgid "replace all" -msgstr "Ersätt alla" +msgstr "ersätt alla" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:301 msgid "Replace all" @@ -4461,11 +4418,11 @@ msgstr "Ersätt: " #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:25 msgid "replaced $ matches" -msgstr "" +msgstr "ersatte $-matchningar" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:26 msgid "replaced match on line $" -msgstr "" +msgstr "ersatte matchning på rad $" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:162 msgid "Report an issue" @@ -4528,9 +4485,8 @@ msgid "Restore" msgstr "Återställ" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:156 -#, fuzzy msgid "Restore defaults" -msgstr "Återställda objekt" +msgstr "Återställ standardinställningar" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/restoreNote.ts:10 msgid "Restore note" @@ -4588,11 +4544,12 @@ msgstr "Revision: %s (%s)" #: packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.tsx:28 msgid "Rich Text" -msgstr "" +msgstr "Rich Text" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:686 msgid "Rich Text editor. Press Escape then Tab to escape focus." msgstr "" +"Rich Text redigerare. Tryck på Escape och sedan på Tab för att undvika fokus." #: packages/app-cli/app/command-batch.js:10 msgid "" @@ -4672,7 +4629,7 @@ msgstr "Spara geolokalisering med anteckningar" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:89 msgid "Scanned code" -msgstr "" +msgstr "Skannad kod" #: packages/app-desktop/gui/lib/SearchInput/SearchInput.tsx:62 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:102 @@ -4705,9 +4662,8 @@ msgid "Search in current note" msgstr "Sök i aktuell anteckning" #: packages/app-desktop/plugins/GotoAnything.tsx:665 -#, fuzzy msgid "Search results" -msgstr "Inga resultat" +msgstr "Sökresultat" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:170 msgid "Search shown" @@ -4730,9 +4686,8 @@ msgid "Searches for the given in all the notes." msgstr "Söker efter det givna i alla anteckningarna." #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:63 -#, fuzzy msgid "See changelog" -msgstr "Fullständig ändringslogg" +msgstr "Se ändringslogg" #: packages/lib/models/settings/builtInMetadata.ts:1199 msgid "See the pre-release page for more details: %s" @@ -4761,14 +4716,12 @@ msgid "Select parent notebook" msgstr "Välj överordnad anteckningsbok" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:9 -#, fuzzy msgid "Selection deleted" -msgstr "färdigställande datum" +msgstr "Urvalet har tagits bort" #: packages/app-mobile/components/FolderPicker.tsx:68 -#, fuzzy msgid "Selects a notebook" -msgstr "Välj överordnad anteckningsbok" +msgstr "Väljer en anteckningsbok" #: packages/app-desktop/gui/MenuBar.tsx:359 msgid "Send bug report" @@ -4824,7 +4777,7 @@ msgstr "" #: packages/app-mobile/components/EditorToolbar/EditorToolbar.tsx:47 msgid "Settings" -msgstr "" +msgstr "Inställningar" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:281 #: packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.ts:44 @@ -4863,9 +4816,8 @@ msgid "Share permissions" msgstr "Dela behörigheter" #: packages/app-desktop/gui/Sidebar/listItemComponents/FolderItem.tsx:56 -#, fuzzy msgid "Shared" -msgstr "Dela" +msgstr "Delad" #: packages/app-mobile/components/screens/ShareManager/index.tsx:107 msgid "Shares" @@ -4912,14 +4864,12 @@ msgid "Show note counts" msgstr "Visa anteckningsantal" #: packages/app-mobile/components/side-menu-content.tsx:258 -#, fuzzy msgid "Show notebook options" -msgstr "Visa anteckningsantal" +msgstr "Visa alternativ för anteckningsboken" #: packages/app-desktop/gui/PasswordInput/PasswordInput.tsx:21 -#, fuzzy msgid "Show password" -msgstr "Ställ in lösenord" +msgstr "Visa lösenord" #: packages/lib/models/settings/builtInMetadata.ts:698 msgid "Show sort order buttons" @@ -4934,9 +4884,8 @@ msgid "Show/hide the sidebar" msgstr "Visa/dölj sidofältet" #: packages/app-mobile/components/screens/tags.tsx:76 -#, fuzzy msgid "Shows notes for tag" -msgstr "Visa anteckningsantal" +msgstr "Visar anteckningar för tagg" #: packages/lib/models/settings/builtInMetadata.ts:863 msgid "Shrink large images before adding them to notes." @@ -5018,11 +4967,11 @@ msgstr "" #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in ascending order" -msgstr "" +msgstr "Sortera \"%s\" i stigande ordning" #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in descending order" -msgstr "" +msgstr "Sortera \"%s\" i fallande ordning" #: packages/lib/models/settings/builtInMetadata.ts:754 msgid "Sort notebooks by" @@ -5207,12 +5156,11 @@ msgstr "Byt profil" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:101 msgid "Switch to back-facing camera" -msgstr "" +msgstr "Byt till bakåtvänd kamera" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:101 -#, fuzzy msgid "Switch to front-facing camera" -msgstr "Byt till profil %d" +msgstr "Växla till kameran på framsidan" #: packages/app-desktop/gui/utils/NoteListUtils.ts:93 msgid "Switch to note type" @@ -5225,14 +5173,12 @@ msgid "Switch to profile %d" msgstr "Byt till profil %d" #: packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.tsx:28 -#, fuzzy msgid "Switch to the %s Editor" -msgstr "Byt till anteckningstyp" +msgstr "Växla till %s-redigeraren" #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:78 -#, fuzzy msgid "Switch to the legacy editor" -msgstr "Byt till anteckningstyp" +msgstr "Byt till den äldre redigeraren" #: packages/app-desktop/gui/utils/NoteListUtils.ts:102 msgid "Switch to to-do type" @@ -5509,6 +5455,7 @@ msgstr "" #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:83 msgid "The following plugins may not support the current markdown editor:" msgstr "" +"Följande plugins kanske inte stöder den nuvarande markdown-redigeraren:" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:257 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx:49 @@ -5637,6 +5584,11 @@ msgid "" "\n" "Error: \"%s\"" msgstr "" +"Webbklienten stöder inte accepterande av krypterade delade " +"anteckningsböcker. Byt till skrivbords- eller mobilappen innan du accepterar " +"delningen.\n" +"\n" +"Fel: \"%s\"" #: packages/app-desktop/gui/Root.tsx:150 msgid "The Web Clipper needs your authorisation to access your data." @@ -5993,9 +5945,8 @@ msgid "Toggle editor layout" msgstr "Växla redigeringslayout" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditorPlugin.ts:11 -#, fuzzy msgid "Toggle editor plugin" -msgstr "Växla redigeringslayout" +msgstr "Växla redigeringsinsticksmodul" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditors.ts:8 msgid "Toggle editors" @@ -6124,9 +6075,8 @@ msgstr "" #: packages/app-mobile/utils/getVersionInfoText.ts:13 #: packages/app-mobile/utils/getVersionInfoText.ts:14 -#, fuzzy msgid "Unknown" -msgstr "Okänd flagga: %s" +msgstr "Okänd" #: packages/app-desktop/bridge.ts:446 msgid "Unknown file type" @@ -6142,9 +6092,8 @@ msgid "" msgstr "Okänd objekttyp hämtad - uppgradera Joplin till den senaste versionen" #: packages/app-mobile/utils/getVersionInfoText.ts:28 -#, fuzzy msgid "Unknown platform" -msgstr "Okänd flagga: %s" +msgstr "Okänd plattform" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:82 msgid "Unordered list" @@ -6176,11 +6125,12 @@ msgid "Unsupported link or message: %s" msgstr "Länk eller meddelande stöds inte: %s" #: packages/app-mobile/commands/openItem.ts:60 -#, fuzzy msgid "" "Unsupported link or message: %s.\n" "Error: %s" -msgstr "Länk eller meddelande stöds inte: %s" +msgstr "" +"Länk eller meddelande som inte stöds: %s.\n" +"Fel: %s" #: packages/app-desktop/gui/ResourceScreen.tsx:123 #: packages/lib/models/BaseItem.ts:921 packages/lib/path-utils.ts:27 @@ -6198,9 +6148,8 @@ msgid "Update available" msgstr "Uppdatering tillgänglig" #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:65 -#, fuzzy msgid "Update later" -msgstr "uppdaterat datum" +msgstr "Uppdatera senare" #: packages/server/src/routes/admin/users.ts:257 #: packages/server/src/routes/index/users.ts:91 @@ -6300,9 +6249,8 @@ msgstr "" "avsluta." #: packages/lib/models/settings/builtInMetadata.ts:1347 -#, fuzzy msgid "Use the legacy Markdown editor" -msgstr "Aktivera Markdown-verktygsfältet" +msgstr "Använd den äldre Markdown-redigeraren" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:552 msgid "" @@ -6397,7 +6345,7 @@ msgstr "Röstskrivning..." #: packages/lib/models/settings/builtInMetadata.ts:1712 msgid "Vosk" -msgstr "" +msgstr "Vosk" #: packages/lib/services/joplinCloudUtils.ts:27 msgid "Waiting for authorisation..." @@ -6504,7 +6452,7 @@ msgstr "" #: packages/lib/models/settings/builtInMetadata.ts:1713 msgid "Whisper" -msgstr "" +msgstr "Whisper" #: packages/app-desktop/ElectronAppWrapper.ts:222 msgid "Window unresponsive." diff --git a/packages/tools/locales/zh_CN.po b/packages/tools/locales/zh_CN.po index 98e4b0c658..d9ab4b6a14 100644 --- a/packages/tools/locales/zh_CN.po +++ b/packages/tools/locales/zh_CN.po @@ -8,8 +8,8 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: \n" "PO-Revision-Date: \n" -"Last-Translator: qx100\n" -"Language-Team: zh_CN <737445366KG@Gmail.com>\n" +"Last-Translator: clsty\n" +"Language-Team: zh_CN \n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -42,7 +42,7 @@ msgstr "(%s)" #: packages/lib/services/plugins/api/JoplinViewsDialogs.ts:76 msgid "(In plugin: %s)" -msgstr "(在插件%s中)" +msgstr "(在插件 %s 中)" #: packages/app-mobile/components/side-menu-content.tsx:265 msgid "(level %d)" @@ -112,7 +112,7 @@ msgstr "%d GB" #: packages/lib/utils/joplinCloud/index.ts:146 #: packages/lib/utils/joplinCloud/index.ts:147 msgid "%d GB storage space" -msgstr "%d GB存储空间" +msgstr "%d GB 存储空间" #: packages/lib/models/settings/builtInMetadata.ts:1227 msgid "%d hour" @@ -133,7 +133,7 @@ msgstr "%d MB" #: packages/lib/utils/joplinCloud/index.ts:134 #: packages/lib/utils/joplinCloud/index.ts:135 msgid "%d MB per note or attachment" -msgstr "每个笔记或附件大小%d MB" +msgstr "每个笔记或附件大小 %d MB" #: packages/lib/models/settings/builtInMetadata.ts:1224 #: packages/lib/models/settings/builtInMetadata.ts:1225 @@ -147,7 +147,7 @@ msgstr "有 %d 条笔记匹配。是否删除?" #: packages/app-cli/app/command-rmnote.ts:40 msgid "%d notes will be permanently deleted. Continue?" -msgstr "%d 笔记将被永久删除。是否继续?" +msgstr "%d 条笔记将被永久删除。是否继续?" #: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:228 #: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:238 @@ -205,7 +205,7 @@ msgstr "%s 未针对同步许多小文件进行优化,因此您的首次同步 #: packages/app-mobile/components/plugins/dialogs/PluginPanelViewer.tsx:75 msgid "%s tab opened" -msgstr "%s 标签已打开" +msgstr "%s 标签页已打开" #: packages/lib/services/ReportService.ts:286 #: packages/lib/services/ReportService.ts:287 @@ -230,7 +230,7 @@ msgstr "%s:%s" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:224 #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:265 msgid "%s: Missing password." -msgstr "缺少主密码。" +msgstr "%s:缺少主密码。" #: packages/app-cli/app/command-tag.js:14 msgid "" @@ -240,8 +240,8 @@ msgid "" "list all the tags (use -l for long option)." msgstr "" " 可以是 “add” 、“remove” 、“list” 或者 “notetags” ,分别用于从 " -"[note] 中赋值或删除 [tag],列出与 [tag] 相关的笔记,列出与[note]相关的tag。" -"`tag list` 命令可以用于列出所有的标签(对于过长选项请使用 -l 参数)。" +"[note] 中赋值或删除 [tag],列出与 [tag] 相关的笔记,列出与 [note] 相关的标签。" +"命令 `tag list` 可列出所有的标签(为过长选项使用 -l 参数)。" #: packages/app-cli/app/command-todo.js:14 msgid "" @@ -293,7 +293,7 @@ msgstr "加速键 “%s” 无效。" msgid "" "Accelerator \"%s\" is used for \"%s\" and \"%s\" commands. This may lead to " "unexpected behaviour." -msgstr "加速键 “%s” 被用于 “%s” 和 “%s” 命令。这可能导致意外的表现。" +msgstr "加速键 “%s” 被用于 “%s” 和 “%s” 命令。这可能导致意外行为。" #: packages/app-desktop/gui/MainScreen.tsx:541 #: packages/app-mobile/components/screens/ShareManager/IncomingShareItem.tsx:40 @@ -310,7 +310,7 @@ msgstr "拒绝访问:请检查您的用户名和密码" #: packages/lib/WebDavApi.js:449 msgid "Access denied: Please re-enter your password and/or username" -msgstr "拒绝访问: 请重新输入密码和/或用户名" +msgstr "拒绝访问:请重新输入密码和/或用户名" #: packages/server/src/routes/admin/users.ts:144 msgid "Account" @@ -358,7 +358,7 @@ msgstr "添加或删除标签:" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:277 msgid "Add recipient:" -msgstr "添加收件人:" +msgstr "添加受邀人:" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:94 msgid "Add tag %s to note" @@ -404,11 +404,11 @@ msgstr "全部" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:109 msgid "" "All data, including notes, notebooks and tags will be permanently deleted." -msgstr "包含笔记,笔记本和标签在内的所有数据将会被永久删除。" +msgstr "包含笔记、笔记本和标签在内的所有数据将会被永久删除。" #: packages/lib/services/ReportService.ts:227 msgid "All item sync failures have been marked as \"ignored\"." -msgstr "所有同步失败的条目都被标记为 \"已忽略\"。" +msgstr "所有同步失败的条目都被标记为“ignored”(已忽略)。" #: packages/app-desktop/gui/Sidebar/listItemComponents/AllNotesItem.tsx:71 #: packages/app-mobile/components/screens/Notes.tsx:208 @@ -418,7 +418,7 @@ msgstr "全部笔记" #: packages/lib/onedrive-api-node-utils.js:46 msgid "All potential ports are in use - please report the issue at %s" -msgstr "所有默认端口都已经被使用中 - 请在 %s 反馈这个问题" +msgstr "所有默认可用端口均被占用 - 请在 %s 反馈这个问题" #: packages/lib/models/settings/builtInMetadata.ts:910 msgid "Allows debugging mobile plugins. See %s for details." @@ -453,7 +453,7 @@ msgid "" "Ambiguous notebook \"%s\". Please use notebook id instead - press \"ti\" to " "see the short notebook id or use $b for current selected notebook" msgstr "" -"不明确的笔记本“%s”。请改用短笔记本id - 按\"ti\"查看短笔记本id,或使用 $b 选择" +"不明确的笔记本“%s”。请改用短笔记本 id - 按“ti”查看短笔记本 id,或使用 $b 选择" "当前笔记本" #: packages/app-cli/app/command-mkbook.ts:33 @@ -461,7 +461,7 @@ msgstr "" msgid "" "Ambiguous notebook \"%s\". Please use short notebook id instead - press " "\"ti\" to see the short notebook id" -msgstr "不明确的笔记本“%s”。请改用短笔记本id - 按\"ti\"查看短笔记本id" +msgstr "不明确的笔记本“%s”。请改用短笔记本 id - 按“ti”查看短笔记本 id" #: packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.ts:13 msgid "An autosaved drawing was found. Attach a copy of it to the note?" @@ -485,7 +485,7 @@ msgid "" "Any email sent to this address will be converted into a note and added to " "your collection. The note will be saved into the Inbox notebook" msgstr "" -"任何发送到此地址的电子邮件都将被转换为笔记并添加到您的收藏中。 该笔记将保存到" +"任何发送到此地址的电子邮件都将被转换为笔记并添加到您的收藏中。该笔记将保存到" "收件箱笔记本中" #: packages/lib/models/Setting.ts:1174 @@ -524,7 +524,7 @@ msgstr "参数:" #: packages/lib/models/settings/builtInMetadata.ts:48 msgid "Aritim Dark" -msgstr "暗黑 (Aritim)" +msgstr "Aritim 暗色" #: packages/app-mobile/utils/lockToSingleInstance.ts:15 msgid "" @@ -550,7 +550,7 @@ msgstr "添加照片" #: packages/app-mobile/components/screens/Note/Note.tsx:1155 msgid "Attach..." -msgstr "添加..." +msgstr "添加……" #: packages/app-cli/app/command-attach.ts:13 msgid "Attaches the given file to the note." @@ -584,7 +584,7 @@ msgid "" "more details: %s" msgstr "" "注意:如果您更改该位置,请确保在同步之前将所有内容复制到该位置,否则将删除所" -"有文件! 更多详细信息请参阅常见问题解答 (FAQ) : %s" +"有文件!更多详情,请参阅常见问题解答(FAQ): %s" #: packages/app-cli/app/command-sync.ts:72 #: packages/app-cli/app/command-sync.ts:86 @@ -624,7 +624,7 @@ msgstr "自动检查更新" #: packages/lib/models/settings/builtInMetadata.ts:1722 msgid "Automatically delete notes in the trash after a number of days" -msgstr "一段时间后自动删除回收站中的笔记" +msgstr "几天后自动删除回收站中的笔记" #: packages/lib/models/settings/builtInMetadata.ts:556 msgid "Automatically switch theme to match system theme" @@ -661,7 +661,7 @@ msgstr "浏览所有插件" #: packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.tsx:275 msgid "Browse..." -msgstr "浏览..." +msgstr "浏览……" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:223 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx:54 @@ -726,15 +726,15 @@ msgstr "取消" #: packages/app-cli/app/app.ts:142 msgid "Cancelling background synchronisation... Please wait." -msgstr "正在取消后台同步... 请稍候。" +msgstr "正在取消后台同步…… 请稍候。" #: packages/lib/Synchronizer.ts:206 msgid "Cancelling..." -msgstr "正在取消..." +msgstr "正在取消……" #: packages/app-cli/app/command-sync.ts:289 msgid "Cancelling... Please wait." -msgstr "正在取消... 请稍候。" +msgstr "正在取消…… 请稍候。" #: packages/lib/shim-init-node.ts:325 msgid "Cannot access %s" @@ -787,7 +787,7 @@ msgstr "无法找到 “%s”" #: packages/app-cli/app/command-sync.ts:202 msgid "Cannot initialise synchroniser." -msgstr "无法启动同步。" +msgstr "无法初始化同步器。" #: packages/lib/services/interop/InteropService.ts:263 msgid "Cannot load \"%s\" module for format \"%s\" and output \"%s\"" @@ -795,7 +795,7 @@ msgstr "无法为格式 “%2$s” 和输出 “%3$s” 加载 “%1$s” 模块 #: packages/lib/services/interop/InteropService.ts:276 msgid "Cannot load \"%s\" module for format \"%s\" and target \"%s\"" -msgstr "无法加载为格式 “%2$s” 和目标 “%3$s” 加载 “%1$s” 模块" +msgstr "无法为格式 “%2$s” 和目标 “%3$s” 加载 “%1$s” 模块" #: packages/lib/models/Note.ts:630 msgid "Cannot move note to \"%s\" notebook" @@ -828,7 +828,7 @@ msgid "" "enabled end-to-end encryption. They may do so from the screen Configuration " "> Encryption." msgstr "" -"无法与收件人 %s 共享加密的笔记本,因为他们没有启用端到端加密。他们可以通过 配" +"无法与受邀人 %s 共享加密的笔记本,因为他们没有启用端到端加密。他们可以通过 配" "置 > 加密 界面来启用。" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:331 @@ -857,7 +857,7 @@ msgstr "字符数" #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:107 msgid "Characters excluding spaces" -msgstr "字符数(不计空格)" +msgstr "字符数(除空格)" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:168 msgid "Check elements to display in the toolbar" @@ -866,7 +866,7 @@ msgstr "勾选要在工具栏中显示的元素" #: packages/app-desktop/gui/MenuBar.tsx:634 #: packages/app-desktop/gui/MenuBar.tsx:929 msgid "Check for updates..." -msgstr "检查更新..." +msgstr "检查更新……" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:264 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:465 @@ -883,7 +883,7 @@ msgstr "复选框列表" #: packages/lib/components/shared/config/config-shared.ts:97 msgid "Checking... Please wait." -msgstr "正在检查... 请稍候。" +msgstr "正在检查……请稍候。" #: packages/app-mobile/components/screens/Note/commands/attachFile.ts:85 msgid "Choose an option" @@ -891,7 +891,7 @@ msgstr "选择一个选项" #: packages/app-desktop/gui/ExtensionBadge.tsx:72 msgid "Chrome Web Store" -msgstr "Chrome 商店" +msgstr "Chrome 网页商店" #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:150 #: packages/app-desktop/gui/PromptDialog.tsx:298 @@ -919,7 +919,7 @@ msgstr "" #: packages/app-desktop/gui/NoteEditor/NoteEditor.tsx:453 msgid "Click to add tags..." -msgstr "单击以添加标签..." +msgstr "单击以添加标签……" #: packages/lib/versionInfo.ts:88 msgid "Client ID: %s" @@ -960,7 +960,7 @@ msgid "" "application again. Make sure you create a backup first by exporting all your " "notes as JEX." msgstr "" -"关闭应用程序,然后删除“%s”中您的配置文件,然后再次启动应用程序。 请确保您已事" +"关闭应用程序,然后删除“%s”中您的配置档,然后再次启动应用程序。请确保您已事" "先将所有笔记以 JEX 格式导出来创建备份。" #: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:26 @@ -970,11 +970,11 @@ msgstr "关闭窗口" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.ts:39 msgid "Cmd-click to open" -msgstr "单击 Cmd 打开" +msgstr "按住 Cmd 打开" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.ts:45 msgid "Cmd-click to open: %s" -msgstr "单击 Cmd 以打开:%s" +msgstr "按住 Cmd 以打开:%s" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:70 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:65 @@ -1030,7 +1030,7 @@ msgstr "命令面板" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/commandPalette.ts:7 msgid "Command palette..." -msgstr "命令面板..." +msgstr "命令面板……" #: packages/lib/services/noteList/defaultListRenderer.ts:24 msgid "Compact" @@ -1108,7 +1108,7 @@ msgstr "冲突(附件)" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:253 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:462 msgid "Connect to Joplin Cloud" -msgstr "连接至 Joplin Cloud" +msgstr "连接至 Joplin 云" #: packages/lib/utils/joplinCloud/index.ts:208 msgid "Consolidated billing" @@ -1133,7 +1133,7 @@ msgstr "继续" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:6 msgid "Control character" -msgstr "字符" +msgstr "控制字符" #: packages/app-mobile/components/screens/Note/Note.tsx:1221 msgid "Convert to note" @@ -1145,7 +1145,7 @@ msgstr "转换为待办事项" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:181 msgid "Converting speech to text..." -msgstr "正在将语音转换为文本..." +msgstr "正在将语音转换为文本……" #: packages/app-desktop/gui/MenuBar.tsx:601 #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:35 @@ -1256,17 +1256,17 @@ msgstr "" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:59 msgid "Could not switch profile: %s" -msgstr "无法切换到配置文件:%s" +msgstr "无法切换到配置档:%s" #: packages/lib/components/EncryptionConfigScreen/utils.ts:224 msgid "Could not upgrade master key: %s" -msgstr "无法升级主密钥(master key):%s" +msgstr "无法升级主密钥:%s" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.ts:27 msgid "" "Could not verify the share status of this notebook - aborting. Please try " "again when you are connected to the internet." -msgstr "无法验证此笔记的共享状态 — 正在终止。请在连接到互联网后再次尝试。" +msgstr "无法验证此笔记的共享状态 — 正在终止。请在连接到互联网后重试。" #: packages/app-mobile/components/biometrics/biometricAuthenticate.ts:30 msgid "Could not verify your identity: %s" @@ -1287,7 +1287,7 @@ msgstr "新建一个笔记本" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/addProfile.ts:9 #: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:88 msgid "Create new profile..." -msgstr "创建新的配置文件..." +msgstr "创建新的配置档……" #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:166 msgid "Create notebook" @@ -1300,7 +1300,7 @@ msgstr "创建用户" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:14 #: packages/app-desktop/gui/NotePropertiesDialog.tsx:67 msgid "Created" -msgstr "创建日期" +msgstr "已创建" #: packages/lib/models/Note.ts:63 msgid "created date" @@ -1312,7 +1312,7 @@ msgstr "已新建本地条目:%d。" #: packages/lib/services/ReportService.ts:288 msgid "Created locally" -msgstr "已新建本地条目" +msgstr "已创建于本地" #: packages/lib/Synchronizer.ts:201 msgid "Created remote items: %d." @@ -1320,7 +1320,7 @@ msgstr "已新建远程条目:%d。" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:140 msgid "Created: " -msgstr "创建: " +msgstr "已创建: " #: packages/app-cli/app/command-import.ts:51 #: packages/app-desktop/gui/ImportScreen.tsx:90 @@ -1330,7 +1330,7 @@ msgstr "已创建:%d 条。" #: packages/app-mobile/components/screens/encryption-config.tsx:125 #: packages/app-mobile/components/screens/Note/Note.tsx:1039 msgid "Created: %s" -msgstr "创建:%s" +msgstr "已创建:%s" #: packages/app-cli/app/command-mknote.js:12 msgid "Creates a new note." @@ -1346,15 +1346,15 @@ msgstr "新建待办事项。" #: packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx:123 msgid "Creating new note..." -msgstr "新建笔记..." +msgstr "新建笔记……" #: packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx:123 msgid "Creating new to-do..." -msgstr "新建待办..." +msgstr "新建待办……" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.tsx:29 msgid "Creating report..." -msgstr "新建报告..." +msgstr "新建报告……" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.ts:41 msgid "Ctrl-click to open" @@ -1408,7 +1408,7 @@ msgstr "剪切" #: packages/lib/models/settings/builtInMetadata.ts:43 msgid "Dark" -msgstr "暗黑" +msgstr "暗色" #: packages/server/src/services/MustacheService.ts:117 msgid "Dashboard" @@ -1509,7 +1509,7 @@ msgstr "是否删除插件 “%s”?" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:112 msgid "Delete profile \"%s\"" -msgstr "删除配置文件 “%s”" +msgstr "删除配置档 “%s”" #: packages/app-mobile/components/ScreenHeader/index.tsx:432 msgid "Delete selected notes" @@ -1525,17 +1525,17 @@ msgid "" msgstr "" "是否删除收件箱笔记本?\n" "\n" -"如果删除收件箱笔记本,最近发送到其中的所有电子邮件都可能会丢失。" +"如果删除收件箱笔记本,最近发送到其中的任何电子邮件都可能会丢失。" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:246 msgid "" "Delete this invitation? The recipient will no longer have access to this " "shared notebook." -msgstr "是否删除该邀请?收件人将无法再次访问此共享笔记本。" +msgstr "是否删除该邀请?受邀人将无法再次访问此共享笔记本。" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:108 msgid "Delete this profile?" -msgstr "是否删除此配置文件?" +msgstr "是否删除此配置档?" #: packages/app-desktop/gui/NotePropertiesDialog.tsx:69 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:207 @@ -1636,7 +1636,7 @@ msgid "" "re-synchronised and sent unencrypted to the sync target. Do you wish to " "continue?" msgstr "" -"禁用加密会导致 *所有笔记与附件* 重新同步,并以非加密的数据形式发送到同步目" +"禁用加密会导致 *所有* 笔记与附件重新同步,并以非加密的数据形式发送到同步目" "标。确定继续吗?" #: packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.ts:19 @@ -1659,7 +1659,7 @@ msgstr "显示该笔记的地理位置 URL。" #: packages/app-cli/app/command-ls.ts:28 msgid "Displays only the first top notes." -msgstr "只显示最上方的 条笔记。" +msgstr "仅显示最上方的 条笔记。" #: packages/app-cli/app/command-ls.ts:31 msgid "" @@ -1712,7 +1712,7 @@ msgid "" "way to decrypt the data! To enable encryption, please enter your password " "below." msgstr "" -"请妥善保管密码,因为出于安全考虑,这将是解密数据的*唯一*方式!要启用加密功" +"请妥善保管密码,因为出于安全考虑,这将是解密数据的 *唯一* 方式!要启用加密功" "能,请在下面输入密码。" #: packages/lib/models/Setting.ts:1220 @@ -1755,15 +1755,15 @@ msgstr "下载中" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:182 msgid "Downloading %s language files..." -msgstr "下载%s语言文件中..." +msgstr "下载 %s 语言文件中……" #: packages/app-cli/app/command-sync.ts:256 msgid "Downloading resources..." -msgstr "下载资源中..." +msgstr "下载资源中……" #: packages/lib/models/settings/builtInMetadata.ts:44 msgid "Dracula" -msgstr "德古拉紫(Dracula)" +msgstr "Dracula" #: packages/app-mobile/components/screens/Note/Note.tsx:1162 msgid "Draw picture" @@ -1783,11 +1783,11 @@ msgstr "Dropbox" #: packages/app-desktop/gui/Root.tsx:189 msgid "Dropbox Login" -msgstr "登录 Dropbox" +msgstr "Dropbox 登录" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:13 msgid "Due" -msgstr "截止日期" +msgstr "截止" #: packages/lib/models/Note.ts:65 msgid "due date" @@ -1812,7 +1812,7 @@ msgid "" "Duplicates the notes matching to [notebook]. If no notebook is " "specified the note is duplicated in the current notebook." msgstr "" -"复制符合 的笔记至 [notebook]。若无指定的笔记本,该笔记将被复制至当前笔" +"复制匹配 的笔记至 [notebook]。若无指定的笔记本,该笔记将被复制至当前笔" "记本。" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.ts:89 @@ -1836,7 +1836,7 @@ msgstr "在新窗口编辑" #: packages/app-desktop/gui/utils/NoteListUtils.ts:70 msgid "Edit in..." -msgstr "编辑于..." +msgstr "编辑于……" #: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:143 msgid "Edit link" @@ -1853,11 +1853,11 @@ msgstr "编辑笔记本" #: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:88 msgid "Edit profile" -msgstr "编辑配置文件" +msgstr "编辑配置档" #: packages/app-desktop/commands/editProfileConfig.ts:9 msgid "Edit profile configuration..." -msgstr "编辑配置文件内的配置..." +msgstr "编辑配置档内的配置……" #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:144 #: packages/lib/models/settings/builtInMetadata.ts:608 @@ -1897,7 +1897,7 @@ msgstr "编辑器:%s" #: packages/app-cli/app/command-ls.ts:32 msgid "Either \"text\" or \"json\"" -msgstr "“text” 或 “json”" +msgstr "“text”或“json”" #: packages/lib/models/settings/builtInMetadata.ts:1290 msgid "Emacs" @@ -1984,7 +1984,7 @@ msgstr "启用加密" #: packages/lib/models/settings/builtInMetadata.ts:994 msgid "Enable file:// URLs for images and videos" -msgstr "启用基于 file:// 协议的图片和视频链接" +msgstr "启用基于 file:// 协议的图片和视频 URL" #: packages/lib/models/settings/builtInMetadata.ts:975 msgid "Enable footnotes" @@ -2049,11 +2049,11 @@ msgstr "在文本编辑器中启用拼写检查" #: packages/lib/models/settings/builtInMetadata.ts:976 msgid "Enable table of contents extension" -msgstr "启用目录扩展 ([TOC])" +msgstr "启用目录扩展" #: packages/lib/models/settings/builtInMetadata.ts:800 msgid "Enable the Markdown toolbar" -msgstr "启用“标记”工具栏" +msgstr "启用 Markdown 工具栏" #: packages/lib/models/settings/builtInMetadata.ts:964 msgid "Enable typographer support" @@ -2065,7 +2065,7 @@ msgstr "启用视频播放器" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:105 msgid "Enable Web Clipper Service" -msgstr "启用网页剪藏器" +msgstr "启用网页剪藏器服务" #: packages/app-cli/app/command-e2ee.ts:128 #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:176 @@ -2086,7 +2086,7 @@ msgid "" "Enabling encryption means *all* your notes and attachments are going to be " "re-synchronised and sent encrypted to the sync target." msgstr "" -"启用加密意味着您的*所有*笔记与附件都将被重新同步,并被加密发送到同步目标。" +"启用加密意味着您的 *所有* 笔记与附件都将被重新同步,并被加密发送到同步目标。" #: packages/lib/models/BaseItem.ts:920 msgid "Encrypted" @@ -2113,7 +2113,7 @@ msgstr "加密状态:%s" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:160 #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:261 msgid "Encryption keys" -msgstr "加密密钥(Encryption keys)" +msgstr "加密密钥(Encryption keys)" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:237 msgid "Encryption:" @@ -2134,7 +2134,7 @@ msgstr "在此输入代码" #: packages/app-cli/app/command-e2ee.ts:39 #: packages/app-cli/app/command-e2ee.ts:85 msgid "Enter master password:" -msgstr "输入主密码(master password):" +msgstr "输入主密码:" #: packages/app-mobile/components/screens/folder.js:100 msgid "Enter notebook title" @@ -2189,19 +2189,19 @@ msgstr "仅显示错误" #: packages/lib/services/interop/InteropService.ts:76 msgid "Evernote Export File (as HTML)" -msgstr "Evernote 导出文件 (HTML)" +msgstr "Evernote 导出文件(HTML)" #: packages/lib/services/interop/InteropService.ts:85 msgid "Evernote Export File (as Markdown)" -msgstr "Evernote 导出文件 (Markdown)" +msgstr "Evernote 导出文件(Markdown)" #: packages/lib/services/interop/InteropService.ts:94 msgid "Evernote Export Files (Directory, as HTML)" -msgstr "Evernote 导出文件 (目录, HTML)" +msgstr "Evernote 导出文件(目录,HTML)" #: packages/lib/services/interop/InteropService.ts:103 msgid "Evernote Export Files (Directory, as Markdown)" -msgstr "Evernote 导出文件 (目录,Markdown)" +msgstr "Evernote 导出文件(目录,Markdown)" #: packages/app-cli/app/command-exit.ts:11 msgid "Exits the application." @@ -2244,7 +2244,7 @@ msgstr "导出" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:16 msgid "Export profile" -msgstr "导出配置文件" +msgstr "导出配置档" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.tsx:70 msgid "Exported successfully!" @@ -2252,47 +2252,47 @@ msgstr "导出成功!" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:36 msgid "Exporting profile..." -msgstr "正在导出配置文件..." +msgstr "正在导出配置档……" #: packages/app-desktop/InteropServiceHelper.ts:199 msgid "Exporting to \"%s\" as \"%s\" format. Please wait..." -msgstr "导出到 “%s”,格式为 “%s”。请稍等..." +msgstr "导出到 “%s”,格式为 “%s”。请稍等……" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.tsx:25 msgid "Exporting..." -msgstr "正在导出..." +msgstr "正在导出……" #: packages/app-cli/app/command-export.ts:14 msgid "" "Exports Joplin data to the given path. By default, it will export the " "complete database including notebooks, notes, tags and resources." msgstr "" -"导出 Joplin 数据到选定路径。默认将导出包含笔记本、笔记、标签与资源等的完整数" +"导出 Joplin 数据到给定路径。默认将导出包含笔记本、笔记、标签与资源等的完整数" "据库。" #: packages/app-cli/app/command-export.ts:24 msgid "Exports only the given note." -msgstr "仅导出选定笔记。" +msgstr "仅导出给定笔记。" #: packages/app-cli/app/command-export.ts:24 msgid "Exports only the given notebook." -msgstr "仅导出选定笔记本。" +msgstr "仅导出给定笔记本。" #: packages/lib/models/settings/builtInMetadata.ts:1444 msgid "Fail-safe" -msgstr "故障保护(Fail-safe)" +msgstr "故障保护" #: packages/lib/models/settings/builtInMetadata.ts:1445 msgid "" "Fail-safe: Do not wipe out local data when sync target is empty (often the " "result of a misconfiguration or bug)" msgstr "" -"故障保护(Fail-safe) :当同步目标为空时(通常是配置错误或Bug),不要删除本地数" +"故障保护(Fail-safe):当同步目标为空时(通常是配置错误或 Bug),不要删除本地数" "据" #: packages/app-cli/app/main.js:107 msgid "Fatal error:" -msgstr "严重错误:" +msgstr "致命错误:" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:618 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:626 @@ -2331,7 +2331,7 @@ msgstr "查找" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:253 msgid "Find: " -msgstr "查找: " +msgstr "查找:" #: packages/app-desktop/gui/ExtensionBadge.tsx:65 msgid "Firefox Extension" @@ -2343,7 +2343,7 @@ msgstr "修复搜索索引" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:552 msgid "Fixing search index..." -msgstr "正在修复搜索索引..." +msgstr "正在修复搜索索引……" #: packages/app-desktop/gui/MenuBar.tsx:866 #: packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.ts:8 @@ -2369,27 +2369,27 @@ msgstr "打开链接" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:38 msgid "For debugging purpose only: export your profile to an external SD card." -msgstr "仅用于调试目的:将您的配置文件导出到外部 SD 卡。" +msgstr "仅用于调试目的:将您的配置档导出到外部 SD 卡。" #: packages/lib/models/settings/builtInMetadata.ts:1424 msgid "For example \"%s\"" -msgstr "例如 \"%s\"" +msgstr "例如“%s”" #: packages/app-cli/app/command-help.ts:37 msgid "For information on how to customise the shortcuts please visit %s" -msgstr "若想了解有关如何自定义快捷键的信息,请访问 %s" +msgstr "关于如何自定义快捷键,请访问 %s" #: packages/app-mobile/components/screens/encryption-config.tsx:302 msgid "" "For more information about End-To-End Encryption (E2EE) and advice on how to " "enable it please check the documentation:" msgstr "" -"若想了解有关端到端加密 (E2EE) 的更多信息,以及如何启用它的建议,请查阅文档:" +"若想了解有关端到端加密(E2EE)的更多信息,以及如何启用它的建议,请查阅文档:" #: packages/app-cli/app/command-help.ts:85 msgid "" "For the list of keyboard shortcuts and config options, type `help keymap`" -msgstr "输入 `help keymap` 来获取完整的键盘快捷键列表" +msgstr "键入 `help keymap` 来获取键盘快捷键列表和配置选项" #: packages/lib/models/settings/builtInMetadata.ts:290 msgid "Force path style" @@ -2428,11 +2428,11 @@ msgstr "已生成" #: packages/app-desktop/gui/ShareNoteDialog.tsx:186 msgid "Generating link..." msgid_plural "Generating links..." -msgstr[0] "生成链接中..." +msgstr[0] "生成链接中……" #: packages/lib/models/Setting.ts:1215 msgid "Geolocation, spellcheck, editor toolbar, image resize" -msgstr "地理位置,拼写检查,编辑器工具栏等设置" +msgstr "地理位置、拼写检查、编辑器工具栏、图像缩放" #: packages/app-desktop/gui/ExtensionBadge.tsx:93 msgid "Get it now:" @@ -2440,7 +2440,7 @@ msgstr "立即获取:" #: packages/lib/models/settings/builtInMetadata.ts:1199 msgid "Get pre-releases when checking for updates" -msgstr "检查更新时获取预发行版本" +msgstr "在检查更新时获取预发行版本" #: packages/app-cli/app/command-config.ts:14 msgid "" @@ -2448,8 +2448,8 @@ msgid "" "value of [name]. If neither [name] nor [value] is provided, it will list the " "current configuration." msgstr "" -"读取或设置配置数值。如果 [value] 值没有提供,它将显示 [name] 值。如果没有提" -"供 [name] 或 [value] 值,它将列出当前配置。" +"读取或设置配置数值。如果 [value] 值没有提供,它将显示 [name] 值。" +"若 [name] 与 [value] 值均未提供,它将列出当前配置。" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:13 msgid "go" @@ -2462,7 +2462,7 @@ msgstr "返回" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:44 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:59 msgid "Go to Joplin Cloud profile" -msgstr "转到 Joplin Cloud 简介" +msgstr "跳转到 Joplin 云简介" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:12 msgid "Go to line" @@ -2470,12 +2470,12 @@ msgstr "跳转到行" #: packages/app-mobile/components/screens/Note/Note.tsx:1051 msgid "Go to source URL" -msgstr "转到源 URL" +msgstr "跳转到源 URL" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/gotoAnything.ts:13 #: packages/app-desktop/plugins/GotoAnything.tsx:737 msgid "Goto Anything..." -msgstr "跳转到任意内容..." +msgstr "跳转到任意内容……" #: packages/app-desktop/gui/Root.tsx:151 msgid "Grant authorisation" @@ -2483,11 +2483,11 @@ msgstr "批准授权" #: packages/app-cli/app/command-sync.ts:116 msgid "Have you authorised the application login in the above URL?" -msgstr "您是否已授权应用程序在上述URL中登录?" +msgstr "您是否已授权应用程序在上述 URL 中登录?" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:19 msgid "Header %d" -msgstr "Header %d" +msgstr "标头 %d" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:95 msgid "Heading" @@ -2506,7 +2506,7 @@ msgstr "以下是我们为使插件更安全而采取的措施:" #: packages/app-mobile/utils/getVersionInfoText.ts:49 msgid "Hermes enabled: %d" -msgstr "已启用Hermes:%d" +msgstr "已启用 Hermes:%d" #: packages/app-desktop/gui/MenuBar.tsx:670 msgid "Hide %s" @@ -2538,7 +2538,7 @@ msgstr "隐藏密码" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:14 msgid "Highlight" -msgstr "突出显示" +msgstr "高亮" #: packages/server/src/services/MustacheService.ts:150 #: packages/server/src/services/MustacheService.ts:279 @@ -2547,7 +2547,7 @@ msgstr "主页" #: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:100 msgid "Horizontal Rule" -msgstr "分割线" +msgstr "水平分割线" #: packages/lib/services/interop/InteropService.ts:186 msgid "HTML Directory" @@ -2585,7 +2585,7 @@ msgstr "空闲" msgid "" "If you have already authorised, please wait for the application to sync to " "Joplin Cloud." -msgstr "如果您已授权,请等待应用程序同步到 Joplin Cloud。" +msgstr "如果您已授权,请等待应用程序同步到 Joplin 云。" #: packages/app-desktop/ElectronAppWrapper.ts:127 #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:360 @@ -2600,7 +2600,7 @@ msgstr "忽略 TLS 证书错误" #: packages/lib/services/ReportService.ts:236 msgid "Ignored items that cannot be synchronised" -msgstr "无法同步的被忽略的条目" +msgstr "无法同步的已忽略条目" #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:106 msgid "Images" @@ -2631,7 +2631,7 @@ msgstr "从 JEX 导入" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:22 msgid "Import notes from a JEX (Joplin Export) file." -msgstr "从 JEX( Joplin 导出格式)文件导入笔记。" +msgstr "从 JEX(Joplin Export)文件导入笔记。" #: packages/lib/models/Setting.ts:1218 msgid "Import or export your data" @@ -2639,19 +2639,19 @@ msgstr "导入或导出数据" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:76 msgid "Imported successfully!" -msgstr "导入成功!" +msgstr "导入成功!" #: packages/app-desktop/gui/MenuBar.tsx:319 msgid "Importing from \"%s\" as \"%s\" format. Please wait..." -msgstr "从 “%s” 导入为 “%s” 格式 。请稍等..." +msgstr "从 “%s” 导入为 “%s” 格式 。请稍等……" #: packages/app-cli/app/command-import.ts:68 msgid "Importing notes..." -msgstr "正在导入笔记..." +msgstr "正在导入笔记……" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteImportButton.tsx:26 msgid "Importing..." -msgstr "正在导入..." +msgstr "正在导入……" #: packages/app-cli/app/command-import.ts:16 msgid "Imports data into Joplin." @@ -2673,7 +2673,7 @@ msgid "" "note or notebook. `$c` can be used to refer to the currently selected item." msgstr "" "在任何命令中,某个笔记或笔记本可通过它的名称或 ID 引用,也可使用代表当前所选" -"笔记和笔记本的快捷变量 ‘$n' 和 '$b'。`$c` 可用于引用当前所选的条目。" +"笔记和笔记本的快捷变量 `$n` 和 `$b`。`$c` 可用于引用当前所选的条目。" #: packages/app-mobile/components/screens/Note/Note.tsx:503 msgid "" @@ -2810,7 +2810,7 @@ msgstr "已安装 (%d):" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:192 #: packages/app-mobile/components/screens/ConfigScreen/plugins/buttons/InstallButton.tsx:17 msgid "Installing..." -msgstr "正在安装…" +msgstr "正在安装……" #: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:36 #: packages/app-desktop/gui/PasswordInput/LabelledPasswordInput.tsx:26 @@ -2875,12 +2875,12 @@ msgstr "Joplin 可以使用多种途径来同步您的笔记。可以从下列 #: packages/lib/models/Setting.ts:1184 packages/lib/SyncTargetJoplinCloud.ts:30 msgid "Joplin Cloud" -msgstr "Joplin Cloud" +msgstr "Joplin 云" #: packages/app-desktop/gui/Root.tsx:190 #: packages/app-mobile/components/screens/JoplinCloudLoginScreen.tsx:148 msgid "Joplin Cloud Login" -msgstr "Joplin Cloud 登录" +msgstr "Joplin 云登录" #: packages/lib/services/plugins/PluginService.ts:525 msgid "Joplin Desktop" @@ -2947,7 +2947,7 @@ msgstr "Joplin 网页剪藏器可以让您将浏览器中的网页和屏幕截 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:605 msgid "Joplin website" -msgstr "Joplin 官网" +msgstr "Joplin 网站" #: packages/lib/SyncTargetJoplinCloud.ts:34 msgid "" @@ -3034,7 +3034,7 @@ msgstr "离开笔记本" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.ts:11 msgid "Leave notebook..." -msgstr "离开笔记本..." +msgstr "离开笔记本……" #: packages/lib/models/settings/builtInMetadata.ts:1256 msgid "Legal" @@ -3046,7 +3046,7 @@ msgstr "信函 (Letter)" #: packages/lib/models/settings/builtInMetadata.ts:42 msgid "Light" -msgstr "明亮" +msgstr "亮色" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:110 msgid "" @@ -3090,7 +3090,7 @@ msgstr "已加载" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:179 #: packages/app-mobile/root.tsx:1247 msgid "Loading..." -msgstr "正在加载…" +msgstr "正在加载……" #: packages/app-desktop/gui/NotePropertiesDialog.tsx:71 msgid "Location" @@ -3102,7 +3102,7 @@ msgid "" "taking place, you may delete the lock file at \"%s\" and resume the " "operation." msgstr "" -"锁定文件已被占用。如果您确认当前未在进行任何同步,可在删除锁定文件 “%s” 后继" +"锁文件已被占用。如果您确认当前未在进行任何同步,可在删除锁文件 “%s” 后继" "续上一步操作。" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:550 @@ -3113,7 +3113,7 @@ msgstr "日志" #: packages/app-desktop/gui/MainScreen.tsx:563 msgid "Login to Joplin Cloud." -msgstr "登录至 Joplin Cloud。" +msgstr "登录至 Joplin 云。" #: packages/app-mobile/components/screens/dropbox-login.tsx:59 msgid "Login with Dropbox" @@ -3129,7 +3129,7 @@ msgstr "登出" #: packages/lib/models/Setting.ts:1217 msgid "Logs, profiles, sync status" -msgstr "日志,配置文件与同步状态" +msgstr "日志,配置档、同步状态" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:8 msgid "Longitude" @@ -3142,11 +3142,11 @@ msgstr "捐助" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:221 msgid "Manage master password" -msgstr "管理主密码(master password)" +msgstr "管理主密码" #: packages/lib/commands/openMasterPasswordDialog.ts:6 msgid "Manage master password..." -msgstr "管理主密码(master password)..." +msgstr "管理主密码……" #: packages/lib/utils/joplinCloud/index.ts:201 msgid "Manage multiple users" @@ -3154,7 +3154,7 @@ msgstr "管理多个用户" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:548 msgid "Manage profiles" -msgstr "管理配置文件" +msgstr "管理配置档" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:555 msgid "Manage shared notebooks" @@ -3212,7 +3212,7 @@ msgstr "标记语言" #: packages/app-mobile/components/screens/encryption-config.tsx:124 msgid "Master Key %s" -msgstr "主密钥(Master Key) %s" +msgstr "主密钥 %s" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:114 #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:273 @@ -3223,11 +3223,11 @@ msgstr "主密码" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:274 #: packages/app-mobile/components/screens/encryption-config.tsx:219 msgid "Master password:" -msgstr "主密码(Master password):" +msgstr "主密码:" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:19 msgid "match case" -msgstr "区分大小写" +msgstr "匹配大小写" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:72 msgid "Math" @@ -3251,7 +3251,7 @@ msgstr "最大总大小" #: packages/lib/models/Setting.ts:1214 msgid "Media player, math, diagrams, table of contents" -msgstr "媒体播放器,数学公式,图表等设置" +msgstr "媒体播放器、数学公式、流程图、目录" #: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:24 msgid "Minimise" @@ -3263,11 +3263,11 @@ msgstr "尚未授予相机权限" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:320 msgid "Missing keys" -msgstr "缺少密钥(keys)" +msgstr "缺少密钥" #: packages/app-mobile/components/screens/encryption-config.tsx:282 msgid "Missing Master Keys" -msgstr "缺少主密钥(Master Keys)" +msgstr "缺少主密钥" #: packages/app-cli/app/cli-utils.js:112 msgid "Missing required argument: %s" @@ -3279,7 +3279,7 @@ msgstr "缺失必选标志值:%s" #: packages/app-mobile/components/side-menu-content.tsx:663 msgid "Mobile data - auto-sync disabled" -msgstr "移动数据自动同步被禁用" +msgstr "移动数据 - 已禁用自动同步" #: packages/app-desktop/gui/MainScreen.tsx:532 msgid "More info" @@ -3311,7 +3311,7 @@ msgid "" "All notes and sub-notebooks within this notebook will also be moved to the " "trash." msgstr "" -"是否将笔记本 “%s”移到回收站?\n" +"是否将笔记本“%s”移到回收站?\n" "\n" "所有该笔记本内的笔记和子笔记本也将同时被移到回收站。" @@ -3325,11 +3325,11 @@ msgstr "移动到笔记本:" #: packages/app-mobile/components/FolderPicker.tsx:59 msgid "Move to notebook..." -msgstr "移动到笔记本..." +msgstr "移动到笔记本……" #: packages/app-cli/app/command-mv.ts:14 msgid "Moves the given to [notebook]" -msgstr "将给定的移动到[notebook]。" +msgstr "将给定的 移动到 [notebook]。" #: packages/app-cli/app/cli-utils.js:177 #: packages/app-cli/app/setupCommand.ts:23 @@ -3438,7 +3438,7 @@ msgstr "否" #: packages/app-cli/app/command-edit.ts:41 msgid "No active notebook." -msgstr "无有效的笔记本。" +msgstr "无活动的笔记本。" #: packages/app-mobile/components/screens/ShareManager/index.tsx:92 msgid "No new invitations" @@ -3454,7 +3454,7 @@ msgstr "未选择笔记本。" #: packages/lib/components/shared/NoteList/getEmptyFolderMessage.ts:15 msgid "No notes in here. Create one by clicking on \"New note\"." -msgstr "此处没有笔记。点击 “新建笔记” 创建。" +msgstr "此处没有笔记。点击 “新建笔记” 创建一个。" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.tsx:202 msgid "No plugins are installed." @@ -3478,12 +3478,12 @@ msgstr "没有建议" #: packages/app-mobile/components/plugins/dialogs/PluginPanelViewer.tsx:120 msgid "No tab selected" -msgstr "未选择标签" +msgstr "未选择标签页" #: packages/app-cli/app/command-edit.ts:31 msgid "" "No text editor is defined. Please set it using `config editor `" -msgstr "未设置指定的文本编辑器。请通过 `config editor ` 设置" +msgstr "未指定文本编辑器。请通过 `config editor ` 设置它" #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:93 msgid "No updates available" @@ -3495,11 +3495,11 @@ msgstr "无" #: packages/lib/models/settings/builtInMetadata.ts:47 msgid "Nord" -msgstr "北欧 (Nord)" +msgstr "Nord" #: packages/app-cli/app/command-sync.ts:123 msgid "Not authenticated with %s. Please provide any missing credentials." -msgstr "未授予 %s 的权限。请提供缺少的凭据。" +msgstr "%s 未授权。请提供任何缺少的凭据。" #: packages/lib/models/Resource.ts:407 msgid "Not downloaded" @@ -3528,7 +3528,7 @@ msgstr "笔记" #: packages/lib/models/settings/builtInMetadata.ts:1549 msgid "Note area growth factor" -msgstr "笔记区域增长因子" +msgstr "笔记区域增长系数" #: packages/app-desktop/gui/Root.tsx:193 msgid "Note attachments" @@ -3536,7 +3536,7 @@ msgstr "笔记附件" #: packages/app-desktop/gui/MenuBar.tsx:574 msgid "Note attachments..." -msgstr "笔记附件..." +msgstr "笔记附件……" #: packages/app-desktop/gui/NoteEditor/commands/focusElementNoteBody.ts:7 msgid "Note body" @@ -3571,7 +3571,7 @@ msgstr "笔记列表" #: packages/lib/models/settings/builtInMetadata.ts:1534 msgid "Note list growth factor" -msgstr "笔记列表增长因子" +msgstr "笔记列表增长系数" #: packages/app-desktop/gui/MenuBar.tsx:793 msgid "Note list style" @@ -3607,7 +3607,7 @@ msgstr "笔记本" #: packages/lib/models/settings/builtInMetadata.ts:1519 msgid "Notebook list growth factor" -msgstr "笔记本列表增长因子" +msgstr "笔记本列表增长系数" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:5 #: packages/app-mobile/components/side-menu-content.tsx:441 @@ -3681,7 +3681,7 @@ msgstr "确认" #: packages/lib/models/settings/builtInMetadata.ts:49 msgid "OLED Dark" -msgstr "纯黑 (OLED)" +msgstr "OLED 暗色" #: packages/lib/services/ReportService.ts:356 msgid "On %s: %s" @@ -3689,11 +3689,11 @@ msgstr "位于 %s: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:27 msgid "on line" -msgstr "在该行" +msgstr "在行" #: packages/app-desktop/gui/MainScreen.tsx:525 msgid "One of your master keys use an obsolete encryption method." -msgstr "您的主密钥(Master Key)之一使用了过时的加密方法。" +msgstr "您的主密钥之一使用了过时的加密方法。" #: packages/app-cli/app/gui/NoteWidget.js:48 msgid "" @@ -3702,13 +3702,13 @@ msgid "" "supplied the password, the encrypted items are being decrypted in the " "background and will be available soon." msgstr "" -"一个或多个条目当前已加密,您可能需要提供主密码(master password)。若需进行此操" -"作请输入 `e2ee decrypt` 。若您已提供密码,此时加密文件正在后台解密,解锁之后" +"一个或多个条目当前已加密,您可能需要提供主密码。若需进行此操" +"作请键入 `e2ee decrypt` 。若您已提供密码,此时加密条目正在后台解密,不久" "即可使用。" #: packages/app-desktop/gui/MainScreen.tsx:554 msgid "One or more master keys need a password." -msgstr "一个或多个主密钥(Master Key)需要密码。" +msgstr "一个或多个主密钥需要密码。" #: packages/lib/SyncTargetOneDrive.ts:36 msgid "OneDrive" @@ -3716,7 +3716,7 @@ msgstr "OneDrive" #: packages/app-desktop/gui/Root.tsx:188 msgid "OneDrive Login" -msgstr "登录 OneDrive" +msgstr "OneDrive 登录" #: packages/lib/services/interop/InteropService.ts:144 msgid "OneNote Notebook" @@ -3736,7 +3736,7 @@ msgstr "打开 %s" #: packages/app-desktop/bridge.ts:454 msgid "Open it" -msgstr "打开" +msgstr "打开它" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/openPdfViewer.ts:7 msgid "Open PDF viewer" @@ -3744,7 +3744,7 @@ msgstr "打开 PDF 查看器" #: packages/app-desktop/commands/openProfileDirectory.ts:8 msgid "Open profile directory" -msgstr "打开配置文件目录" +msgstr "打开配置档目录" #: packages/app-mobile/components/CameraView/CameraView.tsx:164 msgid "Open settings" @@ -3756,15 +3756,15 @@ msgstr "开源" #: packages/lib/models/settings/builtInMetadata.ts:73 msgid "Open Sync Wizard..." -msgstr "打开同步向导..." +msgstr "打开同步向导……" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:87 msgid "Open..." -msgstr "打开..." +msgstr "打开……" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:206 msgid "Opening section %s" -msgstr "正在打开节%s" +msgstr "正在打开节 %s" #: packages/app-mobile/components/Dropdown.tsx:215 msgid "Opens dropdown" @@ -3796,7 +3796,7 @@ msgstr "有序列表" #: packages/app-desktop/gui/MenuBar.tsx:518 msgid "Other applications..." -msgstr "其他应用..." +msgstr "其他应用……" #: packages/app-cli/app/command-import.ts:29 msgid "Output format: %s" @@ -3851,11 +3851,11 @@ msgstr "PDF 文件" #: packages/lib/utils/joplinCloud/index.ts:408 msgid "Per user. Minimum of %d users." -msgstr "每个用户。至少有%d个用户。" +msgstr "每个用户。至少有 %d 个用户。" #: packages/lib/commands/permanentlyDeleteNote.ts:8 msgid "Permanently delete note" -msgstr "永久删除选中的笔记" +msgstr "永久删除笔记" #: packages/lib/models/Note.ts:943 msgid "Permanently delete note \"%s\"?" @@ -3898,14 +3898,14 @@ msgstr "请确认您要重新加密整个数据库。" msgid "" "Please enter your password in the master key list below before upgrading the " "key." -msgstr "在升级密钥之前,请在下面的主密钥(Master Key)列表中输入您的密码。" +msgstr "在升级密钥之前,请在下面的主密钥列表中输入您的密码。" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:380 msgid "" "Please note that if it is a large notebook, it may take a few minutes for " "all the notes to show up on the recipient's device." msgstr "" -"请注意,如果是大型笔记本,可能需要几分钟时间才能在收件人的设备上显示出所有的" +"请注意,如果是大型笔记本,可能需要几分钟时间才能在受邀人的设备上显示出所有的" "笔记。" #: packages/lib/onedrive-api-node-utils.js:118 @@ -3916,21 +3916,21 @@ msgid "" "any files outside this directory nor to any other personal data. No data " "will be shared with any third party." msgstr "" -"请在浏览器中打开以下链接以验证本应用程序。本应用会建立 “Apps/Joplin” 文件目" -"录,并只会读写该目录中的文件。本应用没有任何权限访问此目录以外的任何文件或个" +"请在浏览器中打开以下 URL 以验证本应用程序。本应用会创建“Apps/Joplin”" +"目录,并只会读写该目录中的文件。本应用没有任何权限访问此目录以外的任何文件或个" "人信息,也不会与第三方分享任何数据。" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:180 msgid "Please record your voice..." -msgstr "请录下您的声音..." +msgstr "请录下您的声音……" #: packages/app-cli/app/command-ls.ts:66 msgid "Please select a notebook first." -msgstr "请先选择笔记本。" +msgstr "请先选择一个笔记本。" #: packages/app-cli/app/app-gui.js:468 msgid "Please select the note or notebook to be deleted first." -msgstr "请先选择需要删除的笔记或笔记本。" +msgstr "请先选择要删除的笔记或笔记本。" #: packages/app-desktop/gui/StatusScreen/StatusScreen.tsx:31 msgid "Please select where the sync status should be exported to" @@ -3952,12 +3952,12 @@ msgstr "请升级 Joplin 到 %s 或更新的版本以使用此插件。" msgid "" "Please wait for all attachments to be downloaded and decrypted. You may also " "switch to %s to edit the note." -msgstr "请等待所有附件均被下载并解密。您可以切换到 %s 编辑笔记。" +msgstr "请等待至所有附件均被下载并解密。您也可以切换到 %s 以编辑笔记。" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx:118 #: packages/app-desktop/gui/ResourceScreen.tsx:302 msgid "Please wait..." -msgstr "请稍候..." +msgstr "请稍候……" #: packages/app-mobile/services/plugins/PlatformImplementation.ts:51 msgid "Plugin message" @@ -4020,7 +4020,7 @@ msgstr "偏好设置" #: packages/app-desktop/gui/MenuBar.tsx:627 msgid "Preferences..." -msgstr "偏好设置..." +msgstr "偏好设置……" #: packages/lib/models/settings/builtInMetadata.ts:586 msgid "Preferred dark theme" @@ -4032,7 +4032,7 @@ msgstr "首选亮色主题" #: packages/lib/models/settings/builtInMetadata.ts:1704 msgid "Preferred voice typing provider" -msgstr "首选语音输入模型" +msgstr "首选语音输入提供者" #: packages/lib/models/settings/builtInMetadata.ts:667 msgid "Preserve colours when pasting text in Rich Text Editor" @@ -4040,7 +4040,7 @@ msgstr "在富文本编辑器中粘贴文本时保留颜色" #: packages/app-cli/app/app-gui.js:758 msgid "Press Ctrl+D or type \"exit\" to exit the application" -msgstr "按 Ctrl+D 或输入 “exit” 退出程序" +msgstr "按 Ctrl+D 或键入 “exit” 退出程序" #: packages/app-desktop/gui/KeymapConfig/ShortcutRecorder.tsx:70 msgid "Press the shortcut" @@ -4050,11 +4050,11 @@ msgstr "按下快捷键" msgid "" "Press the shortcut and then press ENTER. Or, press BACKSPACE to clear the " "shortcut." -msgstr "按下快捷键然后按下回车。或者:按下退格键以清除快捷键。" +msgstr "按下快捷键然后按下回车。或者,按下退格键以清除快捷键。" #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:40 msgid "Press to set the decryption password." -msgstr "点按设置解密密码。" +msgstr "点击设置解密密码。" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:17 msgid "previous" @@ -4062,7 +4062,7 @@ msgstr "上一个" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:281 msgid "Previous match" -msgstr "上次匹配" +msgstr "上一个匹配" #: packages/app-desktop/gui/NotePropertiesDialog.tsx:368 msgid "Previous versions of this note" @@ -4102,27 +4102,27 @@ msgstr "正在处理" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:111 msgid "Processing photo..." -msgstr "正在处理照片…" +msgstr "正在处理照片……" #: packages/server/src/routes/admin/users.ts:254 msgid "Profile" -msgstr "配置文件" +msgstr "配置档" #: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:96 msgid "Profile name" -msgstr "配置文件名称" +msgstr "配置档名称" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/addProfile.ts:18 msgid "Profile name:" -msgstr "配置文件:" +msgstr "配置档名称:" #: packages/lib/versionInfo.ts:90 msgid "Profile Version: %s" -msgstr "配置文件版本:%s" +msgstr "配置档版本:%s" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:172 msgid "Profiles" -msgstr "配置文件" +msgstr "配置档" #: packages/app-mobile/components/screens/Note/Note.tsx:1248 msgid "Properties" @@ -4130,7 +4130,7 @@ msgstr "笔记属性" #: packages/lib/models/settings/builtInMetadata.ts:1413 msgid "Proxy enabled" -msgstr "使用代理" +msgstr "启用代理" #: packages/lib/models/settings/builtInMetadata.ts:1435 msgid "Proxy timeout (seconds)" @@ -4138,7 +4138,7 @@ msgstr "代理超时(秒)" #: packages/lib/models/settings/builtInMetadata.ts:1423 msgid "Proxy URL" -msgstr "代理URL" +msgstr "代理 URL" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:240 msgid "Public-private key pair:" @@ -4146,7 +4146,7 @@ msgstr "公-私钥对:" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareNoteDialog.ts:6 msgid "Publish note..." -msgstr "发布笔记..." +msgstr "发布笔记……" #: packages/app-desktop/gui/ShareNoteDialog.tsx:213 msgid "Publish Notes" @@ -4155,7 +4155,7 @@ msgstr "发布笔记" #: packages/app-desktop/gui/SyncWizard/Dialog.tsx:168 #: packages/lib/utils/joplinCloud/index.ts:153 msgid "Publish notes to the internet" -msgstr "把笔记发布到互联网" +msgstr "将笔记发布到互联网" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:78 msgid "QR Code" @@ -4198,19 +4198,19 @@ msgstr "阅读时间:%s 分钟" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:313 msgid "Recipient has accepted the invitation" -msgstr "收件人接受了邀请" +msgstr "受邀人已接受邀请" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:311 msgid "Recipient has not yet accepted the invitation" -msgstr "收件人还没有接受邀请" +msgstr "受邀人暂未接受邀请" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:312 msgid "Recipient has rejected the invitation" -msgstr "收件人拒绝了邀请" +msgstr "受邀人已拒绝邀请" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:341 msgid "Recipients:" -msgstr "收件人:" +msgstr "受邀人:" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx:72 msgid "Recommended" @@ -4225,7 +4225,7 @@ msgstr "推荐插件" #: packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.tsx:168 #: packages/app-mobile/components/ScreenHeader/index.tsx:351 msgid "Redo" -msgstr "恢复" +msgstr "重做" #: packages/app-mobile/components/screens/LogScreen.tsx:229 #: packages/app-mobile/components/screens/onedrive-login.js:121 @@ -4299,7 +4299,7 @@ msgstr "全部替换" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:239 msgid "Replace with..." -msgstr "替换为..." +msgstr "替换为……" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:260 msgid "Replace: " @@ -4340,11 +4340,11 @@ msgstr "重置应用布局" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:221 #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:222 msgid "Reset master password" -msgstr "重置主密码(master password)" +msgstr "重置主密码" #: packages/lib/models/settings/builtInMetadata.ts:862 msgid "Resize large images:" -msgstr "缩放大尺寸图片:" +msgstr "缩放大尺寸图片:" #: packages/app-cli/app/command-import.ts:54 #: packages/app-desktop/gui/ImportScreen.tsx:93 @@ -4387,7 +4387,7 @@ msgstr "恢复笔记本" #: packages/app-cli/app/command-restore.ts:12 msgid "Restore the items matching from the trash." -msgstr "从回收站恢复符合 的笔记。" +msgstr "从回收站恢复匹配 的笔记。" #: packages/lib/services/trash/index.ts:88 msgid "Restored items" @@ -4495,7 +4495,7 @@ msgstr "另存为 %s" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:94 msgid "Save as..." -msgstr "另存为..." +msgstr "另存为……" #: packages/app-desktop/gui/NotePropertiesDialog.tsx:325 #: packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.tsx:110 @@ -4515,7 +4515,7 @@ msgstr "保存地理位置信息到笔记中" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:89 msgid "Scanned code" -msgstr "扫码" +msgstr "已扫码" #: packages/app-desktop/gui/lib/SearchInput/SearchInput.tsx:62 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:102 @@ -4529,15 +4529,15 @@ msgstr "搜索" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx:118 #: packages/app-mobile/components/screens/ConfigScreen/plugins/SearchPlugins.tsx:157 msgid "Search for plugins..." -msgstr "搜索插件..." +msgstr "搜索插件……" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:226 msgid "Search for..." -msgstr "搜索..." +msgstr "搜索……" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:170 msgid "Search hidden" -msgstr "隐藏搜索" +msgstr "搜索已隐藏" #: packages/app-desktop/gui/NoteListControls/commands/focusSearch.ts:6 msgid "Search in all the notes" @@ -4553,7 +4553,7 @@ msgstr "搜索结果" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:170 msgid "Search shown" -msgstr "显示搜索" +msgstr "搜索已显示" #: packages/app-cli/app/gui/FolderListWidget.ts:56 msgid "Search:" @@ -4565,7 +4565,7 @@ msgstr "搜索:" #: packages/app-desktop/gui/ResourceScreen.tsx:299 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:785 msgid "Search..." -msgstr "搜索..." +msgstr "搜索……" #: packages/app-cli/app/command-search.js:13 msgid "Searches for the given in all the notes." @@ -4591,11 +4591,11 @@ msgstr "全选" #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:145 msgid "Select emoji..." -msgstr "选择 emoji..." +msgstr "选择 emoji……" #: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:149 msgid "Select file..." -msgstr "选择文件..." +msgstr "选择文件……" #: packages/app-mobile/components/screens/folder.js:109 msgid "Select parent notebook" @@ -4611,7 +4611,7 @@ msgstr "选择笔记本" #: packages/app-desktop/gui/MenuBar.tsx:359 msgid "Send bug report" -msgstr "发送错误日志" +msgstr "发送错误报告" #: packages/app-cli/app/command-server.js:38 msgid "Server is already running on port %d" @@ -4640,7 +4640,7 @@ msgstr "设置提醒:" msgid "" "Set it to 0 to make it take the complete available space. Recommended width " "is 600." -msgstr "设置为 0 则占用全部可用空间。建议宽度为 600。" +msgstr "设为 0 将占用全部可用空间。建议宽度为 600。" #: packages/app-desktop/gui/MainScreen.tsx:508 #: packages/app-desktop/gui/MainScreen.tsx:555 @@ -4690,7 +4690,7 @@ msgstr "共享笔记本" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareFolderDialog.ts:6 msgid "Share notebook..." -msgstr "共享笔记本..." +msgstr "共享笔记本……" #: packages/lib/utils/joplinCloud/index.ts:215 msgid "Share permissions" @@ -4698,7 +4698,7 @@ msgstr "共享权限" #: packages/app-desktop/gui/Sidebar/listItemComponents/FolderItem.tsx:56 msgid "Shared" -msgstr "共享" +msgstr "已共享" #: packages/app-mobile/components/screens/ShareManager/index.tsx:107 msgid "Shares" @@ -4706,7 +4706,7 @@ msgstr "共享" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:364 msgid "Sharing notebook..." -msgstr "共享笔记本中..." +msgstr "正在共享笔记本……" #: packages/app-cli/app/command-help.ts:45 msgid "Shortcuts are not available in CLI mode." @@ -4714,7 +4714,7 @@ msgstr "快捷键在 CLI 模式下不可用。" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:211 msgid "Show advanced" -msgstr "显示高级选项" +msgstr "显示高级" #: packages/app-desktop/gui/ConfigScreen/controls/ToggleAdvancedSettingsButton.tsx:24 msgid "Show Advanced Settings" @@ -4730,7 +4730,7 @@ msgstr "显示已完成待办事项" #: packages/server/src/routes/admin/users.ts:206 msgid "Show disabled" -msgstr "显示禁用的密钥" +msgstr "显示禁用的" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:160 msgid "Show disabled keys" @@ -4754,7 +4754,7 @@ msgstr "显示密码" #: packages/lib/models/settings/builtInMetadata.ts:698 msgid "Show sort order buttons" -msgstr "显示排序方法按钮" +msgstr "显示排序按钮" #: packages/lib/models/settings/builtInMetadata.ts:1007 msgid "Show tray icon" @@ -4762,7 +4762,7 @@ msgstr "显示托盘图标" #: packages/app-mobile/components/ScreenHeader/index.tsx:263 msgid "Show/hide the sidebar" -msgstr "显示/隐藏侧边栏" +msgstr "显示/隐藏边栏" #: packages/app-mobile/components/screens/tags.tsx:76 msgid "Shows notes for tag" @@ -4770,15 +4770,15 @@ msgstr "显示笔记标签" #: packages/lib/models/settings/builtInMetadata.ts:863 msgid "Shrink large images before adding them to notes." -msgstr "在添加大尺寸图片进笔记之前将其缩放到合适尺寸。" +msgstr "在添加大尺寸图片进笔记之前将其缩小到合适尺寸。" #: packages/app-mobile/components/SideMenu.tsx:258 msgid "Side menu closed" -msgstr "关闭侧边栏" +msgstr "侧边菜单已关闭" #: packages/app-mobile/components/SideMenu.tsx:258 msgid "Side menu opened" -msgstr "打开侧边菜单" +msgstr "侧边菜单已打开" #: packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.ts:10 #: packages/app-desktop/gui/Sidebar/Sidebar.tsx:77 @@ -4805,22 +4805,22 @@ msgstr "已跳过:%d 条。" #: packages/lib/models/settings/builtInMetadata.ts:46 msgid "Solarised Dark" -msgstr "日光暗 (Solarised)" +msgstr "Solarised 暗色" #: packages/lib/models/settings/builtInMetadata.ts:45 msgid "Solarised Light" -msgstr "日光亮 (Solarised)" +msgstr "Solarised 亮色" #: packages/lib/models/settings/settingValidations.ts:21 msgid "" "Some attachments could not be downloaded. Please try to download them again." -msgstr "部分附件无法下载。 请尝试再次下载它们。" +msgstr "无法下载部分附件。请尝试再次下载它们。" #: packages/lib/models/settings/settingValidations.ts:18 msgid "" "Some attachments need to be downloaded. Set the attachment download mode to " "\"always\" and try again." -msgstr "部分附件需要下载。 将附件下载模式设置为“总是”并重试。" +msgstr "需要下载部分附件。将附件下载模式设置为“总是”并重试。" #: packages/app-desktop/gui/MainScreen.tsx:519 #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:52 @@ -4842,7 +4842,7 @@ msgstr "某些条目无法被同步。请先尝试同步它们。" #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in ascending order" -msgstr "将“%s”按降升序排序" +msgstr "将“%s”按升序排序" #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in descending order" @@ -4876,7 +4876,7 @@ msgstr "原数据格式:%s" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:139 msgid "Source: " -msgstr "来源: " +msgstr "来源:" #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:435 msgid "Spacer" @@ -4908,18 +4908,18 @@ msgid "" "Start, stop or check the API server. To specify on which port it should run, " "set the api.port config variable. Commands are (%s)." msgstr "" -"启动,停止或检查 API 服务。可以通过设置 ‘api.port’ 变量指定 API 服务运行在哪" -"个端口上。执行命令 (%s) 。" +"启动,停止或检查 API 服务器。要指定其运行端口,可设置 api.port 配置变量。" +"执行命令 (%s)。" #: packages/app-cli/app/command-e2ee.ts:57 msgid "" "Starting decryption... Please wait as it may take several minutes depending " "on how much there is to decrypt." -msgstr "开始解密,请稍候... 取决于需解密的文件数量,该环节可能需要几分钟。" +msgstr "开始解密,请稍候……取决于需解密的文件数量,该环节可能需要几分钟。" #: packages/app-cli/app/command-sync.ts:233 msgid "Starting synchronisation..." -msgstr "开始同步..." +msgstr "开始同步……" #: packages/app-cli/app/command-edit.ts:76 msgid "Starting to edit note. Close the editor to get back to the prompt." @@ -4931,7 +4931,7 @@ msgstr "统计数据" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteContentProperties.ts:8 msgid "Statistics..." -msgstr "统计数据..." +msgstr "统计数据……" #: packages/app-mobile/components/screens/encryption-config.tsx:312 #: packages/app-mobile/components/screens/status.tsx:172 @@ -4944,7 +4944,7 @@ msgstr "状态:%s" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:82 msgid "Status: Started on port %d" -msgstr "状态:在 %d 端口运行" +msgstr "状态:启动于端口 %d" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:124 msgid "Step 1: Enable the clipper service" @@ -5018,7 +5018,7 @@ msgstr "在笔记和待办事项类型之间切换" #: packages/app-desktop/gui/MenuBar.tsx:552 #: packages/app-mobile/components/side-menu-content.tsx:625 msgid "Switch profile" -msgstr "切换配置文件" +msgstr "切换配置档" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:101 msgid "Switch to back-facing camera" @@ -5036,7 +5036,7 @@ msgstr "切换为笔记类型" #: packages/app-desktop/commands/switchProfile2.ts:7 #: packages/app-desktop/commands/switchProfile3.ts:7 msgid "Switch to profile %d" -msgstr "切换到配置文件 %d" +msgstr "切换到配置档 %d" #: packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.tsx:28 msgid "Switch to the %s Editor" @@ -5048,7 +5048,7 @@ msgstr "切换到传统编辑器" #: packages/app-desktop/gui/utils/NoteListUtils.ts:102 msgid "Switch to to-do type" -msgstr "切换为待办事项" +msgstr "切换为待办事项类型" #: packages/app-cli/app/command-use.ts:12 msgid "" @@ -5078,7 +5078,7 @@ msgstr "同步目标升级" #: packages/app-cli/app/command-sync.ts:41 msgid "Sync to provided target (defaults to sync.target config value)" -msgstr "同步到所提供的目标(默认为同步目标配置值)" +msgstr "同步到所提供的目标(默认为 sync.target 配置值)" #: packages/lib/versionInfo.ts:89 msgid "Sync Version: %s" @@ -5090,7 +5090,7 @@ msgstr "同步笔记" #: packages/lib/models/Setting.ts:1212 msgid "Sync, encryption, proxy" -msgstr "同步与加密,代理设置" +msgstr "同步、加密、代理" #: packages/lib/models/Setting.ts:1173 msgid "Synchronisation" @@ -5125,27 +5125,27 @@ msgstr "同步" #: packages/lib/models/settings/builtInMetadata.ts:1240 msgid "Synchronise only over WiFi connection" -msgstr "只通过 WiFi 同步" +msgstr "只通过 WiFi 连接同步" #: packages/app-cli/app/command-sync.ts:36 msgid "Synchronises with remote storage." -msgstr "与远程储存同步。" +msgstr "与远程存储同步。" #: packages/app-desktop/gui/ShareNoteDialog.tsx:185 msgid "Synchronising..." -msgstr "正在同步..." +msgstr "正在同步……" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:363 msgid "Synchronizing..." -msgstr "正在同步..." +msgstr "正在同步……" #: packages/lib/models/settings/builtInMetadata.ts:1255 msgid "Tabloid" -msgstr "文摘(Tabloid)" +msgstr "文摘(Tabloid)" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:215 msgid "tag1, tag2, ..." -msgstr "标签1, 标签2, ..." +msgstr "标签1, 标签2, ……" #: packages/app-cli/app/command-import.ts:55 #: packages/app-desktop/gui/ImportScreen.tsx:94 @@ -5168,7 +5168,7 @@ msgstr "拍照" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/TaskButton.tsx:73 msgid "Task \"%s\" failed with error: %s" -msgstr "任务 \"%s\" 失败,错误: %s" +msgstr "任务“%s”失败,错误:%s" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:87 msgid "Task list" @@ -5196,13 +5196,13 @@ msgid "" "The [Web Clipper](%s) is a browser extension that allows you to save web " "pages and screenshots from your browser." msgstr "" -"[Web Clipper](%s) 是一个浏览器扩展程序,可让您保存浏览器中的网页和屏幕截图。" +"[Web Clipper](%s) 是一个浏览器扩展程序,可让您从浏览器保存网页和屏幕截图。" #: packages/lib/services/profileConfig/index.ts:106 msgid "" "The active profile cannot be deleted. Switch to a different profile and try " "again." -msgstr "无法删除正在使用的配置文件。请切换到另一配置文件后再次尝试。" +msgstr "无法删除正在使用的配置档。请切换到另一配置档后再次尝试。" #: packages/app-desktop/bridge.ts:504 msgid "" @@ -5212,12 +5212,12 @@ msgstr "应用将要关闭。请重新启动它以完成此过程。" #: packages/app-desktop/app.ts:353 msgid "" "The application did not close properly. Would you like to start in safe mode?" -msgstr "该程序没有正确关闭。您想在安全模式下启动吗?" +msgstr "该程序之前未正确关闭。您想在安全模式下启动吗?" #: packages/lib/onedrive-api-node-utils.js:87 msgid "" "The application has been authorised - you may now close this browser tab." -msgstr "授权成功 - 您可以关闭此页面了。" +msgstr "授权成功 - 您可以关闭此浏览器标签页了。" #: packages/lib/components/shared/dropbox-login-shared.js:39 msgid "The application has been authorised!" @@ -5261,14 +5261,14 @@ msgstr "默认的加密方式已变更,您应当重新加密数据。" #: packages/lib/services/profileConfig/index.ts:105 msgid "The default profile cannot be deleted" -msgstr "无法删除默认配置文件" +msgstr "无法删除默认配置档" #: packages/lib/models/settings/builtInMetadata.ts:1248 msgid "" "The editor command (may include arguments) that will be used to open a note. " "If none is provided it will try to auto-detect the default editor." msgstr "" -"该文本编辑器命令(可包含参数)将会被用于打开笔记。若未提供将尝试自动检测默认" +"该文本编辑器命令(可包含参数)将会被用于打开笔记。若未提供,它将尝试自动检测默认" "编辑器。" #: packages/lib/models/settings/builtInMetadata.ts:1521 @@ -5280,8 +5280,8 @@ msgid "" "item with a factor of 2 will take twice as much space as an item with a " "factor of 1.Restart app to see changes." msgstr "" -"拉伸系数属性用于指定条目之间的容量比例。如,因子为 2 的条目所占容量是因子为 " -"1 的条目的两倍。该更改在软件重启后生效。" +"系数属性用于指定条目在其容器的可用空间中,如何根据其他条目进行增大或缩小。例如,系数为 2 的条目所占容量是系数为 " +"1 的条目的两倍。该更改在应用重启后生效。" #: packages/app-desktop/gui/NoteEditor/NoteEditor.tsx:612 msgid "The following attachment matches your search query:" @@ -5318,12 +5318,12 @@ msgid "" "application does not currently have access to them. It is likely they will " "eventually be downloaded via synchronisation." msgstr "" -"具有这些ID的密钥用于加密某些条目,但应用程序当前无法访问这些密钥。它们很可能" -"最终会通过同步获取。" +"具有这些 ID 的密钥已用于加密某些条目,但应用程序目前无法访问它们。" +"它们可能最终会通过同步获取。" #: packages/lib/components/EncryptionConfigScreen/utils.ts:222 msgid "The master key has been upgraded successfully!" -msgstr "主密钥(Master Key)已成功升级!" +msgstr "主密钥已成功升级!" #: packages/app-mobile/components/screens/encryption-config.tsx:283 msgid "" @@ -5331,12 +5331,12 @@ msgid "" "however the application does not currently have access to them. It is likely " "they will eventually be downloaded via synchronisation." msgstr "" -"具有这些 ID 的主密钥(Master Key)正被用于加密某些条目,但应用程序目前无法访问" -"它们。这些条目最终会通过同步获取,但目前可能仍未被同步。" +"具有这些 ID 的主密钥已用于加密某些条目,但应用程序目前无法访问它们。" +"它们可能最终会通过同步获取。" #: packages/lib/services/RevisionService.ts:272 msgid "The note \"%s\" has been successfully restored to the notebook \"%s\"." -msgstr "笔记 “%s” 已成功恢复到笔记本 “%s” 中。" +msgstr "笔记“%s”已成功恢复到笔记本“%s”中。" #: packages/app-desktop/gui/TrashNotification/TrashNotification.tsx:68 msgid "The note was successfully moved to the trash." @@ -5366,9 +5366,9 @@ msgid "" "\n" "The error was: \"%s\"" msgstr "" -"收件人无法从列表中删除。请再试一次。\n" +"无法从列表中删除受邀人。请重试。\n" "\n" -"错误:“%s”" +"错误为:“%s”" #: packages/lib/models/settings/settingValidations.ts:35 msgid "" @@ -5396,7 +5396,7 @@ msgstr "" #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:46 msgid "The sync target needs to be upgraded. Press this banner to proceed." -msgstr "同步目标需要升级。按这个横幅继续。" +msgstr "同步目标需要升级。点按这个横幅以继续。" #: packages/app-desktop/gui/MainScreen.tsx:507 msgid "The synchronisation password is missing." @@ -5404,7 +5404,7 @@ msgstr "缺少同步密码。" #: packages/lib/models/Tag.ts:233 msgid "The tag \"%s\" already exists. Please choose a different name." -msgstr "标签 “%s” 已存在。请选择一个不同的名称。" +msgstr "标签“%s”已存在。请选择一个不同的名称。" #: packages/lib/models/settings/builtInMetadata.ts:86 msgid "" @@ -5432,17 +5432,17 @@ msgstr "网页剪藏器需要您的授权才能访问您的数据。" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:76 msgid "The web clipper service is enabled and set to auto-start." -msgstr "网页剪藏器已启用并已设置为自动启动。" +msgstr "网页剪藏器服务已启用并已设置为自动启动。" #: packages/app-desktop/gui/ClipperConfigScreen.tsx:100 msgid "The web clipper service is not enabled." -msgstr "网页剪藏未启用。" +msgstr "网页剪藏服务未启用。" #: packages/lib/utils/webDAVUtils.ts:28 msgid "" "The WebDAV implementation of %s is incompatible with Joplin, and as such is " "no longer supported. Please use a different sync method." -msgstr "%s的 WebDAV 工具与 Joplin 不兼容,因此不再受支持。请使用其他同步方法。" +msgstr "%s 的 WebDAV 实现与 Joplin 不兼容,因此不再受支持。请使用其他同步方法。" #: packages/lib/models/settings/builtInMetadata.ts:543 msgid "Theme" @@ -5450,11 +5450,11 @@ msgstr "主题" #: packages/lib/models/Setting.ts:1211 msgid "Themes, editor font" -msgstr "主题与编辑器字体设置" +msgstr "主题、编辑器字体" #: packages/lib/components/shared/NoteList/getEmptyFolderMessage.ts:17 msgid "There are currently no notes. Create one by clicking on the (+) button." -msgstr "当前没有任何笔记。点击 (+) 按钮创建。" +msgstr "当前没有任何笔记。点击 (+) 按钮创建一个。" #: packages/lib/components/shared/NoteList/getEmptyFolderMessage.ts:9 msgid "There are no notes in the trash folder." @@ -5488,8 +5488,8 @@ msgid "" "cause the sync warning to appear, but still aren't synced. To unignore, " "click \"retry\"." msgstr "" -"这些条目未能同步。但由于已标记为 \"忽略\",因此它们不会导致同步警告。要取消忽" -"略,请单击 \"重试\"。" +"这些条目未能同步,但已被标记为“ignored”(已忽略),因此不会导致同步警告出现。要取消忽" +"略,请单击“重试”。" #: packages/lib/services/ReportService.ts:187 msgid "" @@ -5497,7 +5497,7 @@ msgid "" "target. In order to find these items, either search for the title or the ID " "(which is displayed in brackets above)." msgstr "" -"这些条目将只保留在本设备上,不会上传到同步目标。若需查找这些项,请搜索标题或 " +"这些条目将保留在本设备上,但不会上传到同步目标。若需查找这些项,请搜索标题或 " "ID(显示在上方括号中)。" #: packages/lib/models/Setting.ts:1196 @@ -5522,8 +5522,8 @@ msgid "" "collaborate on it. It does not however allow you to share a notebook with " "someone else, unless you have the feature \"%s\"." msgstr "" -"这允许其他用户与你共享笔记本,然后你们可以在笔记本上进行协作。但它不允许您与" -"他人共享笔记本,除非您拥有\"%s\"功能。" +"这允许其他用户向您共享笔记本,然后你们可以在笔记本上进行协作。但它不允许您向" +"他人共享笔记本,除非您拥有“%s”功能。" #: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:150 msgid "This attachment does not have OCR data (Status: %s)" @@ -5542,14 +5542,14 @@ msgstr "该授权令牌仅用于允许第三方应用程序访问 Joplin。" #: packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.tsx:103 msgid "This drawing may have unsaved changes." -msgstr "该本次绘图可能有未保存的更改。" +msgstr "本次绘图可能有未保存的更改。" #: packages/app-desktop/gui/ResourceScreen.tsx:291 msgid "" "This is an advanced tool to show the attachments that are linked to your " "notes. Please be careful when deleting one of them as they cannot be " "restored afterwards." -msgstr "这是用于展示与笔记相关联的附件的高级工具。需要小心,删除后无法恢复。" +msgstr "这是用于展示已链接到笔记的附件的高级工具。需要小心,删除后无法恢复。" #: packages/app-mobile/components/ScreenHeader/index.tsx:237 msgid "This note could not be deleted: %s" @@ -5579,11 +5579,11 @@ msgstr "该笔记已被修改:" msgid "" "This note has no content. Click on \"%s\" to toggle the editor and edit the " "note." -msgstr "该笔记没有任何内容。点击 “%s” 切换到编辑器并编辑笔记。" +msgstr "该笔记没有内容。点击 “%s” 切换到编辑器并编辑笔记。" #: packages/app-desktop/gui/NoteRevisionViewer.tsx:130 msgid "This note has no history" -msgstr "此笔记没有历史记录" +msgstr "该笔记没有历史记录" #: packages/lib/services/plugins/PluginService.ts:527 msgid "This plugin doesn't support %s." @@ -5601,7 +5601,7 @@ msgid "" "enabling it your firewall may ask you to give permission to Joplin to listen " "to a particular port." msgstr "" -"该服务允许浏览器扩展插件与 Joplin 通信。当启用它时,您的防火墙可能会请求允许 " +"该服务允许浏览器扩展插件与 Joplin 通信。当启用它时,您的防火墙可能会询问您来授权 " "Joplin 监听一个特定的端口。" #: packages/lib/components/shared/NoteList/getEmptyFolderMessage.ts:11 @@ -5628,7 +5628,7 @@ msgstr "这将永久删除回收站中的所有条目。是否继续?" #: packages/app-cli/app/command-rmnote.ts:40 msgid "This will permanently delete the note \"%s\". Continue?" -msgstr "这将永久删除笔记 \"%s\"。是否继续?" +msgstr "这将永久删除笔记“%s”。是否继续?" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.ts:17 #: packages/app-mobile/components/screens/ShareManager/AcceptedShareItem.tsx:60 @@ -5665,11 +5665,11 @@ msgstr "要让 Joplin 与 Dropbox 同步,请按下列步骤操作:" msgid "" "To allow Joplin to synchronise with Joplin Cloud, please login using this " "URL:" -msgstr "要让 Joplin 与 Joplin Cloud 同步,请使用以下URL登录:" +msgstr "要让 Joplin 与 Joplin 云同步,请使用以下 URL 登录:" #: packages/lib/components/EncryptionConfigScreen/utils.ts:53 msgid "To continue, please enter your master password below." -msgstr "若要继续,请在下面输入您的主密码(master password)。" +msgstr "若要继续,请在下面输入您的主密码。" #: packages/app-cli/app/app-gui.js:458 msgid "To delete a tag, untag the associated notes." @@ -5691,7 +5691,7 @@ msgstr "按 ESC 键退出命令行模式" msgid "" "To manually sort the notes, the sort order must be changed to \"%s\" in the " "menu \"%s\" > \"%s\"" -msgstr "欲手动排序笔记,应在菜单 “%2$s” > “%3$s” 中将排序方式调整为 “%1$s”" +msgstr "要手动排序笔记,须在菜单 “%2$s” > “%3$s” 中将排序方式调整为 “%1$s”" #: packages/app-cli/app/command-help.ts:82 msgid "To maximise/minimise the console, press \"tc\"." @@ -5704,13 +5704,13 @@ msgstr "按 Tab 键或 Shift+Tab 组合键切换面板。" #: packages/app-cli/app/command-status.js:44 msgid "" "To retry decryption of these items. Run `e2ee decrypt --retry-failed-items`" -msgstr "运行 `e2ee decrypt —retry-failed-items` 来尝试再次解密这些条目" +msgstr "运行 `e2ee decrypt --retry-failed-items` 来尝试再次解密这些条目" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:63 msgid "" "To switch the profile, the app is going to close and you will need to " "restart it." -msgstr "切换配置文件后,应用程序将会自动关闭,您需要重新启动它。" +msgstr "切换配置档后,应用程序将会自动关闭,您需要重新启动它。" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:587 msgid "" @@ -5821,12 +5821,12 @@ msgid "" "Type `help [command]` for more information about a command; or type `help " "all` for the complete usage information." msgstr "" -"输入 `help [command]` 来获取有关该命令的更多信息;或输入 `help all` 获取完整" +"键入 `help [command]` 来获取有关该命令的更多信息;或键入 `help all` 获取完整" "的用法提示。" #: packages/app-cli/app/main.js:105 msgid "Type `joplin help` for usage information." -msgstr "输入 `joplin help` 获取用法信息。" +msgstr "键入 `joplin help` 获取用法信息。" #: packages/app-desktop/plugins/GotoAnything.tsx:684 msgid "" @@ -5834,8 +5834,8 @@ msgid "" "by a tag name, or @ followed by a notebook name. Or type : to search for " "commands." msgstr "" -"输入笔记标题或部分内容以转跳到它。或者输入 # 跟着一个标签名,或者输入 @ 跟着" -"一个笔记本名字,或者输入 : 以搜索命令。" +"键入笔记标题或部分内容以转跳到它。或者键入 # 跟着一个标签名,或者键入 @ 跟着" +"一个笔记本名字,或者键入 : 以搜索命令。" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:235 msgid "Type new tags or select from list" @@ -5851,7 +5851,7 @@ msgstr "无法编辑类型为 %s 的资源" #: packages/app-mobile/components/screens/LogScreen.tsx:108 msgid "Unable to share log data. Reason: %s" -msgstr "发送日志数据失败: %s" +msgstr "无法分享日志数据,原因: %s" #: packages/lib/models/settings/builtInMetadata.ts:616 msgid "Uncompleted to-dos on top" @@ -5869,8 +5869,8 @@ msgid "" "Uninstall and reinstall the application. Make sure you create a backup first " "by exporting all your notes as JEX from the desktop application." msgstr "" -"卸载然后重新安装本程序。 请确保您已事先从桌面应用程序将所有笔记以 JEX 格式导" -"出来创建备份。" +"卸载然后重新安装本程序。请确保您已事先从桌面应用程序将所有笔记以 JEX 格式导" +"出以创建备份。" #: packages/app-mobile/utils/getVersionInfoText.ts:13 #: packages/app-mobile/utils/getVersionInfoText.ts:14 @@ -5883,7 +5883,7 @@ msgstr "未知文件类型" #: packages/lib/utils/processStartFlags.ts:185 msgid "Unknown flag: %s" -msgstr "未知标记:%s" +msgstr "未知标志:%s" #: packages/lib/Synchronizer.ts:1128 msgid "" @@ -5900,7 +5900,7 @@ msgstr "无序列表" #: packages/app-desktop/gui/ShareNoteDialog.tsx:165 msgid "Unpublish note" -msgstr "取消共享笔记" +msgstr "取消发布笔记" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:168 msgid "Unshare" @@ -5910,7 +5910,7 @@ msgstr "取消共享" msgid "" "Unshare this notebook? The recipients will no longer have access to its " "content." -msgstr "取消共享这个笔记本?收件人将无法再访问到它的内容。" +msgstr "取消共享这个笔记本?受邀人将无法再访问到它的内容。" #: packages/app-mobile/components/screens/Note/Note.tsx:811 msgid "Unsupported image type: %s" @@ -5926,7 +5926,7 @@ msgid "" "Unsupported link or message: %s.\n" "Error: %s" msgstr "" -"不支持链接或信息:%s.\n" +"不支持的链接或信息:%s.\n" "错误:%s" #: packages/app-desktop/gui/ResourceScreen.tsx:123 @@ -5951,7 +5951,7 @@ msgstr "稍后更新" #: packages/server/src/routes/admin/users.ts:257 #: packages/server/src/routes/index/users.ts:91 msgid "Update profile" -msgstr "更新配置文件" +msgstr "更新配置档" #: packages/server/src/services/TaskService.ts:23 msgid "Update total sizes" @@ -5979,7 +5979,7 @@ msgstr "已更新远程条目:%d。" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:140 msgid "Updated: " -msgstr "更新: " +msgstr "已更新:" #: packages/app-cli/app/command-import.ts:52 #: packages/app-desktop/gui/ImportScreen.tsx:91 @@ -5993,7 +5993,7 @@ msgstr "已更新:%s" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:207 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:170 msgid "Updating..." -msgstr "正在更新…" +msgstr "正在更新……" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:80 msgid "Upgrade" @@ -6035,11 +6035,11 @@ msgstr "使用拼写检查器" msgid "" "Use the arrows and page up/down to scroll the lists and text areas " "(including this console)." -msgstr "通过方向键与 Page Up / Down 键来滚动列表与文本区域(包含此控制台)。" +msgstr "通过方向键与 PageUp/PageDown 键来滚动列表与文本区域(包含此控制台)。" #: packages/app-desktop/gui/MainScreen.tsx:750 msgid "Use the arrows to move the layout items. Press \"Escape\" to exit." -msgstr "使用箭头移动布局项。按 “Escape” 退出。" +msgstr "使用箭头移动布局项目。按“ESC”退出。" #: packages/lib/models/settings/builtInMetadata.ts:1347 msgid "Use the legacy Markdown editor" @@ -6050,7 +6050,7 @@ msgid "" "Use this to rebuild the search index if there is a problem with search. It " "may take a long time depending on the number of notes." msgstr "" -"如果搜索功能遇到问题,可以使用这个重建索引。花费的时间取决于笔记的数量。" +"如果搜索功能遇到问题,可以使用这个重建索引。所需时间取决于笔记数量。" #: packages/app-mobile/components/biometrics/BiometricPopup.tsx:83 msgid "" @@ -6064,7 +6064,7 @@ msgid "" "Used for most text in the markdown editor. If not found, a generic " "proportional (variable width) font is used." msgstr "" -"用于 markdown 编辑器中的大多数文本。如果没找到,会使用默认(非等宽)字体。" +"用于 Markdown 编辑器中的大多数文本。如果没找到,会使用默认非等宽字体。" #: packages/lib/models/settings/builtInMetadata.ts:1115 msgid "" @@ -6072,8 +6072,8 @@ msgid "" "tables, checkboxes, code). If not found, a generic monospace (fixed width) " "font is used." msgstr "" -"用于那些需要使用固定宽度的字体来清晰地布局文本的场景(例如:表格、多选框和代" -"码块)。如果没有找到,会使用默认(等宽)字体。" +"用于需要固定宽度的字体来清晰地布局文本的场景(例如表格、多选框和代" +"码块)。如果没有找到,会使用默认等宽字体。" #: packages/server/src/services/MustacheService.ts:125 msgid "User deletions" @@ -6130,7 +6130,7 @@ msgstr "语音输入语言文件(URL)" #: packages/app-mobile/components/screens/Note/Note.tsx:1191 #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:225 msgid "Voice typing..." -msgstr "使用语音输入..." +msgstr "使用语音输入……" #: packages/lib/models/settings/builtInMetadata.ts:1712 msgid "Vosk" @@ -6138,7 +6138,7 @@ msgstr "Vosk" #: packages/lib/services/joplinCloudUtils.ts:27 msgid "Waiting for authorisation..." -msgstr "正在等待授权..." +msgstr "正在等待授权……" #: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:224 #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:122 @@ -6147,7 +6147,7 @@ msgstr "警告" #: packages/app-desktop/gui/ResourceScreen.tsx:309 msgid "Warning: not all resources shown for performance reasons (limit: %s)." -msgstr "警告:由于性能原因无法显示所有资源(最多:%s)。" +msgstr "警告:由于性能原因无法显示所有资源(限制:%s)。" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:116 msgid "We have a system for reporting and removing problematic plugins." @@ -6157,7 +6157,7 @@ msgstr "我们有一个报告和移除有问题插件的系统。" msgid "" "We mark plugins developed by trusted Joplin community members as " "\"recommended\"." -msgstr "我们将值得信赖的 Joplin 社区成员开发的插件标记为 \"推荐\"。" +msgstr "我们将值得信赖的 Joplin 社区成员开发的插件标记为“推荐”。" #: packages/lib/models/Setting.ts:1182 #: packages/lib/utils/joplinCloud/index.ts:166 @@ -6204,9 +6204,9 @@ msgid "" msgstr "" "欢迎使用 Joplin!\n" "\n" -"输入 `:help shortcuts` 获取键盘快捷键列表,或输入 `:help` 获取用法信息。\n" +"键入 `:help shortcuts` 获取键盘快捷键列表,或键入 `:help` 获取用法信息。\n" "\n" -"例:输入 `mb` 新建笔记本;输入 `mn`新建笔记。" +"例:按下 `mb` 新建笔记本;按下 `mn` 新建笔记。" #: packages/lib/WelcomeUtils.ts:63 msgid "Welcome!" @@ -6229,7 +6229,7 @@ msgid "" "When enabled, the application will scan your attachments and extract the " "text from it. This will allow you to search for text in these attachments." msgstr "" -"启用后,应用程序将扫描您的附件并从中提取文本。 这将允许您搜索这些附件中的文" +"启用后,应用程序将扫描您的附件并从中提取文本。这将允许您搜索这些附件中的文" "本。" #: packages/lib/models/settings/builtInMetadata.ts:1713 @@ -6269,12 +6269,12 @@ msgid "" "You are about to attach a large image (%dx%d pixels). Would you like to " "resize it down to %d pixels before attaching it?" msgstr "" -"正在尝试将大图片 (%dx%d 像素) 添加到笔记中,是否要需要将其缩放为 %d 像素后再" +"正在尝试将大图片 (%dx%d 像素) 添加到笔记中,是否需要将其缩小为 %d 像素后再" "添加?" #: packages/lib/services/joplinCloudUtils.ts:45 msgid "You are logged in into Joplin Cloud, you can leave this screen now." -msgstr "您已登录到 Joplin Cloud,现在可以关闭此界面。" +msgstr "您已登录到 Joplin 云,现在可以关闭此界面。" #: packages/app-mobile/components/NoteList.tsx:98 msgid "You currently have no notebooks." @@ -6286,21 +6286,21 @@ msgstr "您尚未安装任何插件。" #: packages/app-cli/app/gui/NoteWidget.js:50 msgid "You may also type `status` for more information." -msgstr "输入 `status` 可获取更多信息。" +msgstr "键入 `status` 可获取更多信息。" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:344 msgid "" "You may use the tool below to re-encrypt your data, for example if you know " "that some of your notes are encrypted with an obsolete encryption method." msgstr "" -"您可以使用下面的工具重新加密您的数据。例如当您知道某些笔记使用了过时的加密方" +"您可以使用下面的工具重新加密您的数据,例如当您知道某些笔记使用了过时的加密方" "法时,可使用该项目。" #: packages/lib/services/joplinCloudUtils.ts:53 msgid "" "You were unable to connect to Joplin Cloud. Please check your credentials " "and try again. Error:" -msgstr "无法连接到 Joplin Cloud 。请检查您的凭据,然后重试。错误:" +msgstr "无法连接到 Joplin 云。请检查您的凭据,然后重试。错误:" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:55 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:70 @@ -6309,7 +6309,7 @@ msgstr "您的账户无法使用此功能" #: packages/app-cli/app/cli-utils.js:160 msgid "Your choice: " -msgstr "您的选择: " +msgstr "您的选择:" #: packages/lib/components/EncryptionConfigScreen/utils.ts:70 msgid "Your data is going to be re-encrypted and synced again." @@ -6318,7 +6318,7 @@ msgstr "您的数据将被重新加密并再次同步。" #: packages/app-desktop/gui/MainScreen.tsx:562 #: packages/app-mobile/components/ScreenHeader/WarningBanner.tsx:55 msgid "Your Joplin Cloud credentials are invalid, please login." -msgstr "您的 Joplin Cloud 认证无效,请登录。" +msgstr "您的 Joplin 云认证无效,请登录。" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:259 msgid "Your password is needed to decrypt some of your data." @@ -6328,7 +6328,7 @@ msgstr "要解密您的某些数据,需要使用您的密码。" msgid "" "Your password is needed to decrypt some of your data. Type `:e2ee decrypt` " "to set it." -msgstr "要解密您的某些数据,需要使用您的密码。输入`:e2ee decrypt`来设置。" +msgstr "要解密您的某些数据,需要使用您的密码。键入 `:e2ee decrypt` 来设置。" #: packages/app-desktop/checkForUpdates.ts:108 msgid "Your version: %s" diff --git a/packages/tools/postPreReleasesToForum.json b/packages/tools/postPreReleasesToForum.json index e61c8245f4..f35a75b7ee 100644 --- a/packages/tools/postPreReleasesToForum.json +++ b/packages/tools/postPreReleasesToForum.json @@ -134,6 +134,14 @@ "v3.3.2": true, "android-v3.3.1": true, "ios-v13.3.1": true, - "v3.2.13": true + "v3.2.13": true, + "android-v3.3.2": true, + "v3.3.3": true, + "android-v3.3.3": true, + "ios-v13.3.2": true, + "android-v3.3.4": true, + "v3.3.4": true, + "android-v3.3.5": true, + "ios-v13.3.3": true } } \ No newline at end of file diff --git a/packages/tools/release-ios.ts b/packages/tools/release-ios.ts index 55c9320609..5b0febd4cb 100644 --- a/packages/tools/release-ios.ts +++ b/packages/tools/release-ios.ts @@ -80,6 +80,13 @@ async function main() { await warningMessage(); + // React Native caches a path to Node in there, which appears to point to a copy of the + // executable in a temp folder. If those temp folders are deleted it will still try to use that + // path and fail. Running "Clean build" won't remove `.xcode.env.local` so it's safer to always + // delete it, since if there's an issue the error makes no sense whatsoever, and several hours + // will be lost trying to fix the issue. + await fs.remove(`${mobileDir}/ios/Pods/../.xcode.env.local`); + const pbxprojFilePath = `${mobileDir}/ios/Joplin.xcodeproj/project.pbxproj`; await checkDeploymentTargets(pbxprojFilePath); diff --git a/packages/tools/sponsors.json b/packages/tools/sponsors.json index aaa219b4b8..04eca1798b 100644 --- a/packages/tools/sponsors.json +++ b/packages/tools/sponsors.json @@ -35,6 +35,9 @@ }, { "name": "Akhil-CM" + }, + { + "name": "ugoertz" } ], "orgs": [ @@ -97,22 +100,11 @@ "title": "Casino Reviews", "imageName": "CasinoReviews.png" }, - { - "url": "https://useviral.com.br/", - "title": "Comprar seguidores Instagram", - "imageName": "Useviral.png" - }, - { - "url": "https://ca.edubirdie.com/", - "title": "Achieve academic success with Edubirdie — your trusted partner for expert writing assistance and resources!", - "imageName": "Edubirdie.png", - "alt": "EduBirdie" - }, { "url": "https://topagency.webflow.io", "title": "WebDesignAgency", "imageName": "WebDesignAgency.png", - "alt": "web design agency" + "alt": "topagency" }, { "url": "https://realgambling.ca/", @@ -131,6 +123,19 @@ "title": "casino without making any upfront cost", "imageName": "Slotozilla.png", "alt": "casino without making any upfront cost" + }, + { + "url": "https://www.reddit.com/r/tiktokRise/", + "title": "Tiktok Rise", + "imageName": "TiktokRise.jpg", + "alt": "Tiktok Rise", + "githubUser": "knickman" + }, + { + "url": "https://essaywriter.pro", + "title": "write my essay services by EssayWriter", + "imageName": "EssayWriterPro.png", + "alt": "write my essay services by EssayWriter" } ], "orgsOld": [ @@ -166,6 +171,12 @@ "title": "BYTV", "imageName": "BYTV.png", "githubUser": "bytv1" + }, + { + "url": "https://ca.edubirdie.com/", + "title": "Achieve academic success with Edubirdie — your trusted partner for expert writing assistance and resources!", + "imageName": "Edubirdie.png", + "alt": "EduBirdie" } ] } diff --git a/packages/tools/website/build.ts b/packages/tools/website/build.ts index bb3596f777..55eeae88d5 100644 --- a/packages/tools/website/build.ts +++ b/packages/tools/website/build.ts @@ -36,7 +36,7 @@ const path = require('path'); const md5File = require('md5-file'); const docDir = `${dirname(dirname(dirname(dirname(__dirname))))}/joplin-website/docs`; -if (!pathExistsSync(docDir)) throw new Error(`"docs" directory does not exist: ${docDir}`); +if (!pathExistsSync(docDir)) throw new Error(`"docs" directory does not exist - create it first. At: ${docDir}`); const websiteAssetDir = `${rootDir}/Assets/WebsiteAssets`; const readmeDir = `${rootDir}/readme`; diff --git a/packages/utils/crypto.ts b/packages/utils/crypto.ts new file mode 100644 index 0000000000..6c043926e0 --- /dev/null +++ b/packages/utils/crypto.ts @@ -0,0 +1,9 @@ +/* eslint-disable import/prefer-default-export */ + +import { randomBytes } from 'crypto'; + +export const getSecureRandomString = (length: number): string => { + const bytes = randomBytes(Math.ceil(length * 2)); + const randomString = bytes.toString('base64').replace(/[^a-zA-Z0-9]/g, ''); + return randomString.slice(0, length); +}; diff --git a/packages/utils/execCommand.ts b/packages/utils/execCommand.ts index a46fa8eacb..694f8aa3e9 100644 --- a/packages/utils/execCommand.ts +++ b/packages/utils/execCommand.ts @@ -10,13 +10,19 @@ interface ExecCommandOptions { quiet?: boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied env?: Record; + detached?: boolean; } export default async (command: string | string[], options: ExecCommandOptions | null = null): Promise => { + const detached = options ? options.detached : false; + + // When launching a detached executable it's better not to pipe the stdout and stderr, as this + // will most likely cause an EPIPE error. + options = { - showInput: true, - showStdout: true, - showStderr: true, + showInput: !detached, + showStdout: !detached, + showStderr: !detached, quiet: false, env: {}, ...options, @@ -39,7 +45,7 @@ export default async (command: string | string[], options: ExecCommandOptions | const args: string[] = typeof command === 'string' ? splitCommandString(command) : command as string[]; const executableName = args[0]; args.splice(0, 1); - const promise = execa(executableName, args, { env: options.env }); + const promise = execa(executableName, args, { env: options.env, detached: options.detached }); if (options.showStdout && promise.stdout) promise.stdout.pipe(process.stdout); if (options.showStderr && promise.stderr) promise.stderr.pipe(process.stderr); const result = await promise; diff --git a/packages/utils/fs.test.ts b/packages/utils/fs.test.ts new file mode 100644 index 0000000000..b0e44742b4 --- /dev/null +++ b/packages/utils/fs.test.ts @@ -0,0 +1,48 @@ +/* eslint-disable import/prefer-default-export */ + +import { mkdirp } from 'fs-extra'; +import { FileLocker } from './fs'; +import { msleep, Second } from './time'; + +const baseTempDir = `${__dirname}/../app-cli/tests/tmp`; + +export const createTempDir = async () => { + const p = `${baseTempDir}/${Date.now()}`; + await mkdirp(p); + return p; +}; + +describe('fs', () => { + + it('should lock files', async () => { + const dirPath = await createTempDir(); + const filePath = `${dirPath}/test.lock`; + + const locker1 = new FileLocker(filePath, { + interval: 10 * Second, + }); + + expect(await locker1.lock()).toBe(true); + expect(await locker1.lock()).toBe(false); + + locker1.unlockSync(); + + const locker2 = new FileLocker(filePath, { + interval: 1.5 * Second, + }); + + expect(await locker2.lock()).toBe(true); + locker2.stopMonitoring_(); + + const locker3 = new FileLocker(filePath, { + interval: 1.5 * Second, + }); + + await msleep(2 * Second); + + expect(await locker3.lock()).toBe(true); + + locker3.unlockSync(); + }); + +}); diff --git a/packages/utils/fs.ts b/packages/utils/fs.ts index a707362e80..d1153dea77 100644 --- a/packages/utils/fs.ts +++ b/packages/utils/fs.ts @@ -1,6 +1,7 @@ -/* eslint-disable import/prefer-default-export */ - import { GlobOptionsWithFileTypesFalse, sync } from 'glob'; +import { stat, utimes } from 'fs/promises'; +import { ensureFile, removeSync } from 'fs-extra'; +import { Second } from './time'; // Wraps glob.sync but with good default options so that it works across // platforms and with consistent sorting. @@ -10,3 +11,81 @@ export const globSync = (pattern: string | string[], options: GlobOptionsWithFil output.sort(); return output; }; + +// ------------------------------------------------------------------------------------------------ +// This is a relatively crude system for "locking" files. It does so by regularly updating the +// timestamp of a file. If the file hasn't been updated for more than x seconds, it means the lock +// is stale and the file can be considered unlocked. +// +// This is good enough for our use case, to detect if a profile is already being used by a running +// instance of Joplin. +// ------------------------------------------------------------------------------------------------ + +interface FileLockerOptions { + interval?: number; +} + +export class FileLocker { + + private filePath_ = ''; + private interval_: ReturnType | null = null; + private options_: FileLockerOptions; + + public constructor(filePath: string, options: FileLockerOptions|null = null) { + this.options_ = { + interval: 10 * Second, + ...options, + }; + + this.filePath_ = filePath; + } + + public get options() { + return this.options_; + } + + public async lock() { + if (!(await this.canLock())) return false; + + await this.updateLock(); + + this.interval_ = setInterval(() => { + void this.updateLock(); + }, this.options_.interval); + + return true; + } + + private async canLock() { + try { + const s = await stat(this.filePath_); + return Date.now() - s.mtime.getTime() > (this.options_.interval as number); + } catch (error) { + const e = error as NodeJS.ErrnoException; + if (e.code === 'ENOENT') return true; + e.message = `Could not find out if this file can be locked: ${this.filePath_}`; + return e; + } + } + + // We want the unlock operation to be synchronous because it may be performed when the app + // is closing. + public unlockSync() { + this.stopMonitoring_(); + removeSync(this.filePath_); + } + + private async updateLock() { + await ensureFile(this.filePath_); + const now = new Date(); + await utimes(this.filePath_, now, now); + } + + public stopMonitoring_() { + if (this.interval_) { + clearInterval(this.interval_); + this.interval_ = null; + } + } + +} diff --git a/packages/utils/ipc.test.ts b/packages/utils/ipc.test.ts new file mode 100644 index 0000000000..0fc572f926 --- /dev/null +++ b/packages/utils/ipc.test.ts @@ -0,0 +1,126 @@ +import { readFile } from 'fs/promises'; +import { createTempDir } from './fs.test'; +import { newHttpError, sendMessage, startServer, stopServer } from './ipc'; + +describe('ipc', () => { + + it('should send and receive messages', async () => { + const tempDir = await createTempDir(); + const secretFilePath = `${tempDir}/secret.txt`; + const startPort = 41168; + + const server1 = await startServer(startPort, secretFilePath, async (request) => { + if (request.action === 'testing') { + return { + text: 'hello1', + }; + } + + throw newHttpError(404); + }); + + const server2 = await startServer(startPort, secretFilePath, async (request) => { + if (request.action === 'testing') { + return { + text: 'hello2', + }; + } + + if (request.action === 'ping') { + return { + text: 'pong', + }; + } + + throw newHttpError(404); + }); + + const secretKey = await readFile(secretFilePath, 'utf-8'); + + { + const responses = await sendMessage(startPort, { + action: 'testing', + data: { + test: 1234, + }, + secretKey, + }); + + expect(responses).toEqual([ + { port: 41168, response: { text: 'hello1' } }, + { port: 41169, response: { text: 'hello2' } }, + ]); + } + + { + const responses = await sendMessage(startPort, { + action: 'ping', + data: null, + secretKey, + }); + + expect(responses).toEqual([ + { port: 41169, response: { text: 'pong' } }, + ]); + } + + { + const responses = await sendMessage(startPort, { + action: 'testing', + data: { + test: 1234, + }, + sourcePort: 41168, + secretKey, + }); + + expect(responses).toEqual([ + { port: 41169, response: { text: 'hello2' } }, + ]); + } + + await stopServer(server1); + await stopServer(server2); + }); + + it('should not process message if secret is invalid', async () => { + const tempDir = await createTempDir(); + const secretFilePath = `${tempDir}/secret.txt`; + const startPort = 41168; + + const server = await startServer(startPort, secretFilePath, async (request) => { + if (request.action === 'testing') { + return { + text: 'hello1', + }; + } + + throw newHttpError(404); + }); + + const secretKey = await readFile(secretFilePath, 'utf-8'); + + { + const responses = await sendMessage(startPort, { + action: 'testing', + data: null, + secretKey: 'wrong_key', + }); + + expect(responses.length).toBe(0); + } + + { + const responses = await sendMessage(startPort, { + action: 'testing', + data: null, + secretKey, + }); + + expect(responses.length).toBe(1); + } + + await stopServer(server); + }); + +}); diff --git a/packages/utils/ipc.ts b/packages/utils/ipc.ts new file mode 100644 index 0000000000..6fde385ca5 --- /dev/null +++ b/packages/utils/ipc.ts @@ -0,0 +1,207 @@ +import { createServer, IncomingMessage, ServerResponse } from 'http'; +import fetch from 'node-fetch'; +import { Server } from 'http'; +import Logger from './Logger'; +import { pathExists } from 'fs-extra'; +import { readFile, writeFile } from 'fs/promises'; +import { getSecureRandomString } from './crypto'; + +const tcpPortUsed = require('tcp-port-used'); +const maxPorts = 10; + +const findAvailablePort = async (startPort: number) => { + for (let i = 0; i < 100; i++) { + const port = startPort + i; + const inUse = await tcpPortUsed.check(port); + if (!inUse) return port; + } + + throw new Error(`All potential ports are in use or not available. Starting from port: ${startPort}`); +}; + +const findListenerPorts = async (startPort: number) => { + const output: number[] = []; + for (let i = 0; i < maxPorts; i++) { + const port = startPort + i; + const inUse = await tcpPortUsed.check(port); + if (inUse) output.push(port); + } + + return output; +}; + +const parseJson = (req: IncomingMessage): Promise => { + return new Promise((resolve, reject) => { + let body = ''; + req.on('data', chunk => { + body += chunk; + }); + req.on('end', () => { + try { + resolve(JSON.parse(body)); + } catch (error) { + reject(error); + } + }); + }); +}; + +interface HttpError extends Error { + httpCode: number; +} + +export interface Message { + action: string; + data: object|number|string|null; + sourcePort?: number; + secretKey?: string; +} + +type Response = string|number|object|boolean; + +export const newHttpError = (httpCode: number, message = '') => { + const error = (new Error(message) as HttpError); + error.httpCode = httpCode; + return error; +}; + +export type IpcMessageHandler = (message: Message)=> Promise; + +export interface IpcServer { + port: number; + httpServer: Server; + secretKey: string; +} + +interface StartServerOptions { + logger?: Logger; +} + +const getSecretKey = async (filePath: string) => { + try { + const keyLength = 64; + + const writeKeyToFile = async () => { + const key = getSecureRandomString(keyLength); + await writeFile(filePath, key, 'utf-8'); + return key; + }; + + if (!(await pathExists(filePath))) { + return await writeKeyToFile(); + } + + const key = await readFile(filePath, 'utf-8'); + if (key.length !== keyLength) return await writeKeyToFile(); + + return key; + } catch (error) { + const e = error as NodeJS.ErrnoException; + e.message = `Could not get secret key from file: ${filePath}`; + throw e; + } +}; + +// `secretKeyFilePath` must be the same for all the instances that can communicate with each others +export const startServer = async (startPort: number, secretKeyFilePath: string, messageHandler: IpcMessageHandler, options: StartServerOptions|null = null): Promise => { + const port = await findAvailablePort(startPort); + const logger = options && options.logger ? options.logger : new Logger(); + + const secretKey = await getSecretKey(secretKeyFilePath); + + return new Promise((resolve, reject) => { + try { + const server = createServer(async (req: IncomingMessage, res: ServerResponse) => { + let message: Message|null = null; + try { + message = await parseJson(req) as Message; + if (message.secretKey !== secretKey) throw newHttpError(401, 'Invalid secret key'); + if (!message.action) throw newHttpError(400, 'Missing "action" property in message'); + const response = await messageHandler(message); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(response)); + } catch (error) { + const httpError = error as HttpError; + const httpCode = httpError.httpCode || 500; + logger.error('Could not response to request:', message, 'Error', httpCode, httpError.message); + res.writeHead(httpCode, { 'Content-Type': 'text/plain' }); + res.end(`Error ${httpCode}: ${httpError.message}`); + } + }); + + server.on('error', error => { + if (logger) logger.error('Server error:', error); + }); + + server.listen(port, () => { + resolve({ + httpServer: server, + port, + secretKey, + }); + }); + } catch (error) { + reject(error); + } + }); +}; + +export const stopServer = async (server: IpcServer) => { + return new Promise((resolve, reject) => { + server.httpServer.close((error) => { + if (error) { + reject(error); + } else { + resolve(null); + } + }); + }); +}; + +export interface SendMessageOutput { + port: number; + response: Response; +} + +export interface SendMessageOptions { + logger?: Logger; + sendToSpecificPortOnly?: boolean; +} + +export const sendMessage = async (startPort: number, message: Message, options: SendMessageOptions|null = null) => { + const output: SendMessageOutput[] = []; + const ports = await findListenerPorts(startPort); + const logger = options && options.logger ? options.logger : new Logger(); + const sendToSpecificPortOnly = !!options && !!options.sendToSpecificPortOnly; + + for (const port of ports) { + if (sendToSpecificPortOnly && port !== startPort) continue; + if (message.sourcePort === port) continue; + + try { + const response = await fetch(`http://localhost:${port}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(message), + }); + + if (!response.ok) { + // It means the server doesn't support this particular message - so just skip it + if (response.status === 404) continue; + const text = await response.text(); + throw new Error(`Request failed: on port ${port}: ${text}`); + } + + output.push({ + port, + response: await response.json(), + }); + } catch (error) { + logger.error(`Could not send message on port ${port}:`, error); + } + } + + return output; +}; diff --git a/packages/utils/package.json b/packages/utils/package.json index 09285e1ec8..44d4a9dc1d 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -17,6 +17,7 @@ "./time": "./dist/time.js", "./types": "./dist/types.js", "./url": "./dist/url.js", + "./ipc": "./dist/ipc.js", "./path": "./dist/path.js" }, "publishConfig": { @@ -42,7 +43,8 @@ "markdown-it": "13.0.2", "moment": "2.30.1", "node-fetch": "2.6.7", - "sprintf-js": "1.1.3" + "sprintf-js": "1.1.3", + "tcp-port-used": "1.0.2" }, "devDependencies": { "@types/fs-extra": "11.0.4", diff --git a/readme/about/changelog/android.md b/readme/about/changelog/android.md index 4da108b17b..679c08d3c2 100644 --- a/readme/about/changelog/android.md +++ b/readme/about/changelog/android.md @@ -1,5 +1,46 @@ # Joplin Android Changelog +## [android-v3.3.5](https://github.com/laurent22/joplin/releases/tag/android-v3.3.5) (Pre-release) - 2025-04-07T19:31:26Z + +- New: Add "swap line up" and "swap line down" to toolbar extended options (#12053 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- New: Plugins: Add command to hide the plugin panel viewer (#12018 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Explain why items could not be decrypted (#12048) (#11872 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Implement new note menu redesign (#11780 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Remove slider component module and replace integer settings with new validated component (#11822) (#10883 by [@mrjo118](https://github.com/mrjo118)) +- Improved: Update react-native-quick-crypto (#12067 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Voice typing: Default to a larger model (#12009 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Voice typing: Disable "Download update" button while downloading an updated model (#12015 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Voice typing: Improve transcription at the end of paragraphs (#12013 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Voice typing: Performance: Disable preview generation logic (#12008 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Changing the type of one list changes it for all the lists (#11986) (#11971 by [@Paramesh-T-S](https://github.com/Paramesh-T-S)) +- Fixed: Fix cursor moves to incorrect position when revising TextInput value (#11821) (#11820 by [@mrjo118](https://github.com/mrjo118)) +- Fixed: Restoring a note which was in a deleted notebook (#12016) (#11934) +- Fixed: Voice typing: Fix incorrectly-calculated audio length (#12012 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) + +## [android-v3.3.4](https://github.com/laurent22/joplin/releases/tag/android-v3.3.4) (Pre-release) - 2025-03-21T18:07:00Z + +- Improved: Voice typing: Improve processing with larger models (#11983 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Voice typing: Improve re-download button UI (#11979) (#11955 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) + +## [android-v3.3.3](https://github.com/laurent22/joplin/releases/tag/android-v3.3.3) (Pre-release) - 2025-03-16T10:29:52Z + +- New: Add setting migration for ocr.enabled (ab86b95) +- Improved: Accessibility: Improve focus handling in the note actions menu and modal dialogs (#11929 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Accessibility: Make default modal close button accessible (#11957 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Voice typing: Transcribe more unprocessed audio after pressing "done" (#11960) (#11956 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Accessibility: Fix missing label on note actions menu dismiss button (#11954 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Make tab size consistent between Markdown editor and viewer (and RTE) (#11940) (#11673) +- Fixed: Voice typing: Fix potential output duplication when finalizing voice typing (#11953 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) + +## [android-v3.3.2](https://github.com/laurent22/joplin/releases/tag/android-v3.3.2) (Pre-release) - 2025-03-03T22:35:08Z + +- Improved: Improve encryption config screen accessibility (#11874) (#11846 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Switch default library used for Whisper voice typing (#11881 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Updated packages @bam.tech/react-native-image-resizer (v3.0.11) +- Fixed: Accessibility: Fix "new note" and "new to-do" buttons are focusable even while invisible (#11899 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Fix disabled encryption keys list showing enabled keys (#11861) (#11858 by [@pedr](https://github.com/pedr)) +- Fixed: Fix voice recorder crash (#11876) (#11864 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) + ## [android-v3.3.1](https://github.com/laurent22/joplin/releases/tag/android-v3.3.1) (Pre-release) - 2025-02-19T16:01:54Z - New: Add support for plugin editor views (#11831) diff --git a/readme/about/changelog/desktop.md b/readme/about/changelog/desktop.md index c4be1f38c2..4965bccfd7 100644 --- a/readme/about/changelog/desktop.md +++ b/readme/about/changelog/desktop.md @@ -1,5 +1,50 @@ # Joplin Desktop Changelog +## [v3.3.4](https://github.com/laurent22/joplin/releases/tag/v3.3.4) (Pre-release) - 2025-04-07T20:23:35Z + +- New: Plugins: Add `setting.globalValues` and deprecate `setting.globalValue` ([ef51386](https://github.com/laurent22/joplin/commit/ef51386)) +- New: Rich Text Editor: Add setting to allow disabling auto-format ([#12022](https://github.com/laurent22/joplin/issues/12022) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Accessibility: Add screen reader announcements when toggling the note list and/or sidebar ([#11776](https://github.com/laurent22/joplin/issues/11776)) ([#11741](https://github.com/laurent22/joplin/issues/11741) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Accessibility: Remove redundant accessibility information from sidebar notebooks ([#12020](https://github.com/laurent22/joplin/issues/12020)) ([#11903](https://github.com/laurent22/joplin/issues/11903) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Explain why items could not be decrypted ([#12048](https://github.com/laurent22/joplin/issues/12048)) ([#11872](https://github.com/laurent22/joplin/issues/11872) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Improve notification accessibility ([#11752](https://github.com/laurent22/joplin/issues/11752) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Increase the likelihood of text generation from image recognition ([#12028](https://github.com/laurent22/joplin/issues/12028)) ([#11608](https://github.com/laurent22/joplin/issues/11608) by [@pedr](https://github.com/pedr)) +- Improved: Multiple instances: Secure local server ([#11999](https://github.com/laurent22/joplin/issues/11999)) ([#11992](https://github.com/laurent22/joplin/issues/11992)) +- Improved: Update Electron to v35.1.4 ([#12068](https://github.com/laurent22/joplin/issues/12068) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: A note scrolls to top if reached by following a link to a section ([#12038](https://github.com/laurent22/joplin/issues/12038)) ([#9291](https://github.com/laurent22/joplin/issues/9291) by [@Schmeilen](https://github.com/Schmeilen)) +- Fixed: App without a profile directory cannot start ([#12021](https://github.com/laurent22/joplin/issues/12021)) +- Fixed: Changing the type of one list changes it for all the lists ([#11986](https://github.com/laurent22/joplin/issues/11986)) ([#11971](https://github.com/laurent22/joplin/issues/11971) by [@Paramesh-T-S](https://github.com/Paramesh-T-S)) +- Fixed: Joplin became unusably slow on MacOS due to incorrect detection of architecture ([#11989](https://github.com/laurent22/joplin/issues/11989)) +- Fixed: Regression: Restarting app is broken ([#11975](https://github.com/laurent22/joplin/issues/11975)) +- Fixed: Restoring a note which was in a deleted notebook ([#12016](https://github.com/laurent22/joplin/issues/12016)) ([#11934](https://github.com/laurent22/joplin/issues/11934)) +- Fixed: Rich Text Editor: Fix "Remove color" button doesn't work ([#12052](https://github.com/laurent22/joplin/issues/12052) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) + +## [v3.3.3](https://github.com/laurent22/joplin/releases/tag/v3.3.3) (Pre-release) - 2025-03-16T11:52:33Z + +- New: Accessibility: Add a menu item that moves focus to the note viewer ([#11967](https://github.com/laurent22/joplin/issues/11967) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- New: Accessibility: Add error indication on Note properties ([#11784](https://github.com/laurent22/joplin/issues/11784) by [@pedr](https://github.com/pedr)) +- New: Accessibility: Add more standard keyboard shortcuts for the notebook sidebar ([#11892](https://github.com/laurent22/joplin/issues/11892) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- New: Add a button to collapse or expand all folders ([#11905](https://github.com/laurent22/joplin/issues/11905)) +- New: Add dialog to select a note and link to it ([#11891](https://github.com/laurent22/joplin/issues/11891)) +- New: Add setting migration for ocr.enabled ([ab86b95](https://github.com/laurent22/joplin/commit/ab86b95)) +- New: Add support for multiple instances ([#11963](https://github.com/laurent22/joplin/issues/11963)) +- New: Added keyboard shortcut and menu item for toggleEditorPlugin command ([7e8dee4](https://github.com/laurent22/joplin/commit/7e8dee4)) +- New: Plugins: Add support for `joplin.shouldUseDarkColors` API ([fe67a44](https://github.com/laurent22/joplin/commit/fe67a44)) +- Improved: Accessibility: Improve "toggle all notebooks" accessibility ([#11918](https://github.com/laurent22/joplin/issues/11918) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Add "Disable synchronisation" to Joplin Cloud prompt message ([#11705](https://github.com/laurent22/joplin/issues/11705)) ([#11696](https://github.com/laurent22/joplin/issues/11696) by [@Vortrix5](https://github.com/Vortrix5)) +- Improved: Improve Rich Text Editor toolbar structure ([#11869](https://github.com/laurent22/joplin/issues/11869)) ([#11663](https://github.com/laurent22/joplin/issues/11663) by [@j-scheitler1](https://github.com/j-scheitler1)) +- Improved: Improve download in install script ([#11921](https://github.com/laurent22/joplin/issues/11921) by Helmut K. C. Tessarek) +- Improved: Make "toggle all folders" button also expand the folder list ([#11917](https://github.com/laurent22/joplin/issues/11917) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Plugins: Mark the LanguageTool Integration plugin as incompatible ([#11715](https://github.com/laurent22/joplin/issues/11715)) ([#11710](https://github.com/laurent22/joplin/issues/11710) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Upgrade to Electron 35.0.1 ([#11968](https://github.com/laurent22/joplin/issues/11968) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Fix adding tags to a note through drag-and-drop ([#11911](https://github.com/laurent22/joplin/issues/11911) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Fix ctrl-p doesn't open the goto anything dialog in the Rich Text Editor ([#11926](https://github.com/laurent22/joplin/issues/11926)) ([#11894](https://github.com/laurent22/joplin/issues/11894) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Fix issue with GotoAnything that would prevent it from highlighting search results in note titles ([#11888](https://github.com/laurent22/joplin/issues/11888)) +- Fixed: Import audio from OneNote as file links ([#11942](https://github.com/laurent22/joplin/issues/11942)) ([#11939](https://github.com/laurent22/joplin/issues/11939) by [@pedr](https://github.com/pedr)) +- Fixed: Make tab size consistent between Markdown editor and viewer (and RTE) ([#11940](https://github.com/laurent22/joplin/issues/11940)) ([#11673](https://github.com/laurent22/joplin/issues/11673)) +- Fixed: Preserve attachment file extensions regardless of the mime type ([#11852](https://github.com/laurent22/joplin/issues/11852)) ([#11759](https://github.com/laurent22/joplin/issues/11759) by [@pedr](https://github.com/pedr)) +- Fixed: Sharing a notebook with nobody prints "No user with ID public_key" ([#11932](https://github.com/laurent22/joplin/issues/11932)) ([#11923](https://github.com/laurent22/joplin/issues/11923) by [@Paramesh-T-S](https://github.com/Paramesh-T-S)) + ## [v3.2.13](https://github.com/laurent22/joplin/releases/tag/v3.2.13) - 2025-02-28T14:38:21Z - Improved: Plugins: Mark the LanguageTool Integration plugin as incompatible ([#11715](https://github.com/laurent22/joplin/issues/11715)) ([#11710](https://github.com/laurent22/joplin/issues/11710) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) diff --git a/readme/about/changelog/ios.md b/readme/about/changelog/ios.md index fd222b784d..f21c7d3735 100644 --- a/readme/about/changelog/ios.md +++ b/readme/about/changelog/ios.md @@ -1,5 +1,34 @@ # Joplin iOS Changelog +## [ios-v13.3.3](https://github.com/laurent22/joplin/releases/tag/ios-v13.3.3) - 2025-04-07T19:20:10Z + +- New: Add "swap line up" and "swap line down" to toolbar extended options (#12053 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- New: Plugins: Add command to hide the plugin panel viewer (#12018 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Explain why items could not be decrypted (#12048) (#11872 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Implement new note menu redesign (#11780 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Remove slider component module and replace integer settings with new validated component (#11822) (#10883 by [@mrjo118](https://github.com/mrjo118)) +- Improved: Update react-native-quick-crypto (#12067 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Changing the type of one list changes it for all the lists (#11986) (#11971 by [@Paramesh-T-S](https://github.com/Paramesh-T-S)) +- Fixed: Fix Markdown toolbar partially covered by keyboard on some iOS devices (#12027) (#11711 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Fix cursor moves to incorrect position when revising TextInput value (#11821) (#11820 by [@mrjo118](https://github.com/mrjo118)) +- Fixed: Restoring a note which was in a deleted notebook (#12016) (#11934) + +## [ios-v13.3.2](https://github.com/laurent22/joplin/releases/tag/ios-v13.3.2) - 2025-03-16T11:47:05Z + +- New: Add setting migration for ocr.enabled (ab86b95) +- Improved: Accessibility: Improve focus handling in the note actions menu and modal dialogs (#11929 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Accessibility: Make default modal close button accessible (#11957 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Improve encryption config screen accessibility (#11874) (#11846 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Re-Add iOS Dark Icon (#11943 by [@itzTheMeow](https://github.com/itzTheMeow)) +- Improved: Updated packages @bam.tech/react-native-image-resizer (v3.0.11) +- Fixed: Accessibility: Fix "new note" and "new to-do" buttons are focusable even while invisible (#11899 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Accessibility: Fix focus gets stuck on "Attach" in the note actions menu (#11958 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Accessibility: Fix missing label on note actions menu dismiss button (#11954 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Accessibility: Fix plugins can't be installed using VoiceOver (#11931 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Fix disabled encryption keys list showing enabled keys (#11861) (#11858 by [@pedr](https://github.com/pedr)) +- Fixed: Fix voice recorder crash (#11876) (#11864 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Make tab size consistent between Markdown editor and viewer (and RTE) (#11940) (#11673) + ## [ios-v13.3.1](https://github.com/laurent22/joplin/releases/tag/ios-v13.3.1) - 2025-02-19T16:04:34Z - New: Add support for plugin editor views (#11831) diff --git a/readme/about/changelog/server.md b/readme/about/changelog/server.md index c9aeef2240..73c3a9d4e6 100644 --- a/readme/about/changelog/server.md +++ b/readme/about/changelog/server.md @@ -1,5 +1,16 @@ # Joplin Server Changelog +## [server-v3.3.13](https://github.com/laurent22/joplin/releases/tag/server-v3.3.13) - 2025-03-30T18:28:47Z + +- Improved: Buildx support for Docker images (#11582 by [@redrathnure](https://github.com/redrathnure)) +- New: Added links to social networks (04fc634) +- Improved: Joplin Server Docker image for ARM64 (#12030) +- Fixed: Disable faulty dark theme to prevent published notes from being unreadable (#11910) + +## [server-v3.3.4](https://github.com/laurent22/joplin/releases/tag/server-v3.3.4) - 2025-03-03T22:29:29Z + +- Security: Improve request validation in default route (#11916 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) + ## [server-v3.3.3](https://github.com/laurent22/joplin/releases/tag/server-v3.3.3) - 2025-02-23T19:06:59Z - Security: Fixed patching user properties (12baa98) diff --git a/readme/about/stats.md b/readme/about/stats.md index 701e534631..fa5068d10a 100644 --- a/readme/about/stats.md +++ b/readme/about/stats.md @@ -1,15 +1,15 @@ --- -updated: 2025-02-01T00:53:21Z +updated: 2025-04-01T02:18:08Z --- # Joplin statistics | Name | Value | | ----- | ----- | -| Total Windows downloads | 6,004,354 | -| Total macOs downloads | 1,908,812 | -| Total Linux downloads | 1,422,483 | -| Windows % | 64% | +| Total Windows downloads | 6,268,462 | +| Total macOs downloads | 1,946,509 | +| Total Linux downloads | 1,468,822 | +| Windows % | 65% | | macOS % | 20% | | Linux % | 15% | @@ -17,213 +17,217 @@ updated: 2025-02-01T00:53:21Z | Version | Date | Windows | macOS | Linux | Total | | ----- | ----- | ----- | ----- | ----- | ----- | -| [v3.2.12](https://github.com/laurent22/joplin/releases/tag/v3.2.12) | 2025-01-23T23:52:04Z | 56,558 | 12,124 | 7,940 | 76,622 | -| [v3.2.11](https://github.com/laurent22/joplin/releases/tag/v3.2.11) | 2025-01-13T17:48:21Z | 64,735 | 14,677 | 6,791 | 86,203 | -| [v3.2.10](https://github.com/laurent22/joplin/releases/tag/v3.2.10) (p) | 2025-01-10T10:17:28Z | 974 | 154 | 178 | 1,306 | -| [v3.2.9](https://github.com/laurent22/joplin/releases/tag/v3.2.9) (p) | 2025-01-09T22:58:42Z | 340 | 77 | 44 | 461 | -| [v3.2.7](https://github.com/laurent22/joplin/releases/tag/v3.2.7) (p) | 2025-01-06T16:35:41Z | 844 | 141 | 590 | 1,575 | -| [v3.2.6](https://github.com/laurent22/joplin/releases/tag/v3.2.6) (p) | 2024-12-23T21:54:40Z | 1,629 | 327 | 468 | 2,424 | -| [v3.2.5](https://github.com/laurent22/joplin/releases/tag/v3.2.5) (p) | 2024-12-18T10:41:13Z | 982 | 191 | 216 | 1,389 | -| [v3.2.4](https://github.com/laurent22/joplin/releases/tag/v3.2.4) (p) | 2024-12-12T17:59:52Z | 998 | 144 | 218 | 1,360 | -| [v3.2.3](https://github.com/laurent22/joplin/releases/tag/v3.2.3) (p) | 2024-11-18T00:09:05Z | 2,683 | 547 | 865 | 4,095 | -| [v3.2.1](https://github.com/laurent22/joplin/releases/tag/v3.2.1) (p) | 2024-11-10T16:16:27Z | 1,218 | 224 | 343 | 1,785 | -| [v3.1.24](https://github.com/laurent22/joplin/releases/tag/v3.1.24) | 2024-11-09T15:08:29Z | 205,998 | 33,385 | 43,644 | 283,027 | -| [v3.1.23](https://github.com/laurent22/joplin/releases/tag/v3.1.23) | 2024-11-07T10:56:45Z | 26,547 | 6,728 | 1,515 | 34,790 | -| [v3.1.22](https://github.com/laurent22/joplin/releases/tag/v3.1.22) | 2024-11-05T08:59:32Z | 28,381 | 8,691 | 1,221 | 38,293 | -| [v3.1.20](https://github.com/laurent22/joplin/releases/tag/v3.1.20) | 2024-10-22T12:21:32Z | 93,704 | 19,089 | 13,887 | 126,680 | -| [v3.1.18](https://github.com/laurent22/joplin/releases/tag/v3.1.18) (p) | 2024-10-11T23:27:10Z | 1,502 | 278 | 585 | 2,365 | -| [v3.1.17](https://github.com/laurent22/joplin/releases/tag/v3.1.17) (p) | 2024-09-26T11:57:54Z | 1,695 | 348 | 528 | 2,571 | -| [v3.1.15](https://github.com/laurent22/joplin/releases/tag/v3.1.15) (p) | 2024-09-17T09:15:10Z | 1,175 | 221 | 490 | 1,886 | +| [v3.3.3](https://github.com/laurent22/joplin/releases/tag/v3.3.3) (p) | 2025-03-16T11:52:33Z | 1,991 | 568 | 576 | 3,135 | +| [v3.2.13](https://github.com/laurent22/joplin/releases/tag/v3.2.13) | 2025-02-28T14:38:21Z | 142,008 | 22,730 | 24,132 | 188,870 | +| [v3.3.2](https://github.com/laurent22/joplin/releases/tag/v3.3.2) (p) | 2025-02-19T17:34:26Z | 2,320 | 550 | 618 | 3,488 | +| [v3.3.1](https://github.com/laurent22/joplin/releases/tag/v3.3.1) (p) | 2025-02-16T17:06:26Z | 821 | 157 | 157 | 1,135 | +| [v3.2.12](https://github.com/laurent22/joplin/releases/tag/v3.2.12) | 2025-01-23T23:52:04Z | 151,244 | 25,115 | 27,847 | 204,206 | +| [v3.2.11](https://github.com/laurent22/joplin/releases/tag/v3.2.11) | 2025-01-13T17:48:21Z | 65,392 | 14,799 | 6,846 | 87,037 | +| [v3.2.10](https://github.com/laurent22/joplin/releases/tag/v3.2.10) (p) | 2025-01-10T10:17:28Z | 1,091 | 160 | 184 | 1,435 | +| [v3.2.9](https://github.com/laurent22/joplin/releases/tag/v3.2.9) (p) | 2025-01-09T22:58:42Z | 353 | 83 | 50 | 486 | +| [v3.2.7](https://github.com/laurent22/joplin/releases/tag/v3.2.7) (p) | 2025-01-06T16:35:41Z | 856 | 148 | 736 | 1,740 | +| [v3.2.6](https://github.com/laurent22/joplin/releases/tag/v3.2.6) (p) | 2024-12-23T21:54:40Z | 1,716 | 329 | 471 | 2,516 | +| [v3.2.5](https://github.com/laurent22/joplin/releases/tag/v3.2.5) (p) | 2024-12-18T10:41:13Z | 985 | 193 | 217 | 1,395 | +| [v3.2.4](https://github.com/laurent22/joplin/releases/tag/v3.2.4) (p) | 2024-12-12T17:59:52Z | 1,000 | 145 | 227 | 1,372 | +| [v3.2.3](https://github.com/laurent22/joplin/releases/tag/v3.2.3) (p) | 2024-11-18T00:09:05Z | 2,688 | 547 | 874 | 4,109 | +| [v3.2.1](https://github.com/laurent22/joplin/releases/tag/v3.2.1) (p) | 2024-11-10T16:16:27Z | 1,229 | 224 | 343 | 1,796 | +| [v3.1.24](https://github.com/laurent22/joplin/releases/tag/v3.1.24) | 2024-11-09T15:08:29Z | 207,460 | 33,448 | 43,757 | 284,665 | +| [v3.1.23](https://github.com/laurent22/joplin/releases/tag/v3.1.23) | 2024-11-07T10:56:45Z | 26,893 | 6,728 | 1,517 | 35,138 | +| [v3.1.22](https://github.com/laurent22/joplin/releases/tag/v3.1.22) | 2024-11-05T08:59:32Z | 28,682 | 8,694 | 1,222 | 38,598 | +| [v3.1.20](https://github.com/laurent22/joplin/releases/tag/v3.1.20) | 2024-10-22T12:21:32Z | 94,267 | 19,143 | 13,901 | 127,311 | +| [v3.1.18](https://github.com/laurent22/joplin/releases/tag/v3.1.18) (p) | 2024-10-11T23:27:10Z | 1,502 | 278 | 587 | 2,367 | +| [v3.1.17](https://github.com/laurent22/joplin/releases/tag/v3.1.17) (p) | 2024-09-26T11:57:54Z | 1,696 | 348 | 529 | 2,573 | +| [v3.1.15](https://github.com/laurent22/joplin/releases/tag/v3.1.15) (p) | 2024-09-17T09:15:10Z | 1,177 | 222 | 492 | 1,891 | | [v3.1.8](https://github.com/laurent22/joplin/releases/tag/v3.1.8) (p) | 2024-09-08T20:32:44Z | 1,216 | 252 | 332 | 1,800 | -| [v3.1.6](https://github.com/laurent22/joplin/releases/tag/v3.1.6) (p) | 2024-09-02T13:19:40Z | 958 | 225 | 423 | 1,606 | +| [v3.1.6](https://github.com/laurent22/joplin/releases/tag/v3.1.6) (p) | 2024-09-02T13:19:40Z | 959 | 225 | 424 | 1,608 | | [v3.1.4](https://github.com/laurent22/joplin/releases/tag/v3.1.4) (p) | 2024-08-27T17:46:38Z | 940 | 173 | 245 | 1,358 | -| [v3.0.15](https://github.com/laurent22/joplin/releases/tag/v3.0.15) | 2024-08-21T09:19:58Z | 202,289 | 37,783 | 44,948 | 285,020 | -| [v3.1.3](https://github.com/laurent22/joplin/releases/tag/v3.1.3) (p) | 2024-08-17T13:08:21Z | 1,222 | 269 | 475 | 1,966 | -| [v3.1.2](https://github.com/laurent22/joplin/releases/tag/v3.1.2) (p) | 2024-08-16T09:00:59Z | 432 | 98 | 78 | 608 | -| [v3.1.1](https://github.com/laurent22/joplin/releases/tag/v3.1.1) (p) | 2024-08-10T11:36:02Z | 1,073 | 197 | 259 | 1,529 | -| [v2.14.23](https://github.com/laurent22/joplin/releases/tag/v2.14.23) | 2024-08-07T11:15:25Z | 10,632 | 2,582 | 600 | 13,814 | -| [v3.0.14](https://github.com/laurent22/joplin/releases/tag/v3.0.14) | 2024-07-28T13:55:50Z | 88,473 | 18,560 | 18,794 | 125,827 | -| [v3.0.12](https://github.com/laurent22/joplin/releases/tag/v3.0.12) | 2024-07-02T17:11:14Z | 43,044 | 12,798 | 7,228 | 63,070 | -| [v3.0.11](https://github.com/laurent22/joplin/releases/tag/v3.0.11) (p) | 2024-06-29T10:20:02Z | 837 | 161 | 264 | 1,262 | +| [v3.0.15](https://github.com/laurent22/joplin/releases/tag/v3.0.15) | 2024-08-21T09:19:58Z | 202,698 | 37,792 | 45,095 | 285,585 | +| [v3.1.3](https://github.com/laurent22/joplin/releases/tag/v3.1.3) (p) | 2024-08-17T13:08:21Z | 1,222 | 269 | 476 | 1,967 | +| [v3.1.2](https://github.com/laurent22/joplin/releases/tag/v3.1.2) (p) | 2024-08-16T09:00:59Z | 432 | 98 | 79 | 609 | +| [v3.1.1](https://github.com/laurent22/joplin/releases/tag/v3.1.1) (p) | 2024-08-10T11:36:02Z | 1,075 | 198 | 259 | 1,532 | +| [v2.14.23](https://github.com/laurent22/joplin/releases/tag/v2.14.23) | 2024-08-07T11:15:25Z | 10,692 | 2,606 | 611 | 13,909 | +| [v3.0.14](https://github.com/laurent22/joplin/releases/tag/v3.0.14) | 2024-07-28T13:55:50Z | 88,869 | 18,565 | 18,805 | 126,239 | +| [v3.0.12](https://github.com/laurent22/joplin/releases/tag/v3.0.12) | 2024-07-02T17:11:14Z | 43,662 | 12,823 | 7,316 | 63,801 | +| [v3.0.11](https://github.com/laurent22/joplin/releases/tag/v3.0.11) (p) | 2024-06-29T10:20:02Z | 839 | 162 | 264 | 1,265 | | [v3.0.10](https://github.com/laurent22/joplin/releases/tag/v3.0.10) (p) | 2024-06-19T15:24:07Z | 1,613 | 287 | 558 | 2,458 | | [v3.0.9](https://github.com/laurent22/joplin/releases/tag/v3.0.9) (p) | 2024-06-12T19:07:50Z | 1,258 | 253 | 377 | 1,888 | -| [v3.0.8](https://github.com/laurent22/joplin/releases/tag/v3.0.8) (p) | 2024-05-22T14:20:45Z | 2,654 | 0 | 909 | 3,563 | -| [v2.14.22](https://github.com/laurent22/joplin/releases/tag/v2.14.22) | 2024-05-22T19:19:02Z | 142,144 | 30,659 | 25,446 | 198,249 | -| [v3.0.6](https://github.com/laurent22/joplin/releases/tag/v3.0.6) (p) | 2024-04-27T13:16:04Z | 2,998 | 680 | 860 | 4,538 | -| [v3.0.3](https://github.com/laurent22/joplin/releases/tag/v3.0.3) (p) | 2024-04-18T15:41:38Z | 1,475 | 306 | 328 | 2,109 | -| [v3.0.2](https://github.com/laurent22/joplin/releases/tag/v3.0.2) (p) | 2024-03-21T18:18:49Z | 2,852 | 701 | 1,096 | 4,649 | -| [v2.14.20](https://github.com/laurent22/joplin/releases/tag/v2.14.20) | 2024-03-18T17:05:17Z | 191,753 | 39,474 | 38,177 | 269,404 | -| [v2.14.19](https://github.com/laurent22/joplin/releases/tag/v2.14.19) | 2024-03-08T10:45:16Z | 64,295 | 18,466 | 8,542 | 91,303 | -| [v2.14.17](https://github.com/laurent22/joplin/releases/tag/v2.14.17) | 2024-03-01T18:10:26Z | 63,030 | 18,627 | 7,611 | 89,268 | -| [v2.14.16](https://github.com/laurent22/joplin/releases/tag/v2.14.16) (p) | 2024-02-22T22:49:10Z | 1,343 | 282 | 377 | 2,002 | -| [v2.14.15](https://github.com/laurent22/joplin/releases/tag/v2.14.15) (p) | 2024-02-19T11:24:57Z | 850 | 178 | 189 | 1,217 | +| [v3.0.8](https://github.com/laurent22/joplin/releases/tag/v3.0.8) (p) | 2024-05-22T14:20:45Z | 2,657 | 0 | 910 | 3,567 | +| [v2.14.22](https://github.com/laurent22/joplin/releases/tag/v2.14.22) | 2024-05-22T19:19:02Z | 142,488 | 30,703 | 25,451 | 198,642 | +| [v3.0.6](https://github.com/laurent22/joplin/releases/tag/v3.0.6) (p) | 2024-04-27T13:16:04Z | 3,001 | 686 | 860 | 4,547 | +| [v3.0.3](https://github.com/laurent22/joplin/releases/tag/v3.0.3) (p) | 2024-04-18T15:41:38Z | 1,475 | 311 | 328 | 2,114 | +| [v3.0.2](https://github.com/laurent22/joplin/releases/tag/v3.0.2) (p) | 2024-03-21T18:18:49Z | 2,858 | 714 | 1,096 | 4,668 | +| [v2.14.20](https://github.com/laurent22/joplin/releases/tag/v2.14.20) | 2024-03-18T17:05:17Z | 192,310 | 39,500 | 38,191 | 270,001 | +| [v2.14.19](https://github.com/laurent22/joplin/releases/tag/v2.14.19) | 2024-03-08T10:45:16Z | 64,548 | 18,468 | 8,545 | 91,561 | +| [v2.14.17](https://github.com/laurent22/joplin/releases/tag/v2.14.17) | 2024-03-01T18:10:26Z | 63,277 | 18,631 | 7,611 | 89,519 | +| [v2.14.16](https://github.com/laurent22/joplin/releases/tag/v2.14.16) (p) | 2024-02-22T22:49:10Z | 1,346 | 283 | 377 | 2,006 | +| [v2.14.15](https://github.com/laurent22/joplin/releases/tag/v2.14.15) (p) | 2024-02-19T11:24:57Z | 851 | 178 | 189 | 1,218 | | [v2.14.14](https://github.com/laurent22/joplin/releases/tag/v2.14.14) (p) | 2024-02-10T16:03:08Z | 1,269 | 242 | 378 | 1,889 | | [v2.14.13](https://github.com/laurent22/joplin/releases/tag/v2.14.13) (p) | 2024-02-09T16:31:54Z | 409 | 80 | 79 | 568 | -| [v2.14.12](https://github.com/laurent22/joplin/releases/tag/v2.14.12) (p) | 2024-02-03T12:11:47Z | 976 | 216 | 244 | 1,436 | -| [v2.14.11](https://github.com/laurent22/joplin/releases/tag/v2.14.11) (p) | 2024-01-26T11:53:05Z | 1,262 | 258 | 456 | 1,976 | -| [v2.14.10](https://github.com/laurent22/joplin/releases/tag/v2.14.10) (p) | 2024-01-18T22:45:04Z | 2,084 | 267 | 370 | 2,721 | -| [v2.13.15](https://github.com/laurent22/joplin/releases/tag/v2.13.15) | 2024-01-15T13:01:19Z | 149,121 | 34,128 | 29,245 | 212,494 | -| [v2.13.14](https://github.com/laurent22/joplin/releases/tag/v2.13.14) | 2024-01-13T19:11:04Z | 19,430 | 7,823 | 2,359 | 29,612 | -| [v2.14.9](https://github.com/laurent22/joplin/releases/tag/v2.14.9) (p) | 2024-01-11T22:17:59Z | 1,055 | 0 | 238 | 1,293 | +| [v2.14.12](https://github.com/laurent22/joplin/releases/tag/v2.14.12) (p) | 2024-02-03T12:11:47Z | 977 | 217 | 244 | 1,438 | +| [v2.14.11](https://github.com/laurent22/joplin/releases/tag/v2.14.11) (p) | 2024-01-26T11:53:05Z | 1,263 | 258 | 457 | 1,978 | +| [v2.14.10](https://github.com/laurent22/joplin/releases/tag/v2.14.10) (p) | 2024-01-18T22:45:04Z | 2,085 | 267 | 370 | 2,722 | +| [v2.13.15](https://github.com/laurent22/joplin/releases/tag/v2.13.15) | 2024-01-15T13:01:19Z | 149,484 | 34,144 | 29,255 | 212,883 | +| [v2.13.14](https://github.com/laurent22/joplin/releases/tag/v2.13.14) | 2024-01-13T19:11:04Z | 19,657 | 7,823 | 2,359 | 29,839 | +| [v2.14.9](https://github.com/laurent22/joplin/releases/tag/v2.14.9) (p) | 2024-01-11T22:17:59Z | 1,055 | 0 | 239 | 1,294 | | [v2.14.8](https://github.com/laurent22/joplin/releases/tag/v2.14.8) (p) | 2024-01-09T22:57:07Z | 752 | 235 | 173 | 1,160 | | [v2.14.7](https://github.com/laurent22/joplin/releases/tag/v2.14.7) (p) | 2024-01-08T11:51:49Z | 620 | 127 | 173 | 920 | -| [v2.14.6](https://github.com/laurent22/joplin/releases/tag/v2.14.6) (p) | 2024-01-06T16:38:32Z | 725 | 149 | 149 | 1,023 | -| [v2.13.13](https://github.com/laurent22/joplin/releases/tag/v2.13.13) | 2024-01-06T13:33:11Z | 53,345 | 15,929 | 6,345 | 75,619 | -| [v2.13.12](https://github.com/laurent22/joplin/releases/tag/v2.13.12) | 2023-12-31T16:08:02Z | 44,729 | 14,282 | 5,083 | 64,094 | -| [v2.13.11](https://github.com/laurent22/joplin/releases/tag/v2.13.11) | 2023-12-24T12:58:53Z | 45,663 | 13,260 | 5,970 | 64,893 | -| [v2.13.10](https://github.com/laurent22/joplin/releases/tag/v2.13.10) | 2023-12-22T10:11:08Z | 19,535 | 8,688 | 1,452 | 29,675 | -| [v2.13.9](https://github.com/laurent22/joplin/releases/tag/v2.13.9) | 2023-12-09T17:18:58Z | 67,456 | 21,923 | 8,601 | 97,980 | -| [v2.13.8](https://github.com/laurent22/joplin/releases/tag/v2.13.8) | 2023-12-03T12:07:08Z | 50,231 | 17,900 | 5,211 | 73,342 | +| [v2.14.6](https://github.com/laurent22/joplin/releases/tag/v2.14.6) (p) | 2024-01-06T16:38:32Z | 726 | 149 | 149 | 1,024 | +| [v2.13.13](https://github.com/laurent22/joplin/releases/tag/v2.13.13) | 2024-01-06T13:33:11Z | 53,674 | 15,929 | 6,345 | 75,948 | +| [v2.13.12](https://github.com/laurent22/joplin/releases/tag/v2.13.12) | 2023-12-31T16:08:02Z | 44,990 | 14,295 | 5,083 | 64,368 | +| [v2.13.11](https://github.com/laurent22/joplin/releases/tag/v2.13.11) | 2023-12-24T12:58:53Z | 45,891 | 13,262 | 5,972 | 65,125 | +| [v2.13.10](https://github.com/laurent22/joplin/releases/tag/v2.13.10) | 2023-12-22T10:11:08Z | 19,656 | 8,692 | 1,459 | 29,807 | +| [v2.13.9](https://github.com/laurent22/joplin/releases/tag/v2.13.9) | 2023-12-09T17:18:58Z | 67,709 | 21,923 | 8,605 | 98,237 | +| [v2.13.8](https://github.com/laurent22/joplin/releases/tag/v2.13.8) | 2023-12-03T12:07:08Z | 50,461 | 17,902 | 5,211 | 73,574 | | [v2.13.6](https://github.com/laurent22/joplin/releases/tag/v2.13.6) (p) | 2023-11-17T19:24:03Z | 2,155 | 443 | 573 | 3,171 | -| [v2.13.5](https://github.com/laurent22/joplin/releases/tag/v2.13.5) (p) | 2023-11-09T20:24:09Z | 1,466 | 338 | 437 | 2,241 | -| [v2.13.4](https://github.com/laurent22/joplin/releases/tag/v2.13.4) (p) | 2023-10-31T00:01:00Z | 1,538 | 372 | 482 | 2,392 | +| [v2.13.5](https://github.com/laurent22/joplin/releases/tag/v2.13.5) (p) | 2023-11-09T20:24:09Z | 1,467 | 339 | 438 | 2,244 | +| [v2.13.4](https://github.com/laurent22/joplin/releases/tag/v2.13.4) (p) | 2023-10-31T00:01:00Z | 1,539 | 372 | 483 | 2,394 | | [v2.13.3](https://github.com/laurent22/joplin/releases/tag/v2.13.3) (p) | 2023-10-24T09:25:33Z | 1,302 | 281 | 301 | 1,884 | -| [v2.12.19](https://github.com/laurent22/joplin/releases/tag/v2.12.19) | 2023-10-21T09:39:18Z | 166,039 | 43,649 | 27,829 | 237,517 | -| [v2.13.2](https://github.com/laurent22/joplin/releases/tag/v2.13.2) (p) | 2023-10-06T17:00:07Z | 2,032 | 503 | 701 | 3,236 | -| [v2.12.18](https://github.com/laurent22/joplin/releases/tag/v2.12.18) | 2023-09-22T14:37:24Z | 108,367 | 36,512 | 18,692 | 163,571 | -| [v2.12.17](https://github.com/laurent22/joplin/releases/tag/v2.12.17) | 2023-09-14T21:54:52Z | 47,051 | 21,030 | 6,625 | 74,706 | -| [v2.13.1](https://github.com/laurent22/joplin/releases/tag/v2.13.1) (p) | 2023-09-13T09:31:50Z | 1,390 | 427 | 660 | 2,477 | -| [v2.12.16](https://github.com/laurent22/joplin/releases/tag/v2.12.16) | 2023-09-11T22:33:37Z | 28,194 | 14,665 | 2,446 | 45,305 | -| [v2.12.15](https://github.com/laurent22/joplin/releases/tag/v2.12.15) | 2023-08-27T11:35:39Z | 63,935 | 28,043 | 8,346 | 100,324 | -| [v2.12.12](https://github.com/laurent22/joplin/releases/tag/v2.12.12) (p) | 2023-08-19T22:44:56Z | 2,619 | 387 | 428 | 3,434 | -| [v2.12.10](https://github.com/laurent22/joplin/releases/tag/v2.12.10) (p) | 2023-07-30T18:25:58Z | 7,260 | 3,814 | 912 | 11,986 | -| [v2.12.9](https://github.com/laurent22/joplin/releases/tag/v2.12.9) (p) | 2023-07-25T16:06:08Z | 2,079 | 369 | 321 | 2,769 | -| [v2.12.7](https://github.com/laurent22/joplin/releases/tag/v2.12.7) (p) | 2023-07-13T12:55:31Z | 2,133 | 662 | 590 | 3,385 | -| [v2.12.5](https://github.com/laurent22/joplin/releases/tag/v2.12.5) (p) | 2023-07-12T15:03:46Z | 1,736 | 162 | 146 | 2,044 | -| [v2.12.4](https://github.com/laurent22/joplin/releases/tag/v2.12.4) (p) | 2023-07-07T22:36:53Z | 1,047 | 430 | 215 | 1,692 | -| [v2.12.3](https://github.com/laurent22/joplin/releases/tag/v2.12.3) (p) | 2023-07-07T10:16:55Z | 382 | 184 | 93 | 659 | -| [v2.11.11](https://github.com/laurent22/joplin/releases/tag/v2.11.11) | 2023-06-23T15:16:37Z | 188,772 | 67,215 | 38,833 | 294,820 | -| [v2.11.9](https://github.com/laurent22/joplin/releases/tag/v2.11.9) (p) | 2023-06-06T16:23:27Z | 2,294 | 571 | 744 | 3,609 | -| [v2.11.6](https://github.com/laurent22/joplin/releases/tag/v2.11.6) (p) | 2023-05-31T20:13:08Z | 1,156 | 433 | 342 | 1,931 | -| [v2.11.5](https://github.com/laurent22/joplin/releases/tag/v2.11.5) (p) | 2023-05-28T00:41:40Z | 1,019 | 306 | 279 | 1,604 | -| [v2.10.19](https://github.com/laurent22/joplin/releases/tag/v2.10.19) | 2023-05-17T12:25:41Z | 124,108 | 48,327 | 22,464 | 194,899 | +| [v2.12.19](https://github.com/laurent22/joplin/releases/tag/v2.12.19) | 2023-10-21T09:39:18Z | 166,401 | 43,652 | 27,848 | 237,901 | +| [v2.13.2](https://github.com/laurent22/joplin/releases/tag/v2.13.2) (p) | 2023-10-06T17:00:07Z | 2,032 | 503 | 703 | 3,238 | +| [v2.12.18](https://github.com/laurent22/joplin/releases/tag/v2.12.18) | 2023-09-22T14:37:24Z | 108,666 | 36,514 | 18,697 | 163,877 | +| [v2.12.17](https://github.com/laurent22/joplin/releases/tag/v2.12.17) | 2023-09-14T21:54:52Z | 47,258 | 21,031 | 6,630 | 74,919 | +| [v2.13.1](https://github.com/laurent22/joplin/releases/tag/v2.13.1) (p) | 2023-09-13T09:31:50Z | 1,390 | 427 | 661 | 2,478 | +| [v2.12.16](https://github.com/laurent22/joplin/releases/tag/v2.12.16) | 2023-09-11T22:33:37Z | 28,315 | 14,666 | 2,447 | 45,428 | +| [v2.12.15](https://github.com/laurent22/joplin/releases/tag/v2.12.15) | 2023-08-27T11:35:39Z | 64,258 | 28,067 | 8,372 | 100,697 | +| [v2.12.12](https://github.com/laurent22/joplin/releases/tag/v2.12.12) (p) | 2023-08-19T22:44:56Z | 2,718 | 387 | 428 | 3,533 | +| [v2.12.10](https://github.com/laurent22/joplin/releases/tag/v2.12.10) (p) | 2023-07-30T18:25:58Z | 7,356 | 3,815 | 914 | 12,085 | +| [v2.12.9](https://github.com/laurent22/joplin/releases/tag/v2.12.9) (p) | 2023-07-25T16:06:08Z | 2,173 | 369 | 321 | 2,863 | +| [v2.12.7](https://github.com/laurent22/joplin/releases/tag/v2.12.7) (p) | 2023-07-13T12:55:31Z | 2,147 | 663 | 591 | 3,401 | +| [v2.12.5](https://github.com/laurent22/joplin/releases/tag/v2.12.5) (p) | 2023-07-12T15:03:46Z | 1,835 | 163 | 149 | 2,147 | +| [v2.12.4](https://github.com/laurent22/joplin/releases/tag/v2.12.4) (p) | 2023-07-07T22:36:53Z | 1,049 | 430 | 215 | 1,694 | +| [v2.12.3](https://github.com/laurent22/joplin/releases/tag/v2.12.3) (p) | 2023-07-07T10:16:55Z | 388 | 184 | 93 | 665 | +| [v2.11.11](https://github.com/laurent22/joplin/releases/tag/v2.11.11) | 2023-06-23T15:16:37Z | 189,081 | 67,221 | 38,843 | 295,145 | +| [v2.11.9](https://github.com/laurent22/joplin/releases/tag/v2.11.9) (p) | 2023-06-06T16:23:27Z | 2,299 | 572 | 744 | 3,615 | +| [v2.11.6](https://github.com/laurent22/joplin/releases/tag/v2.11.6) (p) | 2023-05-31T20:13:08Z | 1,159 | 433 | 342 | 1,934 | +| [v2.11.5](https://github.com/laurent22/joplin/releases/tag/v2.11.5) (p) | 2023-05-28T00:41:40Z | 1,023 | 306 | 279 | 1,608 | +| [v2.10.19](https://github.com/laurent22/joplin/releases/tag/v2.10.19) | 2023-05-17T12:25:41Z | 124,340 | 48,329 | 22,470 | 195,139 | | [v2.11.4](https://github.com/laurent22/joplin/releases/tag/v2.11.4) (p) | 2023-05-16T10:02:21Z | 1,078 | 466 | 412 | 1,956 | -| [v2.11.3](https://github.com/laurent22/joplin/releases/tag/v2.11.3) (p) | 2023-05-16T09:09:57Z | 120 | 38 | 37 | 195 | -| [v2.10.18](https://github.com/laurent22/joplin/releases/tag/v2.10.18) | 2023-05-09T13:27:43Z | 56,058 | 24,252 | 6,774 | 87,084 | -| [v2.10.17](https://github.com/laurent22/joplin/releases/tag/v2.10.17) | 2023-05-08T17:27:28Z | 18,786 | 11,506 | 885 | 31,177 | -| [v2.10.16](https://github.com/laurent22/joplin/releases/tag/v2.10.16) | 2023-04-27T09:27:45Z | 9,049 | 4,255 | 779 | 14,083 | +| [v2.11.3](https://github.com/laurent22/joplin/releases/tag/v2.11.3) (p) | 2023-05-16T09:09:57Z | 132 | 38 | 38 | 208 | +| [v2.10.18](https://github.com/laurent22/joplin/releases/tag/v2.10.18) | 2023-05-09T13:27:43Z | 56,244 | 24,253 | 6,782 | 87,279 | +| [v2.10.17](https://github.com/laurent22/joplin/releases/tag/v2.10.17) | 2023-05-08T17:27:28Z | 18,962 | 11,506 | 885 | 31,353 | +| [v2.10.16](https://github.com/laurent22/joplin/releases/tag/v2.10.16) | 2023-04-27T09:27:45Z | 9,205 | 4,255 | 779 | 14,239 | | [v2.10.15](https://github.com/laurent22/joplin/releases/tag/v2.10.15) (p) | 2023-04-26T22:02:16Z | 370 | 139 | 56 | 565 | -| [v2.10.13](https://github.com/laurent22/joplin/releases/tag/v2.10.13) (p) | 2023-04-03T16:53:46Z | 4,392 | 829 | 1,078 | 6,299 | -| [v2.10.12](https://github.com/laurent22/joplin/releases/tag/v2.10.12) (p) | 2023-03-23T12:17:13Z | 3,364 | 518 | 604 | 4,486 | -| [v2.10.11](https://github.com/laurent22/joplin/releases/tag/v2.10.11) (p) | 2023-03-17T10:54:02Z | 2,729 | 383 | 392 | 3,504 | -| [v2.10.10](https://github.com/laurent22/joplin/releases/tag/v2.10.10) (p) | 2023-03-13T23:16:37Z | 2,239 | 283 | 255 | 2,777 | -| [v2.10.9](https://github.com/laurent22/joplin/releases/tag/v2.10.9) (p) | 2023-03-12T16:16:45Z | 1,770 | 214 | 297 | 2,281 | -| [v2.10.8](https://github.com/laurent22/joplin/releases/tag/v2.10.8) (p) | 2023-02-26T12:53:55Z | 4,250 | 573 | 872 | 5,695 | -| [v2.10.7](https://github.com/laurent22/joplin/releases/tag/v2.10.7) (p) | 2023-02-24T10:56:20Z | 1,915 | 191 | 279 | 2,385 | -| [v2.10.6](https://github.com/laurent22/joplin/releases/tag/v2.10.6) (p) | 2023-02-20T14:00:05Z | 2,715 | 342 | 290 | 3,347 | -| [v2.10.5](https://github.com/laurent22/joplin/releases/tag/v2.10.5) | 2023-01-16T15:00:53Z | 365 | 101 | 307 | 773 | -| [v2.10.4](https://github.com/laurent22/joplin/releases/tag/v2.10.4) (p) | 2023-01-05T13:09:20Z | 7,932 | 1,303 | 1,812 | 11,047 | -| [v2.10.3](https://github.com/laurent22/joplin/releases/tag/v2.10.3) (p) | 2022-12-31T15:53:23Z | 2,503 | 312 | 411 | 3,226 | -| [v2.10.2](https://github.com/laurent22/joplin/releases/tag/v2.10.2) (p) | 2022-12-18T18:05:08Z | 3,937 | 587 | 634 | 5,158 | -| [v2.9.17](https://github.com/laurent22/joplin/releases/tag/v2.9.17) | 2022-11-15T10:28:37Z | 335,069 | 108,775 | 83,339 | 527,183 | -| [v2.9.12](https://github.com/laurent22/joplin/releases/tag/v2.9.12) (p) | 2022-11-01T17:06:05Z | 11,116 | 611 | 540 | 12,267 | -| [v2.9.11](https://github.com/laurent22/joplin/releases/tag/v2.9.11) (p) | 2022-10-23T16:09:58Z | 3,266 | 529 | 755 | 4,550 | -| [v2.9.4](https://github.com/laurent22/joplin/releases/tag/v2.9.4) (p) | 2022-08-18T16:52:26Z | 8,309 | 1,867 | 2,193 | 12,369 | -| [v2.9.3](https://github.com/laurent22/joplin/releases/tag/v2.9.3) (p) | 2022-08-18T13:11:09Z | 363 | 91 | 269 | 723 | -| [v2.9.2](https://github.com/laurent22/joplin/releases/tag/v2.9.2) (p) | 2022-08-12T18:12:12Z | 1,533 | 446 | 0 | 1,979 | -| [v2.9.1](https://github.com/laurent22/joplin/releases/tag/v2.9.1) (p) | 2022-07-11T09:59:32Z | 7,750 | 1,342 | 1,404 | 10,496 | -| [v2.8.8](https://github.com/laurent22/joplin/releases/tag/v2.8.8) | 2022-05-17T14:48:06Z | 351,157 | 114,351 | 113,560 | 579,068 | -| [v2.8.7](https://github.com/laurent22/joplin/releases/tag/v2.8.7) (p) | 2022-05-06T11:34:27Z | 4,255 | 365 | 422 | 5,042 | -| [v2.8.6](https://github.com/laurent22/joplin/releases/tag/v2.8.6) (p) | 2022-05-03T10:08:25Z | 3,881 | 402 | 328 | 4,611 | -| [v2.8.5](https://github.com/laurent22/joplin/releases/tag/v2.8.5) (p) | 2022-04-27T13:51:50Z | 3,972 | 369 | 351 | 4,692 | -| [v2.8.4](https://github.com/laurent22/joplin/releases/tag/v2.8.4) (p) | 2022-04-19T18:00:09Z | 4,461 | 589 | 327 | 5,377 | -| [v2.8.2](https://github.com/laurent22/joplin/releases/tag/v2.8.2) (p) | 2022-04-14T11:35:45Z | 3,860 | 279 | 266 | 4,405 | -| [v2.7.15](https://github.com/laurent22/joplin/releases/tag/v2.7.15) | 2022-03-17T13:03:23Z | 156,090 | 56,775 | 51,261 | 264,126 | -| [v2.7.14](https://github.com/laurent22/joplin/releases/tag/v2.7.14) | 2022-02-27T11:30:53Z | 34,428 | 16,782 | 4,802 | 56,012 | -| [v2.7.13](https://github.com/laurent22/joplin/releases/tag/v2.7.13) | 2022-02-24T17:42:12Z | 54,789 | 25,724 | 11,712 | 92,225 | -| [v2.7.12](https://github.com/laurent22/joplin/releases/tag/v2.7.12) (p) | 2022-02-14T15:06:14Z | 4,651 | 464 | 482 | 5,597 | -| [v2.7.11](https://github.com/laurent22/joplin/releases/tag/v2.7.11) (p) | 2022-02-12T13:00:02Z | 3,791 | 194 | 168 | 4,153 | -| [v2.7.10](https://github.com/laurent22/joplin/releases/tag/v2.7.10) (p) | 2022-02-11T18:19:09Z | 3,297 | 125 | 82 | 3,504 | -| [v2.7.8](https://github.com/laurent22/joplin/releases/tag/v2.7.8) (p) | 2022-01-19T09:35:27Z | 5,523 | 770 | 824 | 7,117 | -| [v2.7.7](https://github.com/laurent22/joplin/releases/tag/v2.7.7) (p) | 2022-01-18T14:05:07Z | 3,621 | 156 | 135 | 3,912 | -| [v2.7.6](https://github.com/laurent22/joplin/releases/tag/v2.7.6) (p) | 2022-01-17T17:08:28Z | 3,543 | 182 | 114 | 3,839 | -| [v2.6.10](https://github.com/laurent22/joplin/releases/tag/v2.6.10) | 2021-12-19T11:31:16Z | 135,988 | 51,198 | 49,310 | 236,496 | -| [v2.6.9](https://github.com/laurent22/joplin/releases/tag/v2.6.9) | 2021-12-17T11:57:32Z | 19,038 | 9,495 | 3,186 | 31,719 | -| [v2.6.7](https://github.com/laurent22/joplin/releases/tag/v2.6.7) (p) | 2021-12-16T10:47:23Z | 3,763 | 166 | 103 | 4,032 | -| [v2.6.6](https://github.com/laurent22/joplin/releases/tag/v2.6.6) (p) | 2021-12-13T12:31:43Z | 3,756 | 256 | 166 | 4,178 | -| [v2.6.5](https://github.com/laurent22/joplin/releases/tag/v2.6.5) (p) | 2021-12-13T10:07:04Z | 3,092 | 50 | 30 | 3,172 | -| [v2.6.4](https://github.com/laurent22/joplin/releases/tag/v2.6.4) (p) | 2021-12-09T19:53:43Z | 3,834 | 290 | 201 | 4,325 | -| [v2.6.2](https://github.com/laurent22/joplin/releases/tag/v2.6.2) (p) | 2021-11-18T12:19:12Z | 5,623 | 793 | 702 | 7,118 | -| [v2.5.12](https://github.com/laurent22/joplin/releases/tag/v2.5.12) | 2021-11-08T11:07:11Z | 82,745 | 32,504 | 25,233 | 140,482 | -| [v2.5.10](https://github.com/laurent22/joplin/releases/tag/v2.5.10) | 2021-11-01T08:22:42Z | 46,998 | 19,049 | 10,096 | 76,143 | -| [v2.5.8](https://github.com/laurent22/joplin/releases/tag/v2.5.8) | 2021-10-31T11:38:03Z | 15,727 | 6,578 | 2,323 | 24,628 | -| [v2.5.7](https://github.com/laurent22/joplin/releases/tag/v2.5.7) (p) | 2021-10-29T14:47:33Z | 3,362 | 203 | 164 | 3,729 | -| [v2.5.6](https://github.com/laurent22/joplin/releases/tag/v2.5.6) (p) | 2021-10-28T22:03:09Z | 3,415 | 177 | 107 | 3,699 | -| [v2.5.4](https://github.com/laurent22/joplin/releases/tag/v2.5.4) (p) | 2021-10-19T10:10:54Z | 4,912 | 568 | 579 | 6,059 | -| [v2.4.12](https://github.com/laurent22/joplin/releases/tag/v2.4.12) | 2021-10-13T17:24:34Z | 47,368 | 19,980 | 9,786 | 77,134 | -| [v2.5.1](https://github.com/laurent22/joplin/releases/tag/v2.5.1) (p) | 2021-10-02T09:51:58Z | 5,948 | 907 | 947 | 7,802 | -| [v2.4.9](https://github.com/laurent22/joplin/releases/tag/v2.4.9) | 2021-09-29T19:08:58Z | 59,115 | 23,259 | 15,912 | 98,286 | -| [v2.4.8](https://github.com/laurent22/joplin/releases/tag/v2.4.8) (p) | 2021-09-22T19:01:46Z | 9,763 | 1,774 | 535 | 12,072 | -| [v2.4.7](https://github.com/laurent22/joplin/releases/tag/v2.4.7) (p) | 2021-09-19T12:53:22Z | 3,881 | 259 | 210 | 4,350 | -| [v2.4.6](https://github.com/laurent22/joplin/releases/tag/v2.4.6) (p) | 2021-09-09T18:57:17Z | 4,647 | 460 | 520 | 5,627 | -| [v2.4.5](https://github.com/laurent22/joplin/releases/tag/v2.4.5) (p) | 2021-09-06T18:03:28Z | 3,887 | 275 | 225 | 4,387 | -| [v2.4.4](https://github.com/laurent22/joplin/releases/tag/v2.4.4) (p) | 2021-08-30T16:02:51Z | 4,097 | 380 | 368 | 4,845 | -| [v2.4.3](https://github.com/laurent22/joplin/releases/tag/v2.4.3) (p) | 2021-08-28T15:27:32Z | 3,597 | 207 | 180 | 3,984 | -| [v2.4.2](https://github.com/laurent22/joplin/releases/tag/v2.4.2) (p) | 2021-08-27T17:13:21Z | 3,359 | 151 | 91 | 3,601 | -| [v2.4.1](https://github.com/laurent22/joplin/releases/tag/v2.4.1) (p) | 2021-08-21T11:52:30Z | 4,209 | 372 | 337 | 4,918 | -| [v2.3.5](https://github.com/laurent22/joplin/releases/tag/v2.3.5) | 2021-08-17T06:43:30Z | 85,049 | 31,433 | 33,139 | 149,621 | -| [v2.3.3](https://github.com/laurent22/joplin/releases/tag/v2.3.3) | 2021-08-14T09:19:40Z | 18,272 | 6,885 | 4,062 | 29,219 | -| [v2.2.7](https://github.com/laurent22/joplin/releases/tag/v2.2.7) | 2021-08-11T11:03:26Z | 18,162 | 7,524 | 2,607 | 28,293 | -| [v2.2.6](https://github.com/laurent22/joplin/releases/tag/v2.2.6) (p) | 2021-08-09T19:29:20Z | 10,620 | 4,619 | 958 | 16,197 | -| [v2.2.5](https://github.com/laurent22/joplin/releases/tag/v2.2.5) (p) | 2021-08-07T10:35:24Z | 3,940 | 278 | 208 | 4,426 | -| [v2.2.4](https://github.com/laurent22/joplin/releases/tag/v2.2.4) (p) | 2021-08-05T16:42:48Z | 3,604 | 208 | 133 | 3,945 | -| [v2.2.2](https://github.com/laurent22/joplin/releases/tag/v2.2.2) (p) | 2021-07-19T10:28:35Z | 5,511 | 737 | 648 | 6,896 | -| [v2.1.9](https://github.com/laurent22/joplin/releases/tag/v2.1.9) | 2021-07-19T10:28:43Z | 49,761 | 18,958 | 16,816 | 85,535 | -| [v2.2.1](https://github.com/laurent22/joplin/releases/tag/v2.2.1) (p) | 2021-07-09T17:38:25Z | 5,141 | 417 | 394 | 5,952 | -| [v2.1.8](https://github.com/laurent22/joplin/releases/tag/v2.1.8) | 2021-07-03T08:25:16Z | 33,376 | 12,205 | 12,736 | 58,317 | -| [v2.1.7](https://github.com/laurent22/joplin/releases/tag/v2.1.7) | 2021-06-26T19:48:55Z | 16,706 | 6,410 | 3,636 | 26,752 | -| [v2.1.5](https://github.com/laurent22/joplin/releases/tag/v2.1.5) (p) | 2021-06-23T15:08:52Z | 4,037 | 254 | 202 | 4,493 | -| [v2.1.3](https://github.com/laurent22/joplin/releases/tag/v2.1.3) (p) | 2021-06-19T16:32:51Z | 4,107 | 312 | 217 | 4,636 | -| [v2.0.11](https://github.com/laurent22/joplin/releases/tag/v2.0.11) | 2021-06-16T17:55:49Z | 26,392 | 9,279 | 9,902 | 45,573 | -| [v2.0.10](https://github.com/laurent22/joplin/releases/tag/v2.0.10) | 2021-06-16T07:58:29Z | 5,606 | 945 | 399 | 6,950 | -| [v2.0.9](https://github.com/laurent22/joplin/releases/tag/v2.0.9) (p) | 2021-06-12T09:30:30Z | 4,021 | 306 | 897 | 5,224 | -| [v2.0.8](https://github.com/laurent22/joplin/releases/tag/v2.0.8) (p) | 2021-06-10T16:15:08Z | 3,530 | 246 | 597 | 4,373 | -| [v2.0.4](https://github.com/laurent22/joplin/releases/tag/v2.0.4) (p) | 2021-06-02T12:54:17Z | 1,642 | 404 | 392 | 2,438 | -| [v2.0.2](https://github.com/laurent22/joplin/releases/tag/v2.0.2) (p) | 2021-05-21T18:07:48Z | 5,586 | 503 | 1,681 | 7,770 | +| [v2.10.13](https://github.com/laurent22/joplin/releases/tag/v2.10.13) (p) | 2023-04-03T16:53:46Z | 4,483 | 829 | 1,078 | 6,390 | +| [v2.10.12](https://github.com/laurent22/joplin/releases/tag/v2.10.12) (p) | 2023-03-23T12:17:13Z | 3,455 | 518 | 604 | 4,577 | +| [v2.10.11](https://github.com/laurent22/joplin/releases/tag/v2.10.11) (p) | 2023-03-17T10:54:02Z | 2,821 | 383 | 392 | 3,596 | +| [v2.10.10](https://github.com/laurent22/joplin/releases/tag/v2.10.10) (p) | 2023-03-13T23:16:37Z | 2,332 | 283 | 255 | 2,870 | +| [v2.10.9](https://github.com/laurent22/joplin/releases/tag/v2.10.9) (p) | 2023-03-12T16:16:45Z | 1,861 | 214 | 297 | 2,372 | +| [v2.10.8](https://github.com/laurent22/joplin/releases/tag/v2.10.8) (p) | 2023-02-26T12:53:55Z | 4,342 | 573 | 872 | 5,787 | +| [v2.10.7](https://github.com/laurent22/joplin/releases/tag/v2.10.7) (p) | 2023-02-24T10:56:20Z | 2,008 | 191 | 279 | 2,478 | +| [v2.10.6](https://github.com/laurent22/joplin/releases/tag/v2.10.6) (p) | 2023-02-20T14:00:05Z | 2,811 | 342 | 290 | 3,443 | +| [v2.10.5](https://github.com/laurent22/joplin/releases/tag/v2.10.5) | 2023-01-16T15:00:53Z | 365 | 102 | 307 | 774 | +| [v2.10.4](https://github.com/laurent22/joplin/releases/tag/v2.10.4) (p) | 2023-01-05T13:09:20Z | 8,025 | 1,303 | 1,812 | 11,140 | +| [v2.10.3](https://github.com/laurent22/joplin/releases/tag/v2.10.3) (p) | 2022-12-31T15:53:23Z | 2,596 | 313 | 413 | 3,322 | +| [v2.10.2](https://github.com/laurent22/joplin/releases/tag/v2.10.2) (p) | 2022-12-18T18:05:08Z | 4,029 | 589 | 635 | 5,253 | +| [v2.9.17](https://github.com/laurent22/joplin/releases/tag/v2.9.17) | 2022-11-15T10:28:37Z | 335,331 | 108,783 | 83,349 | 527,463 | +| [v2.9.12](https://github.com/laurent22/joplin/releases/tag/v2.9.12) (p) | 2022-11-01T17:06:05Z | 11,212 | 612 | 541 | 12,365 | +| [v2.9.11](https://github.com/laurent22/joplin/releases/tag/v2.9.11) (p) | 2022-10-23T16:09:58Z | 3,357 | 530 | 757 | 4,644 | +| [v2.9.4](https://github.com/laurent22/joplin/releases/tag/v2.9.4) (p) | 2022-08-18T16:52:26Z | 8,400 | 1,868 | 2,195 | 12,463 | +| [v2.9.3](https://github.com/laurent22/joplin/releases/tag/v2.9.3) (p) | 2022-08-18T13:11:09Z | 363 | 92 | 272 | 727 | +| [v2.9.2](https://github.com/laurent22/joplin/releases/tag/v2.9.2) (p) | 2022-08-12T18:12:12Z | 1,533 | 447 | 0 | 1,980 | +| [v2.9.1](https://github.com/laurent22/joplin/releases/tag/v2.9.1) (p) | 2022-07-11T09:59:32Z | 7,842 | 1,343 | 1,406 | 10,591 | +| [v2.8.8](https://github.com/laurent22/joplin/releases/tag/v2.8.8) | 2022-05-17T14:48:06Z | 351,445 | 114,366 | 113,573 | 579,384 | +| [v2.8.7](https://github.com/laurent22/joplin/releases/tag/v2.8.7) (p) | 2022-05-06T11:34:27Z | 4,347 | 366 | 425 | 5,138 | +| [v2.8.6](https://github.com/laurent22/joplin/releases/tag/v2.8.6) (p) | 2022-05-03T10:08:25Z | 3,975 | 403 | 329 | 4,707 | +| [v2.8.5](https://github.com/laurent22/joplin/releases/tag/v2.8.5) (p) | 2022-04-27T13:51:50Z | 4,063 | 370 | 353 | 4,786 | +| [v2.8.4](https://github.com/laurent22/joplin/releases/tag/v2.8.4) (p) | 2022-04-19T18:00:09Z | 4,552 | 590 | 329 | 5,471 | +| [v2.8.2](https://github.com/laurent22/joplin/releases/tag/v2.8.2) (p) | 2022-04-14T11:35:45Z | 3,955 | 280 | 267 | 4,502 | +| [v2.7.15](https://github.com/laurent22/joplin/releases/tag/v2.7.15) | 2022-03-17T13:03:23Z | 156,218 | 56,778 | 51,273 | 264,269 | +| [v2.7.14](https://github.com/laurent22/joplin/releases/tag/v2.7.14) | 2022-02-27T11:30:53Z | 34,527 | 16,783 | 4,804 | 56,114 | +| [v2.7.13](https://github.com/laurent22/joplin/releases/tag/v2.7.13) | 2022-02-24T17:42:12Z | 54,908 | 25,726 | 11,714 | 92,348 | +| [v2.7.12](https://github.com/laurent22/joplin/releases/tag/v2.7.12) (p) | 2022-02-14T15:06:14Z | 4,742 | 465 | 485 | 5,692 | +| [v2.7.11](https://github.com/laurent22/joplin/releases/tag/v2.7.11) (p) | 2022-02-12T13:00:02Z | 3,882 | 195 | 170 | 4,247 | +| [v2.7.10](https://github.com/laurent22/joplin/releases/tag/v2.7.10) (p) | 2022-02-11T18:19:09Z | 3,389 | 126 | 84 | 3,599 | +| [v2.7.8](https://github.com/laurent22/joplin/releases/tag/v2.7.8) (p) | 2022-01-19T09:35:27Z | 5,616 | 771 | 826 | 7,213 | +| [v2.7.7](https://github.com/laurent22/joplin/releases/tag/v2.7.7) (p) | 2022-01-18T14:05:07Z | 3,714 | 157 | 137 | 4,008 | +| [v2.7.6](https://github.com/laurent22/joplin/releases/tag/v2.7.6) (p) | 2022-01-17T17:08:28Z | 3,635 | 183 | 116 | 3,934 | +| [v2.6.10](https://github.com/laurent22/joplin/releases/tag/v2.6.10) | 2021-12-19T11:31:16Z | 136,200 | 51,202 | 49,313 | 236,715 | +| [v2.6.9](https://github.com/laurent22/joplin/releases/tag/v2.6.9) | 2021-12-17T11:57:32Z | 19,192 | 9,498 | 3,188 | 31,878 | +| [v2.6.7](https://github.com/laurent22/joplin/releases/tag/v2.6.7) (p) | 2021-12-16T10:47:23Z | 3,856 | 180 | 105 | 4,141 | +| [v2.6.6](https://github.com/laurent22/joplin/releases/tag/v2.6.6) (p) | 2021-12-13T12:31:43Z | 3,849 | 257 | 168 | 4,274 | +| [v2.6.5](https://github.com/laurent22/joplin/releases/tag/v2.6.5) (p) | 2021-12-13T10:07:04Z | 3,182 | 51 | 32 | 3,265 | +| [v2.6.4](https://github.com/laurent22/joplin/releases/tag/v2.6.4) (p) | 2021-12-09T19:53:43Z | 3,928 | 291 | 203 | 4,422 | +| [v2.6.2](https://github.com/laurent22/joplin/releases/tag/v2.6.2) (p) | 2021-11-18T12:19:12Z | 5,720 | 793 | 702 | 7,215 | +| [v2.5.12](https://github.com/laurent22/joplin/releases/tag/v2.5.12) | 2021-11-08T11:07:11Z | 82,923 | 32,507 | 25,234 | 140,664 | +| [v2.5.10](https://github.com/laurent22/joplin/releases/tag/v2.5.10) | 2021-11-01T08:22:42Z | 47,120 | 19,049 | 10,097 | 76,266 | +| [v2.5.8](https://github.com/laurent22/joplin/releases/tag/v2.5.8) | 2021-10-31T11:38:03Z | 15,857 | 6,581 | 2,325 | 24,763 | +| [v2.5.7](https://github.com/laurent22/joplin/releases/tag/v2.5.7) (p) | 2021-10-29T14:47:33Z | 3,453 | 203 | 164 | 3,820 | +| [v2.5.6](https://github.com/laurent22/joplin/releases/tag/v2.5.6) (p) | 2021-10-28T22:03:09Z | 3,507 | 177 | 107 | 3,791 | +| [v2.5.4](https://github.com/laurent22/joplin/releases/tag/v2.5.4) (p) | 2021-10-19T10:10:54Z | 5,007 | 568 | 580 | 6,155 | +| [v2.4.12](https://github.com/laurent22/joplin/releases/tag/v2.4.12) | 2021-10-13T17:24:34Z | 47,543 | 19,982 | 9,788 | 77,313 | +| [v2.5.1](https://github.com/laurent22/joplin/releases/tag/v2.5.1) (p) | 2021-10-02T09:51:58Z | 6,044 | 907 | 947 | 7,898 | +| [v2.4.9](https://github.com/laurent22/joplin/releases/tag/v2.4.9) | 2021-09-29T19:08:58Z | 59,258 | 23,261 | 15,914 | 98,433 | +| [v2.4.8](https://github.com/laurent22/joplin/releases/tag/v2.4.8) (p) | 2021-09-22T19:01:46Z | 9,854 | 1,774 | 535 | 12,163 | +| [v2.4.7](https://github.com/laurent22/joplin/releases/tag/v2.4.7) (p) | 2021-09-19T12:53:22Z | 3,973 | 259 | 210 | 4,442 | +| [v2.4.6](https://github.com/laurent22/joplin/releases/tag/v2.4.6) (p) | 2021-09-09T18:57:17Z | 4,742 | 460 | 520 | 5,722 | +| [v2.4.5](https://github.com/laurent22/joplin/releases/tag/v2.4.5) (p) | 2021-09-06T18:03:28Z | 3,982 | 275 | 225 | 4,482 | +| [v2.4.4](https://github.com/laurent22/joplin/releases/tag/v2.4.4) (p) | 2021-08-30T16:02:51Z | 4,192 | 380 | 371 | 4,943 | +| [v2.4.3](https://github.com/laurent22/joplin/releases/tag/v2.4.3) (p) | 2021-08-28T15:27:32Z | 3,688 | 207 | 180 | 4,075 | +| [v2.4.2](https://github.com/laurent22/joplin/releases/tag/v2.4.2) (p) | 2021-08-27T17:13:21Z | 3,455 | 151 | 91 | 3,697 | +| [v2.4.1](https://github.com/laurent22/joplin/releases/tag/v2.4.1) (p) | 2021-08-21T11:52:30Z | 4,299 | 372 | 337 | 5,008 | +| [v2.3.5](https://github.com/laurent22/joplin/releases/tag/v2.3.5) | 2021-08-17T06:43:30Z | 85,238 | 31,437 | 33,142 | 149,817 | +| [v2.3.3](https://github.com/laurent22/joplin/releases/tag/v2.3.3) | 2021-08-14T09:19:40Z | 18,441 | 6,886 | 4,062 | 29,389 | +| [v2.2.7](https://github.com/laurent22/joplin/releases/tag/v2.2.7) | 2021-08-11T11:03:26Z | 18,318 | 7,524 | 2,607 | 28,449 | +| [v2.2.6](https://github.com/laurent22/joplin/releases/tag/v2.2.6) (p) | 2021-08-09T19:29:20Z | 10,716 | 4,619 | 958 | 16,293 | +| [v2.2.5](https://github.com/laurent22/joplin/releases/tag/v2.2.5) (p) | 2021-08-07T10:35:24Z | 4,031 | 278 | 208 | 4,517 | +| [v2.2.4](https://github.com/laurent22/joplin/releases/tag/v2.2.4) (p) | 2021-08-05T16:42:48Z | 3,696 | 208 | 133 | 4,037 | +| [v2.2.2](https://github.com/laurent22/joplin/releases/tag/v2.2.2) (p) | 2021-07-19T10:28:35Z | 5,605 | 737 | 648 | 6,990 | +| [v2.1.9](https://github.com/laurent22/joplin/releases/tag/v2.1.9) | 2021-07-19T10:28:43Z | 49,937 | 18,959 | 16,817 | 85,713 | +| [v2.2.1](https://github.com/laurent22/joplin/releases/tag/v2.2.1) (p) | 2021-07-09T17:38:25Z | 5,237 | 417 | 394 | 6,048 | +| [v2.1.8](https://github.com/laurent22/joplin/releases/tag/v2.1.8) | 2021-07-03T08:25:16Z | 33,498 | 12,205 | 12,736 | 58,439 | +| [v2.1.7](https://github.com/laurent22/joplin/releases/tag/v2.1.7) | 2021-06-26T19:48:55Z | 16,801 | 6,410 | 3,636 | 26,847 | +| [v2.1.5](https://github.com/laurent22/joplin/releases/tag/v2.1.5) (p) | 2021-06-23T15:08:52Z | 4,131 | 254 | 202 | 4,587 | +| [v2.1.3](https://github.com/laurent22/joplin/releases/tag/v2.1.3) (p) | 2021-06-19T16:32:51Z | 4,199 | 312 | 217 | 4,728 | +| [v2.0.11](https://github.com/laurent22/joplin/releases/tag/v2.0.11) | 2021-06-16T17:55:49Z | 26,520 | 9,280 | 9,904 | 45,704 | +| [v2.0.10](https://github.com/laurent22/joplin/releases/tag/v2.0.10) | 2021-06-16T07:58:29Z | 5,732 | 946 | 399 | 7,077 | +| [v2.0.9](https://github.com/laurent22/joplin/releases/tag/v2.0.9) (p) | 2021-06-12T09:30:30Z | 4,140 | 306 | 899 | 5,345 | +| [v2.0.8](https://github.com/laurent22/joplin/releases/tag/v2.0.8) (p) | 2021-06-10T16:15:08Z | 3,624 | 246 | 598 | 4,468 | +| [v2.0.4](https://github.com/laurent22/joplin/releases/tag/v2.0.4) (p) | 2021-06-02T12:54:17Z | 1,642 | 404 | 393 | 2,439 | +| [v2.0.2](https://github.com/laurent22/joplin/releases/tag/v2.0.2) (p) | 2021-05-21T18:07:48Z | 5,717 | 503 | 1,681 | 7,901 | | [v2.0.1](https://github.com/laurent22/joplin/releases/tag/v2.0.1) (p) | 2021-05-15T13:22:58Z | 948 | 287 | 1,039 | 2,274 | -| [v1.8.5](https://github.com/laurent22/joplin/releases/tag/v1.8.5) | 2021-05-10T11:58:14Z | 41,432 | 16,301 | 19,436 | 77,169 | -| [v1.8.4](https://github.com/laurent22/joplin/releases/tag/v1.8.4) (p) | 2021-05-09T18:05:05Z | 3,467 | 152 | 473 | 4,092 | -| [v1.8.3](https://github.com/laurent22/joplin/releases/tag/v1.8.3) (p) | 2021-05-04T10:38:16Z | 4,486 | 320 | 951 | 5,757 | -| [v1.8.2](https://github.com/laurent22/joplin/releases/tag/v1.8.2) (p) | 2021-04-25T10:50:51Z | 4,782 | 451 | 1,300 | 6,533 | -| [v1.8.1](https://github.com/laurent22/joplin/releases/tag/v1.8.1) (p) | 2021-03-29T10:46:41Z | 5,791 | 840 | 2,466 | 9,097 | -| [v1.7.11](https://github.com/laurent22/joplin/releases/tag/v1.7.11) | 2021-02-03T12:50:01Z | 120,681 | 42,980 | 64,447 | 228,108 | -| [v1.7.10](https://github.com/laurent22/joplin/releases/tag/v1.7.10) | 2021-01-30T13:25:29Z | 14,581 | 4,882 | 4,528 | 23,991 | +| [v1.8.5](https://github.com/laurent22/joplin/releases/tag/v1.8.5) | 2021-05-10T11:58:14Z | 41,607 | 16,303 | 19,436 | 77,346 | +| [v1.8.4](https://github.com/laurent22/joplin/releases/tag/v1.8.4) (p) | 2021-05-09T18:05:05Z | 3,560 | 152 | 473 | 4,185 | +| [v1.8.3](https://github.com/laurent22/joplin/releases/tag/v1.8.3) (p) | 2021-05-04T10:38:16Z | 4,593 | 320 | 951 | 5,864 | +| [v1.8.2](https://github.com/laurent22/joplin/releases/tag/v1.8.2) (p) | 2021-04-25T10:50:51Z | 4,874 | 451 | 1,300 | 6,625 | +| [v1.8.1](https://github.com/laurent22/joplin/releases/tag/v1.8.1) (p) | 2021-03-29T10:46:41Z | 5,885 | 840 | 2,466 | 9,191 | +| [v1.7.11](https://github.com/laurent22/joplin/releases/tag/v1.7.11) | 2021-02-03T12:50:01Z | 120,853 | 42,986 | 64,451 | 228,290 | +| [v1.7.10](https://github.com/laurent22/joplin/releases/tag/v1.7.10) | 2021-01-30T13:25:29Z | 14,598 | 4,883 | 4,528 | 24,009 | | [v1.7.9](https://github.com/laurent22/joplin/releases/tag/v1.7.9) (p) | 2021-01-28T09:50:21Z | 531 | 148 | 514 | 1,193 | | [v1.7.6](https://github.com/laurent22/joplin/releases/tag/v1.7.6) (p) | 2021-01-27T10:36:05Z | 346 | 108 | 304 | 758 | | [v1.7.5](https://github.com/laurent22/joplin/releases/tag/v1.7.5) (p) | 2021-01-26T09:53:05Z | 439 | 219 | 468 | 1,126 | | [v1.7.4](https://github.com/laurent22/joplin/releases/tag/v1.7.4) (p) | 2021-01-22T17:58:38Z | 729 | 219 | 641 | 1,589 | -| [v1.6.8](https://github.com/laurent22/joplin/releases/tag/v1.6.8) | 2021-01-20T18:11:34Z | 23,029 | 7,731 | 7,635 | 38,395 | +| [v1.6.8](https://github.com/laurent22/joplin/releases/tag/v1.6.8) | 2021-01-20T18:11:34Z | 23,198 | 7,731 | 7,635 | 38,564 | | [v1.7.3](https://github.com/laurent22/joplin/releases/tag/v1.7.3) (p) | 2021-01-20T11:23:50Z | 383 | 95 | 459 | 937 | -| [v1.6.7](https://github.com/laurent22/joplin/releases/tag/v1.6.7) | 2021-01-11T23:20:33Z | 14,619 | 4,663 | 4,572 | 23,854 | -| [v1.6.6](https://github.com/laurent22/joplin/releases/tag/v1.6.6) | 2021-01-09T16:15:31Z | 12,860 | 3,440 | 4,818 | 21,118 | -| [v1.6.5](https://github.com/laurent22/joplin/releases/tag/v1.6.5) (p) | 2021-01-09T01:24:32Z | 3,745 | 91 | 324 | 4,160 | +| [v1.6.7](https://github.com/laurent22/joplin/releases/tag/v1.6.7) | 2021-01-11T23:20:33Z | 14,774 | 4,669 | 4,572 | 24,015 | +| [v1.6.6](https://github.com/laurent22/joplin/releases/tag/v1.6.6) | 2021-01-09T16:15:31Z | 12,863 | 3,440 | 4,818 | 21,121 | +| [v1.6.5](https://github.com/laurent22/joplin/releases/tag/v1.6.5) (p) | 2021-01-09T01:24:32Z | 3,837 | 91 | 324 | 4,252 | | [v1.6.4](https://github.com/laurent22/joplin/releases/tag/v1.6.4) (p) | 2021-01-07T19:11:32Z | 422 | 93 | 220 | 735 | | [v1.6.2](https://github.com/laurent22/joplin/releases/tag/v1.6.2) (p) | 2021-01-04T22:34:55Z | 704 | 242 | 606 | 1,552 | -| [v1.5.14](https://github.com/laurent22/joplin/releases/tag/v1.5.14) | 2020-12-30T01:48:46Z | 15,118 | 5,233 | 5,551 | 25,902 | +| [v1.5.14](https://github.com/laurent22/joplin/releases/tag/v1.5.14) | 2020-12-30T01:48:46Z | 15,273 | 5,233 | 5,551 | 26,057 | | [v1.6.1](https://github.com/laurent22/joplin/releases/tag/v1.6.1) (p) | 2020-12-29T19:37:45Z | 202 | 53 | 184 | 439 | -| [v1.5.13](https://github.com/laurent22/joplin/releases/tag/v1.5.13) | 2020-12-29T18:29:15Z | 781 | 242 | 228 | 1,251 | -| [v1.5.12](https://github.com/laurent22/joplin/releases/tag/v1.5.12) | 2020-12-28T15:14:08Z | 2,562 | 1,796 | 943 | 5,301 | -| [v1.5.11](https://github.com/laurent22/joplin/releases/tag/v1.5.11) | 2020-12-27T19:54:07Z | 14,356 | 4,659 | 4,307 | 23,322 | +| [v1.5.13](https://github.com/laurent22/joplin/releases/tag/v1.5.13) | 2020-12-29T18:29:15Z | 784 | 242 | 228 | 1,254 | +| [v1.5.12](https://github.com/laurent22/joplin/releases/tag/v1.5.12) | 2020-12-28T15:14:08Z | 2,565 | 1,796 | 943 | 5,304 | +| [v1.5.11](https://github.com/laurent22/joplin/releases/tag/v1.5.11) | 2020-12-27T19:54:07Z | 14,415 | 4,662 | 4,308 | 23,385 | | [v1.5.10](https://github.com/laurent22/joplin/releases/tag/v1.5.10) (p) | 2020-12-26T12:35:36Z | 324 | 124 | 286 | 734 | | [v1.5.9](https://github.com/laurent22/joplin/releases/tag/v1.5.9) (p) | 2020-12-23T18:01:08Z | 358 | 386 | 427 | 1,171 | | [v1.5.8](https://github.com/laurent22/joplin/releases/tag/v1.5.8) (p) | 2020-12-20T09:45:19Z | 594 | 179 | 660 | 1,433 | | [v1.5.7](https://github.com/laurent22/joplin/releases/tag/v1.5.7) (p) | 2020-12-10T12:58:33Z | 920 | 269 | 1,010 | 2,199 | | [v1.5.4](https://github.com/laurent22/joplin/releases/tag/v1.5.4) (p) | 2020-12-05T12:07:49Z | 732 | 182 | 652 | 1,566 | -| [v1.4.19](https://github.com/laurent22/joplin/releases/tag/v1.4.19) | 2020-12-01T11:11:16Z | 29,514 | 13,616 | 11,714 | 54,844 | -| [v1.4.18](https://github.com/laurent22/joplin/releases/tag/v1.4.18) | 2020-11-28T12:21:41Z | 11,749 | 3,910 | 3,162 | 18,821 | -| [v1.4.16](https://github.com/laurent22/joplin/releases/tag/v1.4.16) | 2020-11-27T19:40:16Z | 1,641 | 861 | 621 | 3,123 | -| [v1.4.15](https://github.com/laurent22/joplin/releases/tag/v1.4.15) | 2020-11-27T13:25:43Z | 1,031 | 513 | 296 | 1,840 | -| [v1.4.12](https://github.com/laurent22/joplin/releases/tag/v1.4.12) | 2020-11-23T18:58:07Z | 3,214 | 1,375 | 1,328 | 5,917 | -| [v1.4.11](https://github.com/laurent22/joplin/releases/tag/v1.4.11) (p) | 2020-11-19T23:06:51Z | 4,528 | 191 | 611 | 5,330 | -| [v1.4.10](https://github.com/laurent22/joplin/releases/tag/v1.4.10) (p) | 2020-11-14T09:53:15Z | 693 | 232 | 703 | 1,628 | +| [v1.4.19](https://github.com/laurent22/joplin/releases/tag/v1.4.19) | 2020-12-01T11:11:16Z | 29,637 | 13,623 | 11,714 | 54,974 | +| [v1.4.18](https://github.com/laurent22/joplin/releases/tag/v1.4.18) | 2020-11-28T12:21:41Z | 11,753 | 3,910 | 3,162 | 18,825 | +| [v1.4.16](https://github.com/laurent22/joplin/releases/tag/v1.4.16) | 2020-11-27T19:40:16Z | 1,644 | 861 | 621 | 3,126 | +| [v1.4.15](https://github.com/laurent22/joplin/releases/tag/v1.4.15) | 2020-11-27T13:25:43Z | 1,034 | 513 | 296 | 1,843 | +| [v1.4.12](https://github.com/laurent22/joplin/releases/tag/v1.4.12) | 2020-11-23T18:58:07Z | 3,217 | 1,376 | 1,328 | 5,921 | +| [v1.4.11](https://github.com/laurent22/joplin/releases/tag/v1.4.11) (p) | 2020-11-19T23:06:51Z | 4,626 | 191 | 611 | 5,428 | +| [v1.4.10](https://github.com/laurent22/joplin/releases/tag/v1.4.10) (p) | 2020-11-14T09:53:15Z | 694 | 232 | 703 | 1,629 | | [v1.4.9](https://github.com/laurent22/joplin/releases/tag/v1.4.9) (p) | 2020-11-11T14:23:17Z | 873 | 177 | 420 | 1,470 | | [v1.4.7](https://github.com/laurent22/joplin/releases/tag/v1.4.7) (p) | 2020-11-07T18:23:29Z | 559 | 208 | 534 | 1,301 | -| [v1.3.18](https://github.com/laurent22/joplin/releases/tag/v1.3.18) | 2020-11-06T12:07:02Z | 35,066 | 11,387 | 10,548 | 57,001 | +| [v1.3.18](https://github.com/laurent22/joplin/releases/tag/v1.3.18) | 2020-11-06T12:07:02Z | 35,217 | 11,388 | 10,548 | 57,153 | | [v1.3.17](https://github.com/laurent22/joplin/releases/tag/v1.3.17) (p) | 2020-11-06T11:35:15Z | 86 | 64 | 43 | 193 | -| [v1.4.6](https://github.com/laurent22/joplin/releases/tag/v1.4.6) (p) | 2020-11-05T22:44:12Z | 748 | 128 | 70 | 946 | -| [v1.3.15](https://github.com/laurent22/joplin/releases/tag/v1.3.15) | 2020-11-04T12:22:50Z | 2,782 | 1,343 | 871 | 4,996 | +| [v1.4.6](https://github.com/laurent22/joplin/releases/tag/v1.4.6) (p) | 2020-11-05T22:44:12Z | 750 | 128 | 70 | 948 | +| [v1.3.15](https://github.com/laurent22/joplin/releases/tag/v1.3.15) | 2020-11-04T12:22:50Z | 2,786 | 1,343 | 871 | 5,000 | | [v1.3.11](https://github.com/laurent22/joplin/releases/tag/v1.3.11) (p) | 2020-10-31T13:22:20Z | 740 | 220 | 499 | 1,459 | -| [v1.3.10](https://github.com/laurent22/joplin/releases/tag/v1.3.10) (p) | 2020-10-29T13:27:14Z | 412 | 150 | 334 | 896 | +| [v1.3.10](https://github.com/laurent22/joplin/releases/tag/v1.3.10) (p) | 2020-10-29T13:27:14Z | 413 | 151 | 334 | 898 | | [v1.3.9](https://github.com/laurent22/joplin/releases/tag/v1.3.9) (p) | 2020-10-23T16:04:26Z | 894 | 278 | 652 | 1,824 | | [v1.3.8](https://github.com/laurent22/joplin/releases/tag/v1.3.8) (p) | 2020-10-21T18:46:29Z | 559 | 153 | 350 | 1,062 | | [v1.3.7](https://github.com/laurent22/joplin/releases/tag/v1.3.7) (p) | 2020-10-20T11:35:55Z | 333 | 121 | 362 | 816 | @@ -231,150 +235,150 @@ updated: 2025-02-01T00:53:21Z | [v1.3.3](https://github.com/laurent22/joplin/releases/tag/v1.3.3) (p) | 2020-10-17T10:56:57Z | 156 | 81 | 52 | 289 | | [v1.3.2](https://github.com/laurent22/joplin/releases/tag/v1.3.2) (p) | 2020-10-11T20:39:49Z | 709 | 217 | 587 | 1,513 | | [v1.3.1](https://github.com/laurent22/joplin/releases/tag/v1.3.1) (p) | 2020-10-11T15:10:18Z | 121 | 87 | 63 | 271 | -| [v1.2.6](https://github.com/laurent22/joplin/releases/tag/v1.2.6) | 2020-10-09T13:56:59Z | 49,034 | 17,792 | 14,090 | 80,916 | +| [v1.2.6](https://github.com/laurent22/joplin/releases/tag/v1.2.6) | 2020-10-09T13:56:59Z | 49,182 | 17,792 | 14,090 | 81,064 | | [v1.2.4](https://github.com/laurent22/joplin/releases/tag/v1.2.4) (p) | 2020-09-30T07:34:29Z | 860 | 288 | 821 | 1,969 | | [v1.2.3](https://github.com/laurent22/joplin/releases/tag/v1.2.3) (p) | 2020-09-29T15:13:02Z | 258 | 100 | 102 | 460 | | [v1.2.2](https://github.com/laurent22/joplin/releases/tag/v1.2.2) (p) | 2020-09-22T20:31:55Z | 1,155 | 242 | 658 | 2,055 | -| [v1.1.4](https://github.com/laurent22/joplin/releases/tag/v1.1.4) | 2020-09-21T11:20:09Z | 28,226 | 13,552 | 7,784 | 49,562 | +| [v1.1.4](https://github.com/laurent22/joplin/releases/tag/v1.1.4) | 2020-09-21T11:20:09Z | 28,229 | 13,554 | 7,785 | 49,568 | | [v1.1.3](https://github.com/laurent22/joplin/releases/tag/v1.1.3) (p) | 2020-09-17T10:30:37Z | 626 | 189 | 484 | 1,299 | | [v1.1.2](https://github.com/laurent22/joplin/releases/tag/v1.1.2) (p) | 2020-09-15T12:58:38Z | 420 | 152 | 271 | 843 | | [v1.1.1](https://github.com/laurent22/joplin/releases/tag/v1.1.1) (p) | 2020-09-11T23:32:47Z | 597 | 232 | 371 | 1,200 | -| [v1.0.245](https://github.com/laurent22/joplin/releases/tag/v1.0.245) | 2020-09-09T12:56:10Z | 22,918 | 10,064 | 5,676 | 38,658 | -| [v1.0.242](https://github.com/laurent22/joplin/releases/tag/v1.0.242) | 2020-09-04T22:00:34Z | 12,904 | 6,463 | 3,046 | 22,413 | -| [v1.0.241](https://github.com/laurent22/joplin/releases/tag/v1.0.241) | 2020-09-04T18:06:00Z | 26,639 | 5,987 | 5,146 | 37,772 | +| [v1.0.245](https://github.com/laurent22/joplin/releases/tag/v1.0.245) | 2020-09-09T12:56:10Z | 22,949 | 10,065 | 5,676 | 38,690 | +| [v1.0.242](https://github.com/laurent22/joplin/releases/tag/v1.0.242) | 2020-09-04T22:00:34Z | 12,906 | 6,463 | 3,046 | 22,415 | +| [v1.0.241](https://github.com/laurent22/joplin/releases/tag/v1.0.241) | 2020-09-04T18:06:00Z | 26,653 | 5,987 | 5,147 | 37,787 | | [v1.0.239](https://github.com/laurent22/joplin/releases/tag/v1.0.239) (p) | 2020-09-01T21:56:36Z | 988 | 268 | 425 | 1,681 | | [v1.0.237](https://github.com/laurent22/joplin/releases/tag/v1.0.237) (p) | 2020-08-29T15:38:04Z | 637 | 963 | 360 | 1,960 | | [v1.0.236](https://github.com/laurent22/joplin/releases/tag/v1.0.236) (p) | 2020-08-28T09:16:54Z | 360 | 150 | 126 | 636 | | [v1.0.235](https://github.com/laurent22/joplin/releases/tag/v1.0.235) (p) | 2020-08-18T22:08:01Z | 2,063 | 528 | 944 | 3,535 | | [v1.0.234](https://github.com/laurent22/joplin/releases/tag/v1.0.234) (p) | 2020-08-17T23:13:02Z | 696 | 164 | 123 | 983 | -| [v1.0.233](https://github.com/laurent22/joplin/releases/tag/v1.0.233) | 2020-08-01T14:51:15Z | 47,641 | 18,246 | 12,387 | 78,274 | +| [v1.0.233](https://github.com/laurent22/joplin/releases/tag/v1.0.233) | 2020-08-01T14:51:15Z | 47,789 | 18,246 | 12,387 | 78,422 | | [v1.0.232](https://github.com/laurent22/joplin/releases/tag/v1.0.232) (p) | 2020-07-28T22:34:40Z | 703 | 262 | 201 | 1,166 | -| [v1.0.227](https://github.com/laurent22/joplin/releases/tag/v1.0.227) | 2020-07-07T20:44:54Z | 41,729 | 15,324 | 9,672 | 66,725 | -| [v1.0.226](https://github.com/laurent22/joplin/releases/tag/v1.0.226) (p) | 2020-07-04T10:21:26Z | 4,970 | 2,293 | 710 | 7,973 | -| [v1.0.224](https://github.com/laurent22/joplin/releases/tag/v1.0.224) | 2020-06-20T22:26:08Z | 25,266 | 11,049 | 6,031 | 42,346 | +| [v1.0.227](https://github.com/laurent22/joplin/releases/tag/v1.0.227) | 2020-07-07T20:44:54Z | 41,969 | 15,324 | 9,673 | 66,966 | +| [v1.0.226](https://github.com/laurent22/joplin/releases/tag/v1.0.226) (p) | 2020-07-04T10:21:26Z | 4,972 | 2,293 | 710 | 7,975 | +| [v1.0.224](https://github.com/laurent22/joplin/releases/tag/v1.0.224) | 2020-06-20T22:26:08Z | 25,294 | 11,049 | 6,031 | 42,374 | | [v1.0.223](https://github.com/laurent22/joplin/releases/tag/v1.0.223) (p) | 2020-06-20T11:51:27Z | 231 | 152 | 101 | 484 | -| [v1.0.221](https://github.com/laurent22/joplin/releases/tag/v1.0.221) (p) | 2020-06-20T01:44:20Z | 898 | 247 | 235 | 1,380 | -| [v1.0.220](https://github.com/laurent22/joplin/releases/tag/v1.0.220) | 2020-06-13T18:26:22Z | 32,819 | 9,969 | 6,444 | 49,232 | -| [v1.0.218](https://github.com/laurent22/joplin/releases/tag/v1.0.218) | 2020-06-07T10:43:34Z | 14,638 | 7,018 | 3,154 | 24,810 | +| [v1.0.221](https://github.com/laurent22/joplin/releases/tag/v1.0.221) (p) | 2020-06-20T01:44:20Z | 901 | 247 | 235 | 1,383 | +| [v1.0.220](https://github.com/laurent22/joplin/releases/tag/v1.0.220) | 2020-06-13T18:26:22Z | 32,823 | 9,970 | 6,445 | 49,238 | +| [v1.0.218](https://github.com/laurent22/joplin/releases/tag/v1.0.218) | 2020-06-07T10:43:34Z | 14,640 | 7,018 | 3,154 | 24,812 | | [v1.0.217](https://github.com/laurent22/joplin/releases/tag/v1.0.217) (p) | 2020-06-06T15:17:27Z | 280 | 138 | 85 | 503 | -| [v1.0.216](https://github.com/laurent22/joplin/releases/tag/v1.0.216) | 2020-05-24T14:21:01Z | 41,402 | 14,352 | 10,217 | 65,971 | -| [v1.0.214](https://github.com/laurent22/joplin/releases/tag/v1.0.214) (p) | 2020-05-21T17:15:15Z | 7,080 | 3,513 | 789 | 11,382 | +| [v1.0.216](https://github.com/laurent22/joplin/releases/tag/v1.0.216) | 2020-05-24T14:21:01Z | 41,560 | 14,355 | 10,220 | 66,135 | +| [v1.0.214](https://github.com/laurent22/joplin/releases/tag/v1.0.214) (p) | 2020-05-21T17:15:15Z | 7,104 | 3,513 | 789 | 11,406 | | [v1.0.212](https://github.com/laurent22/joplin/releases/tag/v1.0.212) (p) | 2020-05-21T07:48:39Z | 255 | 112 | 72 | 439 | | [v1.0.211](https://github.com/laurent22/joplin/releases/tag/v1.0.211) (p) | 2020-05-20T08:59:16Z | 354 | 174 | 113 | 641 | | [v1.0.209](https://github.com/laurent22/joplin/releases/tag/v1.0.209) (p) | 2020-05-17T18:32:51Z | 1,449 | 894 | 173 | 2,516 | | [v1.0.207](https://github.com/laurent22/joplin/releases/tag/v1.0.207) (p) | 2020-05-10T16:37:35Z | 1,252 | 305 | 1,044 | 2,601 | -| [v1.0.201](https://github.com/laurent22/joplin/releases/tag/v1.0.201) | 2020-04-15T22:55:13Z | 54,403 | 20,100 | 18,214 | 92,717 | -| [v1.0.200](https://github.com/laurent22/joplin/releases/tag/v1.0.200) | 2020-04-12T12:17:46Z | 9,631 | 4,935 | 1,931 | 16,497 | -| [v1.0.199](https://github.com/laurent22/joplin/releases/tag/v1.0.199) | 2020-04-10T18:41:58Z | 19,841 | 5,931 | 3,827 | 29,599 | -| [v1.0.197](https://github.com/laurent22/joplin/releases/tag/v1.0.197) | 2020-03-30T17:21:22Z | 23,476 | 9,870 | 6,588 | 39,934 | -| [v1.0.195](https://github.com/laurent22/joplin/releases/tag/v1.0.195) | 2020-03-22T19:56:12Z | 19,171 | 7,993 | 4,535 | 31,699 | -| [v1.0.194](https://github.com/laurent22/joplin/releases/tag/v1.0.194) (p) | 2020-03-14T00:00:32Z | 1,339 | 1,429 | 543 | 3,311 | -| [v1.0.193](https://github.com/laurent22/joplin/releases/tag/v1.0.193) | 2020-03-08T08:58:53Z | 28,842 | 10,967 | 7,444 | 47,253 | -| [v1.0.192](https://github.com/laurent22/joplin/releases/tag/v1.0.192) (p) | 2020-03-06T23:27:52Z | 558 | 164 | 113 | 835 | +| [v1.0.201](https://github.com/laurent22/joplin/releases/tag/v1.0.201) | 2020-04-15T22:55:13Z | 54,405 | 20,101 | 18,216 | 92,722 | +| [v1.0.200](https://github.com/laurent22/joplin/releases/tag/v1.0.200) | 2020-04-12T12:17:46Z | 9,635 | 4,935 | 1,931 | 16,501 | +| [v1.0.199](https://github.com/laurent22/joplin/releases/tag/v1.0.199) | 2020-04-10T18:41:58Z | 19,864 | 5,931 | 3,827 | 29,622 | +| [v1.0.197](https://github.com/laurent22/joplin/releases/tag/v1.0.197) | 2020-03-30T17:21:22Z | 23,482 | 9,870 | 6,609 | 39,961 | +| [v1.0.195](https://github.com/laurent22/joplin/releases/tag/v1.0.195) | 2020-03-22T19:56:12Z | 19,174 | 7,995 | 4,535 | 31,704 | +| [v1.0.194](https://github.com/laurent22/joplin/releases/tag/v1.0.194) (p) | 2020-03-14T00:00:32Z | 1,339 | 1,431 | 543 | 3,313 | +| [v1.0.193](https://github.com/laurent22/joplin/releases/tag/v1.0.193) | 2020-03-08T08:58:53Z | 28,843 | 10,968 | 7,445 | 47,256 | +| [v1.0.192](https://github.com/laurent22/joplin/releases/tag/v1.0.192) (p) | 2020-03-06T23:27:52Z | 560 | 164 | 113 | 837 | | [v1.0.190](https://github.com/laurent22/joplin/releases/tag/v1.0.190) (p) | 2020-03-06T01:22:22Z | 439 | 133 | 110 | 682 | -| [v1.0.189](https://github.com/laurent22/joplin/releases/tag/v1.0.189) (p) | 2020-03-04T17:27:15Z | 427 | 136 | 121 | 684 | +| [v1.0.189](https://github.com/laurent22/joplin/releases/tag/v1.0.189) (p) | 2020-03-04T17:27:15Z | 428 | 136 | 122 | 686 | | [v1.0.187](https://github.com/laurent22/joplin/releases/tag/v1.0.187) (p) | 2020-03-01T12:31:06Z | 983 | 277 | 298 | 1,558 | -| [v1.0.179](https://github.com/laurent22/joplin/releases/tag/v1.0.179) | 2020-01-24T22:42:41Z | 71,728 | 29,097 | 22,595 | 123,420 | +| [v1.0.179](https://github.com/laurent22/joplin/releases/tag/v1.0.179) | 2020-01-24T22:42:41Z | 71,750 | 29,118 | 22,596 | 123,464 | | [v1.0.178](https://github.com/laurent22/joplin/releases/tag/v1.0.178) | 2020-01-20T19:06:45Z | 17,677 | 6,006 | 2,620 | 26,303 | -| [v1.0.177](https://github.com/laurent22/joplin/releases/tag/v1.0.177) (p) | 2019-12-30T14:40:40Z | 2,011 | 478 | 748 | 3,237 | +| [v1.0.177](https://github.com/laurent22/joplin/releases/tag/v1.0.177) (p) | 2019-12-30T14:40:40Z | 2,013 | 478 | 749 | 3,240 | | [v1.0.176](https://github.com/laurent22/joplin/releases/tag/v1.0.176) (p) | 2019-12-14T10:36:44Z | 3,176 | 2,579 | 496 | 6,251 | -| [v1.0.175](https://github.com/laurent22/joplin/releases/tag/v1.0.175) | 2019-12-08T11:48:47Z | 73,780 | 17,022 | 16,625 | 107,427 | -| [v1.0.174](https://github.com/laurent22/joplin/releases/tag/v1.0.174) | 2019-11-12T18:20:58Z | 30,740 | 11,802 | 8,253 | 50,795 | -| [v1.0.173](https://github.com/laurent22/joplin/releases/tag/v1.0.173) | 2019-11-11T08:33:35Z | 5,152 | 2,122 | 772 | 8,046 | -| [v1.0.170](https://github.com/laurent22/joplin/releases/tag/v1.0.170) | 2019-10-13T22:13:04Z | 27,856 | 8,821 | 7,714 | 44,391 | -| [v1.0.169](https://github.com/laurent22/joplin/releases/tag/v1.0.169) | 2019-09-27T18:35:13Z | 17,274 | 5,971 | 3,784 | 27,029 | +| [v1.0.175](https://github.com/laurent22/joplin/releases/tag/v1.0.175) | 2019-12-08T11:48:47Z | 73,789 | 17,022 | 16,625 | 107,436 | +| [v1.0.174](https://github.com/laurent22/joplin/releases/tag/v1.0.174) | 2019-11-12T18:20:58Z | 30,749 | 11,803 | 8,253 | 50,805 | +| [v1.0.173](https://github.com/laurent22/joplin/releases/tag/v1.0.173) | 2019-11-11T08:33:35Z | 5,154 | 2,123 | 773 | 8,050 | +| [v1.0.170](https://github.com/laurent22/joplin/releases/tag/v1.0.170) | 2019-10-13T22:13:04Z | 27,868 | 8,821 | 7,715 | 44,404 | +| [v1.0.169](https://github.com/laurent22/joplin/releases/tag/v1.0.169) | 2019-09-27T18:35:13Z | 17,282 | 5,971 | 3,784 | 27,037 | | [v1.0.168](https://github.com/laurent22/joplin/releases/tag/v1.0.168) | 2019-09-25T21:21:38Z | 5,394 | 2,316 | 745 | 8,455 | -| [v1.0.167](https://github.com/laurent22/joplin/releases/tag/v1.0.167) | 2019-09-10T08:48:37Z | 17,000 | 5,747 | 3,740 | 26,487 | +| [v1.0.167](https://github.com/laurent22/joplin/releases/tag/v1.0.167) | 2019-09-10T08:48:37Z | 17,011 | 5,747 | 3,740 | 26,498 | | [v1.0.166](https://github.com/laurent22/joplin/releases/tag/v1.0.166) | 2019-09-09T17:35:54Z | 2,006 | 601 | 261 | 2,868 | -| [v1.0.165](https://github.com/laurent22/joplin/releases/tag/v1.0.165) | 2019-08-14T21:46:29Z | 19,119 | 7,022 | 5,491 | 31,632 | -| [v1.0.161](https://github.com/laurent22/joplin/releases/tag/v1.0.161) | 2019-07-13T18:30:00Z | 19,445 | 6,391 | 4,161 | 29,997 | -| [v1.0.160](https://github.com/laurent22/joplin/releases/tag/v1.0.160) | 2019-06-15T00:21:40Z | 30,852 | 7,795 | 8,132 | 46,779 | -| [v1.0.159](https://github.com/laurent22/joplin/releases/tag/v1.0.159) | 2019-06-08T00:00:19Z | 5,272 | 2,220 | 1,289 | 8,781 | -| [v1.0.158](https://github.com/laurent22/joplin/releases/tag/v1.0.158) | 2019-05-27T19:01:18Z | 9,890 | 3,588 | 1,961 | 15,439 | +| [v1.0.165](https://github.com/laurent22/joplin/releases/tag/v1.0.165) | 2019-08-14T21:46:29Z | 19,120 | 7,022 | 5,491 | 31,633 | +| [v1.0.161](https://github.com/laurent22/joplin/releases/tag/v1.0.161) | 2019-07-13T18:30:00Z | 19,449 | 6,391 | 4,161 | 30,001 | +| [v1.0.160](https://github.com/laurent22/joplin/releases/tag/v1.0.160) | 2019-06-15T00:21:40Z | 30,866 | 7,796 | 8,133 | 46,795 | +| [v1.0.159](https://github.com/laurent22/joplin/releases/tag/v1.0.159) | 2019-06-08T00:00:19Z | 5,272 | 2,220 | 1,297 | 8,789 | +| [v1.0.158](https://github.com/laurent22/joplin/releases/tag/v1.0.158) | 2019-05-27T19:01:18Z | 9,891 | 3,590 | 1,962 | 15,443 | | [v1.0.157](https://github.com/laurent22/joplin/releases/tag/v1.0.157) | 2019-05-26T17:55:53Z | 2,248 | 881 | 314 | 3,443 | | [v1.0.153](https://github.com/laurent22/joplin/releases/tag/v1.0.153) (p) | 2019-05-15T06:27:29Z | 916 | 143 | 131 | 1,190 | -| [v1.0.152](https://github.com/laurent22/joplin/releases/tag/v1.0.152) | 2019-05-13T09:08:07Z | 13,946 | 4,475 | 4,091 | 22,512 | -| [v1.0.151](https://github.com/laurent22/joplin/releases/tag/v1.0.151) | 2019-05-12T15:14:32Z | 2,002 | 574 | 984 | 3,560 | +| [v1.0.152](https://github.com/laurent22/joplin/releases/tag/v1.0.152) | 2019-05-13T09:08:07Z | 13,948 | 4,475 | 4,091 | 22,514 | +| [v1.0.151](https://github.com/laurent22/joplin/releases/tag/v1.0.151) | 2019-05-12T15:14:32Z | 2,002 | 575 | 984 | 3,561 | | [v1.0.150](https://github.com/laurent22/joplin/releases/tag/v1.0.150) | 2019-05-12T11:27:48Z | 496 | 175 | 94 | 765 | | [v1.0.148](https://github.com/laurent22/joplin/releases/tag/v1.0.148) (p) | 2019-05-08T19:12:24Z | 186 | 93 | 120 | 399 | | [v1.0.145](https://github.com/laurent22/joplin/releases/tag/v1.0.145) | 2019-05-03T09:16:53Z | 7,083 | 2,901 | 1,465 | 11,449 | -| [v1.0.143](https://github.com/laurent22/joplin/releases/tag/v1.0.143) | 2019-04-22T10:51:38Z | 11,993 | 3,593 | 2,808 | 18,394 | +| [v1.0.143](https://github.com/laurent22/joplin/releases/tag/v1.0.143) | 2019-04-22T10:51:38Z | 11,994 | 3,593 | 2,808 | 18,395 | | [v1.0.142](https://github.com/laurent22/joplin/releases/tag/v1.0.142) | 2019-04-02T16:44:51Z | 14,863 | 4,607 | 4,757 | 24,227 | -| [v1.0.140](https://github.com/laurent22/joplin/releases/tag/v1.0.140) | 2019-03-10T20:59:58Z | 13,723 | 4,217 | 3,406 | 21,346 | +| [v1.0.140](https://github.com/laurent22/joplin/releases/tag/v1.0.140) | 2019-03-10T20:59:58Z | 13,725 | 4,217 | 3,407 | 21,349 | | [v1.0.139](https://github.com/laurent22/joplin/releases/tag/v1.0.139) (p) | 2019-03-09T10:06:48Z | 186 | 104 | 69 | 359 | -| [v1.0.138](https://github.com/laurent22/joplin/releases/tag/v1.0.138) (p) | 2019-03-03T17:23:00Z | 223 | 128 | 107 | 458 | +| [v1.0.138](https://github.com/laurent22/joplin/releases/tag/v1.0.138) (p) | 2019-03-03T17:23:00Z | 226 | 128 | 107 | 461 | | [v1.0.137](https://github.com/laurent22/joplin/releases/tag/v1.0.137) (p) | 2019-03-03T01:12:51Z | 650 | 95 | 107 | 852 | | [v1.0.135](https://github.com/laurent22/joplin/releases/tag/v1.0.135) | 2019-02-27T23:36:57Z | 12,704 | 3,996 | 4,106 | 20,806 | | [v1.0.134](https://github.com/laurent22/joplin/releases/tag/v1.0.134) | 2019-02-27T10:21:44Z | 1,512 | 610 | 244 | 2,366 | | [v1.0.132](https://github.com/laurent22/joplin/releases/tag/v1.0.132) | 2019-02-26T23:02:05Z | 1,150 | 491 | 119 | 1,760 | -| [v1.0.127](https://github.com/laurent22/joplin/releases/tag/v1.0.127) | 2019-02-14T23:12:48Z | 10,023 | 3,212 | 2,957 | 16,192 | +| [v1.0.127](https://github.com/laurent22/joplin/releases/tag/v1.0.127) | 2019-02-14T23:12:48Z | 10,026 | 3,212 | 2,958 | 16,196 | | [v1.0.126](https://github.com/laurent22/joplin/releases/tag/v1.0.126) (p) | 2019-02-09T19:46:16Z | 1,001 | 115 | 138 | 1,254 | -| [v1.0.125](https://github.com/laurent22/joplin/releases/tag/v1.0.125) | 2019-01-26T18:14:33Z | 10,392 | 3,596 | 1,726 | 15,714 | -| [v1.0.120](https://github.com/laurent22/joplin/releases/tag/v1.0.120) | 2019-01-10T21:42:53Z | 15,705 | 5,251 | 6,544 | 27,500 | -| [v1.0.119](https://github.com/laurent22/joplin/releases/tag/v1.0.119) | 2018-12-18T12:40:22Z | 9,006 | 3,301 | 2,040 | 14,347 | +| [v1.0.125](https://github.com/laurent22/joplin/releases/tag/v1.0.125) | 2019-01-26T18:14:33Z | 10,403 | 3,597 | 1,726 | 15,726 | +| [v1.0.120](https://github.com/laurent22/joplin/releases/tag/v1.0.120) | 2019-01-10T21:42:53Z | 15,706 | 5,252 | 6,546 | 27,504 | +| [v1.0.119](https://github.com/laurent22/joplin/releases/tag/v1.0.119) | 2018-12-18T12:40:22Z | 9,007 | 3,302 | 2,041 | 14,350 | | [v1.0.118](https://github.com/laurent22/joplin/releases/tag/v1.0.118) | 2019-01-11T08:34:13Z | 763 | 288 | 115 | 1,166 | -| [v1.0.117](https://github.com/laurent22/joplin/releases/tag/v1.0.117) | 2018-11-24T12:05:24Z | 16,347 | 4,940 | 6,409 | 27,696 | -| [v1.0.116](https://github.com/laurent22/joplin/releases/tag/v1.0.116) | 2018-11-20T19:09:24Z | 4,069 | 1,165 | 740 | 5,974 | +| [v1.0.117](https://github.com/laurent22/joplin/releases/tag/v1.0.117) | 2018-11-24T12:05:24Z | 16,348 | 4,940 | 6,409 | 27,697 | +| [v1.0.116](https://github.com/laurent22/joplin/releases/tag/v1.0.116) | 2018-11-20T19:09:24Z | 4,069 | 1,167 | 740 | 5,976 | | [v1.0.115](https://github.com/laurent22/joplin/releases/tag/v1.0.115) | 2018-11-16T16:52:02Z | 3,723 | 1,341 | 827 | 5,891 | | [v1.0.114](https://github.com/laurent22/joplin/releases/tag/v1.0.114) | 2018-10-24T20:14:10Z | 11,470 | 3,540 | 3,855 | 18,865 | -| [v1.0.111](https://github.com/laurent22/joplin/releases/tag/v1.0.111) | 2018-09-30T20:15:09Z | 12,486 | 3,526 | 3,707 | 19,719 | +| [v1.0.111](https://github.com/laurent22/joplin/releases/tag/v1.0.111) | 2018-09-30T20:15:09Z | 12,513 | 3,536 | 3,707 | 19,756 | | [v1.0.110](https://github.com/laurent22/joplin/releases/tag/v1.0.110) | 2018-09-29T12:29:21Z | 1,010 | 449 | 142 | 1,601 | | [v1.0.109](https://github.com/laurent22/joplin/releases/tag/v1.0.109) | 2018-09-27T18:01:41Z | 2,146 | 743 | 356 | 3,245 | | [v1.0.108](https://github.com/laurent22/joplin/releases/tag/v1.0.108) (p) | 2018-09-29T18:49:29Z | 66 | 60 | 38 | 164 | | [v1.0.107](https://github.com/laurent22/joplin/releases/tag/v1.0.107) | 2018-09-16T19:51:07Z | 7,209 | 2,175 | 1,734 | 11,118 | | [v1.0.106](https://github.com/laurent22/joplin/releases/tag/v1.0.106) | 2018-09-08T15:23:40Z | 4,601 | 1,495 | 341 | 6,437 | -| [v1.0.105](https://github.com/laurent22/joplin/releases/tag/v1.0.105) | 2018-09-05T11:29:36Z | 4,701 | 1,632 | 1,484 | 7,817 | -| [v1.0.104](https://github.com/laurent22/joplin/releases/tag/v1.0.104) | 2018-06-28T20:25:36Z | 15,186 | 4,739 | 7,409 | 27,334 | +| [v1.0.105](https://github.com/laurent22/joplin/releases/tag/v1.0.105) | 2018-09-05T11:29:36Z | 4,702 | 1,632 | 1,485 | 7,819 | +| [v1.0.104](https://github.com/laurent22/joplin/releases/tag/v1.0.104) | 2018-06-28T20:25:36Z | 15,187 | 4,740 | 7,410 | 27,337 | | [v1.0.103](https://github.com/laurent22/joplin/releases/tag/v1.0.103) | 2018-06-21T19:38:13Z | 2,128 | 926 | 703 | 3,757 | | [v1.0.101](https://github.com/laurent22/joplin/releases/tag/v1.0.101) | 2018-06-17T18:35:11Z | 1,374 | 648 | 434 | 2,456 | | [v1.0.100](https://github.com/laurent22/joplin/releases/tag/v1.0.100) | 2018-06-14T17:41:43Z | 959 | 477 | 272 | 1,708 | -| [v1.0.99](https://github.com/laurent22/joplin/releases/tag/v1.0.99) | 2018-06-10T13:18:23Z | 1,325 | 638 | 406 | 2,369 | +| [v1.0.99](https://github.com/laurent22/joplin/releases/tag/v1.0.99) | 2018-06-10T13:18:23Z | 1,329 | 642 | 408 | 2,379 | | [v1.0.97](https://github.com/laurent22/joplin/releases/tag/v1.0.97) | 2018-06-09T19:23:34Z | 357 | 193 | 86 | 636 | -| [v1.0.96](https://github.com/laurent22/joplin/releases/tag/v1.0.96) | 2018-05-26T16:36:39Z | 2,800 | 1,267 | 1,748 | 5,815 | +| [v1.0.96](https://github.com/laurent22/joplin/releases/tag/v1.0.96) | 2018-05-26T16:36:39Z | 2,800 | 1,267 | 1,749 | 5,816 | | [v1.0.95](https://github.com/laurent22/joplin/releases/tag/v1.0.95) | 2018-05-25T13:04:30Z | 466 | 258 | 163 | 887 | | [v1.0.94](https://github.com/laurent22/joplin/releases/tag/v1.0.94) | 2018-05-21T20:52:59Z | 1,214 | 629 | 444 | 2,287 | -| [v1.0.93](https://github.com/laurent22/joplin/releases/tag/v1.0.93) | 2018-05-14T11:36:01Z | 1,878 | 1,356 | 802 | 4,036 | -| [v1.0.91](https://github.com/laurent22/joplin/releases/tag/v1.0.91) | 2018-05-10T14:48:04Z | 872 | 593 | 364 | 1,829 | -| [v1.0.89](https://github.com/laurent22/joplin/releases/tag/v1.0.89) | 2018-05-09T13:05:05Z | 546 | 284 | 157 | 987 | +| [v1.0.93](https://github.com/laurent22/joplin/releases/tag/v1.0.93) | 2018-05-14T11:36:01Z | 1,878 | 1,357 | 802 | 4,037 | +| [v1.0.91](https://github.com/laurent22/joplin/releases/tag/v1.0.91) | 2018-05-10T14:48:04Z | 872 | 594 | 364 | 1,830 | +| [v1.0.89](https://github.com/laurent22/joplin/releases/tag/v1.0.89) | 2018-05-09T13:05:05Z | 546 | 285 | 157 | 988 | | [v1.0.85](https://github.com/laurent22/joplin/releases/tag/v1.0.85) | 2018-05-01T21:08:24Z | 1,696 | 992 | 677 | 3,365 | -| [v1.0.83](https://github.com/laurent22/joplin/releases/tag/v1.0.83) | 2018-04-04T19:43:58Z | 5,627 | 2,577 | 2,703 | 10,907 | +| [v1.0.83](https://github.com/laurent22/joplin/releases/tag/v1.0.83) | 2018-04-04T19:43:58Z | 5,629 | 2,577 | 2,703 | 10,909 | | [v1.0.82](https://github.com/laurent22/joplin/releases/tag/v1.0.82) | 2018-03-31T19:16:31Z | 751 | 450 | 167 | 1,368 | | [v1.0.81](https://github.com/laurent22/joplin/releases/tag/v1.0.81) | 2018-03-28T08:13:58Z | 1,039 | 637 | 826 | 2,502 | | [v1.0.79](https://github.com/laurent22/joplin/releases/tag/v1.0.79) | 2018-03-23T18:00:11Z | 971 | 580 | 428 | 1,979 | | [v1.0.78](https://github.com/laurent22/joplin/releases/tag/v1.0.78) | 2018-03-17T15:27:18Z | 1,354 | 943 | 915 | 3,212 | | [v1.0.77](https://github.com/laurent22/joplin/releases/tag/v1.0.77) | 2018-03-16T15:12:35Z | 202 | 144 | 88 | 434 | | [v1.0.72](https://github.com/laurent22/joplin/releases/tag/v1.0.72) | 2018-03-14T09:44:35Z | 451 | 296 | 98 | 845 | -| [v1.0.70](https://github.com/laurent22/joplin/releases/tag/v1.0.70) | 2018-02-28T20:04:30Z | 2,003 | 1,093 | 1,296 | 4,392 | -| [v1.0.67](https://github.com/laurent22/joplin/releases/tag/v1.0.67) | 2018-02-19T22:51:08Z | 1,945 | 645 | 0 | 2,590 | -| [v1.0.66](https://github.com/laurent22/joplin/releases/tag/v1.0.66) | 2018-02-18T23:09:09Z | 449 | 174 | 108 | 731 | -| [v1.0.65](https://github.com/laurent22/joplin/releases/tag/v1.0.65) | 2018-02-17T20:02:25Z | 348 | 170 | 156 | 674 | -| [v1.0.64](https://github.com/laurent22/joplin/releases/tag/v1.0.64) | 2018-02-16T00:58:20Z | 1,194 | 583 | 1,146 | 2,923 | -| [v1.0.63](https://github.com/laurent22/joplin/releases/tag/v1.0.63) | 2018-02-14T19:40:36Z | 412 | 200 | 115 | 727 | -| [v1.0.62](https://github.com/laurent22/joplin/releases/tag/v1.0.62) | 2018-02-12T20:19:58Z | 713 | 345 | 399 | 1,457 | -| [v0.10.61](https://github.com/laurent22/joplin/releases/tag/v0.10.61) | 2018-02-08T18:27:39Z | 1,122 | 678 | 987 | 2,787 | -| [v0.10.60](https://github.com/laurent22/joplin/releases/tag/v0.10.60) | 2018-02-06T13:09:56Z | 770 | 565 | 577 | 1,912 | -| [v0.10.54](https://github.com/laurent22/joplin/releases/tag/v0.10.54) | 2018-01-31T20:21:30Z | 1,958 | 1,503 | 347 | 3,808 | -| [v0.10.52](https://github.com/laurent22/joplin/releases/tag/v0.10.52) | 2018-01-31T19:25:18Z | 163 | 677 | 41 | 881 | -| [v0.10.51](https://github.com/laurent22/joplin/releases/tag/v0.10.51) | 2018-01-28T18:47:02Z | 1,429 | 1,643 | 352 | 3,424 | -| [v0.10.48](https://github.com/laurent22/joplin/releases/tag/v0.10.48) | 2018-01-23T11:19:51Z | 2,111 | 1,795 | 56 | 3,962 | -| [v0.10.47](https://github.com/laurent22/joplin/releases/tag/v0.10.47) | 2018-01-16T17:27:17Z | 1,333 | 1,317 | 91 | 2,741 | -| [v0.10.43](https://github.com/laurent22/joplin/releases/tag/v0.10.43) | 2018-01-08T10:12:10Z | 3,486 | 2,401 | 1,237 | 7,124 | -| [v0.10.41](https://github.com/laurent22/joplin/releases/tag/v0.10.41) | 2018-01-05T20:38:12Z | 1,243 | 1,597 | 266 | 3,106 | +| [v1.0.70](https://github.com/laurent22/joplin/releases/tag/v1.0.70) | 2018-02-28T20:04:30Z | 2,004 | 1,093 | 1,296 | 4,393 | +| [v1.0.67](https://github.com/laurent22/joplin/releases/tag/v1.0.67) | 2018-02-19T22:51:08Z | 1,947 | 645 | 0 | 2,592 | +| [v1.0.66](https://github.com/laurent22/joplin/releases/tag/v1.0.66) | 2018-02-18T23:09:09Z | 450 | 174 | 108 | 732 | +| [v1.0.65](https://github.com/laurent22/joplin/releases/tag/v1.0.65) | 2018-02-17T20:02:25Z | 349 | 171 | 156 | 676 | +| [v1.0.64](https://github.com/laurent22/joplin/releases/tag/v1.0.64) | 2018-02-16T00:58:20Z | 1,195 | 583 | 1,147 | 2,925 | +| [v1.0.63](https://github.com/laurent22/joplin/releases/tag/v1.0.63) | 2018-02-14T19:40:36Z | 413 | 200 | 115 | 728 | +| [v1.0.62](https://github.com/laurent22/joplin/releases/tag/v1.0.62) | 2018-02-12T20:19:58Z | 714 | 345 | 400 | 1,459 | +| [v0.10.61](https://github.com/laurent22/joplin/releases/tag/v0.10.61) | 2018-02-08T18:27:39Z | 1,123 | 678 | 987 | 2,788 | +| [v0.10.60](https://github.com/laurent22/joplin/releases/tag/v0.10.60) | 2018-02-06T13:09:56Z | 771 | 565 | 577 | 1,913 | +| [v0.10.54](https://github.com/laurent22/joplin/releases/tag/v0.10.54) | 2018-01-31T20:21:30Z | 1,959 | 1,503 | 347 | 3,809 | +| [v0.10.52](https://github.com/laurent22/joplin/releases/tag/v0.10.52) | 2018-01-31T19:25:18Z | 164 | 677 | 41 | 882 | +| [v0.10.51](https://github.com/laurent22/joplin/releases/tag/v0.10.51) | 2018-01-28T18:47:02Z | 1,430 | 1,643 | 352 | 3,425 | +| [v0.10.48](https://github.com/laurent22/joplin/releases/tag/v0.10.48) | 2018-01-23T11:19:51Z | 2,112 | 1,795 | 56 | 3,963 | +| [v0.10.47](https://github.com/laurent22/joplin/releases/tag/v0.10.47) | 2018-01-16T17:27:17Z | 1,334 | 1,317 | 91 | 2,742 | +| [v0.10.43](https://github.com/laurent22/joplin/releases/tag/v0.10.43) | 2018-01-08T10:12:10Z | 3,486 | 2,401 | 1,239 | 7,126 | +| [v0.10.41](https://github.com/laurent22/joplin/releases/tag/v0.10.41) | 2018-01-05T20:38:12Z | 1,245 | 1,597 | 266 | 3,108 | | [v0.10.40](https://github.com/laurent22/joplin/releases/tag/v0.10.40) | 2018-01-02T23:16:57Z | 1,638 | 1,832 | 362 | 3,832 | -| [v0.10.39](https://github.com/laurent22/joplin/releases/tag/v0.10.39) | 2017-12-11T21:19:44Z | 5,994 | 4,442 | 3,323 | 13,759 | +| [v0.10.39](https://github.com/laurent22/joplin/releases/tag/v0.10.39) | 2017-12-11T21:19:44Z | 5,995 | 4,443 | 3,324 | 13,762 | | [v0.10.38](https://github.com/laurent22/joplin/releases/tag/v0.10.38) | 2017-12-08T10:12:06Z | 1,089 | 1,274 | 330 | 2,693 | | [v0.10.37](https://github.com/laurent22/joplin/releases/tag/v0.10.37) | 2017-12-07T19:38:05Z | 291 | 892 | 109 | 1,292 | | [v0.10.36](https://github.com/laurent22/joplin/releases/tag/v0.10.36) | 2017-12-05T09:34:40Z | 1,052 | 1,405 | 465 | 2,922 | | [v0.10.35](https://github.com/laurent22/joplin/releases/tag/v0.10.35) | 2017-12-02T15:56:08Z | 1,611 | 1,593 | 769 | 3,973 | | [v0.10.34](https://github.com/laurent22/joplin/releases/tag/v0.10.34) | 2017-12-02T14:50:28Z | 117 | 717 | 85 | 919 | | [v0.10.33](https://github.com/laurent22/joplin/releases/tag/v0.10.33) | 2017-12-02T13:20:39Z | 95 | 711 | 51 | 857 | -| [v0.10.31](https://github.com/laurent22/joplin/releases/tag/v0.10.31) | 2017-12-01T09:56:44Z | 918 | 1,499 | 435 | 2,852 | -| [v0.10.30](https://github.com/laurent22/joplin/releases/tag/v0.10.30) | 2017-11-30T20:28:16Z | 841 | 1,428 | 452 | 2,721 | -| [v0.10.28](https://github.com/laurent22/joplin/releases/tag/v0.10.28) | 2017-11-30T01:07:46Z | 1,490 | 1,761 | 906 | 4,157 | -| [v0.10.26](https://github.com/laurent22/joplin/releases/tag/v0.10.26) | 2017-11-29T16:02:17Z | 306 | 756 | 291 | 1,353 | -| [v0.10.25](https://github.com/laurent22/joplin/releases/tag/v0.10.25) | 2017-11-24T14:27:49Z | 258 | 752 | 6,769 | 7,779 | -| [v0.10.23](https://github.com/laurent22/joplin/releases/tag/v0.10.23) | 2017-11-21T19:38:41Z | 246 | 714 | 65 | 1,025 | -| [v0.10.22](https://github.com/laurent22/joplin/releases/tag/v0.10.22) | 2017-11-20T21:45:57Z | 191 | 700 | 49 | 940 | -| [v0.10.21](https://github.com/laurent22/joplin/releases/tag/v0.10.21) | 2017-11-18T00:53:15Z | 169 | 693 | 44 | 906 | -| [v0.10.20](https://github.com/laurent22/joplin/releases/tag/v0.10.20) | 2017-11-17T17:18:25Z | 160 | 706 | 56 | 922 | -| [v0.10.19](https://github.com/laurent22/joplin/releases/tag/v0.10.19) | 2017-11-20T18:59:48Z | 179 | 707 | 54 | 940 | \ No newline at end of file +| [v0.10.31](https://github.com/laurent22/joplin/releases/tag/v0.10.31) | 2017-12-01T09:56:44Z | 918 | 1,499 | 436 | 2,853 | +| [v0.10.30](https://github.com/laurent22/joplin/releases/tag/v0.10.30) | 2017-11-30T20:28:16Z | 842 | 1,428 | 452 | 2,722 | +| [v0.10.28](https://github.com/laurent22/joplin/releases/tag/v0.10.28) | 2017-11-30T01:07:46Z | 1,491 | 1,761 | 906 | 4,158 | +| [v0.10.26](https://github.com/laurent22/joplin/releases/tag/v0.10.26) | 2017-11-29T16:02:17Z | 307 | 756 | 291 | 1,354 | +| [v0.10.25](https://github.com/laurent22/joplin/releases/tag/v0.10.25) | 2017-11-24T14:27:49Z | 259 | 752 | 6,771 | 7,782 | +| [v0.10.23](https://github.com/laurent22/joplin/releases/tag/v0.10.23) | 2017-11-21T19:38:41Z | 247 | 714 | 65 | 1,026 | +| [v0.10.22](https://github.com/laurent22/joplin/releases/tag/v0.10.22) | 2017-11-20T21:45:57Z | 192 | 700 | 49 | 941 | +| [v0.10.21](https://github.com/laurent22/joplin/releases/tag/v0.10.21) | 2017-11-18T00:53:15Z | 170 | 693 | 44 | 907 | +| [v0.10.20](https://github.com/laurent22/joplin/releases/tag/v0.10.20) | 2017-11-17T17:18:25Z | 161 | 706 | 56 | 923 | +| [v0.10.19](https://github.com/laurent22/joplin/releases/tag/v0.10.19) | 2017-11-20T18:59:48Z | 181 | 707 | 55 | 943 | \ No newline at end of file diff --git a/readme/apps/import_export.md b/readme/apps/import_export.md index 2274c01d8e..e4e9995ab5 100644 --- a/readme/apps/import_export.md +++ b/readme/apps/import_export.md @@ -48,15 +48,10 @@ Joplin can also import OneNote notebooks. To do this: ### Importing from other applications -In general the way to import notes from any application into Joplin is to convert the notes to ENEX files (Evernote format) and to import these ENEX files into Joplin using the method above. Most note-taking applications support ENEX files so it should be relatively straightforward. For help about specific applications, see below: - -* Standard Notes: Please see [this tutorial](https://programadorwebvalencia.com/migrate-notes-from-standard-notes-to-joplin/) -* Tomboy Notes: Export the notes to ENEX files [as described here](https://askubuntu.com/questions/243691/how-can-i-export-my-tomboy-notes-into-evernote/608551) for example, and import these ENEX files into Joplin. -* OneNote: First [import the notes from OneNote into Evernote](https://discussion.evernote.com/topic/107736-is-there-a-way-to-import-from-onenote-into-evernote-on-the-mac/). Then export the ENEX file from Evernote and import it into Joplin. -* NixNote: Synchronise with Evernote, then export the ENEX files and import them into Joplin. More info [in this thread](https://discourse.joplinapp.org/t/import-from-nixnote/183/3). +In general the way to import notes from other applications into Joplin is to convert the notes to ENEX files (Evernote format), HTML or Markdown, and to import these files into Joplin. For help about specific applications, see this wiki document: [Importing notes from other notebook applications](https://discourse.joplinapp.org/t/importing-notes-from-other-notebook-applications/22425). ## Exporting Joplin can export to the JEX format (Joplin Export file), which is a tar file that can contain multiple notes, notebooks, etc. This is a lossless format in that all the notes, but also metadata such as geo-location, updated time, tags, etc. are preserved. This format is convenient for backup purposes and can be re-imported into Joplin. A "raw" format is also available. This is the same as the JEX format except that the data is saved to a directory and each item represented by a single file. -Joplin is also capable of exporting to a number of other formats including HTML and PDF which can be done for single notes, notebooks or everything. \ No newline at end of file +Joplin is also capable of exporting to a number of other formats including HTML and PDF which can be done for single notes, notebooks or everything. diff --git a/readme/apps/multiple_instances.md b/readme/apps/multiple_instances.md new file mode 100644 index 0000000000..5c046e526e --- /dev/null +++ b/readme/apps/multiple_instances.md @@ -0,0 +1,48 @@ +# Running multiple instances of Joplin + +Joplin Desktop offers the capability to run **multiple instances** of the application simultaneously. Each instance is a separate application with its own configuration, plugins, and settings, meaning changes made in one instance do not affect the other. This feature is particularly useful for users who wish to maintain a clear separation between work and personal notes or utilise Joplin in multi-desktop environments. + +## Key Features of Multiple Instances + +1. **Independent Applications**: + +Each instance is completely isolated, operating as a standalone version of Joplin. This ensures no overlap in settings, plugins, or notes between instances. + +2. **Use Case Scenarios**: + +- Maintain separate environments for work and personal notes. +- Use Joplin on multi-desktop setups, with an instance on each virtual desktop. + +## Supported Number of Instances + +Currently, Joplin supports up to **two running instances**: + +1. **Primary Instance**: The primary application instance, with full access to all Joplin features. + +2. **Secondary Instance**: A secondary application instance that functions independently. However, it does not support the **Web Clipper service**, which can only run in the main instance. + +## How to Launch a Second Instance + +To start a second instance of Joplin: + +1. Open the main Joplin application. + +2. Navigate to the menu and select: + +**File** => **Open secondary app instance...** + +3. A new instance of Joplin will open with its own profile. + +This second instance operates independently, allowing you to customise it as needed. + +## Caveats + +### Launching the primary instance when the secondary instance is active + +Technically, the secondary instance is still initiated from the same executable file, which might confuse the operating system. Most operating systems reasonably assume that if you attempt to launch a GUI application that is already running, your intention is to bring that application into focus. + +In practical terms, this means the following: + +If you close the primary instance while the secondary instance remains open, and then attempt to reopen the primary instance—for instance, by clicking on its icon—the operating system will most likely refocus on the secondary instance instead of launching the primary one. To address this issue, the secondary instance includes a menu item labelled **Open primary app instance...**. Clicking on this option will explicitly launch the primary instance. + +In the same way, the secondary instance should generally be launched only from the first one, using the **Open secondary app instance...** menu item. diff --git a/readme/apps/rich_text_editor.md b/readme/apps/rich_text_editor.md index 65954d130b..4a4239ace5 100644 --- a/readme/apps/rich_text_editor.md +++ b/readme/apps/rich_text_editor.md @@ -6,6 +6,8 @@ At its core, Joplin stores notes in [Markdown format](https://github.com/laurent In some cases however, the extra markup format that appears in notes can be seen as a drawback. Bold text will `look **like this**` for example, and tables might not be particularly readable. For that reason, Joplin also features a Rich Text editor, which allows you to edit notes with a [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG) editing experience. Bold text will "look **like this**" and tables will be more readable, among others. +## Limitations + However **there is a catch**: in Joplin, notes, even when edited with this Rich Text editor, are **still Markdown** under the hood. This is generally a good thing, because it means you can switch at any time between Markdown and Rich Text editor, and the note is still readable. It is also good if you sync with the mobile application, which doesn't have a rich text editor. The catch is that since Markdown is used under the hood, it means the rich text editor has a number of limitations it inherits from that format: - For a start, **most Markdown plugins will not be compatible**. If you open a Markdown note that makes use of such plugin in the Rich Text editor, it is likely you will lose the plugin special formatting. The only supported plugins are the "fenced" plugins - those that wrap a section of text in triple backticks (for example, KaTeX, Mermaid, etc. are working). You can see a plugin's compatibility on the Markdown config screen. @@ -21,3 +23,24 @@ However **there is a catch**: in Joplin, notes, even when edited with this Rich - All reference links (`[title][link-name]`) are converted to inline links (`[title](https://example.com)`) when Joplin saves changes from the Rich Text editor. Those are the known limitations but if you notice any other issue not listed here, please let us know [in the forum](https://discourse.joplinapp.org/). + +## Markup autocompletion + +By default, the Rich Text Editor automatically replaces certain text patterns with formatted content. Replacements are applied after each pattern is typed. + +By default, the following patterns are replaced: + +- `**bold**`: Formats `bold` as **bold**. +- `*italic*`: Formats `italic` as *italic*. +- `==highlighted==`: Highlights `highlighted`. +- `code`: Formats `code` as inline code. +- `$math$`: Auto-formats to inline math (using KaTeX math syntax). After rendering, equations can be edited by double-clicking or with the "edit" option in the right click menu. +- `# Heading 1`: Creates a level 1 heading. The `#` should be at the start of the line. +- `## Heading 2`: Creates a level 2 heading. +- `## Heading 3`: Creates a level 3 heading. +- `- List`: Creates a bulleted list. +- `1. List`: Creates a numbered list. + +Most replacements require pressing the space or enter key after the closing formatting character. For example, typing `==test==` does not highlight "test", but pressing a space after the last `=` does. + +These replacements can be disabled in settings > note, using the "Auto-format Markdown" setting. diff --git a/readme/apps/screen_reader_accessibility.md b/readme/apps/screen_reader_accessibility.md new file mode 100644 index 0000000000..f5517bc0ec --- /dev/null +++ b/readme/apps/screen_reader_accessibility.md @@ -0,0 +1,61 @@ +# Screen reader accessibility + +This document includes tips for using Joplin from either: +- a keyboard-only device (desktop) +- a screen reader (desktop and/or mobile) + +## Navigating the desktop app + +Joplin's UI is divided into regions. In addition to the standard screen reader "jump to" shortcuts, Joplin includes its own shortcuts for navigating to these regions. + +See items in the "Go"/"Focus" section of the menubar for keyboard shortcuts to move focus directly to some of these regions. + +### Accessibility regions + +Joplin's main window is divided into three main regions: +1. **Navigation 1**: The sidebar. +2. **Navigation 2**: The notes list. +3. **Main content**: The editor and viewer. + +Within the **main content**, Joplin has: +1. **Editable**: A note title input. +2. Two toolbars. +3. **Editable**: A note editor. +4. **Frame**: A note viewer. + +Screen readers usually support jumping between these different regions. Here's the relevant documentation for some of the more popular screen readers: +- [Orca](https://help.gnome.org/users/orca/stable/howto_structural_navigation.html.en) (Linux screen reader) +- [NVDA](https://download.nvaccess.org/documentation/en/userGuide.html#SingleLetterNavigation) +- [Voice Over](https://www.apple.com/voiceover/info/guide/_1134.html#mchlp2719) + +### Finding notes, tags, and notebooks + +It can sometimes be difficult to find a tag/note/notebook using Joplin's navigation sidebars. In these cases, the ["Go to Anything" dialog](https://github.com/laurent22/joplin/blob/dev/readme/apps/search.md#goto-anything) can simplify accessing an item. + +It's also possible to find notes using the search in the sidebar. By default, pressing F6 moves focus to this search entry. + +### Toggling tab-key navigation + +By default, pressing tab indents, rather than moves focus. This can be changed by toggling "Tab moves focus" from the "View" menu. By default, this can also be toggled by pressing ctrl-m. + +## Navigating the mobile app + +The mobile app is divided into several screens. These are the main ones: +1. A collapsed-by-default sidebar. +2. A notes list screen. +3. A note viewer. +4. A note editor. +5. A configuration screen. + +By default, the sidebar is hidden and the notes list is visible. The configuration screen can be opened using a button in the sidebar. + +### Creating a new note + +An "add new" button is located near the end of the focus order in these cases: +1. The sidebar is expanded, or +2. The notes list screen is visible for an editable notebook. + +Clicking "add new" opens a menu with "new note" and "new to-do" menu items. These items are be located just before the toggle in the focus order. + +See [to-dos](https://github.com/laurent22/joplin/blob/dev/readme/apps/to-dos.md) for information about how notes and to-dos are different. + diff --git a/readme/apps/search.md b/readme/apps/search.md index f29d61078f..8a69bc7069 100644 --- a/readme/apps/search.md +++ b/readme/apps/search.md @@ -54,4 +54,6 @@ Notes are sorted by "relevance". Currently it means the notes that contain the r ## Goto Anything -In the desktop application, press Ctrl+P or Cmd+P and type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. \ No newline at end of file +In the desktop application, press Ctrl+P or Cmd+P and type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name. + +The Goto Anything dialog can also be opened from the "Go" section of the application menubar. diff --git a/readme/apps/sync/webdav.md b/readme/apps/sync/webdav.md index eefef7d54f..4a04f09f18 100644 --- a/readme/apps/sync/webdav.md +++ b/readme/apps/sync/webdav.md @@ -16,4 +16,5 @@ WebDAV-compatible services that are known to work with Joplin: - [Stack](https://www.transip.nl/stack/) - [Synology WebDAV Server](https://www.synology.com/en-us/dsm/packages/WebDAVServer) - [WebDAV Nav](https://www.schimera.com/products/webdav-nav-server/), a macOS server. -- [Zimbra](https://www.zimbra.com/) \ No newline at end of file +- [Zimbra](https://www.zimbra.com/) +- [Infomaniak kDrive](https://www.infomaniak.com/fr/ksuite/kdrive) diff --git a/readme/apps/teams.md b/readme/apps/teams.md index c0fbce7f8a..cfeec7f7f5 100644 --- a/readme/apps/teams.md +++ b/readme/apps/teams.md @@ -25,3 +25,17 @@ To remove a member from your team click on the Profile icon (👤) for that user The team admin account is only for managing users and billing. To collaborate with the other members of your team, add yourself as a member using a different email address. +## Migrating to a Team account + +If you already have a Basic or Pro account, it is not currently possible to migrate your data to a Team account, as they work very differently. + +You can however follow these steps to manually migrate your data to a Team account: + +- Open the Joplin desktop app +- Click Synchronise and wait for it to complete +- Export all your notes as JEX (File => Export => JEX) +- Create a new profile (File => Switch profile) +- Import your JEX file +- Cancel your current Joplin Cloud subscription +- Open a new Joplin Cloud Team subscription +- Synchronise your new profile with this new subscription \ No newline at end of file diff --git a/readme/apps/use_cases/business.md b/readme/apps/use_cases/business.md index 8d49244cef..8104af2550 100644 --- a/readme/apps/use_cases/business.md +++ b/readme/apps/use_cases/business.md @@ -1,47 +1,45 @@ -# Joplin for your business +# Joplin for Your Business -Whether you're running a company, a team or a growing start-up, efficiency and organisation are key to success. Discover how Joplin can transform the way you run your business with these five practical uses: +Whether you're running a company, managing a team, or growing a start-up, efficiency and organisation are essential for success. Discover how Joplin can transform the way you manage your business with these five practical applications: -## 1\. Quick and efficient note-taking +## 1\. Quick and Efficient Note-Taking -At meetings, ideas flow and decisions are made quickly. With Joplin, you can instantly capture key points, action items and creative ideas without wasting time searching for pen and paper. What's more, with Joplin's multimodal note-taking and, more specifically, its voice input function, you can record and transcribe your meetings. And with Joplin Cloud and Joplin Server Business, notes are automatically synchronised across all your devices, so you can access this valuable information wherever you are. +In meetings, ideas flow rapidly, and decisions are made quickly. With Joplin, you can instantly capture key points, action items, and creative ideas without wasting time searching for pen and paper. Moreover, with Joplin's multimodal note-taking capabilities—particularly its voice input feature—you can record and transcribe your meetings effortlessly. Notes are automatically synchronised across all your devices via Joplin Cloud and Joplin Server Business, ensuring that you can access this valuable information wherever you are. - + -## 2\. Project management and tracking +## 2\. Project Management and Tracking -Centralise all your project documents, tasks and discussions in Joplin. Create dedicated notebooks for each project and share them with your team members for seamless collaboration. Tasks can be easily tracked using the integrated task list function, keeping you organised and on schedule. +Centralise all your project documents, tasks, and discussions in Joplin. Create dedicated notebooks for each project and share them with your team members for seamless collaboration. Tasks can be easily tracked using the integrated task list feature, keeping you organised and on schedule. - + -Tips: With Joplin you can add alarms to your to-dos, ideal for reminding yourself of your deadlines and informing your colleagues about key moments in your projects. +**Tips**: With Joplin, you can add alarms to your to-do lists, ideal for reminding yourself of deadlines and notifying your colleagues about key moments in your projects. -## 3\. Creating a shared knowledge base +## 3\. Creating a Shared Knowledge Base -With Joplin, collaboration isn't limited to face-to-face meetings. Share notebooks with colleagues and work together on ideas, documents and projects, wherever you are. Changes are synchronised in real time, ensuring that everyone has the latest information. +Collaboration in Joplin isn't limited to face-to-face meetings. Share notebooks with colleagues and work together on ideas, documents, and projects from anywhere. Real-time synchronisation ensures that everyone has access to the latest information. - + -Tips: In addition, by managing the rights of each user, you can define who can work on and view the various notes and notebooks. +**Tips**: By managing user permissions, you can define who has access to view or edit specific notes and notebooks. -## 4\. Customer information management +## 4\. Customer Information Management -Keep all your important customer information at your fingertips with Joplin. Create dedicated notes for each customer to track meetings, conversations and specific requests. Files, images, drawings and screenshots can be attached directly to the notes, allowing you to have all the relevant information in one place. What's more, with Joplin's ‘E-mail to note’ function, you can automatically transfer your customers' e-mails to their dedicated note, giving you a complete customer file to take with you wherever you go, even when you're offline... +Keep all your important customer information readily accessible with Joplin. Create dedicated notes for each customer to track meetings, conversations, and specific requests. Attach files, images, drawings, and screenshots directly to notes, consolidating all relevant information in one place. Additionally, Joplin's ‘E-mail to Note’ feature enables you to automatically transfer customer e-mails into their dedicated notes, creating comprehensive customer records that are accessible even offline. - + -Tips: With Joplin Cloud, you can also turn your notes into a web page that can be easily distributed via a link. If you need to, you can share a note with one of your colleagues or service providers who don't use Joplin and who need information about the customer. +**Tips**: With Joplin Cloud, you can turn notes into web pages easily shared via a link. This is particularly useful when sharing customer information with colleagues or external service providers who do not use Joplin. -## 5\. Protect your data +## 5\. Protect Your Data -The confidentiality and security of your company's data is paramount. With Joplin, your sensitive information and that of your customers is protected by advanced security features. The application supports end-to-end encryption, ensuring that only you and your staff have access to your data. +The confidentiality and security of your company's data are paramount. Joplin ensures that your sensitive information, as well as that of your customers, is protected with advanced security features. The application supports end-to-end encryption, ensuring that only authorised users can access your data. -What's more, with Joplin Server Business you have the option of hosting your data on your server, offering an extra layer of protection against cyber threats. +Additionally, with Joplin Server Business, you have the option of hosting your data on your own server, adding an extra layer of protection against cyber threats. -Finally, the addition of Joplin to your business allows you to comply with several data protection regulations, as presented in this CNIL report [CNIL GDPR and ISO report](https://www.cnil.fr/fr/liso-27701-une-norme-internationale-pour-la-protection-des-donnees-personnelles#:~:text=Depuis%20de%20nombreuses%20ann%C3%A9es%2C%20la,des%20mesures%20de%20s%C3%A9curit%C3%A9%20n%C3%A9cessaires.). +Integrating Joplin into your business also helps ensure compliance with various data protection regulations, as detailed in this [CNIL report](https://www.cnil.fr/fr/liso-27701-une-norme-internationale-pour-la-protection-des-donnees-personnelles#:~:text=Depuis%20de%20nombreuses%20ann%C3%A9es%2C%20la,des%20mesures%20de%20s%C3%A9curit%C3%A9%20n%C3%A9cessaires.). -- GDPR compliance, which governs the processing of personal data within the European Union. - -- ISO/IEC 27001, which certifies an ‘**information security management system**’; - -- ISO/IEC 27002, which details best practice for implementing the necessary **security measures**. \ No newline at end of file +- **GDPR compliance**, which governs the processing of personal data within the European Union. +- **ISO/IEC 27001**, certifying an ‘information security management system’. +- **ISO/IEC 27002**, detailing best practices for implementing necessary security measures. \ No newline at end of file diff --git a/readme/apps/use_cases/personal_organisation.md b/readme/apps/use_cases/personal_organisation.md index a45a8c8644..7c06293fb5 100644 --- a/readme/apps/use_cases/personal_organisation.md +++ b/readme/apps/use_cases/personal_organisation.md @@ -1,40 +1,40 @@ -# Joplin for personal organisation +# Joplin for Personal Organisation -Joplin can help you transform your personal organisation by centralising a myriad of tasks, to-do lists and ideas. This article explores how you can improve your personal organisation with Joplin. +Joplin can revolutionise your personal organisation by centralising a variety of tasks, to-do lists, and ideas. This article delves into how Joplin can enhance your personal organisation. -## 1\. Prepare and organise your trips and holidays +## 1\. Prepare and Organise Your Trips and Holidays -If you're preparing a trip or holidays, Joplin will help you keep track of all important things including hotel reservations, booking confirmations, things to see and places to visit. +When planning a trip or holiday, Joplin helps you keep track of all essential details, including hotel reservations, booking confirmations, attractions, and places to visit. ### Examples: -- Store all the booking details, reservation confirmations numbers in Joplin. This will ensure that all important information is in one place and easily accessible. -- Create a budget for a trip. List all potential expenses such as flights, accommodations, meals, transportation, and souvenirs. Joplin app can help you keep track of your budget. -- List potential destinations, attractions, and activities. Note down the best time to visit, local customs, and must-try local cuisines. You can also create an itinerary for each day of your trip. -- Prepare your packing list. For better organization, you can divide the list between different categories like clothes, toiletries, gadgets, and travel documents . +- Store all booking details and reservation confirmation numbers in Joplin, ensuring that critical information is consolidated in one easily accessible place. +- Create a budget for your trip. List potential expenses such as flights, accommodation, meals, transport, and souvenirs. Joplin can help you manage and monitor your budget. +- Make a list of potential destinations, attractions, and activities. Note the best times to visit, local customs, and must-try local cuisines. You can also create a detailed daily itinerary. +- Prepare a packing list. For better organisation, categorise items such as clothing, toiletries, gadgets, and travel documents. -## 2\. Make lists of things to buy or do +## 2\. Create Lists for Tasks or Purchases -Make lists for daily tasks like shopping or long-term projects like personal goals. Use checklists, bullet lists and numbered lists to organise your tasks. +Whether it's for daily tasks like shopping or long-term goals, Joplin helps you create checklists, bullet points, and numbered lists to organise and prioritise effectively. -## 3\. Store your important documents and access them anytime +## 3\. Store Important Documents for Easy Access -You can store important documents, attach payment confirmations and receipts. You can take photos of documents with your mobile and save them in your Joplin notes. You can also attach PDFs to notes and store them in Joplin for future reference. And because Joplin is offline first, you can access it anywhere on your synchronised devices. +Store critical documents, attach payment confirmations, and save receipts directly in Joplin. Capture photos of documents using your mobile device and store them in your notes. You can also attach PDFs for future reference. Since Joplin operates offline-first, you can access your notes anywhere on synchronised devices. -*Bonus : Joplin notes are end-to-end encrypted, so you can store important documents and information in complete security.* +*Bonus: Joplin notes are end-to-end encrypted, so your documents and information are stored securely.* -## 4\. Save articles and web pages with web clipper +## 4\. Save Articles and Web Pages with the Web Clipper -You can save interesting articles, web pages, blog posts or recipes and save them to Joplin thanks to Joplin web clipper extension. +Easily save interesting articles, web pages, blog posts, or recipes using the Joplin Web Clipper extension. -## 5\. Sharing and gather informations with your travelling companions +## 5\. Share and Collect Information with Travel Companions -Efficiently share travel plans, itineraries, and important updates with your travel partners using Joplin even if they don't. Easily gather and consolidate input and suggestions from everyone involved to ensure a well-coordinated and enjoyable trip. +Share travel plans, itineraries, and updates effortlessly with your travel companions, even if they don’t use Joplin. Consolidate input and suggestions from everyone involved to ensure a well-organised and enjoyable trip. - \ No newline at end of file + \ No newline at end of file diff --git a/readme/apps/use_cases/research.md b/readme/apps/use_cases/research.md index db5120aa8a..ea67189f65 100644 --- a/readme/apps/use_cases/research.md +++ b/readme/apps/use_cases/research.md @@ -1,51 +1,47 @@ -# Joplin for research +# Joplin for Research -Joplin allows you to store all your notes, ideas and lists in one place. You can access them at home or on-the-go thanks to synchronisation feature. Gather all the information you need and keep it accessible and organised. +Joplin enables you to store all your notes, ideas, and lists in one convenient location. With its synchronisation feature, you can access your information at home or on the go. Gather all the data you need and keep it organised and accessible. -Here are several ways how you can improve and streamline your research process with Joplin: +Here are several ways you can enhance and streamline your research process using Joplin: -## 1\. Organise your research +## 1\. Organise Your Research -Set-up an organised note-taking system. Create a dedicated notebook in Joplin for each project. You can also create sub-notebooks for each topic or category. Structure clearly your notes separating them into sections using headers, bullet points or numbered lists. You can also create tables, edit colours, highlight text or add hand-written notes or drawings. You can also use tags to indicate the status of a note (e.g., “to-do”, “in progress”, “done”), the relevance to specific research questions, or any other aspect that is important to your work. For best efficiency, make sure to stay consistent in your formatting. +Establish a well-structured note-taking system. Create a dedicated notebook in Joplin for each project, and consider creating sub-notebooks for individual topics or categories. Structure your notes clearly by using headers, bullet points, or numbered lists. You can also create tables, edit colours, highlight text, or add hand-written notes and drawings. Use tags to indicate the status of a note (e.g., “to-do”, “in progress”, “done”), its relevance to specific research questions, or other aspects crucial to your work. To maximise efficiency, maintain consistency in your formatting. -## 2\. Take advantage of search functionality +## 2\. Leverage the Search Functionality -You can search your notes by keyword, tag, or even text within the images. Thanks to Optical Character Recognition (OCR) Joplin can help you easily search the text in images for specific keywords or phrases. This can save a significant amount of time when you’re looking for specific information in a large document or across multiple documents. +Search your notes by keyword, tag, or even text within images. With Optical Character Recognition (OCR), Joplin allows you to search for specific words or phrases in images. This feature can save a significant amount of time when you’re looking for particular information in a large document or across multiple documents. -## 3\. Add variety of resources to your research +## 3\. Incorporate a Variety of Resources into Your Research -Add visual aids to your notes such as photos, documents or even hand written notes or drawings. You may also take photos of interesting articles or inspirational materials and store them in Joplin notes. You can also use voice to speech feature to create written notes. +Enhance your notes with visual aids such as photos, documents, or hand-written notes and drawings. You can also take photographs of interesting articles or inspirational materials and store them in Joplin. Additionally, use the Voice Typing feature to convert spoken words into text in real-time. -## 4\. Use web clipper extension to preserve important articles or blog posts +## 4\. Use the Web Clipper Extension to Save Important Articles or Blog Posts -Joplin web Clipper can be a powerful tool for research. Here are some tips on how to use it effectively: +Joplin's Web Clipper is an invaluable tool for research. Here are some tips for using it effectively: -- **Clip Relevant Pages** +- **Clip Relevant Pages** +When you find a web page with useful information, save it using the Web Clipper. This could include blog posts, news articles, research papers, or other types of content. -Whenever you come across a web page that contains useful information for your research, use the Web Clipper to save it. This could be a blog post, a news article, a research paper, or any other type of content. +- **Save Pay-Per-View Content** +If you have temporary access to subscription-based content, use the Web Clipper to save articles or journals relevant to your work. This ensures you can access the material even after your subscription expires. -- **Save Pay-Per-View Content** +- **Organise Your Clips** +Sort your clips into notebooks and arrange them by topic, source, or any other category that suits your research. -If you have temporary access to pay-per-view journals or other subscription-based content, use Joplin Web Clipper to save in Joplin the content that seems relevant to your work. This way, you can access it even after your access expires. +- **Add Tags and Notes** +After clipping a page, add tags and notes to remember why you saved it and its relevance to your research. This makes it easier to locate and utilise the clip later. Additionally, tagging helps track the source of the information in case you need to reference it again. -- **Organize Your Clips** +## 5\. Share Findings with Your Research Group -Organise your clips into notebooks and keep your clips arranged by topic, source, or any other category that makes sense for you and your research. - -- **Add Tags and Notes** - -After clipping a web page, add tags and notes to help you remember why you saved it and how it’s relevant to your research. This will make it easier to find and use the clip later. IT will also help to identify the source of information, in case you need to go back to it later. - -## 5\. Share findings with your research group - -Engage your colleagues in the research group by sharing your notes and notebooks. Create a shared notebook that all members of the group can access and contribute to. This can serve as a central repository for all ideas and information related to the project. +Collaborate with your colleagues by sharing notes and notebooks. Create a shared notebook that all group members can access and contribute to, serving as a central repository for ideas and information related to the project. -Tip: Group related ideas together using tags or colour themes. This will help you identify patterns and themes, and can also help in structuring the project or research. \ No newline at end of file +**Tip:** Group related ideas together using tags or colour themes. This will help you identify patterns and themes, making it easier to structure your project or research. \ No newline at end of file diff --git a/readme/apps/use_cases/students.md b/readme/apps/use_cases/students.md index 8f232732cc..080ffb7cfc 100644 --- a/readme/apps/use_cases/students.md +++ b/readme/apps/use_cases/students.md @@ -1,39 +1,39 @@ -# Joplin for students +# Joplin for Students -Joplin is a great tool to keep your coursework and assignments organised and accessible. Here's some ideas how Joplin can be useful to students: +Joplin is an excellent tool to keep your coursework and assignments organised and easily accessible. Here are some ideas on how Joplin can be beneficial for students: -## 1\. Keep your notes organised in notebooks +## 1\. Keep Your Notes Organised in Notebooks -Store in Joplin all the information, coursework, class notes and other important resources that you gather throughout the school year. You can keep a dedicated notebook for each class and add sub-notebooks for each topic, notes or projects you do for that class. You can then categorise notes using tags to regroup similar topics or assignments types. +Store all your information, coursework, class notes, and other essential resources in Joplin throughout the school year. Create a dedicated notebook for each class and add sub-notebooks for specific topics, notes, or projects related to that class. You can categorise notes using tags to group similar topics or types of assignments. -Synchronise your notes across multiple devices so that you can take notes on your tablet during class, and then review them on your laptop or phone later. +Synchronise your notes across multiple devices so that you can take notes on your tablet during class and review them later on your laptop or phone. -## 2\. Take handwritten notes +## 2\. Take Handwritten Notes -For students who prefer the traditional feel of handwriting, Joplin offers robust support for handwritten notes. Using a stylus on a tablet or touchscreen device, students can seamlessly integrate their handwritten notes into Joplin. This feature is perfect for capturing complex diagrams, equations, and sketches that are often difficult to type out. +For students who enjoy the traditional feel of handwriting, Joplin provides excellent support for handwritten notes. Using a stylus on a tablet or touchscreen device, you can easily integrate your handwritten notes into Joplin. This feature is perfect for capturing complex diagrams, equations, and sketches that may be difficult to type out. -## 3\. Manage task lists, to-dos and reminders +## 3\. Manage Task Lists, To-Dos, and Reminders -Joplin is an excellent tool for managing task lists, to-dos, and reminders, helping students stay organized and on top of their academic responsibilities. You can create detailed to-do lists with checkboxes, set reminders for upcoming deadlines, and prioritize tasks to ensure that nothing falls through the cracks. This functionality turns Joplin into a powerful personal organizer, making it easier to manage coursework, assignments, and personal tasks all in one place. +Joplin is a fantastic tool for managing task lists, to-dos, and reminders, helping students stay organised and on top of their academic responsibilities. Create detailed to-do lists with checkboxes, set reminders for upcoming deadlines, and prioritise tasks to ensure nothing is overlooked. This functionality makes Joplin a powerful personal organiser, enabling you to manage coursework, assignments, and personal tasks all in one place. -## 4\. Collaborate on group projects +## 4\. Collaborate on Group Projects -Collaboration on group projects is made simple and efficient with Joplin. You can share notebooks and notes with peers, allowing for real-time updates and collective brainstorming. This shared workspace facilitates seamless communication and coordination, ensuring that all group members are on the same page. Whether it’s dividing tasks, sharing research, or compiling final reports, Joplin's collaborative features help streamline the group project process. +Collaboration on group projects becomes simple and efficient with Joplin. Share notebooks and notes with peers, allowing for real-time updates and collective brainstorming. This shared workspace facilitates seamless communication and coordination, ensuring all group members are on the same page. Whether dividing tasks, sharing research, or compiling final reports, Joplin’s collaborative features streamline the group project process. -## 5\. Multimedia support +## 5\. Multimedia Support -You can add images, attachments and photos to your notes in Joplin to capture and store information from lectures or experiments. Take a full advantage of each device used, whether it's camera phone to attach photos, stylus tablet to take handwritten notes or desktop to comfortably write and edit your assignments. Thanks to synchronisation feature you can access your notes from any device of your choice, even offline. +You can enrich your notes in Joplin by adding images, attachments, and photos to capture and store information from lectures or experiments. Take full advantage of each device you use, whether it’s your phone’s camera for attaching photos, a stylus on a tablet for handwritten notes, or a desktop computer for writing and editing assignments. Thanks to the synchronisation feature, you can access your notes on any device of your choice, even offline. -* * * +--- -With the Joplin Cloud plan for students you can benefit from +50% on your subscription to our synchronisation service. More info here: https://joplinapp.org/plans/ \ No newline at end of file +With the Joplin Cloud plan for students, you can enjoy a +50% discount on your subscription to our synchronisation service. More information is available here: https://joplinapp.org/plans/ \ No newline at end of file diff --git a/readme/dev/BUILD.md b/readme/dev/BUILD.md index e0981b66bf..fa8bf4eaf9 100644 --- a/readme/dev/BUILD.md +++ b/readme/dev/BUILD.md @@ -43,7 +43,7 @@ Then you can test the various applications: cd packages/app-desktop yarn start -You can also run it under WSL 2. To do so, [follow these instructions](https://www.beekeeperstudio.io/blog/building-electron-windows-ubuntu-wsl2) to setup your environment. +Use the regular Command Prompt to develop in Windows. We [do not recommend using WSL for this](https://github.com/laurent22/joplin/blob/dev/readme/dev/build_troubleshooting.md#other-issues) and we do not support this use case. ## Testing the Terminal application diff --git a/readme/dev/accessibility.md b/readme/dev/accessibility.md new file mode 100644 index 0000000000..3d6616ad26 --- /dev/null +++ b/readme/dev/accessibility.md @@ -0,0 +1,92 @@ +# Development: Accessibility + +Joplin has a strong focus on accessibility. It's important to make sure that new pull requests and features keep Joplin accessible. + +## Making new components accessible + +When creating new components, it's important to make sure that they're accessible. How to do this varies a bit between desktop and mobile, but in general: +- [All focusable controls should have labels.](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships) (Usually, these labels are provided by text inside the controls.) +- [Buttons should be at least 24px by 24px.](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html) +- [The app should be keyboard-accessible](https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html). +- [The app should be usable from a screen reader](#testing-with-a-screen-reader) +- [Controls should have sufficient contrast](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html). + - See [the WebAIM contrast checker](https://webaim.org/resources/contrastchecker/). + +For a full list of accessibility guidelines, see [the WCAG 2.2](https://www.w3.org/TR/WCAG22/). + +For more information, see: +- **Resources for HTML**: + - [Providing Accessible Names and Descriptions](https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/). + - [Standard keyboard and ARIA for different component types](https://www.w3.org/WAI/ARIA/apg/patterns/). + - [Developing a Keyboard Interface](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/). +- **Resources for React Native**: + - React Native [lists accessibility-related properties in its documentation](https://reactnative.dev/docs/accessibility). + +## Testing with a screen reader + +Joplin should be accessible to [screen readers](https://en.wikipedia.org/wiki/Screen_reader). A big part of this is testing changes to the UI with a screen reader. Here's how to do that. + +### Desktop: Setting up a screen reader + +Most systems have a built-in screen reader that can be enabled and disabled in settings, or with keyboard shortcuts: +- **Windows**: + - [Windows Narrator](https://www.microsoft.com/en-us/windows/tips/narrator) comes with Windows. + - [NVDA](https://www.nvaccess.org/download/) is a popular OpenSource screen reader for Windows. It doesn't come with Windows and must be downloaded. +- **MacOS**: + - [VoiceOver](https://support.apple.com/guide/voiceover/get-started-vo4be8816d70/mac) comes with MacOS. +- **Linux**: + - Many Linux systems come with the [Orca](https://help.gnome.org/users/orca/stable/) screen reader. It's often possible to launch it from the command line by running `orca`. + +Each of these screen readers has its own keyboard shortcuts and navigation methods. In general, however, it's possible to use keyboard shortcuts such as tab and shift-tab to navigate while a screen reader is enabled. See the documentation links above for documentation specific to each screen reader. + +**Notes**: +- Joplin works best if the screen reader is started before launching the app. +- Screen readers usually have shortcuts for jumping to different parts of the page, including headings and [landmarks](https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/general-principles.html). See [the ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/at.html) for a list of these shortcuts for different screen readers. +- Screen readers often have different **focus** and **browse** modes. + - Focus mode is used, for example, when typing text in a text box. Keypresses are sent to the textbox rather than being interpreted as screen reader commands. + - In browse mode, keyboard shortcuts can be used to more quickly jump between different parts of the page. For example, in the Orca screen reader, pressing b jumps to the next button, but only in browse mode. + - See also [NVDA browse and focus modes](https://download.nvaccess.org/documentation/userGuide.html#BrowseMode), [Orca: Filling out forms (discusses browse and focus modes)](https://help.gnome.org/users/orca/stable/howto_forms.html.en). + +### Mobile: Setting up a screen reader + +Like desktop, most physical Android and iOS devices come with screen readers. However, setting up a screen reader on an Android emulator or iOS simulator can be a bit more difficult: +- **Physical Android device**: + - Android devices usually come with [TalkBack](https://support.google.com/accessibility/android/answer/6007100?hl%3Den#). + - See `developer.android.com`'s [guide for testing with TalkBack](https://developer.android.com/guide/topics/ui/accessibility/testing#talkback). +- **Physical iOS device**: + - iOS comes with [VoiceOver](https://support.apple.com/guide/iphone/turn-on-and-practice-voiceover-iph3e2e415f/ios). +- **iOS simulator**: + 1. Start the simulator. + 2. Enable [VoiceOver for MacOS](https://support.apple.com/guide/voiceover/get-started-vo4be8816d70/mac). + 3. Focus the simulator's window. + 4. Press control-option-rightArrow repeatedly to move VoiceOver focus into the simulator. + - **Note**: Sometimes, VoiceOver reads "Simulator not responding" before allowing VoiceOver focus to be moved into the simulator. +- **Android emulator**: + - On Android, it may be necessary to install TalkBack from the Google Play Store (in an emulator with the Play Store installed) or [build a TalkBack APK from source](https://github.com/google/talkback). + - After installation, TalkBack can be enabled as on a physical Android device, from the settings app. +- **Web**: + - Enable the operating system's screen reader (see "Desktop: Setting up a screen reader"). + +On a physical mobile device, VoiceOver and TalkBack share certain gestures: +- **To focus the next item**: Swipe from left to right. +- **To focus the previous item**: Swipe from right to left. +- **To jump focus to a particular part of the screen**: Tap once on the part of the screen to focus. +- **To click on the focused item**: Double-tap anywhere on the screen. + +## Adding automated tests to prevent regressions + +The desktop and mobile apps use different frameworks for checking for accessibility-related regressions: The desktop app uses [Playwright](https://playwright.dev/) and the mobile app uses [react-native-testing-library](https://callstack.github.io/react-native-testing-library/). + +### Desktop: Automated accessibility testing + +Existing Playwright automated tests can be found in `packages/app-desktop/integration-tests`. See the [integration-tests/README.md](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/integration-tests/README.md) file for details. + +For general accessibility issues (e.g. contrast, missing labels), we use [`@axe-core/playwright`](https://www.npmjs.com/package/@axe-core/playwright). This library scans everything visible on the screen and returns a set of errors. See `integration-tests/wcag.spec.ts` for existing tests that make use of this library. + +Keyboard interface tests should go in the other test files. For example, `richText.spec.ts` currently includes several tests related to using the editor with a keyboard. While writing these tests, the [expect.toBeFocused](https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-be-focused), [keyboard.press](https://playwright.dev/docs/api/class-keyboard#keyboard-press), and [getByRole](https://playwright.dev/docs/api/class-frame#frame-get-by-role) APIs might be particularly useful. + +The [getByRole](https://playwright.dev/docs/api/class-frame#frame-get-by-role) API can also be used to verify that components have specific [role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) information. + +### Mobile: Automated accessibility testing + +On mobile, [react-native-testing-library](https://callstack.github.io/react-native-testing-library/) also provides a [getByRole](https://callstack.github.io/react-native-testing-library/docs/api/queries#by-role) selector that can be used to ensure that accessibility information is present. See, for example, [this test for list of installed plugins](https://github.com/laurent22/joplin/blob/bf58a52394947acc42f8ca527b3ce22464d989c3/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.installed.test.tsx#L231). diff --git a/readme/dev/build_troubleshooting.md b/readme/dev/build_troubleshooting.md index 1704825d0b..cc799b2a07 100644 --- a/readme/dev/build_troubleshooting.md +++ b/readme/dev/build_troubleshooting.md @@ -1,8 +1,6 @@ # Build troubleshooting -## Desktop application - -### On Windows +## Desktop application - on Windows If `yarn dist` fails, it may need administrative rights. @@ -15,7 +13,7 @@ call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliar set "PATH=C:\Program Files\nodejs;%PATH%" ``` -### On Linux and macOS +## Desktop application - on Linux and macOS If there's an error `while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directory`, run `sudo apt-get install libgconf-2-4` @@ -24,18 +22,20 @@ If you get a node-gyp related error, you might need to manually install it: `npm If you get unexpected `npm` dependency errors on a fresh git pull, try `npm run clean` If `npm i` gives you a fatal error like the following: + ``` node-pre-gyp WARN Tried to download(403): https://mapbox-node-binary.s3.amazonaws.com/sqlite3/v5.0.1/napi-v6-linux-x64.tar.gz node-pre-gyp WARN Pre-built binaries not found for sqlite3@5.0.1 and node@14.15.4 (node-v83 ABI, glibc) (falling back to source compile with node-gyp) /bin/sh: 1: python: not found ``` + Try `sudo apt install python` (or the `apt` equivalent for your operating system) and then run `npm i` again. If you get the error `libtool: unrecognized option '-static'`, follow the instructions [in this post](https://stackoverflow.com/a/38552393/561309) to use the correct libtool version. -### Other issues +## Desktop application - other issues -> The application window doesn't open or is white +### The application window doesn't open or is white This is an indication that there's an early initialisation error. Try this: @@ -44,19 +44,13 @@ This is an indication that there's an early initialisation error. Try this: - Also try to delete node_modules and rebuild. - If all else fails, switch your computer off and on again, to make sure you start clean. -> How to work on the app from Windows? +### How to work on the app from Windows? **You should not use WSL at all** because this is a GUI app that lives outside of WSL, and the WSL layer can cause all kind of very hard to debug issues. It can also lock files in node_modules that cannot be unlocked when the app crashes. (You need to restart your computer.) Likewise, don't run the TypeScript watch command from WSL. So everything should be done from a Windows Command prompt or Windows PowerShell running as Administrator. All build and start commands are designed to work cross-platform, including on Windows. -> Error when building the application - -If you find a error when building the application, [verify that you have all the requirements](https://github.com/laurent22/joplin/blob/dev/readme/dev/BUILD.md), including Rust. - -## Mobile application - -### iOS +## Mobile application - iOS If there is an error `/joplin/packages/app-mobile/ios/Pods/Target Support Files/Pods-Joplin/Pods-Joplin.debug.xcconfig: unable to open file (in target "Joplin" in project "Joplin") (in target 'Joplin' from project 'Joplin')` run the following commands: diff --git a/readme/dev/spec/modal_focus_management.md b/readme/dev/spec/modal_focus_management.md new file mode 100644 index 0000000000..d09689c404 --- /dev/null +++ b/readme/dev/spec/modal_focus_management.md @@ -0,0 +1,71 @@ +# Modal focus management + +Most Joplin dialogs should follow the [modal dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/). On desktop, this is usually done with the native [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog) element. On mobile, it's a bit more complicated. + +## Mobile + +### Managing focus + +On mobile, the `` component allows moving focus to a component or preventing a component from being accessibility focused. For example, +```jsx +{children} +``` +prevents `children` from being focused using accessibility tools in a cross-platform way. The `inert` prop is named after the [HTML `inert` attribute](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/inert). + +Similarly, the following logic auto-focuses `children` when the view first renders: +```jsx +// Danger: This implicitly sets `accessible={true}`, which prevents +// VoiceOver from focusing individual children in `children`. +{children} +``` + +Changing the `refocusCounter` prop causes the `AccessibleView` to be focused again. + +### Native `Modal`s + +React Native has a built-in `Modal` component. + +The `components/Modal` component wraps this built-in `Modal` component. Among other things, this wrapper tracks whether `Modal`s are open, closing, or closed. This allows greater customization over where focus moves after modals are dismissed. + +When a `Modal` is visible, it prevents content behind it from being focused. With the React Native built-in `Modal`, setting focus to items behind a visible `Modal` does nothing. On Android, this is also the case briefly after the `Modal` is dismissed. + +The custom `Modal` works with `AccessibleView` to improve focus behavior. The `Modal` keeps track of the last `AccessibleView` that was focused while the `Modal` was open. When the `Modal` is dismissed, it auto-focuses this `AccessibleView`. This is useful, for example, if an button in a `Modal` shows UI that needs to be auto-focused when the `Modal` is dismissed. The custom `Modal` determines when the native `Modal` is dismissed, and could then move focus to the just-shown UI. + +### Inaccessible 3rd-party modals + +Sometimes a library includes a component that should handle focus in a modal-like way, but doesn't. Examples include [`react-native-paper`'s Modal](https://github.com/callstack/react-native-paper/issues/3912) and [`react-native-popup-menu`'s Menu](https://github.com/instea/react-native-popup-menu/issues/138). The components in the `FocusControl` object can often improve focus management for these libraries. + +`FocusControl` provides three components: +- A `FocusControl.Provider` that sets up shared focus-related state. +- A `FocusControl.MainAppContent` that should wrap the main application content (everything that isn't part of a modal). +- A `FocusControl.ModalWrapper` that should be used to wrap content within modals. This allows `FocusControl` to determine whether a modal is visible. + +When a modal is visible, the `MainAppContent` is wrapped with an `inert` `AccessibleView`, preventing it from receiving accessibility focus. This traps focus within the visible modal components. + +In general, prefer Joplin's `components/Modal` component to react-native-paper `Modal`s. As an example, however, a [`react-native-paper` `Modal`](https://callstack.github.io/react-native-paper/docs/components/Modal/) might be rendered with: +```tsx + + + + {...content here...} + + + +``` + +Above, the `FocusControl.ModalWrapper` communicates whether the dialog is visible to the global ``. This allows the `MainAppContent` (not shown above) to be marked as focusable or unfocusable depending on whether the `Modal` is visible or not. + +:::danger + +The [``](https://callstack.github.io/react-native-paper/docs/components/Portal/) is important part of the example. A `` is a react-native-paper component that renders its children outside of the main app content (near the global `PaperProvider`). + +If the `` is omitted, then the modal, and the `FocusControl.ModalWrapper`'s children, will be rendered within the main app content. This will cause them to be marked as unfocusable when the modal is visible, preventing screen readers from accessing the modal's content. + +When adding a `FocusControl.ModalWrapper`, it's important to verify that the modal can still be used by a screen reader. + +::: diff --git a/readme/dev/spec/multiple_instances.md b/readme/dev/spec/multiple_instances.md new file mode 100644 index 0000000000..5dd4096ab5 --- /dev/null +++ b/readme/dev/spec/multiple_instances.md @@ -0,0 +1,27 @@ +# Multiple instance support + +Joplin Desktop supports multiple instances through **profile locking** and **IPC messaging**. + +### Profile Locking + +- A lock file is updated every `x` seconds by each instance. + +- If a valid lock exists, the new instance sends a message to all running instances and closes. This message prompts the other active instance to move to the front. + +### IPC Messaging + +- IPC is implemented using lightweight HTTP servers in each instance, communicating via `POST` requests. + +- When a message is sent, the implementation automatically discovers running IPC servers. + +- Messages are secured using a secret that is shared by all applications. That secret is read from the profile directory of the main instance. It is created by the server if it doesn't exist. This ensures that, for example, a browser cannot send a valid message to the apps. + +### Instance Differentiation + +- The `--alt-instance-id` flag must be used to launch an alternative instance. This disables services like the Web Clipper. + +- The `altInstanceId` setting is consulted by the application to determine its type (main or alternative instance). + +### Current Limitations + +The system is designed to handle multiple instances but currently supports only two through the UI: a **main instance** and an **alternative instance**. \ No newline at end of file diff --git a/readme/dev/spec/voice_typing.md b/readme/dev/spec/voice_typing.md index 8460fa2e58..a2ce83f716 100644 --- a/readme/dev/spec/voice_typing.md +++ b/readme/dev/spec/voice_typing.md @@ -12,7 +12,7 @@ Whisper.cpp provides a number of pre-trained models for transcribing speech in d ### Downloading the models -By default, Joplin downloads Whisper models from [this GitHub repository](https://github.com/personalizedrefrigerator/joplin-voice-typing-test/releases). It's possible to download models from a custom location by changing the **Voice typing language files (URL)** in from the "Note" tab of the configuration screen. +By default, Joplin downloads Whisper models from [this GitHub repository](https://github.com/joplin/voice-typing-models). It's possible to download models from a custom location by changing the **Voice typing language files (URL)** in from the "Note" tab of the configuration screen. ### Customizing Whisper diff --git a/readme/licenses.md b/readme/licenses.md index 14eae028f9..ffdc758881 100644 --- a/readme/licenses.md +++ b/readme/licenses.md @@ -3201,36 +3201,6 @@ From https://github.com/react-native-community/push-notification-ios. **MIT**: -``` -MIT License - -Copyright (c) 2020 react-native-community - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE.% -``` - -### @react-native-community/slider@4.4.4 - -From https://github.com/callstack/react-native-slider. - -**MIT**: - Copyright: Copyright (c) 2019 react-native-community See [Appendix B](#appendix-b-the-mit-license) for the full MIT license. diff --git a/readme/privacy.md b/readme/privacy.md index 638688e309..de45172d3f 100644 --- a/readme/privacy.md +++ b/readme/privacy.md @@ -12,7 +12,7 @@ In order to provide certain features, Joplin may need to connect to third-party | Wifi connection check | On mobile, Joplin checks for Wifi connectivity to give the option to synchronise data only when Wifi is enabled. | Enabled | No (1) | | Spellchecker dictionary | On Linux and Windows, the desktop application downloads the spellchecker dictionary from `redirector.gvt1.com`. | Enabled | Yes (2) | | Plugin repository | The desktop application downloads the list of available plugins from the [official GitHub repository](https://github.com/joplin/plugins). If this repository is not accessible (eg. in China) the app will try to get the plugin list from [various mirrors](https://github.com/laurent22/joplin/blob/8ac6017c02017b6efd59f5fcab7e0b07f8d44164/packages/lib/services/plugins/RepositoryApi.ts#L22), in which case the plugin screen [works slightly differently](https://github.com/laurent22/joplin/issues/5161#issuecomment-925226975). | Enabled | No -| Voice typing | If you use the voice typing feature on Android, the application will download the language files from https://alphacephei.com/vosk/models | Disabled | Yes +| Voice typing | If you use the voice typing feature on Android, the application will download the language files from https://github.com/joplin/voice-typing-models/ or https://alphacephei.com/vosk/models. | Disabled | Yes | OCR | If you have enabled optical character recognition on desktop, the application will download the language files from https://cdn.jsdelivr.net/npm/@tesseract.js-data/. | Disabled | Yes | Crash reports | If you have enabled crash auto-upload, the application will upload the report to Sentry when a crash happens. When Sentry is initialised it will also connect to `sentry.io`. | Disabled | Yes diff --git a/readme/welcome/5_privacy.md b/readme/welcome/5_privacy.md index 862659640b..581db62710 100644 --- a/readme/welcome/5_privacy.md +++ b/readme/welcome/5_privacy.md @@ -14,7 +14,7 @@ In order to provide certain features, Joplin may need to connect to third-party | Wifi connection check | On mobile, Joplin checks for Wifi connectivity to give the option to synchronise data only when Wifi is enabled. | Enabled | No (1) | | Spellchecker dictionary | On Linux and Windows, the desktop application downloads the spellchecker dictionary from `redirector.gvt1.com`. | Enabled | Yes (2) | | Plugin repository | The desktop application downloads the list of available plugins from the [official GitHub repository](https://github.com/joplin/plugins). If this repository is not accessible (eg. in China) the app will try to get the plugin list from [various mirrors](https://github.com/laurent22/joplin/blob/8ac6017c02017b6efd59f5fcab7e0b07f8d44164/packages/lib/services/plugins/RepositoryApi.ts#L22), in which case the plugin screen [works slightly differently](https://github.com/laurent22/joplin/issues/5161#issuecomment-925226975). | Enabled | No -| Voice typing | If you use the voice typing feature on Android, the application will download the language files from https://alphacephei.com/vosk/models | Disabled | Yes +| Voice typing | If you use the voice typing feature on Android, the application will download the language files from https://github.com/joplin/voice-typing-models/ or https://alphacephei.com/vosk/models. | Disabled | Yes (1) https://github.com/laurent22/joplin/issues/5705
(2) If the spellchecker is disabled, [it will not download the dictionary](https://discourse.joplinapp.org/t/new-version-of-joplin-contacting-google-servers-on-startup/23000/40?u=laurent). diff --git a/yarn.lock b/yarn.lock index 5d7ae26fb7..5b7c21086f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8279,7 +8279,7 @@ __metadata: "@joplin/renderer": ~3.3 "@joplin/tools": ~3.3 "@joplin/utils": ~3.3 - "@playwright/test": 1.45.3 + "@playwright/test": 1.51.1 "@sentry/electron": 4.24.0 "@testing-library/react-hooks": 8.0.1 "@types/jest": 29.5.12 @@ -8297,7 +8297,7 @@ __metadata: compare-versions: 6.1.1 countable: 3.0.1 debounce: 1.2.1 - electron: 34.0.0 + electron: 35.1.4 electron-builder: 24.13.3 electron-updater: 6.2.1 electron-window-state: 5.0.3 @@ -8319,7 +8319,6 @@ __metadata: node-fetch: 2.6.7 node-notifier: 10.0.1 node-rsa: 1.1.1 - notyf: 3.10.0 pdfjs-dist: 3.11.174 pretty-bytes: 5.6.0 re-resizable: 6.9.17 @@ -8362,14 +8361,13 @@ __metadata: "@joplin/renderer": ~3.3 "@joplin/tools": ~3.3 "@joplin/utils": ~3.3 - "@js-draw/material-icons": 1.27.2 + "@js-draw/material-icons": 1.29.1 "@pmmmwh/react-refresh-webpack-plugin": ^0.5.15 "@react-native-clipboard/clipboard": 1.14.3 "@react-native-community/datetimepicker": 8.2.0 "@react-native-community/geolocation": 3.3.0 "@react-native-community/netinfo": 11.3.3 "@react-native-community/push-notification-ios": 1.11.0 - "@react-native-community/slider": 4.5.5 "@react-native/babel-preset": 0.74.86 "@react-native/metro-config": 0.74.87 "@sqlite.org/sqlite-wasm": 3.46.0-build2 @@ -8404,7 +8402,7 @@ __metadata: jest: 29.7.0 jest-environment-jsdom: 29.7.0 jetifier: 2.0.0 - js-draw: 1.27.2 + js-draw: 1.29.2 jsdom: 24.1.1 lodash: 4.17.21 md5: 2.3.0 @@ -8429,7 +8427,7 @@ __metadata: react-native-paper: 5.13.1 react-native-popup-menu: 0.16.1 react-native-quick-actions: 0.3.13 - react-native-quick-crypto: 0.7.5 + react-native-quick-crypto: 0.7.12 react-native-rsa-native: 2.0.5 react-native-safe-area-context: 4.10.8 react-native-securerandom: 1.0.1 @@ -8841,7 +8839,6 @@ __metadata: "@types/zxcvbn": 4.4.5 bcryptjs: 2.4.3 bulma: 1.0.2 - bulma-prefers-dark: 0.1.0-beta.1 compare-versions: 6.1.1 dayjs: 1.11.12 formidable: 2.1.2 @@ -8993,6 +8990,7 @@ __metadata: moment: 2.30.1 node-fetch: 2.6.7 sprintf-js: 1.1.3 + tcp-port-used: 1.0.2 ts-jest: 29.1.5 typescript: 5.4.5 languageName: unknown @@ -9133,21 +9131,21 @@ __metadata: languageName: node linkType: hard -"@js-draw/material-icons@npm:1.27.2": - version: 1.27.2 - resolution: "@js-draw/material-icons@npm:1.27.2" +"@js-draw/material-icons@npm:1.29.1": + version: 1.29.1 + resolution: "@js-draw/material-icons@npm:1.29.1" peerDependencies: js-draw: ^1.0.1 - checksum: dcaac6c45d20df6542fe874e0cd6f7a40f77faaebe494f4eb45c6b1c3595223a01b36067549159341a18af018253ea5c1c99bd82c47060a8b33596263ae83026 + checksum: e3de5520b4154228ab3d593ae06d7310f3e1a100e5f2507e8b8b317a51cc3566d907c252e2cc92b89274059e05b0a57eb69b2f27483cab45fcbc1ccae7bac0d2 languageName: node linkType: hard -"@js-draw/math@npm:^1.27.2": - version: 1.27.2 - resolution: "@js-draw/math@npm:1.27.2" +"@js-draw/math@npm:^1.29.2": + version: 1.29.2 + resolution: "@js-draw/math@npm:1.29.2" dependencies: bezier-js: 6.1.3 - checksum: 021dce0af104890312cf4eeb8a645d89bb2eaf0d3cdc181cdfcb6bb7b90deeeae484f5bec06bc96ac1ebc4f83918eff9a63239d8165a493b3794fd36e92bd330 + checksum: e8c1fa984a06d3f80351363c10c63d3f648df0de14a9a37f92cd4c1670dbecc1840cf9c837fdd7d436deb13648132288ef2bd8926d389100e0dc82ad2ba448cf languageName: node linkType: hard @@ -10675,14 +10673,14 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:1.45.3": - version: 1.45.3 - resolution: "@playwright/test@npm:1.45.3" +"@playwright/test@npm:1.51.1": + version: 1.51.1 + resolution: "@playwright/test@npm:1.51.1" dependencies: - playwright: 1.45.3 + playwright: 1.51.1 bin: playwright: cli.js - checksum: 3e2c88d40f98cf94ab7947263804d1ee78c4bb21a35c8dbb64855eed5565ffc688509c5f07bda5438cba6c354374981448dcba3dbe326d1699b4fef75c9ce43d + checksum: c3c14f37451cc0323234e37fa559a075e5aee9f5e6ca06fa3b4a132ec6c809909b22877daca1fa48c95a1112d859346c10111e7003f44582a05100dd5385bbef languageName: node linkType: hard @@ -11231,20 +11229,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/slider@npm:4.4.4": - version: 4.4.4 - resolution: "@react-native-community/slider@npm:4.4.4" - checksum: 65d79b72d100aab75e9051315798935a2419202e157f5e35dedb4e2843ccdd93816b553c9a7a41642f5140e5a05e20326a27ef65e9ed9c53efc59d8755a5d91f - languageName: node - linkType: hard - -"@react-native-community/slider@patch:@react-native-community/slider@npm%3A4.4.4#./.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch::locator=root%40workspace%3A.": - version: 4.4.4 - resolution: "@react-native-community/slider@patch:@react-native-community/slider@npm%3A4.4.4#./.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch::version=4.4.4&hash=1120f2&locator=root%40workspace%3A." - checksum: c4397dd2e914f52e3d9b4058d3cf850e67d99c85a59492835647513af85ba62ba182c5c7655fd35d6f768155d45c0c8b5eb0adaad803165d9fec508b77b19a2b - languageName: node - linkType: hard - "@react-native/assets-registry@npm:0.74.83": version: 0.74.83 resolution: "@react-native/assets-registry@npm:0.74.83" @@ -13632,12 +13616,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.9.0": - version: 20.11.26 - resolution: "@types/node@npm:20.11.26" +"@types/node@npm:^22.7.7": + version: 22.13.10 + resolution: "@types/node@npm:22.13.10" dependencies: - undici-types: ~5.26.4 - checksum: 2cd5a373d2880b7f07ae65d4d23660cdcabddb12772ce631b6caad2f9283350b8a17c802590f67f0ccec5a9c7d43ae0ecd9637c5ad870b8f793328af8c61eae5 + undici-types: ~6.20.0 + checksum: 1cd6b899df728732c60c0defad63e26ca18d87a3b81bd75666fe9aed6cdf9e488433976b22ffcabfdeef9d351cf8ff94853b0686e6708ef62065482ccf5b0a6e languageName: node linkType: hard @@ -18080,13 +18064,6 @@ __metadata: languageName: node linkType: hard -"bulma-prefers-dark@npm:0.1.0-beta.1": - version: 0.1.0-beta.1 - resolution: "bulma-prefers-dark@npm:0.1.0-beta.1" - checksum: 26a88776a97ed0b57307a1aee60380a1b484ebd27d101c9995b5d220ff79fe909b060577c42c674c2863c52d4de85396c6f36031b3f7828386e21c1eda1e4445 - languageName: node - linkType: hard - "bulma@npm:1.0.2": version: 1.0.2 resolution: "bulma@npm:1.0.2" @@ -22993,16 +22970,16 @@ __metadata: languageName: node linkType: hard -"electron@npm:34.0.0": - version: 34.0.0 - resolution: "electron@npm:34.0.0" +"electron@npm:35.1.4": + version: 35.1.4 + resolution: "electron@npm:35.1.4" dependencies: "@electron/get": ^2.0.0 - "@types/node": ^20.9.0 + "@types/node": ^22.7.7 extract-zip: ^2.0.1 bin: electron: cli.js - checksum: 0ee9a4bb3444cf13f8d0eeef928f9d8feee33d919c414672d794d6332d81fe3740c781060eba5374417742dd9f8159da2050c0fc14fac64d143be8ccb53e280c + checksum: 78ac69d76823d9f2096726684f57b9d9e1bbf90a2207aaa003d69ed96eb2c7d93b0b8a6cee25a99382332606f09ea3b6b9f0868a5a6eca53429b5ff087fa1c10 languageName: node linkType: hard @@ -31141,13 +31118,13 @@ __metadata: languageName: node linkType: hard -"js-draw@npm:1.27.2": - version: 1.27.2 - resolution: "js-draw@npm:1.27.2" +"js-draw@npm:1.29.2": + version: 1.29.2 + resolution: "js-draw@npm:1.29.2" dependencies: - "@js-draw/math": ^1.27.2 + "@js-draw/math": ^1.29.2 "@melloware/coloris": 0.22.0 - checksum: 0dc5c1531ca7bd86dfe50817d00209d19836357fcdd58657cb83faa27a81d7f00327adcc5c668db17e026d7cec01c470a0da49d0163c2f83901bbcd3969adc74 + checksum: 29937a44c048c3927f4851b8bac66ce71316ce36b2de2ab6bfa2d1a9c1f634f51a10126e3491a7de5559fc5af442822a6a66ebd892ac8d793923a9f9f958c8c9 languageName: node linkType: hard @@ -35836,13 +35813,6 @@ __metadata: languageName: node linkType: hard -"notyf@npm:3.10.0": - version: 3.10.0 - resolution: "notyf@npm:3.10.0" - checksum: 6cc533fccb0d74e544edf10e82d2942975adc4c993a68c966694bbb451dc06056d02e8dced4ecfce2c4586682223759cb1f9f3e3f609c83458e99c2bf5494b00 - languageName: node - linkType: hard - "now-and-later@npm:^2.0.0": version: 2.0.1 resolution: "now-and-later@npm:2.0.1" @@ -38000,27 +37970,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.45.3": - version: 1.45.3 - resolution: "playwright-core@npm:1.45.3" +"playwright-core@npm:1.51.1": + version: 1.51.1 + resolution: "playwright-core@npm:1.51.1" bin: playwright-core: cli.js - checksum: cecb58877b2c643403d7a72c24a7aa0fdd087a3c7f9a5ea5403851336ea831d8e304b1f159aacbbabd12e5c47eaac054333746c9e5431ec07b13d64dbf3b50ec + checksum: 1eb37e22e97435a5ed6389b4caa666fbe618348861cae97e67586e20c8fed9ac3d3dc899ff3b9237d0ddfcf087d5b552b80be247e246fc45b75282f96be714bb languageName: node linkType: hard -"playwright@npm:1.45.3": - version: 1.45.3 - resolution: "playwright@npm:1.45.3" +"playwright@npm:1.51.1": + version: 1.51.1 + resolution: "playwright@npm:1.51.1" dependencies: fsevents: 2.3.2 - playwright-core: 1.45.3 + playwright-core: 1.51.1 dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: d9d23b155ccd001553214f710561b01e48eb409676102f8ab94c0b4aa5ac5f398becc1a96528b0554944e07e34189503d891913e0e0a4aa58ad36b9c08747983 + checksum: 2d78f53eed438d26df9743f98227d2e33bb5a378c8d0144a4ed75b4010c9c517d5a6ee6c1c9245ad61dc615054c5e19d2ded5a6e285e35d2bca39b05bc9c4a35 languageName: node linkType: hard @@ -40022,6 +39992,13 @@ __metadata: languageName: node linkType: hard +"react-native-popup-menu@patch:react-native-popup-menu@npm%3A0.16.1#./.yarn/patches/react-native-popup-menu-npm-0.16.1-28fd66ecb5.patch::locator=root%40workspace%3A.": + version: 0.16.1 + resolution: "react-native-popup-menu@patch:react-native-popup-menu@npm%3A0.16.1#./.yarn/patches/react-native-popup-menu-npm-0.16.1-28fd66ecb5.patch::version=0.16.1&hash=42fb35&locator=root%40workspace%3A." + checksum: 50d91792b3f8cc2584ac22c537eebb8cf771ad1241843dd403395361f8465edb3628c5751671ad55c832a3a217a711dca72497b0bc34b21a3ce37ce1bd720d52 + languageName: node + linkType: hard + "react-native-quick-actions@npm:0.3.13": version: 0.3.13 resolution: "react-native-quick-actions@npm:0.3.13" @@ -40041,19 +40018,16 @@ __metadata: languageName: node linkType: hard -"react-native-quick-crypto@npm:0.7.5": - version: 0.7.5 - resolution: "react-native-quick-crypto@npm:0.7.5" +"react-native-quick-crypto@npm:0.7.12": + version: 0.7.12 + resolution: "react-native-quick-crypto@npm:0.7.12" dependencies: "@craftzdog/react-native-buffer": ^6.0.5 events: ^3.3.0 readable-stream: ^4.5.2 string_decoder: ^1.3.0 util: ^0.12.5 - peerDependencies: - react: "*" - react-native: "*" - checksum: 07f5abc4ddf7fbc8d5bfa9b459f04a7e1fbc3afe9b1add8e07d0918401abb2a8a987da00741b2baa4e94d2125cec10b3a77c4136250a20f6f3376cefba048135 + checksum: f640cfc8dd3643d2530ef830c788033648311530c211385b2eaaa55bfc353b0af2400bd9a0df09338ee7ff4db2c72b99e06f06e7d6adf10a240db5ae82570896 languageName: node linkType: hard @@ -47195,6 +47169,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.20.0": + version: 6.20.0 + resolution: "undici-types@npm:6.20.0" + checksum: b7bc50f012dc6afbcce56c9fd62d7e86b20a62ff21f12b7b5cbf1973b9578d90f22a9c7fe50e638e96905d33893bf2f9f16d98929c4673c2480de05c6c96ea8b + languageName: node + linkType: hard + "unherit@npm:^1.0.4": version: 1.1.3 resolution: "unherit@npm:1.1.3"