diff --git a/.eslintignore b/.eslintignore index 0cbe191010d..79764cf70da 100644 --- a/.eslintignore +++ b/.eslintignore @@ -641,6 +641,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/applyTemplateToEditor.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.test.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.js +packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/polyfills.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/startAutosaveLoop.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/types.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/watchEditorForTemplateChanges.js @@ -703,6 +704,7 @@ packages/app-mobile/components/plugins/dialogs/hooks/useViewInfos.js packages/app-mobile/components/plugins/dialogs/hooks/useWebViewSetup.js packages/app-mobile/components/plugins/types.js packages/app-mobile/components/plugins/utils/createOnLogHandler.js +packages/app-mobile/components/plugins/utils/useOnDevPluginsUpdated.js packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.js packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.js diff --git a/.gitignore b/.gitignore index 83f939737d4..18586771d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -616,6 +616,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/applyTemplateToEditor.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.test.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.js +packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/polyfills.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/startAutosaveLoop.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/types.js packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/watchEditorForTemplateChanges.js @@ -678,6 +679,7 @@ packages/app-mobile/components/plugins/dialogs/hooks/useViewInfos.js packages/app-mobile/components/plugins/dialogs/hooks/useWebViewSetup.js packages/app-mobile/components/plugins/types.js packages/app-mobile/components/plugins/utils/createOnLogHandler.js +packages/app-mobile/components/plugins/utils/useOnDevPluginsUpdated.js packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.js packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.js diff --git a/.yarn/patches/app-builder-lib-npm-24.13.3-86a66c0bf3.patch b/.yarn/patches/app-builder-lib-npm-24.13.3-86a66c0bf3.patch index 310ffc5f6b0..eaf90825797 100644 --- a/.yarn/patches/app-builder-lib-npm-24.13.3-86a66c0bf3.patch +++ b/.yarn/patches/app-builder-lib-npm-24.13.3-86a66c0bf3.patch @@ -1,7 +1,15 @@ -# This patch prevents the installer from considering itself as a running instance of Joplin. +# This patch's goal is to work around an issue in the NSIS uninstaller on Windows: +# - For future uninstallers, this patch backports an upstream commit that changes how +# running copies of the app are found. +# - See https://github.com/electron-userland/electron-builder/pull/8133 +# - If an existing uninstaller fails, gives an option to continue with the installation +# despite the failure. +# - Updates "uninstall failed" error messages to state that uninstallation failed (rather +# than incorrectly stating that the issue was with closing the app). +# # See https://github.com/laurent22/joplin/pull/11541 diff --git a/templates/nsis/include/allowOnlyOneInstallerInstance.nsh b/templates/nsis/include/allowOnlyOneInstallerInstance.nsh -index fe5d45c730f36c9fe8d8cfea12e242e501b67139..af2ce5c90ac910b079e24992519bffe33d57668a 100644 +index fe5d45c730f36c9fe8d8cfea12e242e501b67139..97b27fce6798e30e3e631221435f09b3579e77c3 100644 --- a/templates/nsis/include/allowOnlyOneInstallerInstance.nsh +++ b/templates/nsis/include/allowOnlyOneInstallerInstance.nsh @@ -42,7 +42,7 @@ @@ -9,7 +17,74 @@ index fe5d45c730f36c9fe8d8cfea12e242e501b67139..af2ce5c90ac910b079e24992519bffe3 !else # find process owned by current user - nsExec::Exec `%SYSTEMROOT%\System32\cmd.exe /c tasklist /FI "USERNAME eq %USERNAME%" /FI "IMAGENAME eq ${_FILE}" /FO csv | %SYSTEMROOT%\System32\find.exe "${_FILE}"` -+ nsExec::Exec `%SYSTEMROOT%\System32\cmd.exe /c tasklist /FI "USERNAME eq %USERNAME%" /FI "PID ne $pid" /FI "IMAGENAME eq ${_FILE}" /FO csv | %SYSTEMROOT%\System32\find.exe "${_FILE}"` ++ nsExec::Exec `"$SYSDIR\cmd.exe" /c tasklist /FI "USERNAME eq %USERNAME%" /FI "IMAGENAME eq ${_FILE}" /FO csv | "$SYSDIR\find.exe" "${_FILE}"` Pop ${_ERR} !endif !macroend +@@ -73,7 +73,7 @@ + !ifdef INSTALL_MODE_PER_ALL_USERS + nsExec::Exec `taskkill /im "${APP_EXECUTABLE_FILENAME}" /fi "PID ne $pid"` + !else +- nsExec::Exec `%SYSTEMROOT%\System32\cmd.exe /c taskkill /im "${APP_EXECUTABLE_FILENAME}" /fi "PID ne $pid" /fi "USERNAME eq %USERNAME%"` ++ nsExec::Exec `"$SYSDIR\cmd.exe" /c taskkill /im "${APP_EXECUTABLE_FILENAME}" /fi "PID ne $pid" /fi "USERNAME eq %USERNAME%"` + !endif + # to ensure that files are not "in-use" + Sleep 300 +@@ -91,7 +91,7 @@ + !ifdef INSTALL_MODE_PER_ALL_USERS + nsExec::Exec `taskkill /f /im "${APP_EXECUTABLE_FILENAME}" /fi "PID ne $pid"` + !else +- nsExec::Exec `%SYSTEMROOT%\System32\cmd.exe /c taskkill /f /im "${APP_EXECUTABLE_FILENAME}" /fi "PID ne $pid" /fi "USERNAME eq %USERNAME%"` ++ nsExec::Exec `"$SYSDIR\cmd.exe" /c taskkill /f /im "${APP_EXECUTABLE_FILENAME}" /fi "PID ne $pid" /fi "USERNAME eq %USERNAME%"` + !endif + !insertmacro FIND_PROCESS "${APP_EXECUTABLE_FILENAME}" $R0 + ${If} $R0 == 0 +diff --git a/templates/nsis/include/installUtil.nsh b/templates/nsis/include/installUtil.nsh +index 47367741632726ba0886ac516461dbe98b7aea58..675965762375925a505ca6d8bbb67507ef696c2e 100644 +--- a/templates/nsis/include/installUtil.nsh ++++ b/templates/nsis/include/installUtil.nsh +@@ -126,10 +126,11 @@ Function handleUninstallResult + Return + + ${if} $R0 != 0 +- MessageBox MB_OK|MB_ICONEXCLAMATION "$(uninstallFailed): $R0" ++ # MessageBox MB_OK|MB_ICONEXCLAMATION "$(uninstallFailed): $R0" + DetailPrint `Uninstall was not successful. Uninstaller error code: $R0.` +- SetErrorLevel 2 +- Quit ++ DetailPrint `Continuing anyway. See https://github.com/laurent22/joplin/pull/11612.` ++ # SetErrorLevel 2 ++ # Quit + ${endif} + FunctionEnd + +@@ -216,11 +217,13 @@ Function uninstallOldVersion + IntOp $R5 $R5 + 1 + + ${if} $R5 > 5 +- MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "$(appCannotBeClosed)" /SD IDCANCEL IDRETRY OneMoreAttempt +- Return ++ MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "$(appCannotBeUninstalled)" /SD IDCANCEL IDRETRY ContinueWithoutUninstall ++ Abort ; Exit early ++ ContinueWithoutUninstall: ++ Return + ${endIf} + +- OneMoreAttempt: ++# OneMoreAttempt: ; Commented out because unused + ExecWait '"$uninstallerFileNameTemp" /S /KEEP_APP_DATA $0 _?=$installationDir' $R0 + ifErrors TryInPlace CheckResult + +diff --git a/templates/nsis/messages.yml b/templates/nsis/messages.yml +index a1c2847fa48d79f835b30b48e999ccaf3c818657..6884c18d1e77dbd6be114401d23cf5caf3e0dd94 100644 +--- a/templates/nsis/messages.yml ++++ b/templates/nsis/messages.yml +@@ -235,3 +235,8 @@ uninstallFailed: + sv: Det gick inte att avinstallera gamla programfiler. Försök att köra installationsprogrammet igen. + uk: Не вдалось видалити старі файли застосунку. Будь ласка, спробуйте запустити встановлювач знов. + zh_TW: 無法俺安裝舊的應用程式檔案。 請嘗試再次執行安裝程式。 ++ ++ ++appCannotBeUninstalled: ++ en: "The old version of ${PRODUCT_NAME} could not be removed. \nClick Retry to skip this step." ++ diff --git a/Assets/JoplinLetterBlue.svg b/Assets/JoplinLetterBlue.svg deleted file mode 100644 index 02a9369957c..00000000000 --- a/Assets/JoplinLetterBlue.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/Dockerfile.server b/Dockerfile.server index 8dff0e0008b..076da5859ad 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -2,11 +2,11 @@ # Build stage # ============================================================================= -FROM node:18-bullseye AS builder +FROM node:18 AS builder RUN apt-get update \ && apt-get install -y \ - python tini \ + python3 tini \ && rm -rf /var/lib/apt/lists/* # Enables Yarn @@ -56,7 +56,7 @@ RUN BUILD_SEQUENCIAL=1 yarn install --inline-builds \ # from a smaller base image. # ============================================================================= -FROM node:18-bullseye-slim +FROM node:18-slim ARG user=joplin RUN useradd --create-home --shell /bin/bash $user diff --git a/packages/app-cli/app/main.js b/packages/app-cli/app/main.js index 2b4f38d6f30..ab3e31c8e6c 100644 --- a/packages/app-cli/app/main.js +++ b/packages/app-cli/app/main.js @@ -1,4 +1,4 @@ -#!/usr/bin/env -S NODE_OPTIONS=--no-deprecation node +#!/usr/bin/env node // Use njstrace to find out what Node.js might be spending time on // var njstrace = require('njstrace').inject(); diff --git a/packages/app-cli/tests/support/onenote/hyperlink_marker_as_first_character.zip b/packages/app-cli/tests/support/onenote/hyperlink_marker_as_first_character.zip new file mode 100644 index 00000000000..7352fd91d10 Binary files /dev/null and b/packages/app-cli/tests/support/onenote/hyperlink_marker_as_first_character.zip differ diff --git a/packages/app-cli/tests/support/onenote/remove_hyperlink_on_title.zip b/packages/app-cli/tests/support/onenote/remove_hyperlink_on_title.zip new file mode 100644 index 00000000000..fedd8123a00 Binary files /dev/null and b/packages/app-cli/tests/support/onenote/remove_hyperlink_on_title.zip differ diff --git a/packages/app-cli/tests/support/test_notes/md/sample-malformed-uri.md b/packages/app-cli/tests/support/test_notes/md/sample-malformed-uri.md new file mode 100644 index 00000000000..b626bf22bf3 --- /dev/null +++ b/packages/app-cli/tests/support/test_notes/md/sample-malformed-uri.md @@ -0,0 +1 @@ +![malformed link](https://malformed_uri/%E0%A4%A.jpg) diff --git a/packages/app-desktop/bridge.ts b/packages/app-desktop/bridge.ts index f6f31e0361a..12941a4071b 100644 --- a/packages/app-desktop/bridge.ts +++ b/packages/app-desktop/bridge.ts @@ -1,5 +1,5 @@ import ElectronAppWrapper from './ElectronAppWrapper'; -import shim from '@joplin/lib/shim'; +import shim, { MessageBoxType } from '@joplin/lib/shim'; import { _, setLocale } from '@joplin/lib/locale'; import { BrowserWindow, nativeTheme, nativeImage, shell, dialog, MessageBoxSyncOptions, safeStorage } from 'electron'; import { dirname, toSystemSlashes } from '@joplin/lib/path-utils'; @@ -384,9 +384,14 @@ export class Bridge { /* returns the index of the clicked button */ public showMessageBox(message: string, options: MessageDialogOptions = {}) { + const defaultButtons = [_('OK')]; + if (options.type !== MessageBoxType.Error && options.type !== MessageBoxType.Info) { + defaultButtons.push(_('Cancel')); + } + const result = this.showMessageBox_(this.activeWindow(), { type: 'question', message: message, - buttons: [_('OK'), _('Cancel')], ...options }); + buttons: defaultButtons, ...options }); return result; } diff --git a/packages/app-desktop/commands/copyDevCommand.ts b/packages/app-desktop/commands/copyDevCommand.ts index e103ed61f26..d7bcb998b8f 100644 --- a/packages/app-desktop/commands/copyDevCommand.ts +++ b/packages/app-desktop/commands/copyDevCommand.ts @@ -1,5 +1,6 @@ import { CommandRuntime, CommandDeclaration } from '@joplin/lib/services/CommandService'; import { _ } from '@joplin/lib/locale'; +import shim, { MessageBoxType } from '@joplin/lib/shim'; const app = require('@electron/remote').app; const { clipboard } = require('electron'); @@ -14,7 +15,7 @@ export const runtime = (): CommandRuntime => { const appPath = app.getPath('exe'); const cmd = `${appPath} --env dev`; clipboard.writeText(cmd); - alert(`The dev mode command has been copied to clipboard:\n\n${cmd}`); + await shim.showMessageBox(`The dev mode command has been copied to clipboard:\n\n${cmd}`, { type: MessageBoxType.Info }); }, }; }; diff --git a/packages/app-desktop/commands/restoreNoteRevision.ts b/packages/app-desktop/commands/restoreNoteRevision.ts index ea7cc86d7c0..c80870643a1 100644 --- a/packages/app-desktop/commands/restoreNoteRevision.ts +++ b/packages/app-desktop/commands/restoreNoteRevision.ts @@ -1,5 +1,6 @@ import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; import RevisionService from '@joplin/lib/services/RevisionService'; +import shim, { MessageBoxType } from '@joplin/lib/shim'; export const declaration: CommandDeclaration = { name: 'restoreNoteRevision', @@ -11,9 +12,9 @@ export const runtime = (): CommandRuntime => { execute: async (_context: CommandContext, noteId: string, reverseRevIndex = 0) => { try { const note = await RevisionService.instance().restoreNoteById(noteId, reverseRevIndex); - alert(RevisionService.instance().restoreSuccessMessage(note)); + await shim.showMessageBox(RevisionService.instance().restoreSuccessMessage(note), { type: MessageBoxType.Info }); } catch (error) { - alert(error.message); + await shim.showErrorDialog(error.message); } }, }; diff --git a/packages/app-desktop/gui/ClipperConfigScreen.tsx b/packages/app-desktop/gui/ClipperConfigScreen.tsx index 6fd1dfa36dd..a6d0c6c4e5b 100644 --- a/packages/app-desktop/gui/ClipperConfigScreen.tsx +++ b/packages/app-desktop/gui/ClipperConfigScreen.tsx @@ -9,6 +9,7 @@ import ClipperServer from '@joplin/lib/ClipperServer'; import Setting from '@joplin/lib/models/Setting'; import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService'; import { AppState } from '../app.reducer'; +import shim, { MessageBoxType } from '@joplin/lib/shim'; class ClipperConfigScreenComponent extends React.Component { public constructor() { @@ -30,7 +31,7 @@ class ClipperConfigScreenComponent extends React.Component { private copyToken_click() { clipboard.writeText(this.props.apiToken); - alert(_('Token has been copied to the clipboard!')); + void shim.showMessageBox(_('Token has been copied to the clipboard!'), { type: MessageBoxType.Info }); } private renewToken_click() { diff --git a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx index 25b00bc4b80..3a5f9783b4d 100644 --- a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx @@ -19,6 +19,7 @@ import shouldShowMissingPasswordWarning from '@joplin/lib/components/shared/conf import MacOSMissingPasswordHelpLink from './controls/MissingPasswordHelpLink'; const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen'); import SettingComponent, { UpdateSettingValueEvent } from './controls/SettingComponent'; +import shim from '@joplin/lib/shim'; interface Font { @@ -144,7 +145,7 @@ class ConfigScreenComponent extends React.Component { screenName = section.name; if (this.hasChanges()) { - const ok = confirm(_('This will open a new screen. Save your current changes?')); + const ok = await shim.showConfirmationDialog(_('This will open a new screen. Save your current changes?')); if (ok) { await shared.saveSettings(this); } diff --git a/packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx b/packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx index f045c7e1549..03b2c729ee5 100644 --- a/packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx +++ b/packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx @@ -3,7 +3,7 @@ import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService'; import { themeStyle } from '@joplin/lib/theme'; import { _ } from '@joplin/lib/locale'; import time from '@joplin/lib/time'; -import shim from '@joplin/lib/shim'; +import shim, { MessageBoxType } from '@joplin/lib/shim'; import dialogs from '../dialogs'; import { decryptedStatText, determineKeyPassword, dontReencryptData, enableEncryptionConfirmationMessages, onSavePasswordClick, onToggleEnabledClick, reencryptData, upgradeMasterKey, useInputPasswords, useNeedMasterPassword, usePasswordChecker, useStats, useToggleShowDisabledMasterKeys } from '@joplin/lib/components/EncryptionConfigScreen/utils'; import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types'; @@ -47,7 +47,7 @@ const EncryptionConfigScreen = (props: Props) => { const onUpgradeMasterKey = useCallback(async (mk: MasterKeyEntity) => { const password = determineKeyPassword(mk.id, masterPasswordKeys, props.masterPassword, props.passwords); const result = await upgradeMasterKey(mk, password); - alert(result); + await shim.showMessageBox(result, { type: MessageBoxType.Info }); }, [props.passwords, masterPasswordKeys, props.masterPassword]); const renderNeedUpgradeSection = () => { diff --git a/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx b/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx index 95ac261840b..1573bbc2460 100644 --- a/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx +++ b/packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx @@ -11,6 +11,7 @@ import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService'; import KvStore from '@joplin/lib/services/KvStore'; import ShareService from '@joplin/lib/services/share/ShareService'; import LabelledPasswordInput from '../PasswordInput/LabelledPasswordInput'; +import shim from '@joplin/lib/shim'; interface Props { themeId: number; @@ -80,7 +81,7 @@ export default function(props: Props) { void reg.waitForSyncFinishedThenSync(); onClose(); } catch (error) { - alert(error.message); + void shim.showErrorDialog(error.message); } finally { setUpdatingPassword(false); } diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx index 8e4f59197af..5b1bd0e683f 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx +++ b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx @@ -48,7 +48,10 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef(null); + const rootRef = useRef(null); + rootRef.current = editorRoot; + const webviewRef = useRef(null); // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied const props_onChangeRef = useRef(null); @@ -410,6 +413,8 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef { + if (!editorRoot) return () => {}; + const theme = themeStyle(props.themeId); // Selection in dark mode is hard to see so make it brighter. @@ -431,10 +436,11 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef { - document.head.removeChild(element); + ownerDoc.head.removeChild(element); }; - // eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied - }, [props.themeId, props.contentMaxWidth]); + }, [props.themeId, props.contentMaxWidth, props.fontSize, editorRoot]); const webview_domReady = useCallback(() => { setWebviewReady(true); @@ -774,7 +784,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef -
+
{props.noteToolbar} diff --git a/packages/app-desktop/gui/NoteRevisionViewer.tsx b/packages/app-desktop/gui/NoteRevisionViewer.tsx index a58f8963bff..dbdde2c305e 100644 --- a/packages/app-desktop/gui/NoteRevisionViewer.tsx +++ b/packages/app-desktop/gui/NoteRevisionViewer.tsx @@ -17,6 +17,7 @@ const urlUtils = require('@joplin/lib/urlUtils'); const ReactTooltip = require('react-tooltip'); const { connect } = require('react-redux'); import shared from '@joplin/lib/components/shared/note-screen-shared'; +import shim, { MessageBoxType } from '@joplin/lib/shim'; interface Props { themeId: number; @@ -97,7 +98,7 @@ class NoteRevisionViewerComponent extends React.PureComponent { this.setState({ restoring: true }); await RevisionService.instance().importRevisionNote(this.state.note); this.setState({ restoring: false }); - alert(RevisionService.instance().restoreSuccessMessage(this.state.note)); + await shim.showMessageBox(RevisionService.instance().restoreSuccessMessage(this.state.note), { type: MessageBoxType.Info }); } private backButton_click() { diff --git a/packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx b/packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx index e3eb8b159d4..df76c33edce 100644 --- a/packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx +++ b/packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx @@ -18,6 +18,7 @@ import { connect } from 'react-redux'; import { reg } from '@joplin/lib/registry'; import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect'; import { ChangeEvent, Dropdown, DropdownOptions, DropdownVariant } from '../Dropdown/Dropdown'; +import shim from '@joplin/lib/shim'; const logger = Logger.create('ShareFolderDialog'); @@ -242,13 +243,13 @@ function ShareFolderDialog(props: Props) { } async function recipient_delete(event: RecipientDeleteEvent) { - if (!confirm(_('Delete this invitation? The recipient will no longer have access to this shared notebook.'))) return; + if (!await shim.showConfirmationDialog(_('Delete this invitation? The recipient will no longer have access to this shared notebook.'))) return; try { await ShareService.instance().deleteShareRecipient(event.shareUserId); } catch (error) { logger.error(error); - alert(_('The recipient could not be removed from the list. Please try again.\n\nThe error was: "%s"', error.message)); + await shim.showErrorDialog(_('The recipient could not be removed from the list. Please try again.\n\nThe error was: "%s"', error.message)); } await ShareService.instance().refreshShareUsers(share.id); @@ -290,7 +291,7 @@ function ShareFolderDialog(props: Props) { }); await ShareService.instance().setPermissions(share.id, shareUserId, permissionsFromString(value)); } catch (error) { - alert(`Could not set permissions: ${error.message}`); + void shim.showErrorDialog(`Could not set permissions: ${error.message}`); logger.error(error); } finally { setRecipientsBeingUpdated(prev => { @@ -383,7 +384,9 @@ function ShareFolderDialog(props: Props) { async function buttonRow_click(event: ClickEvent) { if (event.buttonName === 'unshare') { - if (!confirm(_('Unshare this notebook? The recipients will no longer have access to its content.'))) return; + if (!await shim.showConfirmationDialog(_('Unshare this notebook? The recipients will no longer have access to its content.'))) { + return; + } await ShareService.instance().unshareFolder(props.folderId); void synchronize(); } diff --git a/packages/app-desktop/gui/ShareNoteDialog.tsx b/packages/app-desktop/gui/ShareNoteDialog.tsx index e09427c5a19..7f9e4c7232e 100644 --- a/packages/app-desktop/gui/ShareNoteDialog.tsx +++ b/packages/app-desktop/gui/ShareNoteDialog.tsx @@ -16,6 +16,7 @@ import { connect } from 'react-redux'; import { AppState } from '../app.reducer'; import { getEncryptionEnabled } from '@joplin/lib/services/synchronizer/syncInfoUtils'; import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; +import shim from '@joplin/lib/shim'; const { clipboard } = require('electron'); interface Props { @@ -146,7 +147,7 @@ export function ShareNoteDialog(props: Props) { reg.logger().error('ShareNoteDialog: Cannot publish note:', error); setSharesState('idle'); - alert(JoplinServerApi.connectionErrorMessage(error)); + void shim.showErrorDialog(JoplinServerApi.connectionErrorMessage(error)); } break; diff --git a/packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx b/packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx index f9fac470e21..bbdb3b37650 100644 --- a/packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx +++ b/packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx @@ -31,6 +31,7 @@ import HeaderItem from '../listItemComponents/HeaderItem'; import AllNotesItem from '../listItemComponents/AllNotesItem'; import ListItemWrapper from '../listItemComponents/ListItemWrapper'; import { focus } from '@joplin/lib/utils/focusHandler'; +import shim from '@joplin/lib/shim'; const Menu = bridge().Menu; const MenuItem = bridge().MenuItem; @@ -309,7 +310,7 @@ const useOnRenderItem = (props: Props) => { } } catch (error) { logger.error(error); - alert(error.message); + await shim.showErrorDialog(error.message); } }, []); diff --git a/packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.ts b/packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.ts index 7b8ac1e2b5b..6d964b296cb 100644 --- a/packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.ts +++ b/packages/app-desktop/gui/WindowCommandsAndDialogs/commands/leaveSharedFolder.ts @@ -2,6 +2,7 @@ import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/ import { _ } from '@joplin/lib/locale'; import ShareService from '@joplin/lib/services/share/ShareService'; import Logger from '@joplin/utils/Logger'; +import shim from '@joplin/lib/shim'; const logger = Logger.create('leaveSharedFolder'); @@ -13,7 +14,7 @@ export const declaration: CommandDeclaration = { export const runtime = (): CommandRuntime => { return { execute: async (_context: CommandContext, folderId: string = null) => { - const answer = confirm(_('This will remove the notebook from your collection and you will no longer have access to its content. Do you wish to continue?')); + const answer = await shim.showConfirmationDialog(_('This will remove the notebook from your collection and you will no longer have access to its content. Do you wish to continue?')); if (!answer) return; try { @@ -28,7 +29,7 @@ export const runtime = (): CommandRuntime => { await ShareService.instance().leaveSharedFolder(folderId, share.user.id); } catch (error) { logger.error(error); - alert(_('Error: %s', error.message)); + await shim.showErrorDialog(_('Error: %s', error.message)); } }, enabledCondition: 'joplinServerConnected && folderIsShareRootAndNotOwnedByUser', diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index 1b74568ddff..cfc6bf514a7 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -1,6 +1,6 @@ { "name": "@joplin/app-desktop", - "version": "3.2.7", + "version": "3.2.10", "description": "Joplin for Desktop", "main": "main.js", "private": true, diff --git a/packages/app-mobile/android/app/build.gradle b/packages/app-mobile/android/app/build.gradle index 85944326ed6..360d8b599f5 100644 --- a/packages/app-mobile/android/app/build.gradle +++ b/packages/app-mobile/android/app/build.gradle @@ -79,8 +79,8 @@ android { applicationId "net.cozic.joplin" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 2097760 - versionName "3.2.4" + versionCode 2097761 + versionName "3.2.5" ndk { abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64" } diff --git a/packages/app-mobile/commands/openItem.ts b/packages/app-mobile/commands/openItem.ts index 65e4812b48d..33b55aa501c 100644 --- a/packages/app-mobile/commands/openItem.ts +++ b/packages/app-mobile/commands/openItem.ts @@ -8,6 +8,7 @@ import BaseItem from '@joplin/lib/models/BaseItem'; import { BaseItemEntity } from '@joplin/lib/services/database/types'; import { ModelType } from '@joplin/lib/BaseModel'; import showResource from './util/showResource'; +import { isCallbackUrl, parseCallbackUrl } from '@joplin/lib/callbackUrlUtils'; const logger = Logger.create('openItemCommand'); @@ -15,32 +16,48 @@ export const declaration: CommandDeclaration = { name: 'openItem', }; +const openItemById = async (itemId: string, hash?: string) => { + logger.info(`Navigating to item ${itemId}`); + const item: BaseItemEntity = await BaseItem.loadItemById(itemId); + + if (item.type_ === ModelType.Note) { + await goToNote(itemId, hash); + } else if (item.type_ === ModelType.Resource) { + await showResource(item); + } else { + throw new Error(`Unsupported item type for links: ${item.type_}`); + } +}; + export const runtime = (): CommandRuntime => { return { execute: async (_context: CommandContext, link: string) => { if (!link) throw new Error('Link cannot be empty'); - if (link.startsWith('joplin://') || link.startsWith(':/')) { - const parsedUrl = parseResourceUrl(link); - if (parsedUrl) { - const { itemId, hash } = parsedUrl; + try { + if (link.startsWith('joplin://') || link.startsWith(':/')) { + const parsedResourceUrl = parseResourceUrl(link); + const parsedCallbackUrl = isCallbackUrl(link) ? parseCallbackUrl(link) : null; - logger.info(`Navigating to item ${itemId}`); - const item: BaseItemEntity = await BaseItem.loadItemById(itemId); - if (item.type_ === ModelType.Note) { - await goToNote(itemId, hash); - } else if (item.type_ === ModelType.Resource) { - await showResource(item); + if (parsedResourceUrl) { + const { itemId, hash } = parsedResourceUrl; + await openItemById(itemId, hash); + } else if (parsedCallbackUrl) { + const id = parsedCallbackUrl.params.id; + if (!id) { + throw new Error('Missing item ID'); + } + await openItemById(id); } else { - logger.error('Unsupported item type for links:', item.type_); + throw new Error('Unsupported link format.'); } + } else if (urlProtocol(link)) { + shim.openUrl(link); } else { - logger.error(`Invalid Joplin link: ${link}`); + throw new Error('Unsupported protocol'); } - } else if (urlProtocol(link)) { - shim.openUrl(link); - } else { - const errorMessage = _('Unsupported link or message: %s', link); + } catch (error) { + const errorMessage = _('Unsupported link or message: %s.\nError: %s', link, error); logger.error(errorMessage); await shim.showErrorDialog(errorMessage); } diff --git a/packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.ts b/packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.ts index 29d89a1b6dd..a5acab20a91 100644 --- a/packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.ts +++ b/packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.ts @@ -7,6 +7,8 @@ import watchEditorForTemplateChanges from './watchEditorForTemplateChanges'; import { ImageEditorCallbacks, ImageEditorControl, LocalizedStrings } from './types'; import startAutosaveLoop from './startAutosaveLoop'; import WebViewToRNMessenger from '../../../../utils/ipc/WebViewToRNMessenger'; +import './polyfills'; + const restoreToolbarState = (toolbar: AbstractToolbar, state: string) => { if (state) { diff --git a/packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/polyfills.ts b/packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/polyfills.ts new file mode 100644 index 00000000000..ee4d188bcd0 --- /dev/null +++ b/packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/polyfills.ts @@ -0,0 +1,11 @@ +// .replaceChildren is not supported in Chromium 83, which is the default for Android 11 +// (unless auto-updated from the Google Play store). +HTMLElement.prototype.replaceChildren ??= function(this: HTMLElement, ...nodes: Node[]) { + while (this.children.length) { + this.children[0].remove(); + } + + for (const node of nodes) { + this.appendChild(node); + } +}; diff --git a/packages/app-mobile/components/plugins/PluginRunnerWebView.tsx b/packages/app-mobile/components/plugins/PluginRunnerWebView.tsx index 14a72c5deb3..c8edf4343fe 100644 --- a/packages/app-mobile/components/plugins/PluginRunnerWebView.tsx +++ b/packages/app-mobile/components/plugins/PluginRunnerWebView.tsx @@ -15,6 +15,7 @@ import { AppState } from '../../utils/types'; import usePrevious from '@joplin/lib/hooks/usePrevious'; import PlatformImplementation from '../../services/plugins/PlatformImplementation'; import AccessibleView from '../accessibility/AccessibleView'; +import useOnDevPluginsUpdated from './utils/useOnDevPluginsUpdated'; const logger = Logger.create('PluginRunnerWebView'); @@ -29,20 +30,33 @@ const usePlugins = ( pluginRunner: PluginRunner, webviewLoaded: boolean, pluginSettings: PluginSettings, + pluginSupportEnabled: boolean, + devPluginPath: string, ) => { const store = useStore(); const lastPluginRunner = usePrevious(pluginRunner); + const [reloadCounter, setReloadCounter] = useState(0); // Only set reloadAll to true here -- this ensures that all plugins are reloaded, // even if loadPlugins is cancelled and re-run. const reloadAllRef = useRef(false); reloadAllRef.current ||= pluginRunner !== lastPluginRunner; + useOnDevPluginsUpdated(async (pluginId: string) => { + logger.info(`Dev plugin ${pluginId} updated. Reloading...`); + await PluginService.instance().unloadPlugin(pluginId); + setReloadCounter(counter => counter + 1); + }, devPluginPath, pluginSupportEnabled); + useAsyncEffect(async (event) => { if (!webviewLoaded) { return; } + if (reloadCounter > 0) { + logger.debug('Reloading with counter set to', reloadCounter); + } + await loadPlugins({ pluginRunner, pluginSettings, @@ -56,7 +70,7 @@ const usePlugins = ( if (!event.cancelled) { reloadAllRef.current = false; } - }, [pluginRunner, store, webviewLoaded, pluginSettings]); + }, [pluginRunner, store, webviewLoaded, pluginSettings, reloadCounter]); }; const useUnloadPluginsOnGlobalDisable = ( @@ -79,6 +93,7 @@ interface Props { serializedPluginSettings: SerializedPluginSettings; pluginSupportEnabled: boolean; pluginStates: PluginStates; + devPluginPath: string; pluginHtmlContents: PluginHtmlContents; themeId: number; } @@ -98,7 +113,7 @@ const PluginRunnerWebViewComponent: React.FC = props => { }, [webviewReloadCounter]); const pluginSettings = usePluginSettings(props.serializedPluginSettings); - usePlugins(pluginRunner, webviewLoaded, pluginSettings); + usePlugins(pluginRunner, webviewLoaded, pluginSettings, props.pluginSupportEnabled, props.devPluginPath); useUnloadPluginsOnGlobalDisable(props.pluginStates, props.pluginSupportEnabled); const onLoadStart = useCallback(() => { @@ -183,6 +198,7 @@ export default connect((state: AppState) => { const result: Props = { serializedPluginSettings: state.settings['plugins.states'], pluginSupportEnabled: state.settings['plugins.pluginSupportEnabled'], + devPluginPath: state.settings['plugins.devPluginPaths'], pluginStates: state.pluginService.plugins, pluginHtmlContents: state.pluginService.pluginHtmlContents, themeId: state.settings.theme, diff --git a/packages/app-mobile/components/plugins/utils/useOnDevPluginsUpdated.ts b/packages/app-mobile/components/plugins/utils/useOnDevPluginsUpdated.ts new file mode 100644 index 00000000000..f1ad164bdd0 --- /dev/null +++ b/packages/app-mobile/components/plugins/utils/useOnDevPluginsUpdated.ts @@ -0,0 +1,60 @@ +import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect'; +import shim from '@joplin/lib/shim'; +import time from '@joplin/lib/time'; +import { basename, join } from 'path'; +import { useRef } from 'react'; + +type OnDevPluginChange = (id: string)=> void; + +const useOnDevPluginsUpdated = (onDevPluginChange: OnDevPluginChange, devPluginPath: string, pluginSupportEnabled: boolean) => { + const onDevPluginChangeRef = useRef(onDevPluginChange); + onDevPluginChangeRef.current = onDevPluginChange; + const isFirstUpdateRef = useRef(true); + + useAsyncEffect(async (event) => { + if (!devPluginPath || !pluginSupportEnabled) return; + + const itemToLastModTime = new Map(); + + // publishPath should point to the publish/ subfolder of a plugin's development + // directory. + const checkPluginChange = async (pluginPublishPath: string) => { + const dirStats = await shim.fsDriver().readDirStats(pluginPublishPath); + let hasChange = false; + let changedPluginId = ''; + for (const item of dirStats) { + if (item.path.endsWith('.jpl')) { + const lastModTime = itemToLastModTime.get(item.path); + const modTime = item.mtime.getTime(); + if (lastModTime === undefined || lastModTime < modTime) { + itemToLastModTime.set(item.path, modTime); + hasChange = true; + changedPluginId = basename(item.path, '.jpl'); + break; + } + } + } + + if (hasChange) { + if (isFirstUpdateRef.current) { + // Avoid sending an event the first time the hook is called. The first iteration + // collects initial timestamp information. In that case, hasChange + // will always be true, even with no plugin reload. + isFirstUpdateRef.current = false; + } else { + onDevPluginChangeRef.current(changedPluginId); + } + } + }; + + while (!event.cancelled) { + const publishFolder = join(devPluginPath, 'publish'); + await checkPluginChange(publishFolder); + + const pollingIntervalSeconds = 5; + await time.sleep(pollingIntervalSeconds); + } + }, [devPluginPath, pluginSupportEnabled]); +}; + +export default useOnDevPluginsUpdated; diff --git a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx index 4539d43cca3..1c849479033 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Platform, Linking, View, Switch, ScrollView, Text, TouchableOpacity, Alert, PermissionsAndroid, Dimensions, AccessibilityInfo } from 'react-native'; +import { Platform, Linking, View, ScrollView, Text, TouchableOpacity, Alert, PermissionsAndroid, Dimensions, AccessibilityInfo } from 'react-native'; import Setting, { AppType, SettingMetadataSection } from '@joplin/lib/models/Setting'; import NavService from '@joplin/lib/services/NavService'; import SearchEngine from '@joplin/lib/services/search/SearchEngine'; @@ -12,7 +12,6 @@ import { connect } from 'react-redux'; import ScreenHeader from '../../ScreenHeader'; import { _ } from '@joplin/lib/locale'; import BaseScreenComponent from '../../base-screen'; -import { themeStyle } from '../../global-style'; import * as shared from '@joplin/lib/components/shared/config/config-shared'; import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; import biometricAuthenticate from '../../biometrics/biometricAuthenticate'; @@ -36,6 +35,8 @@ import EnablePluginSupportPage from './plugins/EnablePluginSupportPage'; import getVersionInfoText from '../../../utils/getVersionInfoText'; import JoplinCloudConfig, { emailToNoteDescription, emailToNoteLabel } from './JoplinCloudConfig'; import shim from '@joplin/lib/shim'; +import SettingsToggle from './SettingsToggle'; +import { UpdateSettingValueCallback } from './types'; interface ConfigScreenState { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied @@ -673,22 +674,16 @@ class ConfigScreenComponent extends BaseScreenComponent - - - {label} - - {/* eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied */} - void updateSettingValue(key, value)} /> - - {descriptionComp} - - ); + private renderToggle(key: string, label: string, value: unknown, updateSettingValue: UpdateSettingValueCallback) { + return ; } // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied diff --git a/packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.tsx b/packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.tsx index 675216b09ab..1bd64af243a 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/FileSystemPathSelector.tsx @@ -3,18 +3,23 @@ import * as React from 'react'; import shim from '@joplin/lib/shim'; import { FunctionComponent, useCallback, useEffect, useState } from 'react'; import { ConfigScreenStyles } from './configScreenStyles'; -import { View, Text } from 'react-native'; +import { View, Text, StyleSheet } from 'react-native'; import Setting, { SettingItem } from '@joplin/lib/models/Setting'; import { openDocumentTree } from '@joplin/react-native-saf-x'; import { UpdateSettingValueCallback } from './types'; import { reg } from '@joplin/lib/registry'; import type FsDriverWeb from '../../../utils/fs-driver/fs-driver-rn.web'; -import { TouchableRipple } from 'react-native-paper'; +import { IconButton, TouchableRipple } from 'react-native-paper'; +import { _ } from '@joplin/lib/locale'; + +type Mode = 'read'|'readwrite'; interface Props { + themeId: number; styles: ConfigScreenStyles; settingMetadata: SettingItem; - mode: 'read'|'readwrite'; + mode: Mode; + description: React.ReactNode|null; updateSettingValue: UpdateSettingValueCallback; } @@ -23,30 +28,28 @@ type ExtendedSelf = (typeof window.self) & { }; declare const self: ExtendedSelf; -const FileSystemPathSelector: FunctionComponent = props => { +const useFileSystemPath = (settingId: string, updateSettingValue: UpdateSettingValueCallback, accessMode: Mode) => { const [fileSystemPath, setFileSystemPath] = useState(''); - const settingId = props.settingMetadata.key; - useEffect(() => { setFileSystemPath(Setting.value(settingId)); }, [settingId]); - const selectDirectoryButtonPress = useCallback(async () => { + const showDirectoryPicker = useCallback(async () => { if (shim.mobilePlatform() === 'web') { // Directory picker IDs can't include certain characters. const pickerId = `setting-${settingId}`.replace(/[^a-zA-Z]/g, '_'); - const handle = await self.showDirectoryPicker({ id: pickerId, mode: props.mode }); + const handle = await self.showDirectoryPicker({ id: pickerId, mode: accessMode }); const fsDriver = shim.fsDriver() as FsDriverWeb; - const uri = await fsDriver.mountExternalDirectory(handle, pickerId, props.mode); - await props.updateSettingValue(settingId, uri); + const uri = await fsDriver.mountExternalDirectory(handle, pickerId, accessMode); + await updateSettingValue(settingId, uri); setFileSystemPath(uri); } else { try { const doc = await openDocumentTree(true); if (doc?.uri) { setFileSystemPath(doc.uri); - await props.updateSettingValue(settingId, doc.uri); + await updateSettingValue(settingId, doc.uri); } else { throw new Error('User cancelled operation'); } @@ -54,32 +57,78 @@ const FileSystemPathSelector: FunctionComponent = props => { reg.logger().info('Didn\'t pick sync dir: ', e); } } - }, [props.updateSettingValue, settingId, props.mode]); + }, [updateSettingValue, settingId, accessMode]); + + const clearPath = useCallback(() => { + setFileSystemPath(''); + void updateSettingValue(settingId, ''); + }, [updateSettingValue, settingId]); // Supported on Android and some versions of Chrome const supported = shim.fsDriver().isUsingAndroidSAF() || (shim.mobilePlatform() === 'web' && 'showDirectoryPicker' in self); - if (!supported) { - return null; - } + + return { clearPath, showDirectoryPicker, fileSystemPath, supported }; +}; + +const pathSelectorStyles = StyleSheet.create({ + innerContainer: { + paddingTop: 0, + paddingBottom: 0, + paddingLeft: 0, + paddingRight: 0, + }, + mainButton: { + flexGrow: 1, + flexShrink: 1, + paddingHorizontal: 16, + paddingVertical: 22, + margin: 0, + }, + buttonContent: { + flexDirection: 'row', + }, +}); + +const FileSystemPathSelector: FunctionComponent = props => { + const settingId = props.settingMetadata.key; + const { clearPath, showDirectoryPicker, fileSystemPath, supported } = useFileSystemPath(settingId, props.updateSettingValue, props.mode); const styleSheet = props.styles.styleSheet; - return ( + const clearButton = ( + + ); + + const containerStyles = props.styles.getContainerStyle(!!props.description); + + const control = - + {props.settingMetadata.label()} - + {fileSystemPath} - ); + {fileSystemPath ? clearButton : null} + ; + + if (!supported) return null; + + return + {control} + {props.description} + ; }; export default FileSystemPathSelector; diff --git a/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx b/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx index 74cd66e2b06..748ffdaace1 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx @@ -38,7 +38,7 @@ const SettingComponent: React.FunctionComponent = props => { const styleSheet = props.styles.styleSheet; const descriptionComp = !settingDescription ? null : {settingDescription}; - const containerStyle = props.styles.getContainerStyle(!!settingDescription); + const containerStyles = props.styles.getContainerStyle(!!settingDescription); const labelId = useId(); @@ -49,8 +49,8 @@ const SettingComponent: React.FunctionComponent = props => { const label = md.label(); return ( - - + + {label} @@ -125,17 +125,19 @@ const SettingComponent: React.FunctionComponent = props => { if (['sync.2.path', 'plugins.devPluginPaths'].includes(md.key) && (shim.fsDriver().isUsingAndroidSAF() || shim.mobilePlatform() === 'web')) { return ( ); } return ( - - + + {md.label()} diff --git a/packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.tsx b/packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.tsx index a9c4f38cf2b..ebace0634b5 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.tsx @@ -24,9 +24,11 @@ const SettingsToggle: FunctionComponent = props => { const theme = themeStyle(props.themeId); const styleSheet = props.styles.styleSheet; + const containerStyles = props.styles.getContainerStyle(!!props.description); + return ( - - + + {props.label} diff --git a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts index 2ddff9b9b9f..b243c7c091d 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts +++ b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts @@ -6,8 +6,11 @@ type SidebarButtonStyle = ViewStyle & { height: number }; export interface ConfigScreenStyleSheet { body: ViewStyle; + settingOuterContainer: ViewStyle; + settingOuterContainerNoBorder: ViewStyle; settingContainer: ViewStyle; settingContainerNoBottomBorder: ViewStyle; + headerWrapperStyle: ViewStyle; headerTextStyle: TextStyle; @@ -39,12 +42,17 @@ export interface ConfigScreenStyleSheet { settingControl: TextStyle; } +interface ContainerStyles { + outerContainer: ViewStyle; + innerContainer: ViewStyle; +} + export interface ConfigScreenStyles { styleSheet: ConfigScreenStyleSheet; selectedSectionButtonColor: string; keyboardAppearance: 'default'|'light'|'dark'; - getContainerStyle(hasDescription: boolean): ViewStyle; + getContainerStyle(hasDescription: boolean): ContainerStyles; } const configScreenStyles = (themeId: number): ConfigScreenStyles => { @@ -107,6 +115,14 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { justifyContent: 'flex-start', flexDirection: 'column', }, + settingOuterContainer: { + flexDirection: 'column', + borderBottomWidth: 1, + borderBottomColor: theme.dividerColor, + }, + settingOuterContainerNoBorder: { + flexDirection: 'column', + }, settingContainer: settingContainerStyle, settingContainerNoBottomBorder: { ...settingContainerStyle, @@ -229,7 +245,9 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { selectedSectionButtonColor: theme.selectedColor, keyboardAppearance: theme.keyboardAppearance, getContainerStyle: (hasDescription) => { - return !hasDescription ? styleSheet.settingContainer : styleSheet.settingContainerNoBottomBorder; + const outerContainer = hasDescription ? styleSheet.settingOuterContainer : styleSheet.settingOuterContainerNoBorder; + const innerContainer = hasDescription ? styleSheet.settingContainerNoBottomBorder : styleSheet.settingContainer; + return { outerContainer, innerContainer }; }, }; }; diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx index 9002609d350..cd4628030ca 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/PluginChips.tsx @@ -92,12 +92,20 @@ const PluginChips: React.FC = props => { return {_('Installed')}; }; + const renderDevChip = () => { + if (!item.devMode) { + return null; + } + return {_('Dev')}; + }; + return {renderIncompatibleChip()} {renderInstalledChip()} {renderErrorsChip()} {renderBuiltInChip()} {renderUpdatableChip()} + {renderDevChip()} {renderDisabledChip()} ; }; diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx index ae6869e7353..dce8550d201 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx @@ -203,7 +203,7 @@ const PluginInfoModalContent: React.FC = props => { item={item} type={ButtonType.Delete} onPress={props.pluginCallbacks.onDelete} - disabled={item.builtIn || (item?.deleted ?? true)} + disabled={item.builtIn || item.devMode || (item?.deleted ?? true)} title={item?.deleted ? _('Deleted') : _('Delete')} /> ); diff --git a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.tsx b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.tsx index 1b46bf0fec7..0d59ac68b99 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/plugins/PluginUploadButton.tsx @@ -91,7 +91,7 @@ const PluginUploadButton: React.FC = props => { }, [props.pluginSettings, props.updatePluginStates]); return ( - + Promise; +export type UpdateSettingValueCallback = (key: string, value: any)=> void|Promise; export interface PluginStatusRecord { [pluginId: string]: boolean; diff --git a/packages/app-mobile/components/screens/Note/Note.test.tsx b/packages/app-mobile/components/screens/Note/Note.test.tsx index cdd7f996734..3a1e209453f 100644 --- a/packages/app-mobile/components/screens/Note/Note.test.tsx +++ b/packages/app-mobile/components/screens/Note/Note.test.tsx @@ -29,6 +29,8 @@ import TestProviderStack from '../../testing/TestProviderStack'; import setupGlobalStore from '../../../utils/testing/setupGlobalStore'; import CommandService from '@joplin/lib/services/CommandService'; +jest.retryTimes(2); + interface WrapperProps { } diff --git a/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj b/packages/app-mobile/ios/Joplin.xcodeproj/project.pbxproj index b4302026c23..01bd11c5c27 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 = 130; + CURRENT_PROJECT_VERSION = 132; 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.2.2; + MARKETING_VERSION = 13.2.4; 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 = 130; + CURRENT_PROJECT_VERSION = 132; DEVELOPMENT_TEAM = A9BXAFS6CT; INFOPLIST_FILE = Joplin/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 13.2.2; + MARKETING_VERSION = 13.2.4; 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 = 130; + CURRENT_PROJECT_VERSION = 132; 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.2.2; + MARKETING_VERSION = 13.2.4; 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 = 130; + CURRENT_PROJECT_VERSION = 132; 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.2.2; + MARKETING_VERSION = 13.2.4; 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 b4aeea0a7b5..523aa8211b7 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,37 +1,116 @@ { - "images" : [ - { - "filename" : "ios_marketing1024x1024.png", - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "ios_marketing_dark1024x1024.png", - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "tinted" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" + "images": [ + { + "filename": "ios_marketing1024x1024.png", + "idiom": "ios-marketing", + "size": "1024x1024", + "scale": "1x" + }, + { + "filename": "iphone_notification20x20@2x.png", + "idiom": "iphone", + "size": "20x20", + "scale": "2x" + }, + { + "filename": "iphone_notification20x20@3x.png", + "idiom": "iphone", + "size": "20x20", + "scale": "3x" + }, + { + "filename": "iphone_settings29x29@2x.png", + "idiom": "iphone", + "size": "29x29", + "scale": "2x" + }, + { + "filename": "iphone_settings29x29@3x.png", + "idiom": "iphone", + "size": "29x29", + "scale": "3x" + }, + { + "filename": "iphone_spotlight40x40@2x.png", + "idiom": "iphone", + "size": "40x40", + "scale": "2x" + }, + { + "filename": "iphone_spotlight40x40@3x.png", + "idiom": "iphone", + "size": "40x40", + "scale": "3x" + }, + { + "filename": "iphone_app60x60@2x.png", + "idiom": "iphone", + "size": "60x60", + "scale": "2x" + }, + { + "filename": "iphone_app60x60@3x.png", + "idiom": "iphone", + "size": "60x60", + "scale": "3x" + }, + { + "filename": "ipad_notification20x20.png", + "idiom": "ipad", + "size": "20x20", + "scale": "1x" + }, + { + "filename": "ipad_notification20x20@2x.png", + "idiom": "ipad", + "size": "20x20", + "scale": "2x" + }, + { + "filename": "ipad_settings29x29.png", + "idiom": "ipad", + "size": "29x29", + "scale": "1x" + }, + { + "filename": "ipad_settings29x29@2x.png", + "idiom": "ipad", + "size": "29x29", + "scale": "2x" + }, + { + "filename": "ipad_spotlight40x40.png", + "idiom": "ipad", + "size": "40x40", + "scale": "1x" + }, + { + "filename": "ipad_spotlight40x40@2x.png", + "idiom": "ipad", + "size": "40x40", + "scale": "2x" + }, + { + "filename": "ipad_app76x76.png", + "idiom": "ipad", + "size": "76x76", + "scale": "1x" + }, + { + "filename": "ipad_app76x76@2x.png", + "idiom": "ipad", + "size": "76x76", + "scale": "2x" + }, + { + "filename": "ipad_pro_app83.5x83.5@2x.png", + "idiom": "ipad", + "size": "83.5x83.5", + "scale": "2x" } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "version": 1, + "author": "xcode" } -} +} \ No newline at end of file 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 old mode 100644 new mode 100755 diff --git a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_marketing_dark1024x1024.png b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_marketing_dark1024x1024.png deleted file mode 100644 index 543f02ed5a4..00000000000 Binary files a/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_marketing_dark1024x1024.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 new file mode 100755 index 00000000000..f7556cf2475 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76.png 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 new file mode 100755 index 00000000000..bae6a354aee Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_app76x76@2x.png 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 new file mode 100755 index 00000000000..5c818e8470e Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20.png 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 new file mode 100755 index 00000000000..c4b9e4c9e18 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_notification20x20@2x.png 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 new file mode 100755 index 00000000000..9ea41ab0a17 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_pro_app83.5x83.5@2x.png 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 new file mode 100755 index 00000000000..957a5b4d203 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29.png 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 new file mode 100755 index 00000000000..bed468976e4 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_settings29x29@2x.png 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 new file mode 100755 index 00000000000..c4b9e4c9e18 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40.png 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 new file mode 100755 index 00000000000..2650ef5a5e3 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ipad_spotlight40x40@2x.png 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 new file mode 100755 index 00000000000..2241e825657 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@2x.png 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 new file mode 100755 index 00000000000..fb2f9c48fdd Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_app60x60@3x.png 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 new file mode 100755 index 00000000000..c4b9e4c9e18 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@2x.png 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 new file mode 100755 index 00000000000..6d053189d72 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_notification20x20@3x.png 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 new file mode 100755 index 00000000000..bed468976e4 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@2x.png 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 new file mode 100755 index 00000000000..ae72ab5e076 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_settings29x29@3x.png 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 new file mode 100755 index 00000000000..2650ef5a5e3 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@2x.png 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 new file mode 100755 index 00000000000..2241e825657 Binary files /dev/null and b/packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/iphone_spotlight40x40@3x.png differ diff --git a/packages/app-mobile/ios/Podfile.lock b/packages/app-mobile/ios/Podfile.lock index f4ef39d9211..235701c7b8f 100644 --- a/packages/app-mobile/ios/Podfile.lock +++ b/packages/app-mobile/ios/Podfile.lock @@ -1337,7 +1337,7 @@ PODS: - React-utils (= 0.74.1) - rn-fetch-blob (0.12.0): - React-Core - - RNCClipboard (1.14.1): + - RNCClipboard (1.14.2): - React-Core - RNCPushNotificationIOS (1.11.0): - React-Core @@ -1758,7 +1758,7 @@ SPEC CHECKSUMS: React-utils: 3285151c9d1e3a28a9586571fc81d521678c196d ReactCommon: f42444e384d82ab89184aed5d6f3142748b54768 rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba - RNCClipboard: 0a720adef5ec193aa0e3de24c3977222c7e52a37 + RNCClipboard: 5e503962f0719ace8f7fdfe9c60282b526305c85 RNCPushNotificationIOS: 64218f3c776c03d7408284a819b2abfda1834bc8 RNDateTimePicker: 40ffda97d071a98a10fdca4fa97e3977102ccd14 RNDeviceInfo: 59344c19152c4b2b32283005f9737c5c64b42fba diff --git a/packages/app-mobile/tools/buildInjectedJs/BundledFile.ts b/packages/app-mobile/tools/buildInjectedJs/BundledFile.ts index 690744668a8..b99e38ac4d6 100644 --- a/packages/app-mobile/tools/buildInjectedJs/BundledFile.ts +++ b/packages/app-mobile/tools/buildInjectedJs/BundledFile.ts @@ -58,18 +58,18 @@ export default class BundledFile { // Some libraries don't work with older browsers/WebViews. // Because Babel transpilation can be slow, we only transpile // these libraries. - // For now, it's just Replit's CodeMirror-vim library. This library - // uses `a?.b` syntax, which seems to be unsupported in iOS 12 Safari. - const moduleNeedsTranspilation = !!(/.*node_modules.*replit.*\.[mc]?js$/.exec(value)); + const moduleNeedsTranspilation = !!( + // Replit's CodeMirror-vim library uses a?.b syntax which seems to be unsupported in iOS 12 Safari. + /.*node_modules.*replit.*\.[mc]?js$/.exec(value) || + // js-draw uses a ??= b syntax, which is unsupported in old Android WebView versions + /.*node_modules.*js-draw.*\.[mc]?js$/.exec(value) + ); if (isModuleFile && !moduleNeedsTranspilation) { return false; } const isJsFile = !!(/\.[cm]?js$/.exec(value)); - if (isJsFile) { - console.log('Compiling with Babel:', value); - } return isJsFile; }, use: { diff --git a/packages/default-plugins/pluginRepositories.json b/packages/default-plugins/pluginRepositories.json index c868a7fdd17..932f84fba58 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": "3e7eac96d10218728120ce81bee2eeffd5f8fdbb" + "commit": "9724793b4a6fb83346ff4f7c639af1e352bd7937" } } diff --git a/packages/lib/Synchronizer.ts b/packages/lib/Synchronizer.ts index afef7ac7574..22b19680174 100644 --- a/packages/lib/Synchronizer.ts +++ b/packages/lib/Synchronizer.ts @@ -337,8 +337,10 @@ export default class Synchronizer { const hasActiveExclusiveLock = await hasActiveLock(locks, currentDate, this.lockHandler().lockTtl, LockType.Exclusive); if (hasActiveExclusiveLock) return 'hasExclusiveLock'; - const hasActiveSyncLock = await hasActiveLock(locks, currentDate, this.lockHandler().lockTtl, LockType.Sync, this.lockClientType(), this.clientId_); - if (!hasActiveSyncLock) return 'syncLockGone'; + if (this.lockHandler().enabled) { + const hasActiveSyncLock = await hasActiveLock(locks, currentDate, this.lockHandler().lockTtl, LockType.Sync, this.lockClientType(), this.clientId_); + if (!hasActiveSyncLock) return 'syncLockGone'; + } return ''; } diff --git a/packages/lib/models/settings/builtInMetadata.ts b/packages/lib/models/settings/builtInMetadata.ts index 1d27109d7ba..7c9a3b8508c 100644 --- a/packages/lib/models/settings/builtInMetadata.ts +++ b/packages/lib/models/settings/builtInMetadata.ts @@ -929,9 +929,23 @@ const builtInMetadata = (Setting: typeof SettingType) => { section: 'plugins', public: true, advanced: true, - appTypes: [AppType.Desktop], + appTypes: [AppType.Desktop, AppType.Mobile], + // For now, development plugins are only enabled on desktop & web. + show: (settings) => { + if (shim.isElectron()) return true; + if (shim.mobilePlatform() !== 'web') return false; + + const pluginSupportEnabled = settings['plugins.pluginSupportEnabled']; + return !!pluginSupportEnabled; + }, label: () => 'Development plugins', - description: () => 'You may add multiple plugin paths, each separated by a comma. You will need to restart the application for the changes to take effect.', + description: () => { + if (shim.mobilePlatform()) { + return 'The path to a plugin\'s development directory. When the plugin is rebuilt, Joplin reloads the plugin automatically.'; + } else { + return 'You may add multiple plugin paths, each separated by a comma. You will need to restart the application for the changes to take effect.'; + } + }, storage: SettingStorage.File, }, diff --git a/packages/lib/services/CommandService.ts b/packages/lib/services/CommandService.ts index 5a1fba54e27..7fa9b026b74 100644 --- a/packages/lib/services/CommandService.ts +++ b/packages/lib/services/CommandService.ts @@ -209,6 +209,10 @@ export default class CommandService extends BaseService { }; } + public unregisterDeclaration(name: string) { + delete this.commands_[name]; + } + public registerRuntime(commandName: string, runtime: CommandRuntime, allowMultiple = false): RegisteredRuntime { if (typeof commandName !== 'string') throw new Error(`Command name must be a string. Got: ${JSON.stringify(commandName)}`); diff --git a/packages/lib/services/commands/ToolbarButtonUtils.ts b/packages/lib/services/commands/ToolbarButtonUtils.ts index 554bb8b8832..a86a5766c84 100644 --- a/packages/lib/services/commands/ToolbarButtonUtils.ts +++ b/packages/lib/services/commands/ToolbarButtonUtils.ts @@ -48,22 +48,24 @@ export default class ToolbarButtonUtils { private commandToToolbarButton(commandName: string, whenClauseContext: WhenClauseContext): ToolbarButtonInfo { const newEnabled = this.service.isEnabled(commandName, whenClauseContext); const newTitle = this.service.title(commandName); + const newIcon = this.service.iconName(commandName); + const newLabel = this.service.label(commandName); if ( this.toolbarButtonCache_[commandName] && this.toolbarButtonCache_[commandName].info.enabled === newEnabled && - this.toolbarButtonCache_[commandName].info.title === newTitle + this.toolbarButtonCache_[commandName].info.title === newTitle && + this.toolbarButtonCache_[commandName].info.iconName === newIcon && + this.toolbarButtonCache_[commandName].info.tooltip === newLabel ) { return this.toolbarButtonCache_[commandName].info; } - const command = this.service.commandByName(commandName, { runtimeMustBeRegistered: true }); - const output: ToolbarButtonInfo = { type: 'button', name: commandName, - tooltip: this.service.label(commandName), - iconName: command.declaration.iconName, + tooltip: newLabel, + iconName: newIcon, enabled: newEnabled, onClick: async () => { await this.service.execute(commandName); diff --git a/packages/lib/services/interop/InteropService_Importer_Md.test.ts b/packages/lib/services/interop/InteropService_Importer_Md.test.ts index e6fde6685c8..7b0f80f3710 100644 --- a/packages/lib/services/interop/InteropService_Importer_Md.test.ts +++ b/packages/lib/services/interop/InteropService_Importer_Md.test.ts @@ -195,4 +195,13 @@ describe('InteropService_Importer_Md', () => { // The invalid image is imported as-is expect(resource.title).toBe('invalid-image.jpg'); }); + + it('should not fail to import file that contains a malformed URI', async () => { + // The first implicit test is that the below call doesn't throw due to the malformed URI + const note = await importNote(`${supportDir}/test_notes/md/sample-malformed-uri.md`); + const itemIds = Note.linkedItemIds(note.body); + expect(itemIds.length).toBe(0); + // The malformed link is imported as-is + expect(note.body).toContain('![malformed link](https://malformed_uri/%E0%A4%A.jpg)'); + }); }); diff --git a/packages/lib/services/interop/InteropService_Importer_Md.ts b/packages/lib/services/interop/InteropService_Importer_Md.ts index 22ef8cf1f0a..892d0b744a6 100644 --- a/packages/lib/services/interop/InteropService_Importer_Md.ts +++ b/packages/lib/services/interop/InteropService_Importer_Md.ts @@ -110,11 +110,18 @@ export default class InteropService_Importer_Md extends InteropService_Importer_ const htmlLinks = htmlUtils.extractFileUrls(md); const fileLinks = unique(markdownLinks.concat(htmlLinks)); for (const encodedLink of fileLinks) { - const link = decodeURI(encodedLink); + let link = ''; + try { + link = decodeURI(encodedLink); + } catch (error) { + // If the URI cannot be decoded, leave it as it is. + continue; + } if (isDataUrl(link)) { // Just leave it as it is. We could potentially import // it as a resource but for now that's good enough. + continue; } else { // Handle anchor links appropriately const trimmedLink = this.trimAnchorLink(link); diff --git a/packages/lib/services/interop/InteropService_Importer_OneNote.test.ts b/packages/lib/services/interop/InteropService_Importer_OneNote.test.ts index ce722377b4e..b63a8faa278 100644 --- a/packages/lib/services/interop/InteropService_Importer_OneNote.test.ts +++ b/packages/lib/services/interop/InteropService_Importer_OneNote.test.ts @@ -176,4 +176,35 @@ describe('InteropService_Importer_OneNote', () => { BaseModel.setIdGenerator(originalIdGenerator); }); + + skipIfNotCI('should remove hyperlink from title', async () => { + let idx = 0; + const originalIdGenerator = BaseModel.setIdGenerator(() => String(idx++)); + const notes = await importNote(`${supportDir}/onenote/remove_hyperlink_on_title.zip`); + + for (const note of notes) { + expect(note.body).toMatchSnapshot(note.title); + } + BaseModel.setIdGenerator(originalIdGenerator); + }); + + skipIfNotCI('should group link parts even if they have different css styles', async () => { + const notes = await importNote(`${supportDir}/onenote/remove_hyperlink_on_title.zip`); + + const noteToTest = notes.find(n => n.title === 'Tips from a Pro Using Trees for Dramatic Landscape Photography'); + + expect(noteToTest).toBeTruthy(); + expect(noteToTest.body.includes('Tips from a Pro: Using Trees for Dramatic Landscape Photography')).toBe(true); + }); + + skipIfNotCI('should render links properly by ignoring wrongly set indices when the first character is a hyperlink marker', async () => { + let idx = 0; + const originalIdGenerator = BaseModel.setIdGenerator(() => String(idx++)); + const notes = await importNote(`${supportDir}/onenote/hyperlink_marker_as_first_character.zip`); + + 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 a70037a0b3e..c0fe8f27389 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 @@ -766,3 +766,404 @@ exports[`InteropService_Importer_OneNote should import a simple OneNote notebook " `; + +exports[`InteropService_Importer_OneNote should remove hyperlink from title: Quick Notes 1`] = ` +" + + + + Quick Notes + + + + + + + + + + + +" +`; + +exports[`InteropService_Importer_OneNote should remove hyperlink from title: Tips from a Pro Using Trees for Dramatic Landscape Photography 1`] = ` +" + + + + Tips from a Pro: Using Trees for Dramatic Landscape Photography + + + + +
 
+
Saturday, February 11, 2023
+
12:56 AM
+
+ + + +" +`; + +exports[`InteropService_Importer_OneNote should remove hyperlink from title: wikipedia link 1`] = ` +" + + + + wikipedia link + + + + +
 
+
Sunday, January 05, 2025
+
10:15 PM
+
+ + + +" +`; + +exports[`InteropService_Importer_OneNote should remove hyperlink from title: 风景 (Web view) 1`] = ` +" + + + + 风景 (Web view) + + + + +
 
+
Sunday, January 5, 2025
+
10:13 PM
+
+ + + +" +`; + +exports[`InteropService_Importer_OneNote should remove hyperlink from title: 风景 1`] = ` +" + + + + 风景 + + + + +
风景
+
Sunday, January 05, 2025
+
10:14 PM
+
+ + + +" +`; + +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`] = ` +" + + + + Is Mexico safe for shooting Street Photography? + + + + +
 
+
Monday, August 28, 2023
+
10:52 AM
+
+ + + +" +`; + +exports[`InteropService_Importer_OneNote should render links properly by ignoring wrongly set indices when the first character is a hyperlink marker: Quick Notes 1`] = ` +" + + + + Quick Notes + + + + + + + + + + + +" +`; diff --git a/packages/lib/services/plugins/api/JoplinCommands.ts b/packages/lib/services/plugins/api/JoplinCommands.ts index 1df70fe1352..7f4d31624e9 100644 --- a/packages/lib/services/plugins/api/JoplinCommands.ts +++ b/packages/lib/services/plugins/api/JoplinCommands.ts @@ -122,6 +122,7 @@ export default class JoplinCommands { CommandService.instance().registerRuntime(declaration.name, runtime); this.plugin_.addOnUnloadListener(() => { CommandService.instance().unregisterRuntime(declaration.name); + CommandService.instance().unregisterDeclaration(declaration.name); }); } diff --git a/packages/lib/services/plugins/loadPlugins.ts b/packages/lib/services/plugins/loadPlugins.ts index ac83b7cf001..f09cd84f8bf 100644 --- a/packages/lib/services/plugins/loadPlugins.ts +++ b/packages/lib/services/plugins/loadPlugins.ts @@ -51,10 +51,7 @@ const loadPlugins = async ({ } } - if (Setting.value('env') === 'dev') { - logger.info('Running dev plugins (if any)...'); - await pluginService.loadAndRunDevPlugins(pluginSettings); - } + await pluginService.loadAndRunDevPlugins(pluginSettings); if (cancelEvent.cancelled) { logger.info('Cancelled.'); diff --git a/packages/lib/services/plugins/reducer.ts b/packages/lib/services/plugins/reducer.ts index de9a3be7366..926e3fc0c58 100644 --- a/packages/lib/services/plugins/reducer.ts +++ b/packages/lib/services/plugins/reducer.ts @@ -202,6 +202,7 @@ const reducer = (draftRoot: Draft, action: any) => { case 'PLUGIN_UNLOAD': delete draft.plugins[action.pluginId]; + delete draft.pluginHtmlContents[action.pluginId]; break; } diff --git a/packages/onenote-converter/src/notebook.rs b/packages/onenote-converter/src/notebook.rs index 82ab8cf252b..460d690460b 100644 --- a/packages/onenote-converter/src/notebook.rs +++ b/packages/onenote-converter/src/notebook.rs @@ -84,7 +84,7 @@ impl Renderer { let section_path = renderer.render(section, notebook_dir)?; log!("section_path: {:?}", section_path); - let path_from_base_dir = unsafe { remove_prefix(section_path.as_str(), base_dir.as_str()) } + let path_from_base_dir = unsafe { remove_prefix(section_path, base_dir.as_str()) } .unwrap() .as_string() .unwrap(); diff --git a/packages/onenote-converter/src/page/mod.rs b/packages/onenote-converter/src/page/mod.rs index e66571e9107..10ead671d0b 100644 --- a/packages/onenote-converter/src/page/mod.rs +++ b/packages/onenote-converter/src/page/mod.rs @@ -35,7 +35,7 @@ impl<'a> Renderer<'a> { } pub(crate) fn render_page(&mut self, page: &Page) -> Result { - let title_text = page.title_text().unwrap_or("Untitled Page"); + let title_text = page.title_text().unwrap_or("Untitled Page".to_string()); let mut content = String::new(); @@ -70,7 +70,7 @@ impl<'a> Renderer<'a> { content.push_str(&page_content); - crate::templates::page::render(title_text, &content, &self.global_styles) + crate::templates::page::render(&title_text, &content, &self.global_styles) } pub(crate) fn gen_class(&mut self, prefix: &str) -> String { diff --git a/packages/onenote-converter/src/page/rich_text.rs b/packages/onenote-converter/src/page/rich_text.rs index c095b0f2552..ccb5c9dbc79 100644 --- a/packages/onenote-converter/src/page/rich_text.rs +++ b/packages/onenote-converter/src/page/rich_text.rs @@ -74,11 +74,25 @@ impl<'a> Renderer<'a> { // all the styles to be shifted by minus one. // A better solution would be to look if there isn't anything wrong with the parser, // but I haven't found what could be causing this yet. - if text.starts_with("\u{000B}") && !indices.is_empty(){ + if text.starts_with("\u{000B}") && !indices.is_empty() { indices.remove(0); styles.pop(); } + // Probably the best solution here would be to rewrite the render_hyperlink to take this + // case in account, backtracking if necessary, but this will do for now + // https://github.com/laurent22/joplin/issues/11617 + if text.starts_with("\u{fddf}") { + let first_indice = match indices.get(0) { + Some(i) => *i, + None => 0, + }; + if first_indice == 1 { + indices.remove(0); + styles.pop(); + } + } + if indices.is_empty() { return Ok(fix_newlines(&text)); } @@ -100,6 +114,7 @@ impl<'a> Renderer<'a> { } let mut in_hyperlink = false; + let mut is_href_finished = true; let content = parts .into_iter() @@ -107,12 +122,18 @@ impl<'a> Renderer<'a> { .zip(styles.iter()) .map(|(text, style)| { if style.hyperlink() { - let text = self.render_hyperlink(text, style, in_hyperlink); - in_hyperlink = true; - - text + let result = + self.render_hyperlink(text.clone(), style, in_hyperlink, is_href_finished); + if result.is_ok() { + in_hyperlink = true; + is_href_finished = result.as_ref().unwrap().1; + Ok(result.unwrap().0) + } else { + Ok(text) + } } else { in_hyperlink = false; + is_href_finished = true; let style = self.parse_style(style); @@ -128,12 +149,17 @@ impl<'a> Renderer<'a> { Ok(fix_newlines(&content)) } + /// The hyperlink is delimited by the HYPERLINK_MARKER until the closing double quote + /// In some cases the hyperlink is broken in more than one style (e.g.: when there are + /// chinese characters on the url path), so we must keep track of the href status + /// https://github.com/laurent22/joplin/issues/11600 fn render_hyperlink( &self, text: String, style: &ParagraphStyling, in_hyperlink: bool, - ) -> Result { + is_href_finished: bool, + ) -> Result<(String, bool)> { const HYPERLINK_MARKER: &str = "\u{fddf}HYPERLINK \""; let style = self.parse_style(style); @@ -141,17 +167,32 @@ impl<'a> Renderer<'a> { if text.starts_with(HYPERLINK_MARKER) { let url = text .strip_prefix(HYPERLINK_MARKER) - .wrap_err("Hyperlink has no start marker")? - .strip_suffix('"') - .wrap_err("Hyperlink has no end marker")?; - - Ok(format!("", url, style)) - } else if in_hyperlink { - Ok(text + "") + .wrap_err("Hyperlink has no start marker")?; + + let url_2 = url.strip_suffix('"'); + + if url_2.is_some() { + return Ok(( + format!("", url_2.unwrap(), style), + true, + )); + } else { + // If we didn't find the double quotes means that href still has content in following styles + Ok((format!("", true)) + } else if in_hyperlink && !is_href_finished { + let url = text.strip_suffix('"'); + if url.is_some() { + return Ok((format!("{}\" style=\"{}\">", url.unwrap(), style), true)); + } else { + Ok((text, false)) + } } else { - Ok(format!( - "{}", - text, style, text + Ok(( + format!("{}", text, style, text), + true, )) } } diff --git a/packages/onenote-converter/src/parser/one/property_set/page_metadata.rs b/packages/onenote-converter/src/parser/one/property_set/page_metadata.rs index 2523f7fd140..786bbb99722 100644 --- a/packages/onenote-converter/src/parser/one/property_set/page_metadata.rs +++ b/packages/onenote-converter/src/parser/one/property_set/page_metadata.rs @@ -32,10 +32,13 @@ pub(crate) fn parse(object: &Object) -> Result { let entity_guid = simple::parse_guid(PropertyType::NotebookManagementEntityGuid, object)? .ok_or_else(|| ErrorKind::MalformedOneNoteFileData("page metadata has no guid".into()))?; - let cached_title = - simple::parse_string(PropertyType::CachedTitleString, object)?.ok_or_else(|| { - ErrorKind::MalformedOneNoteFileData("page metadata has no cached title".into()) - })?; + // The page might not have a title but we can use the first Section outline from the body as the fallback later + let cached_title = simple::parse_string(PropertyType::CachedTitleString, object)? + .ok_or_else(|| { + let guid = simple::parse_guid(PropertyType::NotebookManagementEntityGuid, object); + return guid.map(|g| g.unwrap().to_string()); + }) + .unwrap_or("Untitled Page".to_string()); let schema_revision_in_order_to_read = simple::parse_u32(PropertyType::SchemaRevisionInOrderToRead, object)?; let schema_revision_in_order_to_write = diff --git a/packages/onenote-converter/src/parser/onenote/page.rs b/packages/onenote-converter/src/parser/onenote/page.rs index 1a6ae603224..485a2370a9e 100644 --- a/packages/onenote-converter/src/parser/onenote/page.rs +++ b/packages/onenote-converter/src/parser/onenote/page.rs @@ -62,16 +62,23 @@ impl Page { /// The page's title text. /// /// This is calculated using a heuristic similar to the one OneNote uses. - pub fn title_text(&self) -> Option<&str> { + pub fn title_text(&self) -> Option { self.title .as_ref() .and_then(|title| title.contents.first()) .and_then(Self::outline_text) + .and_then(|t| Some(Self::remove_hyperlink(t.to_owned()))) .or_else(|| { self.contents .iter() .filter_map(|page_content| page_content.outline()) - .filter_map(Self::outline_text) + .filter_map(|t| { + let v = Self::outline_text(t); + if v.is_none() { + return None; + } + return Some(Self::remove_hyperlink(v.unwrap().to_owned())); + }) .next() }) } @@ -85,6 +92,33 @@ impl Page { .and_then(|content| content.rich_text()) .and_then(|text| Some(&*text.text).filter(|s| !s.is_empty())) } + + fn remove_hyperlink(title: String) -> String { + const HYPERLINK_MARKER: &str = "\u{fddf}HYPERLINK \""; + + let mut title_copy = title.clone(); + + loop { + // Find the first hyperlink mark + if let Some(marker_start) = title_copy.find(HYPERLINK_MARKER) { + let hyperlink_part = &title_copy[marker_start + HYPERLINK_MARKER.len()..]; + + // Find the closing double quote of the hyperlink + if let Some(quote_end) = hyperlink_part.find('"') { + let before_hyperlink = &title_copy[..marker_start]; + let after_hyperlink = &hyperlink_part[quote_end + 1..]; + title_copy = format!("{}{}", before_hyperlink, after_hyperlink); + } else { + // Sometimes links are broken, in these cases we only consider what is before the mark + title_copy = title[..marker_start].to_string(); + } + } else { + break; + } + } + + title_copy + } } /// A page title. diff --git a/packages/onenote-converter/src/section.rs b/packages/onenote-converter/src/section.rs index 2edc29cf4a6..8c3dc22fc5a 100644 --- a/packages/onenote-converter/src/section.rs +++ b/packages/onenote-converter/src/section.rs @@ -64,7 +64,7 @@ impl Renderer { let _ = unsafe { write_file(&page_path, page_html.as_bytes()) }; let page_path_without_basedir = - unsafe { remove_prefix(page_path.as_str(), output_dir.as_str()) } + unsafe { remove_prefix(page_path, output_dir.as_str()) } .unwrap() .as_string() .unwrap(); @@ -72,7 +72,6 @@ impl Renderer { } } - log!("Section finished rendering: {:?}", section.display_name()); let toc_html = templates::section::render(section.display_name(), toc)?; let toc_file = unsafe { join_path( diff --git a/packages/onenote-converter/src/utils.rs b/packages/onenote-converter/src/utils.rs index 1f8f6553caa..5371e9f1ea7 100644 --- a/packages/onenote-converter/src/utils.rs +++ b/packages/onenote-converter/src/utils.rs @@ -82,7 +82,7 @@ extern "C" { #[wasm_bindgen(js_name = removePrefix, catch)] pub unsafe fn remove_prefix( - base_path: &str, + base_path: String, prefix: &str, ) -> std::result::Result; diff --git a/packages/plugin-repo-cli/index.ts b/packages/plugin-repo-cli/index.ts index f8091d9c7d5..825c5376ef1 100644 --- a/packages/plugin-repo-cli/index.ts +++ b/packages/plugin-repo-cli/index.ts @@ -245,7 +245,7 @@ async function commandBuild(args: CommandBuildArgs) { chdir(previousDir); - const searchResults = (await execCommand('npm search joplin-plugin --searchlimit 5000 --json', { showStdout: false, showStderr: false })).trim(); + const searchResults = (await execCommand('npm search keywords:joplin-plugin --searchlimit 5000 --json', { showStdout: false, showStderr: false })).trim(); const npmPackages = pluginInfoFromSearchResults(JSON.parse(searchResults)); for (const npmPackage of npmPackages) { diff --git a/packages/tools/generate-images.json b/packages/tools/generate-images.json index 8638169c416..7110c377815 100644 --- a/packages/tools/generate-images.json +++ b/packages/tools/generate-images.json @@ -71,7 +71,6 @@ "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_89ddfe84307b49fa96580655b5d7c045_216bb492f34224f24aabacb5f98c3620_fe652082bfb7427cd5c74566ecc24322_ebf1ccaf3f5b77b01ff690b763a411f9_216bb492f34224f24aabacb5f98c3620_950b970a784b14c329e09e78af827a77_ebf1ccaf3f5b77b01ff690b763a411f9_d33dafc8081155149dd1d8c1713bf03f_950b970a784b14c329e09e78af827a77_94949c497e46ed0c67082175f5bb22f8": true, - "13_81d4e1a631a97f3db0d033de420f14df_packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_marketing_dark1024x1024.png_1024_1024____": true + "icns_to_icon_set_9f922e3fd5465dd99eabaa24a3dd3c1d_12cd001124be423c65c1b36deccad273_717fde633fffdca1ae13cca5e4d05143_a8f4a3f97ceaefa3151416a40cfab0af_12cd001124be423c65c1b36deccad273_0bf543ae51a3980ce194156b14a77eee_a8f4a3f97ceaefa3151416a40cfab0af_35d1c48c7d56a96cafd8fb898432f25d_0bf543ae51a3980ce194156b14a77eee_b38e4ecf1acfdc684712c8f7d9b9305d": true } -} +} \ No newline at end of file diff --git a/packages/tools/generate-images.ts b/packages/tools/generate-images.ts index 90bfa0a0eed..ea457bb12d5 100644 --- a/packages/tools/generate-images.ts +++ b/packages/tools/generate-images.ts @@ -82,10 +82,6 @@ const sources: Source[] = [ id: 12, name: 'JoplinCloudIcon2.svg', }, - { - id: 13, - name: '../JoplinLetterBlue.svg', - }, ]; function sourceById(id: number) { @@ -108,10 +104,106 @@ const operations: Operation[] = [ height: 1024, }, { - source: 13, - dest: 'packages/app-mobile/ios/Joplin/Images.xcassets/AppIcon.appiconset/ios_marketing_dark1024x1024.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, }, // ============================================================================ diff --git a/packages/tools/git-changelog.ts b/packages/tools/git-changelog.ts index 9a83c6e0503..d50f62e88ad 100644 --- a/packages/tools/git-changelog.ts +++ b/packages/tools/git-changelog.ts @@ -15,6 +15,9 @@ interface LogEntry { enum Platform { Unknown = 'unknown', Android = 'android', + Windows = 'windows', + MacOs = 'macos', + Linux = 'linux', Ios = 'ios', Desktop = 'desktop', Clipper = 'clipper', @@ -94,6 +97,9 @@ async function gitTags() { function platformFromTag(tagName: string): Platform { if (tagName.indexOf('v') === 0) return Platform.Desktop; if (tagName.indexOf('android') >= 0) return Platform.Android; + if (tagName.indexOf('windows') >= 0) return Platform.Windows; + if (tagName.indexOf('linux') >= 0) return Platform.Linux; + if (tagName.indexOf('macos') >= 0) return Platform.MacOs; if (tagName.indexOf('ios') >= 0) return Platform.Ios; if (tagName.indexOf('clipper') === 0) return Platform.Clipper; if (tagName.indexOf('cli') === 0) return Platform.Cli; @@ -106,7 +112,7 @@ function platformFromTag(tagName: string): Platform { } export const filesApplyToPlatform = (files: string[], platform: string): boolean => { - const isMainApp = ['android', 'ios', 'desktop', 'cli', 'server'].includes(platform); + const isMainApp = ['android', 'ios', 'windows', 'linux', 'macos', 'desktop', 'cli', 'server'].includes(platform); const isMobile = ['android', 'ios'].includes(platform); for (const file of files) { @@ -254,7 +260,7 @@ function filterLogs(logs: LogEntry[], platform: Platform) { if (platform === 'android' && prefix.indexOf('android') >= 0) addIt = true; if (platform === 'ios' && prefix.indexOf('ios') >= 0) addIt = true; if (platform === 'desktop' && prefix.indexOf('desktop') >= 0) addIt = true; - if (platform === 'desktop' && (prefix.indexOf('desktop') >= 0 || prefix.indexOf('api') >= 0 || prefix.indexOf('plugins') >= 0 || prefix.indexOf('macos') >= 0)) addIt = true; + if (platform === 'desktop' && (prefix.indexOf('desktop') >= 0 || prefix.indexOf('api') >= 0 || prefix.indexOf('plugins') >= 0 || prefix.indexOf('macos') >= 0 || prefix.indexOf('windows') >= 0 || prefix.indexOf('linux') >= 0)) addIt = true; if (platform === 'cli' && prefix.indexOf('cli') >= 0) addIt = true; if (platform === 'clipper' && prefix.indexOf('clipper') >= 0) addIt = true; if (platform === 'server' && prefix.indexOf('server') >= 0) addIt = true; @@ -312,7 +318,7 @@ function formatCommitMessage(commit: string, msg: string, author: Author, option const isPlatformPrefix = (prefixString: string) => { const prefix = prefixString.split(',').map(p => p.trim().toLowerCase()); for (const p of prefix) { - if (['android', 'mobile', 'ios', 'desktop', 'cli', 'clipper', 'all', 'api', 'plugins', 'server', 'cloud'].indexOf(p) >= 0) return true; + if (['android', 'mobile', 'ios', 'desktop', 'windows', 'linux', 'macos', 'cli', 'clipper', 'all', 'api', 'plugins', 'server', 'cloud'].indexOf(p) >= 0) return true; } return false; }; diff --git a/packages/tools/locales/da_DK.po b/packages/tools/locales/da_DK.po index 30ce0d725c3..b2ad3cbf655 100644 --- a/packages/tools/locales/da_DK.po +++ b/packages/tools/locales/da_DK.po @@ -7,6 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: Joplin-CLI 1.0.0\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" "Last-Translator: ERYpTION\n" "Language-Team: \n" "Language: da_DK\n" @@ -18,9 +20,7 @@ msgstr "" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:592 msgid "- Camera: to allow taking a picture and attaching it to a note." -msgstr "" -"- Kamera: Tilladelse til fotografering og vedhæftning af et billede til en " -"note." +msgstr "- Kamera: For at kunne tage et billede og vedhæfte det til en note." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:595 msgid "- Location: to allow attaching geo-location information to a note." @@ -49,7 +49,7 @@ msgstr "(I udvidelse: %s)" #: packages/app-mobile/components/side-menu-content.tsx:265 msgid "(level %d)" -msgstr "" +msgstr "(niveau %d)" #: packages/lib/SyncTargetNone.ts:16 msgid "(None)" @@ -262,9 +262,8 @@ msgstr "" "opgave). Brug \"clear\" til at konvertere en opgave til en alm. note." #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:62 -#, fuzzy msgid "A new update (%s) is available" -msgstr "Opdatering tilgængelig" +msgstr "En ny opdatering (%s) er tilgængelig" #: packages/lib/models/settings/builtInMetadata.ts:1239 msgid "A3" @@ -373,7 +372,7 @@ msgstr "Tilføj modtager:" #: packages/app-mobile/components/screens/NoteTagsDialog.tsx:94 msgid "Add tag %s to note" -msgstr "" +msgstr "Tilføj etiketten %s til note" #: packages/app-mobile/components/screens/Note/Note.tsx:1574 msgid "Add title" @@ -384,9 +383,8 @@ msgid "Add to dictionary" msgstr "Tilføj til ordbog" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:98 -#, fuzzy msgid "Add to note" -msgstr "Tilføj titel" +msgstr "Tilføj til note" #: packages/server/src/services/MustacheService.ts:162 #: packages/server/src/services/MustacheService.ts:286 @@ -410,9 +408,8 @@ msgid "Advanced tools" msgstr "Avancerede værktøjer" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:18 -#, fuzzy msgid "all" -msgstr "Installer" +msgstr "alle" #: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:109 msgid "" @@ -521,13 +518,12 @@ msgid "Apply" msgstr "Anvend" #: 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 "" -"Er du sikker på, at du vil vende tilbage til standardlayoutet? Den aktuelle " -"layoutkonfiguration vil gå tabt." +"Er du sikker på, at du vil gendanne værktøjslinjens standardlayout?\n" +"Dette kan ikke gøres om." #: packages/app-desktop/gui/ClipperConfigScreen.tsx:37 msgid "Are you sure you want to renew the authorisation token?" @@ -554,6 +550,8 @@ msgid "" "At present, Joplin Web can only be open in one tab at a time. Please close " "the other instance of Joplin." msgstr "" +"På nuværende tidspunkt kan Joplin Web kun være åben i én fane ad gangen. Luk " +"den anden instans af Joplin." #: packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.ts:25 msgid "Attach" @@ -639,7 +637,7 @@ msgstr "Auto-par klammer, parenteser, citater, etc." #: packages/lib/models/settings/builtInMetadata.ts:656 msgid "Autocomplete Markdown and HTML" -msgstr "" +msgstr "Autofuldførelse af Markdown og HTML" #: packages/lib/models/settings/builtInMetadata.ts:1184 msgid "Automatically check for updates" @@ -701,7 +699,7 @@ msgstr "af %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:20 msgid "by word" -msgstr "" +msgstr "efter ord" #: packages/server/src/routes/admin/users.ts:160 msgid "Can Share" @@ -872,13 +870,12 @@ msgid "Change language" msgstr "Skift sprog" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:124 -#, fuzzy msgid "Change ratio" -msgstr "Indstillinger" +msgstr "Skift forhold" #: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:98 msgid "Change shortcut for \"%s\"" -msgstr "" +msgstr "Skift genvej for \"%s\"" #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:106 msgid "Characters" @@ -890,7 +887,7 @@ msgstr "Tegn eksklusiv mellemrum" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:168 msgid "Check elements to display in the toolbar" -msgstr "" +msgstr "Markér elementer, der skal vises i værktøjslinjen" #: packages/app-desktop/gui/MenuBar.tsx:634 #: packages/app-desktop/gui/MenuBar.tsx:929 @@ -955,9 +952,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 "Luk" +msgstr "luk" #: packages/app-desktop/gui/MenuBar.tsx:359 #: packages/app-desktop/gui/NoteContentPropertiesDialog.tsx:175 @@ -977,13 +973,12 @@ msgid "Close dropdown" msgstr "Luk rullelisten" #: packages/app-mobile/components/accessibility/AccessibleModalMenu.tsx:46 -#, fuzzy msgid "Close menu" -msgstr "Luk" +msgstr "Luk menu" #: packages/app-mobile/components/SideMenu.tsx:300 msgid "Close side menu" -msgstr "" +msgstr "Luk sidemenu" #: packages/lib/models/settings/settingValidations.ts:33 msgid "" @@ -1031,7 +1026,7 @@ msgstr "Samarbejd med andre om notesbøger" #: packages/app-desktop/gui/Sidebar/listItemComponents/ExpandIcon.tsx:28 msgid "Collapsed, press space to expand." -msgstr "" +msgstr "Sammenklappet, tryk på mellemrum for at udvide." #: packages/lib/services/ReportService.ts:351 msgid "Coming alarms" @@ -1040,9 +1035,9 @@ msgstr "Kommende alarmer" #: packages/lib/models/settings/builtInMetadata.ts:1365 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 "" "Komma-adskilt liste med stier til mapper der indlæses certifikater fra, " "eller stier til individuelle certifikatfiler. For eksempel: /my/cert_dir, /" @@ -1071,14 +1066,12 @@ msgid "Compact" msgstr "Kompakt" #: packages/app-desktop/gui/NoteList/utils/useOnKeyDown.ts:153 -#, fuzzy msgid "Complete" -msgstr "Fuldført" +msgstr "Fuldfør" #: packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.ts:40 -#, fuzzy msgid "Complete to-do" -msgstr "Fuldført" +msgstr "Fuldfør gøremål" #: packages/app-desktop/gui/NoteListHeader/utils/getColumnTitle.ts:12 #: packages/app-desktop/gui/NotePropertiesDialog.tsx:70 @@ -1151,11 +1144,10 @@ msgid "Consolidated billing" msgstr "Konsolideret fakturering" #: packages/app-desktop/gui/Sidebar/listItemComponents/NoteCount.tsx:11 -#, fuzzy msgid "Contains %d note" msgid_plural "Contains %d notes" -msgstr[0] "Konverter til note" -msgstr[1] "Konverter til note" +msgstr[0] "Indeholder %d note" +msgstr[1] "Indeholder %d noter" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx:106 msgid "Content provided by %s" @@ -1170,9 +1162,8 @@ msgid "Continue" msgstr "Fortsæt" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:6 -#, fuzzy msgid "Control character" -msgstr "Tegn" +msgstr "Kontroltegn" #: packages/app-mobile/components/screens/Note/Note.tsx:1221 msgid "Convert to note" @@ -1410,14 +1401,12 @@ msgid "Ctrl-click to open: %s" msgstr "Ctrl-klik for at åbne: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:24 -#, fuzzy msgid "current match" -msgstr "Næste match" +msgstr "nuværende match" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:152 -#, fuzzy msgid "Current password" -msgstr "Indtast adgangskode" +msgstr "Nuværende adgangskode" #: packages/app-desktop/checkForUpdates.ts:90 msgid "Current version is up-to-date." @@ -1613,14 +1602,12 @@ msgid "Deletes the notes without asking for confirmation." msgstr "Sletter noterne uden at bede om bekræftelse." #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:550 -#, fuzzy msgid "Deletion log" -msgstr "Slet linje" +msgstr "Sletningslog" #: packages/app-mobile/components/NoteItem.tsx:144 -#, fuzzy msgid "Deselect" -msgstr "Vælg" +msgstr "Fravælg" #: packages/app-cli/app/command-export.ts:24 msgid "Destination format: %s" @@ -1666,9 +1653,8 @@ msgid "Disabled" msgstr "Deaktiveret" #: packages/app-mobile/components/screens/encryption-config.tsx:323 -#, fuzzy msgid "Disabled keys" -msgstr "Skjul deaktiverede nøgler" +msgstr "Deaktiverede taster" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:200 #: packages/app-mobile/components/screens/encryption-config.tsx:251 @@ -1872,12 +1858,11 @@ msgstr "Rediger i ekstern editor" #: packages/app-desktop/commands/openNoteInNewWindow.ts:10 msgid "Edit in new window" -msgstr "" +msgstr "Redigér i nyt vindue" #: packages/app-desktop/gui/utils/NoteListUtils.ts:70 -#, fuzzy msgid "Edit in..." -msgstr "Rediger link" +msgstr "Rediger i..." #: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:143 msgid "Edit link" @@ -1908,9 +1893,8 @@ msgid "Editor" msgstr "Editor" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/Toolbar.tsx:38 -#, fuzzy msgid "Editor actions" -msgstr "Tekstredigeringsskrifttype" +msgstr "Editor-handlinger" #: packages/lib/models/settings/builtInMetadata.ts:1060 msgid "Editor font" @@ -2026,7 +2010,7 @@ msgstr "Aktivér kryptering" #: packages/lib/models/settings/builtInMetadata.ts:980 msgid "Enable file:// URLs for images and videos" -msgstr "" +msgstr "Aktivér file:// URLs for billeder og videoer" #: packages/lib/models/settings/builtInMetadata.ts:961 msgid "Enable footnotes" @@ -2082,9 +2066,8 @@ msgid "Enable soft breaks" msgstr "Slå bløde ombrydninger til" #: packages/lib/models/settings/builtInMetadata.ts:1289 -#, fuzzy msgid "Enable spell checking in Markdown editor" -msgstr "Aktiver stavekontrol i teksteditoren" +msgstr "Aktiver stavekontrol i Markdown-editor" #: packages/lib/models/settings/builtInMetadata.ts:789 msgid "Enable spellcheck in the text editor" @@ -2123,6 +2106,8 @@ msgid "" "Enables Markdown list continuation, auto-closing HTML tags, and other markup " "autocompletions." msgstr "" +"Aktiverer Markdown-listefortsættelse, automatisk lukning af HTML-tags og " +"andre markup-autoudførelser." #: packages/lib/components/EncryptionConfigScreen/utils.ts:50 msgid "" @@ -2168,9 +2153,8 @@ msgid "End-to-end encryption" msgstr "End-to-end kryptering" #: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:208 -#, fuzzy msgid "Ends voice typing" -msgstr "Stemmeindtastning..." +msgstr "Afslutter stemmeindtastning" #: packages/app-mobile/components/screens/dropbox-login.tsx:70 msgid "Enter code here" @@ -2190,9 +2174,8 @@ msgid "Enter password" msgstr "Indtast adgangskode" #: packages/app-mobile/components/NoteItem.tsx:120 -#, fuzzy msgid "Entering selection mode" -msgstr "Åbner sektion %s" +msgstr "Går i valgtilstand" #: packages/app-cli/app/help-utils.js:56 msgid "Enum" @@ -2259,7 +2242,7 @@ msgstr "Udvid %s" #: packages/app-desktop/gui/Sidebar/listItemComponents/ExpandIcon.tsx:26 msgid "Expanded, press space to collapse." -msgstr "" +msgstr "Udvidet, tryk på mellemrum for at skjule." #: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:182 #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:212 @@ -2285,9 +2268,8 @@ msgid "Export Debug Report" msgstr "Eksporter fejlrapport" #: packages/app-desktop/commands/exportDeletionLog.ts:11 -#, fuzzy msgid "Export deletion log" -msgstr "Eksporter alle" +msgstr "Eksporter sletningslog" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:16 msgid "Export profile" @@ -2373,9 +2355,8 @@ msgid "Filter tags" msgstr "Filtrer etiketter" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:14 -#, fuzzy msgid "Find" -msgstr "Find: " +msgstr "Find" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:253 msgid "Find: " @@ -2413,7 +2394,7 @@ msgstr "Fokuser på titel" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:97 msgid "Follow link" -msgstr "" +msgstr "Følg link" #: packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx:38 msgid "For debugging purpose only: export your profile to an external SD card." @@ -2508,11 +2489,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å tilbage" #: packages/app-desktop/gui/JoplinCloudConfigScreen.tsx:44 #: packages/app-mobile/components/screens/ConfigScreen/JoplinCloudConfig.tsx:59 @@ -2521,7 +2502,7 @@ msgstr "Gå til Joplin Cloud-profil" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:12 msgid "Go to line" -msgstr "" +msgstr "Gå til line" # Kilde-URL er en dårlig oversættelse #: packages/app-mobile/components/screens/Note/Note.tsx:1051 @@ -2589,9 +2570,8 @@ msgid "Hide keyboard" msgstr "Skjul tastatur" #: packages/app-desktop/gui/PasswordInput/PasswordInput.tsx:21 -#, fuzzy msgid "Hide password" -msgstr "Ugyldig adgangskode" +msgstr "Skjul adgangskode" #: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:14 msgid "Highlight" @@ -2808,14 +2788,12 @@ msgid "Incompatible" msgstr "Inkompatibel" #: packages/app-desktop/gui/NoteList/utils/useOnKeyDown.ts:153 -#, fuzzy msgid "Incomplete" -msgstr "Fuldført" +msgstr "Ikke fuldført" #: packages/app-desktop/gui/NoteListItem/utils/prepareViewProps.ts:40 -#, fuzzy msgid "Incomplete to-do" -msgstr "Vis færdige opgaver" +msgstr "Ufuldførte gøremål" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:97 msgid "Increase indent level" @@ -2831,7 +2809,7 @@ msgstr "Indryk mere" #: packages/app-mobile/components/DialogManager/hooks/useDialogControl.ts:21 msgid "Info" -msgstr "" +msgstr "Info" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:222 msgid "Information" @@ -2933,9 +2911,8 @@ msgid "Items that cannot be synchronised" msgstr "Emner kan ikke synkroniseres" #: packages/app-desktop/gui/MenuBar.tsx:923 -#, fuzzy msgid "Join us on %s" -msgstr "Følg os på Twitter" +msgstr "Følg os på %s" #: packages/app-desktop/gui/SyncWizard/Dialog.tsx:267 msgid "" @@ -2991,9 +2968,8 @@ msgid "Joplin Forum" msgstr "Joplin-forum" #: packages/app-mobile/utils/lockToSingleInstance.ts:16 -#, fuzzy msgid "Joplin is already running." -msgstr "Serveren kører allerede på port %d" +msgstr "Joplin kører allerede" #: packages/lib/services/plugins/PluginService.ts:523 msgid "Joplin Mobile" @@ -3242,9 +3218,8 @@ msgid "Manage shared notebooks" msgstr "Administrer delte notesbøger" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:165 -#, fuzzy msgid "Manage toolbar options" -msgstr "Administrer dine udvidelser" +msgstr "Administrer indstillinger for værktøjslinjen" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:331 msgid "Manage your plugins" @@ -3278,9 +3253,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-editor" #: packages/app-cli/app/command-done.ts:15 msgid "Marks a to-do as done." @@ -3311,11 +3285,11 @@ msgstr "Hovedadgangskode:" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:19 msgid "match case" -msgstr "" +msgstr "match bogstavstørrelse" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:72 msgid "Math" -msgstr "" +msgstr "Matematik" #: packages/lib/models/settings/builtInMetadata.ts:421 msgid "Max concurrent connections" @@ -3342,9 +3316,8 @@ msgid "Minimise" msgstr "Minimér" #: packages/app-mobile/components/CameraView/CameraView.tsx:163 -#, fuzzy msgid "Missing camera permission" -msgstr "Manglende hovednøgler" +msgstr "Mangler kameratilladelse" #: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:320 msgid "Missing keys" @@ -3432,14 +3405,12 @@ msgid "Never resize" msgstr "Ændr aldrig størrelse" #: packages/app-mobile/setupQuickActions.ts:33 -#, fuzzy msgid "New attachment" -msgstr "Note vedhæftninger" +msgstr "Ny vedhæftning" #: packages/app-mobile/setupQuickActions.ts:34 -#, fuzzy msgid "New drawing" -msgstr "Tegner" +msgstr "Ny tegning" #: packages/app-mobile/components/screens/ShareManager/index.tsx:120 msgid "New invitations" @@ -3468,9 +3439,8 @@ msgid "" msgstr "Ny notesbog \"%s\" bliver oprettet og filen \"%s\" importeres til den" #: packages/app-mobile/setupQuickActions.ts:32 -#, fuzzy msgid "New photo" -msgstr "Tag et foto" +msgstr "Nyt foto" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/newSubFolder.ts:6 msgid "New sub-notebook" @@ -3494,7 +3464,7 @@ msgstr "Ny version: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:16 msgid "next" -msgstr "" +msgstr "næste" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:271 msgid "Next match" @@ -3578,14 +3548,12 @@ msgstr "" "path>`" #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:93 -#, fuzzy msgid "No updates available" -msgstr "Opdatering tilgængelig" +msgstr "Ingen opdatering tilgængelig" #: 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" @@ -3747,12 +3715,11 @@ msgstr "Nummeret liste" #: packages/lib/models/settings/builtInMetadata.ts:531 msgid "OCR: Clear cache and re-download language data files" -msgstr "" +msgstr "OCR: Ryd cache og download sprogdatafiler igen" #: packages/lib/models/settings/builtInMetadata.ts:512 -#, fuzzy msgid "OCR: Language data URL or path" -msgstr "Sprog, datoformat" +msgstr "OCR: URL eller sti til sprogdata" #: packages/app-desktop/bridge.ts:360 packages/app-desktop/bridge.ts:373 #: packages/app-desktop/bridge.ts:389 packages/app-desktop/bridge.ts:398 @@ -3788,7 +3755,7 @@ msgstr "På %s: %s" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:27 msgid "on line" -msgstr "" +msgstr "på linje" #: packages/app-desktop/gui/MainScreen.tsx:525 msgid "One of your master keys use an obsolete encryption method." @@ -3819,9 +3786,8 @@ msgid "OneDrive Login" msgstr "OneDrive login" #: packages/lib/services/interop/InteropService.ts:144 -#, fuzzy msgid "OneNote Notebook" -msgstr "Ny notesbog" +msgstr "OneNote-notesbog" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/print.ts:18 msgid "Only one note can be printed at a time." @@ -3848,9 +3814,8 @@ msgid "Open profile directory" msgstr "Åbn profilmappe" #: packages/app-mobile/components/CameraView/CameraView.tsx:164 -#, fuzzy msgid "Open settings" -msgstr "Åbner sektion %s" +msgstr "Åbn indstillinger" #: packages/app-mobile/components/screens/ConfigScreen/plugins/EnablePluginSupportPage.tsx:115 msgid "Open Source" @@ -3869,19 +3834,16 @@ msgid "Opening section %s" msgstr "Åbner sektion %s" #: packages/app-mobile/components/Dropdown.tsx:215 -#, fuzzy msgid "Opens dropdown" -msgstr "Luk rullelisten" +msgstr "Åbner rulleliste" #: packages/app-mobile/components/NoteItem.tsx:162 -#, fuzzy msgid "Opens note" -msgstr "Åbn den" +msgstr "Åbner note" #: packages/app-mobile/components/side-menu-content.tsx:273 -#, fuzzy msgid "Opens notebook" -msgstr "Ny notesbog" +msgstr "Åbner notesbog" #: packages/app-cli/app/command-e2ee.ts:41 #: packages/app-cli/app/command-e2ee.ts:87 @@ -4144,11 +4106,11 @@ msgstr "Foretrukket lyst tema" #: packages/lib/models/settings/builtInMetadata.ts:1690 msgid "Preferred voice typing provider" -msgstr "" +msgstr "Foretrukken udbyder af stemmeskrivning" #: packages/lib/models/settings/builtInMetadata.ts:667 msgid "Preserve colours when pasting text in Rich Text Editor" -msgstr "" +msgstr "Bevar farver, når du indsætter tekst i Rich Text Editor" #: packages/app-cli/app/app-gui.js:758 msgid "Press Ctrl+D or type \"exit\" to exit the application" @@ -4171,9 +4133,8 @@ msgid "Press to set the decryption password." msgstr "Klik for at gemme dekrypterings-adgangskodeord." #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:17 -#, fuzzy msgid "previous" -msgstr "Forrige match" +msgstr "forrige" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:281 msgid "Previous match" @@ -4216,9 +4177,8 @@ msgid "Processing" msgstr "Behandler" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:111 -#, fuzzy msgid "Processing photo..." -msgstr "Behandler" +msgstr "Behandler foto..." #: packages/server/src/routes/admin/users.ts:254 msgid "Profile" @@ -4274,9 +4234,8 @@ msgid "Publish notes to the internet" msgstr "Udgiv noter til internettet" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:78 -#, fuzzy msgid "QR Code" -msgstr "Kode" +msgstr "QR-kode" #: packages/app-desktop/app.ts:219 #: packages/app-desktop/ElectronAppWrapper.ts:123 @@ -4294,9 +4253,8 @@ msgid "Re-encryption" msgstr "Genkryptering" #: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:180 -#, fuzzy msgid "Re-enter password" -msgstr "Indtast adgangskode" +msgstr "Indtast adgangskode igen" #: packages/lib/models/settings/builtInMetadata.ts:1166 msgid "Re-upload local data to sync target" @@ -4362,9 +4320,8 @@ msgid "Remove" msgstr "Fjern" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:329 -#, fuzzy msgid "Remove %s from share" -msgstr "Slet etikette \"%s\" fra alle noter?" +msgstr "Fjern %s fra deling" #: packages/app-desktop/gui/Sidebar/hooks/useOnRenderItem.tsx:110 msgid "Remove tag \"%s\" from all notes?" @@ -4396,9 +4353,8 @@ msgid "Renew token" msgstr "Forny token" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:21 -#, fuzzy msgid "replace" -msgstr "Erstat" +msgstr "erstat" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:15 #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:291 @@ -4406,9 +4362,8 @@ msgid "Replace" msgstr "Erstat" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:22 -#, fuzzy msgid "replace all" -msgstr "Erstat alt" +msgstr "erstat alt" #: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:301 msgid "Replace all" @@ -4424,11 +4379,11 @@ msgstr "Erstat: " #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:25 msgid "replaced $ matches" -msgstr "" +msgstr "erstattet $ matcher" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:26 msgid "replaced match on line $" -msgstr "" +msgstr "erstattet match på linje $" #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginInfoModal.tsx:162 msgid "Report an issue" @@ -4491,9 +4446,8 @@ msgid "Restore" msgstr "Gendan" #: packages/app-mobile/components/EditorToolbar/ToolbarEditorDialog.tsx:156 -#, fuzzy msgid "Restore defaults" -msgstr "Gendannede elementer" +msgstr "Gendan standarder" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/restoreNote.ts:10 msgid "Restore note" @@ -4551,11 +4505,11 @@ 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 "" +msgstr "Rich Text-editor. Tryk på Escape og derefter Tab for at forlade fokus." #: packages/app-cli/app/command-batch.js:10 msgid "" @@ -4636,7 +4590,7 @@ msgstr "Gem geo-lokation i noter" #: packages/app-mobile/components/CameraView/ScannedBarcodes.tsx:89 msgid "Scanned code" -msgstr "" +msgstr "Scannet kode" #: packages/app-desktop/gui/lib/SearchInput/SearchInput.tsx:62 #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:102 @@ -4669,9 +4623,8 @@ msgid "Search in current note" msgstr "Søg i denne note" #: packages/app-desktop/plugins/GotoAnything.tsx:665 -#, fuzzy msgid "Search results" -msgstr "Ingen resultater" +msgstr "Søgeresultater" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:169 msgid "Search shown" @@ -4694,9 +4647,8 @@ msgid "Searches for the given in all the notes." msgstr "Søger efter mønster i alle noter." #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:63 -#, fuzzy msgid "See changelog" -msgstr "Komplet ændringslog" +msgstr "Se ændringslog" #: packages/lib/models/settings/builtInMetadata.ts:1185 msgid "See the pre-release page for more details: %s" @@ -4725,14 +4677,12 @@ msgid "Select parent notebook" msgstr "Vælg overordnet notesbog" #: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/utils/localisation.ts:9 -#, fuzzy msgid "Selection deleted" -msgstr "dato for færdiggørelse" +msgstr "Valg slettet" #: packages/app-mobile/components/FolderPicker.tsx:68 -#, fuzzy msgid "Selects a notebook" -msgstr "Vælg overordnet notesbog" +msgstr "Vælger en notesbog" #: packages/app-desktop/gui/MenuBar.tsx:359 msgid "Send bug report" @@ -4788,7 +4738,7 @@ msgstr "" #: packages/app-mobile/components/EditorToolbar/EditorToolbar.tsx:47 msgid "Settings" -msgstr "" +msgstr "Indstillinger" #: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:280 #: packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.ts:44 @@ -4827,9 +4777,8 @@ msgid "Share permissions" msgstr "Deletilladelser" #: packages/app-desktop/gui/Sidebar/listItemComponents/FolderItem.tsx:56 -#, fuzzy msgid "Shared" -msgstr "Del" +msgstr "Delt" #: packages/app-mobile/components/screens/ShareManager/index.tsx:107 msgid "Shares" @@ -4876,14 +4825,12 @@ msgid "Show note counts" msgstr "Vis noteantal" #: packages/app-mobile/components/side-menu-content.tsx:258 -#, fuzzy msgid "Show notebook options" -msgstr "Vis noteantal" +msgstr "Vis indstillinger for notesbog" #: packages/app-desktop/gui/PasswordInput/PasswordInput.tsx:21 -#, fuzzy msgid "Show password" -msgstr "Indstil adgangskoden" +msgstr "Vis adgangskode" #: packages/lib/models/settings/builtInMetadata.ts:698 msgid "Show sort order buttons" @@ -4898,9 +4845,8 @@ msgid "Show/hide the sidebar" msgstr "Vis/skjul sidebjælken" #: packages/app-mobile/components/screens/tags.tsx:76 -#, fuzzy msgid "Shows notes for tag" -msgstr "Vis noteantal" +msgstr "Vis noter for etiketter" #: packages/lib/models/settings/builtInMetadata.ts:863 msgid "Shrink large images before adding them to notes." @@ -4982,11 +4928,11 @@ msgstr "" #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in ascending order" -msgstr "" +msgstr "Sorter \"%s\" i stigende rækkefølge" #: packages/app-desktop/gui/ResourceScreen.tsx:92 msgid "Sort \"%s\" in descending order" -msgstr "" +msgstr "Sorter \"%s\" i faldende rækkefølge" #: packages/lib/models/settings/builtInMetadata.ts:754 msgid "Sort notebooks by" @@ -5168,12 +5114,11 @@ msgstr "Skift profil" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:101 msgid "Switch to back-facing camera" -msgstr "" +msgstr "Skift til bagudvendt kamera" #: packages/app-mobile/components/CameraView/ActionButtons.tsx:101 -#, fuzzy msgid "Switch to front-facing camera" -msgstr "Skift til profil %d" +msgstr "Skift til frontkameraet" #: packages/app-desktop/gui/utils/NoteListUtils.ts:93 msgid "Switch to note type" @@ -5186,14 +5131,12 @@ msgid "Switch to profile %d" msgstr "Skift til profil %d" #: packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.tsx:28 -#, fuzzy msgid "Switch to the %s Editor" -msgstr "Skift til note" +msgstr "Skift til %s editoren" #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:78 -#, fuzzy msgid "Switch to the legacy editor" -msgstr "Skift til note" +msgstr "Skift til den gamle editor" #: packages/app-desktop/gui/utils/NoteListUtils.ts:102 msgid "Switch to to-do type" @@ -5470,6 +5413,7 @@ msgstr "" #: packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx:83 msgid "The following plugins may not support the current markdown editor:" msgstr "" +"Følgende plugins understøtter muligvis ikke den aktuelle markdown-editor:" #: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:257 #: packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/RecommendedBadge.tsx:49 @@ -5594,6 +5538,10 @@ msgid "" "\n" "Error: \"%s\"" msgstr "" +"Webklienten understøtter ikke accept af krypterede delte notebooks. Skift " +"venligst til desktop- eller mobilappen, før du accepterer delingen.\n" +"\n" +"Fejl: \"%s\"" #: packages/app-desktop/gui/Root.tsx:150 msgid "The Web Clipper needs your authorisation to access your data." @@ -5948,9 +5896,8 @@ msgid "Toggle editor layout" msgstr "Skift editor layout" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditorPlugin.ts:11 -#, fuzzy msgid "Toggle editor plugin" -msgstr "Skift editor layout" +msgstr "Skift editor-plugin" #: packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditors.ts:8 msgid "Toggle editors" @@ -6079,9 +6026,8 @@ msgstr "" #: packages/app-mobile/utils/getVersionInfoText.ts:13 #: packages/app-mobile/utils/getVersionInfoText.ts:14 -#, fuzzy msgid "Unknown" -msgstr "Ukendt flag: %s" +msgstr "Ukendt" #: packages/app-desktop/bridge.ts:441 msgid "Unknown file type" @@ -6099,9 +6045,8 @@ msgstr "" "version" #: packages/app-mobile/utils/getVersionInfoText.ts:28 -#, fuzzy msgid "Unknown platform" -msgstr "Ukendt markering: %s" +msgstr "Ukendt platform" #: packages/app-mobile/components/NoteEditor/commandDeclarations.ts:82 msgid "Unordered list" @@ -6149,9 +6094,8 @@ msgid "Update available" msgstr "Opdatering tilgængelig" #: packages/app-desktop/gui/UpdateNotification/UpdateNotification.tsx:65 -#, fuzzy msgid "Update later" -msgstr "opdateringsdato" +msgstr "Opdater senere" #: packages/server/src/routes/admin/users.ts:257 #: packages/server/src/routes/index/users.ts:91 @@ -6251,9 +6195,8 @@ msgstr "" "afslutte." #: packages/lib/models/settings/builtInMetadata.ts:1333 -#, fuzzy msgid "Use the legacy Markdown editor" -msgstr "Aktivér Markdown-værktøjslinjen" +msgstr "Brug den gamle Markdown-editor" #: packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx:551 msgid "" @@ -6285,8 +6228,8 @@ msgid "" "tables, checkboxes, code). If not found, a generic monospace (fixed width) " "font is used." msgstr "" -"Bruges hvor en font med fast bredde er nødvendig for at vise læsbar tekst " -"(f.eks. tabeller, afkrydsningsfelter, kode). Hvis ikke fundet, bruges en " +"Bruges hvor en font med fast bredde er nødvendig for at vise læsbar tekst (f." +"eks. tabeller, afkrydsningsfelter, kode). Hvis ikke fundet, bruges en " "generisk monospatieret (fast bredde) font." #: packages/server/src/services/MustacheService.ts:125 @@ -6348,7 +6291,7 @@ msgstr "Stemmeindtastning..." #: packages/lib/models/settings/builtInMetadata.ts:1698 msgid "Vosk" -msgstr "" +msgstr "Vosk" #: packages/lib/services/joplinCloudUtils.ts:27 msgid "Waiting for authorisation..." @@ -6455,7 +6398,7 @@ msgstr "" #: packages/lib/models/settings/builtInMetadata.ts:1699 msgid "Whisper" -msgstr "" +msgstr "Whisper" #: packages/app-desktop/ElectronAppWrapper.ts:222 msgid "Window unresponsive." @@ -6556,8 +6499,8 @@ msgid "" "Your password is needed to decrypt some of your data. Type `:e2ee decrypt` " "to set it." msgstr "" -"Din adgangskode er nødvendig for at dekryptere nogle af dine data. Tast " -"`:e2ee decrypt` for at indstille den." +"Din adgangskode er nødvendig for at dekryptere nogle af dine data. Tast `:" +"e2ee decrypt` for at indstille den." #: packages/app-desktop/checkForUpdates.ts:108 msgid "Your version: %s" diff --git a/packages/tools/postPreReleasesToForum.json b/packages/tools/postPreReleasesToForum.json index a3bace0579d..4a0b98b4e80 100644 --- a/packages/tools/postPreReleasesToForum.json +++ b/packages/tools/postPreReleasesToForum.json @@ -119,6 +119,11 @@ "v3.2.5": true, "v3.2.6": true, "v3.2.7": true, - "android-v3.2.4": true + "android-v3.2.4": true, + "v3.2.8": true, + "android-v3.2.5": true, + "ios-v13.2.4": true, + "v3.2.9": true, + "v3.2.10": true } } \ No newline at end of file diff --git a/readme/about/changelog/android.md b/readme/about/changelog/android.md index 1d4c9eac613..54ce3390b08 100644 --- a/readme/about/changelog/android.md +++ b/readme/about/changelog/android.md @@ -1,5 +1,10 @@ # Joplin Android Changelog +## [android-v3.2.5](https://github.com/laurent22/joplin/releases/tag/android-v3.2.5) (Pre-release) - 2025-01-07T23:35:43Z + +- Improved: Allow re-downloading voice typing models on URL change and error (#11557 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Upgrade js-draw to 1.26.0 (#11589 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) + ## [android-v3.2.4](https://github.com/laurent22/joplin/releases/tag/android-v3.2.4) (Pre-release) - 2025-01-06T12:50:23Z - New: Plugin API: Add support for the renderMarkup command (#11494 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) diff --git a/readme/about/changelog/desktop.md b/readme/about/changelog/desktop.md index 58628050bc8..2e996167530 100644 --- a/readme/about/changelog/desktop.md +++ b/readme/about/changelog/desktop.md @@ -1,5 +1,19 @@ # Joplin Desktop Changelog +## [v3.2.10](https://github.com/laurent22/joplin/releases/tag/v3.2.10) (Pre-release) - 2025-01-10T10:17:28Z + +- Improved: Allow installer to skip uninstallation step after repeated failures ([#11612](https://github.com/laurent22/joplin/issues/11612)) ([#11508](https://github.com/laurent22/joplin/issues/11508) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Drawing: Fix "insert drawing" button is not disabled in read-only notes (Upgrade Freehand Drawing to v2.14.0) ([#11613](https://github.com/laurent22/joplin/issues/11613) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Fix syncLockGoneError on sync with certain share configs ([#11611](https://github.com/laurent22/joplin/issues/11611)) ([#11594](https://github.com/laurent22/joplin/issues/11594) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) + +## [v3.2.9](https://github.com/laurent22/joplin/releases/tag/v3.2.9) (Pre-release) - 2025-01-09T22:58:42Z + +- Improved: Remove "URI malformed" alert ([#11576](https://github.com/laurent22/joplin/issues/11576)) ([#11575](https://github.com/laurent22/joplin/issues/11575) by Self Not Found) +- Fixed: Fix keyboard can't add text after certain error/info dialogs are shown ([#11603](https://github.com/laurent22/joplin/issues/11603) by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Links from imported notes from OneNote were being wrongly rendered ([#11618](https://github.com/laurent22/joplin/issues/11618)) ([#11617](https://github.com/laurent22/joplin/issues/11617) by [@pedr](https://github.com/pedr)) +- Fixed: OneNote Importer should only use text on fallback title ([#11598](https://github.com/laurent22/joplin/issues/11598)) ([#11597](https://github.com/laurent22/joplin/issues/11597) by [@pedr](https://github.com/pedr)) +- Fixed: OneNote imported notes have broken links when there are chineses characters on link ([#11602](https://github.com/laurent22/joplin/issues/11602)) ([#11600](https://github.com/laurent22/joplin/issues/11600) by [@pedr](https://github.com/pedr)) + ## [v3.2.7](https://github.com/laurent22/joplin/releases/tag/v3.2.7) (Pre-release) - 2025-01-06T16:35:41Z - Improved: Plugins: Add Toast plugin API ([#11583](https://github.com/laurent22/joplin/issues/11583)) ([#11579](https://github.com/laurent22/joplin/issues/11579)) diff --git a/readme/about/changelog/ios.md b/readme/about/changelog/ios.md index 1864e190b21..0e4011f728a 100644 --- a/readme/about/changelog/ios.md +++ b/readme/about/changelog/ios.md @@ -1,5 +1,23 @@ # Joplin iOS Changelog +## [ios-v13.2.4](https://github.com/laurent22/joplin/releases/tag/ios-v13.2.4) - 2025-01-08T00:11:58Z + +- New: Plugin API: Add support for the renderMarkup command (#11494 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Accessibility: Improve sidemenu notebook list accessibility (#11556 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Add an accordion for disabled master keys on encryption screen (#11529) (#11486 by [@pedr](https://github.com/pedr)) +- Improved: Add more options when long pressing the icon on mobile (#11517) (#10374) +- Improved: Display html notes using white theme (#11510) (#10609) +- Improved: Mark biometric lock feature as stable (955d39b) +- Improved: Plugin API: Implement the `toggleVisiblePanes` command (#11496 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Removed old hack that was making the note body move up and down (#11511) +- Improved: Updated packages @react-native-clipboard/clipboard (v1.14.2), @rollup/plugin-node-resolve (v15.2.4), adm-zip (v0.5.16) +- Improved: Upgrade js-draw to 1.26.0 (#11589 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Improved: Use real timers for sync operations (#11081) +- Fixed: Fix blank screen on bringing app from background (#11555) (#11544 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Fix editor shows nothing when there are no selected note IDs (#11514) (#11264 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Fix missing "Insert Time" button (#11542) (#11539 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) +- Fixed: Locked out of mobile app due to broken fingerprint scanner (#10926) + ## [ios-v13.2.2](https://github.com/laurent22/joplin/releases/tag/ios-v13.2.2) - 2024-12-12T16:39:28Z - New: Accessibility: Add checked/unchecked accessibility information to the "sort notes by" dialog (#11411 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator)) diff --git a/readme/api/references/mobile_plugin_debugging.md b/readme/api/references/mobile_plugin_debugging.md index 64ec33c5d32..de65f773aad 100644 --- a/readme/api/references/mobile_plugin_debugging.md +++ b/readme/api/references/mobile_plugin_debugging.md @@ -32,6 +32,20 @@ After loading, plugins are run in an `