1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-29 23:48:19 +02:00

Compare commits

..

9 Commits

Author SHA1 Message Date
Laurent Cozic
f6baf036dc Android 3.5.5 2025-12-26 10:53:47 +00:00
Henry Heino
610f00029f Mobile: Update js-draw to v1.33.0 (#13990) 2025-12-26 10:19:30 +00:00
renovate[bot]
10be1a0240 Update dependency @types/serviceworker to v0.0.154 (#13991)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-26 09:41:24 +00:00
Laurent Cozic
99a9be535c Update renovate.json5 2025-12-25 18:47:55 +00:00
renovate[bot]
614a95abb8 Update dependency react-native-webview to v13.16.0 (#13982)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-24 12:16:03 +00:00
Henry Heino
7cbaae3847 Mobile: Editor: Fix search/replace UI is partially offscreen on small-screen devices (#13978) 2025-12-24 10:11:23 +00:00
Henry Heino
9e2a6d22ea Chore: Mobile: Allow disabling features known to be incompatible with small screens at build time (#13980) 2025-12-24 10:10:56 +00:00
Henry Heino
f576e116a8 Mobile: Feature flags: Fix "voice typing" feature flag (#13981) 2025-12-24 10:10:12 +00:00
Joplin Bot
b0e912157b Doc: Auto-update documentation
Auto-updated using release-website.sh
2025-12-24 01:52:27 +00:00
17 changed files with 201 additions and 91 deletions

View File

@@ -959,6 +959,7 @@ packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/hooks/useBackHandler.js
packages/app-mobile/utils/hooks/useDebounced.js
packages/app-mobile/utils/hooks/useIsScreenReaderEnabled.js
packages/app-mobile/utils/hooks/useKeyboardState.js
packages/app-mobile/utils/hooks/useOnLongPressProps.js

1
.gitignore vendored
View File

@@ -931,6 +931,7 @@ packages/app-mobile/utils/fs-driver/testUtil/verifyDirectoryMatches.js
packages/app-mobile/utils/getPackageInfo.js
packages/app-mobile/utils/getVersionInfoText.js
packages/app-mobile/utils/hooks/useBackHandler.js
packages/app-mobile/utils/hooks/useDebounced.js
packages/app-mobile/utils/hooks/useIsScreenReaderEnabled.js
packages/app-mobile/utils/hooks/useKeyboardState.js
packages/app-mobile/utils/hooks/useOnLongPressProps.js

View File

@@ -89,8 +89,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097784
versionName "3.5.4"
versionCode 2097785
versionName "3.5.5"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -37,6 +37,7 @@ const defaultEditorProps = {
onUndoRedoDepthChange: ()=>{},
onScroll: ()=>{},
onAttach: async ()=>{},
onSearchVisibleChange: ()=>{},
noteResources: {},
plugins: {},
mode: EditorType.Markdown,

View File

@@ -34,14 +34,17 @@ import { MarkupLanguage } from '@joplin/renderer';
import WarningBanner from './WarningBanner';
import useIsScreenReaderEnabled from '../../utils/hooks/useIsScreenReaderEnabled';
import Logger from '@joplin/utils/Logger';
import { Second } from '@joplin/utils/time';
import useDebounced from '../../utils/hooks/useDebounced';
const logger = Logger.create('NoteEditor');
type ChangeEventHandler = (event: ChangeEvent)=> void;
type ScrollEventHandler = (event: EditorScrolledEvent)=> void;
type UndoRedoDepthChangeHandler = (event: UndoRedoDepthChangeEvent)=> void;
type SelectionChangeEventHandler = (event: SelectionRangeChangeEvent)=> void;
type OnAttachCallback = (filePath?: string)=> Promise<void>;
type OnChange = (event: ChangeEvent)=> void;
type OnSearchVisibleChange = (visible: boolean)=> void;
type OnScroll = (event: EditorScrolledEvent)=> void;
type OnUndoRedoDepthChange = (event: UndoRedoDepthChangeEvent)=> void;
type OnSelectionChange = (event: SelectionRangeChangeEvent)=> void;
type OnAttach = (filePath?: string)=> Promise<void>;
interface Props {
ref: Ref<EditorControl>;
@@ -60,11 +63,12 @@ interface Props {
plugins: PluginStates;
noteResources: ResourceInfos;
onScroll: ScrollEventHandler;
onChange: ChangeEventHandler;
onSelectionChange: SelectionChangeEventHandler;
onUndoRedoDepthChange: UndoRedoDepthChangeHandler;
onAttach: OnAttachCallback;
onScroll: OnScroll;
onChange: OnChange;
onSearchVisibleChange: OnSearchVisibleChange;
onSelectionChange: OnSelectionChange;
onUndoRedoDepthChange: OnUndoRedoDepthChange;
onAttach: OnAttach;
}
function fontFamilyFromSettings() {
@@ -260,6 +264,19 @@ const useHighlightActiveLine = () => {
return canHighlight && Setting.value('editor.highlightActiveLine');
};
const useHasSpaceForToolbar = () => {
const [hasSpaceForToolbar, setHasSpaceForToolbar] = useState(true);
const onContainerLayout = useCallback((event: LayoutChangeEvent) => {
const containerHeight = event.nativeEvent.layout.height;
setHasSpaceForToolbar(containerHeight >= 140);
}, []);
const debouncedHasSpaceForToolbar = useDebounced(hasSpaceForToolbar, Second / 4);
return { hasSpaceForToolbar: debouncedHasSpaceForToolbar, onContainerLayout };
};
function NoteEditor(props: Props) {
const webviewRef = useRef<WebViewControl>(null);
@@ -295,6 +312,7 @@ function NoteEditor(props: Props) {
const [searchState, setSearchState] = useState(defaultSearchState);
const editorControlRef = useRef<EditorControl|null>(null);
const lastSearchVisibleRef = useRef<boolean|undefined>(undefined);
const onEditorEvent = (event: EditorEvent) => {
let exhaustivenessCheck: never;
switch (event.kind) {
@@ -325,15 +343,21 @@ function NoteEditor(props: Props) {
// If the change to the search was done by this editor, it was already applied to the
// search state. Skipping the update in this case also helps avoid overwriting the
// search state with an older value.
const showSearch = event.searchState.dialogVisible ?? lastSearchVisibleRef.current;
if (hasExternalChange) {
setSearchState(event.searchState);
if (event.searchState.dialogVisible) {
if (showSearch) {
editorControl.searchControl.showSearch();
} else {
editorControl.searchControl.hideSearch();
}
}
if (showSearch !== lastSearchVisibleRef.current) {
props.onSearchVisibleChange(showSearch);
lastSearchVisibleRef.current = showSearch;
}
break;
}
case EditorEventType.Remove:
@@ -387,18 +411,6 @@ function NoteEditor(props: Props) {
return editorControl;
});
const [hasSpaceForToolbar, setHasSpaceForToolbar] = useState(true);
const toolbarEnabled = props.toolbarEnabled && hasSpaceForToolbar;
const onContainerLayout = useCallback((event: LayoutChangeEvent) => {
const containerHeight = event.nativeEvent.layout.height;
if (containerHeight < 140) {
setHasSpaceForToolbar(false);
} else {
setHasSpaceForToolbar(true);
}
}, []);
const onAttach = useCallback(async (type: string, base64: string) => {
const tempFilePath = join(Setting.value('tempDir'), `paste.${uuid.createNano()}.${toFileExtension(type)}`);
@@ -416,7 +428,11 @@ function NoteEditor(props: Props) {
searchVisible: searchState.dialogVisible,
}), [selectionState, searchState.dialogVisible]);
const { hasSpaceForToolbar, onContainerLayout } = useHasSpaceForToolbar();
const toolbarEnabled = props.toolbarEnabled && hasSpaceForToolbar;
const toolbar = <EditorToolbar editorState={toolbarEditorState} />;
const EditorComponent = props.mode === EditorType.Markdown ? MarkdownEditor : RichTextEditor;
return (

View File

@@ -7,7 +7,7 @@ import NoteBodyViewer from '../../NoteBodyViewer/NoteBodyViewer';
import checkPermissions from '../../../utils/checkPermissions';
import NoteEditor from '../../NoteEditor/NoteEditor';
import * as React from 'react';
import { Keyboard, View, TextInput, StyleSheet, Linking, Share, NativeSyntheticEvent } from 'react-native';
import { Keyboard, View, TextInput, StyleSheet, Linking, Share, NativeSyntheticEvent, useWindowDimensions } from 'react-native';
import { Platform, PermissionsAndroid } from 'react-native';
import { connect } from 'react-redux';
import Note from '@joplin/lib/models/Note';
@@ -77,7 +77,10 @@ import { IconButton } from 'react-native-paper';
import { writeTextToCacheFile } from '../../../utils/ShareUtils';
import shareFile from '../../../utils/shareFile';
import NotePositionService from '@joplin/lib/services/NotePositionService';
import useKeyboardState from '../../../utils/hooks/useKeyboardState';
import VoiceTyping from '../../../services/voiceTyping/VoiceTyping';
import useDebounced from '../../../utils/hooks/useDebounced';
import { Second } from '@joplin/utils/time';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const emptyArray: any[] = [];
@@ -122,12 +125,14 @@ interface Props extends BaseProps {
interface ComponentProps extends Props {
dialogs: DialogControl;
visibleEditorPluginIds: string[];
lowVerticalSpace: boolean;
}
interface State {
note: NoteEntity;
mode: NoteViewerMode;
readOnly: boolean;
searchVisible: boolean;
folder: FolderEntity|null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
lastSavedNote: any;
@@ -215,6 +220,7 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
showCamera: false,
showImageEditor: false,
showAudioRecorder: false,
searchVisible: false,
imageEditorResource: null,
noteResources: {},
imageEditorResourceFilepath: null,
@@ -390,6 +396,9 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
return this.props.useEditorBeta;
}
private onSearchVisibleChange_ = (visible: boolean) => {
this.setState({ searchVisible: visible });
};
private onUndoRedoDepthChange(event: UndoRedoDepthChangeEvent) {
if (this.useEditorBeta()) {
@@ -1600,6 +1609,14 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
// Currently keyword highlighting is supported only when FTS is available.
const keywords = this.props.searchQuery && !!this.props.ftsEnabled ? this.props.highlightedWords : emptyArray;
const increaseSpaceForEditor = this.props.lowVerticalSpace
&& this.state.mode === 'edit'
// For now, only dismiss other UI when search is visible. This provides a way to re-show the hidden UI (by dismissing search).
&& this.state.searchVisible
// Tapping on the title input when search is visible should edit the title, even if showing the keyboard decreases the
// available space.
&& !this.titleTextFieldRef.current?.isFocused();
let bodyComponent = null;
if (editorView) {
@@ -1669,7 +1686,7 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
bodyComponent = <NoteEditor
ref={this.editorRef}
toolbarEnabled={this.props.toolbarEnabled}
toolbarEnabled={this.props.toolbarEnabled && !increaseSpaceForEditor}
themeId={this.props.themeId}
noteId={this.props.noteId}
noteHash={this.props.noteHash}
@@ -1680,6 +1697,7 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
onChange={this.onMarkdownEditorTextChange}
onSelectionChange={this.onEditorSelectionChange}
onUndoRedoDepthChange={this.onUndoRedoDepthChange}
onSearchVisibleChange={this.onSearchVisibleChange_}
onAttach={this.onAttach}
noteResources={this.state.noteResources}
readOnly={this.state.readOnly}
@@ -1790,25 +1808,27 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
const { editorPlugin: activeEditorPlugin } = getActivePluginEditorView(this.props.plugins, this.props.windowId);
const header = <ScreenHeader
folderPickerOptions={this.folderPickerOptions()}
menuOptions={this.menuOptions()}
showSaveButton={showSaveButton}
saveButtonDisabled={saveButtonDisabled}
onSaveButtonPress={this.saveNoteButton_press}
showSideMenuButton={false}
showSearchButton={false}
showUndoButton={(this.state.undoRedoButtonState.canUndo || this.state.undoRedoButtonState.canRedo) && this.state.mode === 'edit'}
showRedoButton={this.state.undoRedoButtonState.canRedo && this.state.mode === 'edit'}
showPluginEditorButton={!!activeEditorPlugin}
undoButtonDisabled={!this.state.undoRedoButtonState.canUndo && this.state.undoRedoButtonState.canRedo}
onUndoButtonPress={this.screenHeader_undoButtonPress}
onRedoButtonPress={this.screenHeader_redoButtonPress}
title={getDisplayParentTitle(this.state.note, this.state.folder)}
/>;
return (
<View style={this.rootStyle(this.props.themeId).root}>
<ScreenHeader
folderPickerOptions={this.folderPickerOptions()}
menuOptions={this.menuOptions()}
showSaveButton={showSaveButton}
saveButtonDisabled={saveButtonDisabled}
onSaveButtonPress={this.saveNoteButton_press}
showSideMenuButton={false}
showSearchButton={false}
showUndoButton={(this.state.undoRedoButtonState.canUndo || this.state.undoRedoButtonState.canRedo) && this.state.mode === 'edit'}
showRedoButton={this.state.undoRedoButtonState.canRedo && this.state.mode === 'edit'}
showPluginEditorButton={!!activeEditorPlugin}
undoButtonDisabled={!this.state.undoRedoButtonState.canUndo && this.state.undoRedoButtonState.canRedo}
onUndoButtonPress={this.screenHeader_undoButtonPress}
onRedoButtonPress={this.screenHeader_redoButtonPress}
title={getDisplayParentTitle(this.state.note, this.state.folder)}
/>
{titleComp}
{!increaseSpaceForEditor && header}
{!increaseSpaceForEditor && titleComp}
{bodyComponent}
{renderVoiceTypingDialogs()}
{renderActionButton()}
@@ -1826,6 +1846,17 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
}
}
const useHasLowAvailableSpace = () => {
const windowDimensions = useWindowDimensions();
const keyboardState = useKeyboardState();
const verticalSpaceAvailable = windowDimensions.height - keyboardState.dockedKeyboardHeight;
const lowVerticalScreenSpace = verticalSpaceAvailable < 270;
// Debounce state updates to avoid multiple re-renders when the keyboard is hidden, then quickly
// re-shown (e.g. when moving focus between text inputs).
return useDebounced(lowVerticalScreenSpace, Second / 10);
};
// We added this change to reset the component state when the props.noteId is changed.
// NoteScreenComponent original implementation assumed that noteId would never change,
// which can cause some bugs where previously set state to another note would interfere
@@ -1833,9 +1864,16 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
const NoteScreenWrapper = (props: Props) => {
const dialogs = useContext(DialogContext);
const visibleEditorPluginIds = useVisiblePluginEditorViewIds(props.plugins, props.windowId);
const lowVerticalSpace = useHasLowAvailableSpace();
return (
<NoteScreenComponent key={props.noteId} dialogs={dialogs} visibleEditorPluginIds={visibleEditorPluginIds} {...props} />
<NoteScreenComponent
key={props.noteId}
dialogs={dialogs}
visibleEditorPluginIds={visibleEditorPluginIds}
lowVerticalSpace={lowVerticalSpace}
{...props}
/>
);
};

View File

@@ -80,6 +80,21 @@ const useCss = (editorTheme: Theme) => {
.toolbar-edge-toolbar:not(.one-row) .toolwidget-tag--exit .toolbar-icon {
display: none;
}
${Setting.value('buildFlag.ui.disableSmallScreenIncompatibleFeatures') ? `
/* As of December 2025, the help overlay is difficult to use on small screens
(slow to load, help text overlapping content in some cases). */
.js-draw .toolbar-help-overlay-button {
display: none;
}
/* As of December 2025, the pipette button is difficult to use on small screens:
It may not be clear that it's necessary to dismiss the tool menu in order to
pick a color from the screen. */
.js-draw .color-input-container > button.pipetteButton.pipetteButton {
display: none;
}
` : ''}
`;
}, [editorTheme]);
};

View File

@@ -1550,7 +1550,7 @@ PODS:
- react-native-vector-icons-material-icons (12.4.0)
- react-native-version-info (1.1.1):
- React-Core
- react-native-webview (13.15.0):
- react-native-webview (13.16.0):
- DoubleConversion
- glog
- hermes-engine
@@ -2373,7 +2373,7 @@ SPEC CHECKSUMS:
react-native-vector-icons-material-design-icons: 76cd460b3540b80527b4a80fb7f867f7deedb498
react-native-vector-icons-material-icons: d67e485a05560416ff6b5977d5fa7e0eb6af6870
react-native-version-info: f0b04e16111c4016749235ff6d9a757039189141
react-native-webview: 0dceb35a9d050f5fa55f7fe2d8c4d1903651eb7d
react-native-webview: 8ad427a520a3b94d2006a62bb7756be726116af5
React-NativeModulesApple: 2c4377e139522c3d73f5df582e4f051a838ff25e
React-oscompat: ef5df1c734f19b8003e149317d041b8ce1f7d29c
React-perflogger: 9a151e0b4c933c9205fd648c246506a83f31395d

View File

@@ -28,7 +28,7 @@
"@joplin/react-native-saf-x": "~3.5",
"@joplin/renderer": "~3.5",
"@joplin/utils": "~3.5",
"@js-draw/material-icons": "1.32.0",
"@js-draw/material-icons": "1.33.0",
"@react-native-clipboard/clipboard": "1.16.3",
"@react-native-community/datetimepicker": "8.4.5",
"@react-native-community/geolocation": "3.4.0",
@@ -51,7 +51,7 @@
"expo-av": "15.1.7",
"expo-camera": "16.1.11",
"expo-local-authentication": "16.0.5",
"js-draw": "1.32.0",
"js-draw": "1.33.0",
"lodash": "4.17.21",
"md5": "2.3.0",
"path-browserify": "1.0.1",
@@ -80,7 +80,7 @@
"react-native-svg": "15.13.0",
"react-native-url-polyfill": "2.0.0",
"react-native-version-info": "1.1.1",
"react-native-webview": "13.15.0",
"react-native-webview": "13.16.0",
"react-native-zip-archive": "7.0.2",
"react-redux": "8.1.3",
"redux": "4.2.1",
@@ -114,7 +114,7 @@
"@types/node": "18.19.130",
"@types/react": "19.0.14",
"@types/react-redux": "7.1.33",
"@types/serviceworker": "0.0.153",
"@types/serviceworker": "0.0.154",
"@types/tar-stream": "3.1.4",
"babel-jest": "29.7.0",
"babel-loader": "9.1.3",

View File

@@ -213,7 +213,7 @@ const modelLocalFilepath = () => {
};
const whisper: VoiceTypingProvider = {
supported: () => !!SpeechToTextModule && Setting.value('featureFlag.voiceTypingEnabled'),
supported: () => !!SpeechToTextModule && Setting.value('buildFlag.voiceTypingEnabled'),
modelLocalFilepath: modelLocalFilepath,
getDownloadUrl: (locale) => {
const lang = languageCodeOnly(locale).toLowerCase();

View File

@@ -0,0 +1,14 @@
import useQueuedAsyncEffect from '@joplin/lib/hooks/useQueuedAsyncEffect';
import { useState } from 'react';
const useDebounced = <T> (value: T, interval: number) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useQueuedAsyncEffect(() => {
setDebouncedValue(value);
}, [value], { interval });
return debouncedValue;
};
export default useDebounced;

View File

@@ -3,11 +3,22 @@ import { SearchState } from '../../types';
import { Plugin, EditorState, Command, Transaction } from 'prosemirror-state';
import { EditorEvent, EditorEventType } from '../../events';
type VisibleMetaState = {
visible: boolean;
changeSource: string;
};
const getVisibleMeta = (tr: Transaction): VisibleMetaState|undefined => (
tr.getMeta(visiblePlugin)
);
const setVisibleMeta = (tr: Transaction, metaState: VisibleMetaState): Transaction => (
tr.setMeta(visiblePlugin, metaState)
);
const visiblePlugin = new Plugin({
state: {
init: () => false,
apply: (tr, value) => {
const visibleMeta = tr.getMeta(visiblePlugin);
const visibleMeta = getVisibleMeta(tr);
if (visibleMeta) {
return visibleMeta.visible;
}
@@ -24,7 +35,7 @@ export const setSearchVisible = (visible: boolean): Command => (state, dispatch)
return false;
}
if (dispatch) {
dispatch(state.tr.setMeta(visiblePlugin, { visible }));
dispatch(setVisibleMeta(state.tr, { visible, changeSource: 'setSearchVisible' }));
}
return true;
};
@@ -58,7 +69,7 @@ const searchExtension = (onEditorEvent: (event: EditorEvent)=> void) => {
onEditorEvent({
kind: EditorEventType.UpdateSearchDialog,
searchState: currentState,
changeSources: [transaction.getMeta(visiblePlugin)?.changeSource ?? 'unknown'],
changeSources: [getVisibleMeta(transaction)?.changeSource ?? 'unknown'],
});
}
};
@@ -81,16 +92,13 @@ const searchExtension = (onEditorEvent: (event: EditorEvent)=> void) => {
],
updateState: (editorState: EditorState, searchState: SearchState, changeSource: string) => {
let transaction = editorState.tr;
setSearchVisible(searchState.dialogVisible)(editorState, (newTransaction) => {
transaction = newTransaction;
});
transaction = setSearchState(transaction, new SearchQuery({
search: searchState.searchText,
caseSensitive: searchState.caseSensitive,
regexp: searchState.useRegex,
replace: searchState.replaceText,
}));
transaction.setMeta(visiblePlugin, { changeSource });
setVisibleMeta(transaction, { changeSource, visible: searchState.dialogVisible });
lastState = { ...searchState };
return transaction;

View File

@@ -16,9 +16,10 @@ const customCssFilePath = (Setting: typeof SettingType, filename: string): strin
return `${Setting.value('rootProfileDir')}/${filename}`;
};
const showVoiceTypingSettings = () => (
type VoiceTypingSettingSlice = Record<'buildFlag.voiceTypingEnabled', boolean>;
const showVoiceTypingSettings = (settings: VoiceTypingSettingSlice) => (
// For now, iOS and web don't support voice typing.
shim.mobilePlatform() === 'android'
shim.mobilePlatform() === 'android' && !!settings['buildFlag.voiceTypingEnabled']
);
export enum CameraDirection {
@@ -1910,14 +1911,20 @@ const builtInMetadata = (Setting: typeof SettingType) => {
// As of December 2025, the voice typing feature doesn't work well on low-resource devices.
// There have been requests to allow disabling the voice typing feature at build time. This
// feature flag allows doing so, by changing the default `value` from `true` to `false`:
'featureFlag.voiceTypingEnabled': {
'buildFlag.voiceTypingEnabled': {
value: true,
type: SettingItemType.Bool,
public: false,
appTypes: [AppType.Mobile],
label: () => 'Voice typing: Enable the voice typing feature',
section: 'note',
advanced: true,
},
'buildFlag.ui.disableSmallScreenIncompatibleFeatures': {
value: false,
type: SettingItemType.Bool,
public: false,
appTypes: [AppType.Mobile],
label: () => 'UI: Disable features known to be incompatible with small screens',
},
'survey.webClientEval2025.progress': {
@@ -1984,7 +1991,7 @@ const builtInMetadata = (Setting: typeof SettingType) => {
appTypes: [AppType.Mobile],
label: () => _('Voice typing: Glossary'),
description: () => _('A comma-separated list of words. May be used for uncommon words, to help voice typing spell them correctly.'),
show: () => showVoiceTypingSettings(),
show: showVoiceTypingSettings,
section: 'note',
},

View File

@@ -188,6 +188,7 @@
"android-v3.5.1": true,
"ios-v13.5.1": true,
"v3.5.9": true,
"android-v3.5.3": true
"android-v3.5.3": true,
"android-v3.5.4": true
}
}

View File

@@ -1,5 +1,12 @@
# Joplin Android Changelog
## [android-v3.5.5](https://github.com/laurent22/joplin/releases/tag/android-v3.5.5) (Pre-release) - 2025-12-26T10:53:20Z
- Improved: Update js-draw to v1.33.0 (#13990 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator))
- Improved: Updated packages react-native-webview (v13.16.0)
- Fixed: Editor: Fix search/replace UI is partially off-screen on small-screen devices (#13978 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator))
- Fixed: Feature flags: Fix "voice typing" feature flag (#13981 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator))
## [android-v3.5.4](https://github.com/laurent22/joplin/releases/tag/android-v3.5.4) (Pre-release) - 2025-12-23T20:00:18Z
- Improved: Accessibility: Dark mode: Improve contrast of conflicts notebook title, error messages in "Logs" (#13925 by [@personalizedrefrigerator](https://github.com/personalizedrefrigerator))

View File

@@ -75,6 +75,7 @@
"prosemirror-model",
"prosemirror-state",
"prosemirror-view",
"prosemirror-tables",
"@fortawesome/fontawesome-svg-core",
"@fortawesome/free-solid-svg-icons",
"@svgr/webpack",

View File

@@ -10526,7 +10526,7 @@ __metadata:
"@joplin/turndown": "npm:~4.0.80"
"@joplin/turndown-plugin-gfm": "npm:~1.0.62"
"@joplin/utils": "npm:~3.5"
"@js-draw/material-icons": "npm:1.32.0"
"@js-draw/material-icons": "npm:1.33.0"
"@pmmmwh/react-refresh-webpack-plugin": "npm:^0.6.0"
"@react-native-clipboard/clipboard": "npm:1.16.3"
"@react-native-community/cli": "npm:16.0.3"
@@ -10552,7 +10552,7 @@ __metadata:
"@types/node": "npm:18.19.130"
"@types/react": "npm:19.0.14"
"@types/react-redux": "npm:7.1.33"
"@types/serviceworker": "npm:0.0.153"
"@types/serviceworker": "npm:0.0.154"
"@types/tar-stream": "npm:3.1.4"
assert-browserify: "npm:2.0.0"
babel-jest: "npm:29.7.0"
@@ -10576,7 +10576,7 @@ __metadata:
jest: "npm:29.7.0"
jest-environment-jsdom: "npm:29.7.0"
jetifier: "npm:2.0.0"
js-draw: "npm:1.32.0"
js-draw: "npm:1.33.0"
jsdom: "npm:26.1.0"
lodash: "npm:4.17.21"
md5: "npm:2.3.0"
@@ -10609,7 +10609,7 @@ __metadata:
react-native-url-polyfill: "npm:2.0.0"
react-native-version-info: "npm:1.1.1"
react-native-web: "npm:0.21.1"
react-native-webview: "npm:13.15.0"
react-native-webview: "npm:13.16.0"
react-native-zip-archive: "npm:7.0.2"
react-redux: "npm:8.1.3"
react-refresh: "npm:0.17.0"
@@ -11365,21 +11365,21 @@ __metadata:
languageName: node
linkType: hard
"@js-draw/material-icons@npm:1.32.0":
version: 1.32.0
resolution: "@js-draw/material-icons@npm:1.32.0"
"@js-draw/material-icons@npm:1.33.0":
version: 1.33.0
resolution: "@js-draw/material-icons@npm:1.33.0"
peerDependencies:
js-draw: ^1.0.1
checksum: 10/89f2b1cf13ccb2146743f13ea6c85e0f086a926eafeb22f5fef7c1622826181a0c0f59dd571e5975a67eb3407f2388b21e32218dad4eafef43cc2d1c4d059337
checksum: 10/7f3ac9ee3b6a1d575abe45e93ff7a2b9f2f2e5fa6d73e67a5a1f4a0be7a03239543d007bf42db6de619fcef68e016ac47a00f053efee2dee0d005177f6cb0733
languageName: node
linkType: hard
"@js-draw/math@npm:^1.32.0":
version: 1.32.0
resolution: "@js-draw/math@npm:1.32.0"
"@js-draw/math@npm:^1.33.0":
version: 1.33.0
resolution: "@js-draw/math@npm:1.33.0"
dependencies:
bezier-js: "npm:6.1.3"
checksum: 10/3fd9d9fb8fb7ae231851e43eb2503d03fd963f665ecdebd99bf74feef24086eee07680d431f413b9b1a72ecd6c3b46ab539d8116bc3a10f1f3cfaefb168a28e7
checksum: 10/8772e7a2392d9e1e27276777ca47ef3e31a407aa2a23efc2b914eb8963467e340b2031998571095c9309c0724fe0858b775fe939727cae9d013abcd819f49a5d
languageName: node
linkType: hard
@@ -17094,10 +17094,10 @@ __metadata:
languageName: node
linkType: hard
"@types/serviceworker@npm:0.0.153":
version: 0.0.153
resolution: "@types/serviceworker@npm:0.0.153"
checksum: 10/d5a0f9d638ebc1a0cbc9292d9a6146b940ac6d32e32b88fcf5e5bd2cd02c343c78ec7c0f9fb4340e3b3762dd5c48a3b54ea546520d31dc7ca319e95c9540a029
"@types/serviceworker@npm:0.0.154":
version: 0.0.154
resolution: "@types/serviceworker@npm:0.0.154"
checksum: 10/462ee7a32c6496aa953434219f4680a12d8f777021fd10c4dbcfd456ea7f3ca0a8170361367f596ac717a83fe72f1ec232e7b1656365d32f3bfeb38f67e937cd
languageName: node
linkType: hard
@@ -34994,13 +34994,13 @@ __metadata:
languageName: node
linkType: hard
"js-draw@npm:1.32.0":
version: 1.32.0
resolution: "js-draw@npm:1.32.0"
"js-draw@npm:1.33.0":
version: 1.33.0
resolution: "js-draw@npm:1.33.0"
dependencies:
"@js-draw/math": "npm:^1.32.0"
"@js-draw/math": "npm:^1.33.0"
"@melloware/coloris": "npm:0.22.0"
checksum: 10/3da563efc659e5081fa102bb570791581edcddedc40b2ba60596b7edd3dfe5dcd96b39a3486f266f6489a0ace4a335f5ee54d4aa726cab13e90d3d38a415a23a
checksum: 10/1ea87975079b6c5ba2cfaa366e4d5b6d921f118e162a51706053f481c2969eff0b896343782c2a05405fdfa874fc92543a10e4bcd2d224a81d753c59d0e3a43b
languageName: node
linkType: hard
@@ -44105,16 +44105,16 @@ __metadata:
languageName: node
linkType: hard
"react-native-webview@npm:13.15.0":
version: 13.15.0
resolution: "react-native-webview@npm:13.15.0"
"react-native-webview@npm:13.16.0":
version: 13.16.0
resolution: "react-native-webview@npm:13.16.0"
dependencies:
escape-string-regexp: "npm:^4.0.0"
invariant: "npm:2.2.4"
peerDependencies:
react: "*"
react-native: "*"
checksum: 10/7d4b23cb0536199ab2339644d8b6ce720e794ac9a8200f23746032a768a61af8727d2401fc000be09b4fb478dce6f795d7d400945a17ab013a3bbc92a5077e4e
checksum: 10/dc3d84c4785676bac3f316ec2119d3239f64fd8b18400dcc1cf8fa4f05b9bf677cf167aa4e7da72c9f60760eb6303c99da22324e15ff5fb380a3b1b31f531132
languageName: node
linkType: hard