1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-26 22:41:17 +02:00

Merge branch 'release-3.4' into dev

This commit is contained in:
Laurent Cozic
2025-09-08 23:55:24 +01:00
6 changed files with 45 additions and 14 deletions

View File

@@ -417,8 +417,10 @@ class Application extends BaseApplication {
if (argv.length) { if (argv.length) {
this.gui_ = this.dummyGui(); this.gui_ = this.dummyGui();
const initialFolder = await Folder.load(Setting.value('activeFolderId'));
await this.switchCurrentFolder(initialFolder);
await this.applySettingsSideEffects(); await this.applySettingsSideEffects();
await this.refreshCurrentFolder();
try { try {
await this.execCommand(argv); await this.execCommand(argv);
} catch (error) { } catch (error) {

View File

@@ -54,6 +54,14 @@ const Camera = (props: Props, ref: ForwardedRef<CameraRef>) => {
logger.error(message); logger.error(message);
}, []); }, []);
const isReadyRef = useRef(false);
const onCameraReady = useCallback(() => {
if (isReadyRef.current) return; // Already emitted
isReadyRef.current = true;
props.onCameraReady();
}, [props.onCameraReady]);
useAsyncEffect(async (event) => { useAsyncEffect(async (event) => {
// iOS issue workaround: Since upgrading to Expo SDK 52, closing and reopening the camera on iOS // iOS issue workaround: Since upgrading to Expo SDK 52, closing and reopening the camera on iOS
// never emits onCameraReady. As a workaround, call .resumePreview and wait for it to resolve, // never emits onCameraReady. As a workaround, call .resumePreview and wait for it to resolve,
@@ -63,16 +71,16 @@ const Camera = (props: Props, ref: ForwardedRef<CameraRef>) => {
// Instead, wait for the preview to start using resumePreview: // Instead, wait for the preview to start using resumePreview:
await camera.resumePreview(); await camera.resumePreview();
if (event.cancelled) return; if (event.cancelled) return;
props.onCameraReady(); onCameraReady();
} }
}, [camera, props.onCameraReady]); }, [camera, onCameraReady]);
return hasPermission?.granted ? <CameraView return hasPermission?.granted ? <CameraView
ref={setCamera} ref={setCamera}
style={props.style} style={props.style}
facing={props.cameraType === CameraDirection.Front ? 'front' : 'back'} facing={props.cameraType === CameraDirection.Front ? 'front' : 'back'}
ratio={props.ratio as CameraRatio} ratio={props.ratio as CameraRatio}
onCameraReady={Platform.OS === 'android' ? props.onCameraReady : undefined} onCameraReady={onCameraReady}
onMountError={onMountError} onMountError={onMountError}
animateShutter={false} animateShutter={false}
barcodeScannerSettings={barcodeScannerSettings} barcodeScannerSettings={barcodeScannerSettings}

View File

@@ -114,7 +114,7 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
// A small border above the header: Covers the part of the shadow that would otherwise // A small border above the header: Covers the part of the shadow that would otherwise
// be shown above the header on Android. // be shown above the header on Android.
aboveHeader: { aboveHeader: {
backgroundColor: '#323640', backgroundColor: theme.backgroundColor2,
paddingBottom: 6, paddingBottom: 6,
marginTop: -6, marginTop: -6,
zIndex: 2, zIndex: 2,

View File

@@ -17,6 +17,7 @@ import Resource from '@joplin/lib/models/Resource';
import { ResourceInfos } from '@joplin/renderer/types'; import { ResourceInfos } from '@joplin/renderer/types';
import useContentScripts from './utils/useContentScripts'; import useContentScripts from './utils/useContentScripts';
import uuid from '@joplin/lib/uuid'; import uuid from '@joplin/lib/uuid';
import AsyncActionQueue from '@joplin/lib/AsyncActionQueue';
const logger = Logger.create('renderer/useWebViewSetup'); const logger = Logger.create('renderer/useWebViewSetup');
@@ -149,6 +150,8 @@ const useWebViewSetup = (props: Props): SetUpResult<RendererControl> => {
void messenger.remoteApi.renderer.setExtraContentScriptsAndRerender(contentScripts); void messenger.remoteApi.renderer.setExtraContentScriptsAndRerender(contentScripts);
}, [messenger, contentScripts]); }, [messenger, contentScripts]);
const onRerenderRequestRef = useRef(()=>{});
const rendererControl = useMemo((): RendererControl => { const rendererControl = useMemo((): RendererControl => {
const renderer = messenger.remoteApi.renderer; const renderer = messenger.remoteApi.renderer;
@@ -201,6 +204,7 @@ const useWebViewSetup = (props: Props): SetUpResult<RendererControl> => {
const key = `${pluginId}.${settingKey}`; const key = `${pluginId}.${settingKey}`;
if (!pluginSettingKeysRef.current.has(key)) { if (!pluginSettingKeysRef.current.has(key)) {
pluginSettingKeysRef.current.add(key); pluginSettingKeysRef.current.add(key);
onRerenderRequestRef.current();
settingsChanged = true; settingsChanged = true;
} }
}, },
@@ -234,16 +238,21 @@ const useWebViewSetup = (props: Props): SetUpResult<RendererControl> => {
return { return {
rerenderToBody: async (markup, options, cancelEvent) => { rerenderToBody: async (markup, options, cancelEvent) => {
const { getSettings, getSettingsChanged } = await prepareRenderer(options); const { getSettings } = await prepareRenderer(options);
if (cancelEvent?.cancelled) return null; if (cancelEvent?.cancelled) return null;
const output = await renderer.rerenderToBody(markup, getSettings()); const render = async () => {
if (cancelEvent?.cancelled) return null; if (cancelEvent?.cancelled) return;
if (getSettingsChanged()) { await renderer.rerenderToBody(markup, getSettings());
return await renderer.rerenderToBody(markup, getSettings()); };
}
return output; const queue = new AsyncActionQueue();
onRerenderRequestRef.current = async () => {
queue.push(render);
};
return await render();
}, },
render: async (markup, options) => { render: async (markup, options) => {
const { getSettings, getSettingsChanged } = await prepareRenderer(options); const { getSettings, getSettingsChanged } = await prepareRenderer(options);

View File

@@ -522,6 +522,13 @@ describe('models/Setting', () => {
expect(Setting.value('revisionService.ttlDays')).toBe(1); expect(Setting.value('revisionService.ttlDays')).toBe(1);
}); });
test('should not fail to save settings that can conflict with uninstalled plugin settings', async () => {
Setting.setValue('editor.imageRendering', true);
expect(Setting.value('editor.imageRendering')).toBe(true);
Setting.setValue('editor.imageRendering', false);
expect(Setting.value('editor.imageRendering')).toBe(false);
});
test('should adjust settings to avoid conflicts', async () => { test('should adjust settings to avoid conflicts', async () => {
const testSettingId = 'plugin-plugin.calebjohn.rich-markdown.inlineImages'; const testSettingId = 'plugin-plugin.calebjohn.rich-markdown.inlineImages';
await Setting.registerSetting(testSettingId, { await Setting.registerSetting(testSettingId, {

View File

@@ -808,15 +808,20 @@ class Setting extends BaseModel {
this.scheduleChangeEvent(); this.scheduleChangeEvent();
}; };
const setValueInternalIfExists = <Key extends string> (key: Key, value: SettingValueType<Key>) => {
if (!this.keyExists(key)) return;
setValueInternal(key, value);
};
setValueInternal(key, value); setValueInternal(key, value);
// Prevent conflicts. Use setValueInternal to avoid infinite recursion in the case // Prevent conflicts. Use setValueInternal to avoid infinite recursion in the case
// where conflictingSettings has invalid data. // where conflictingSettings has invalid data.
for (const conflict of conflictingSettings) { for (const conflict of conflictingSettings) {
if (conflict.key1 === key && conflict.value1 === value) { if (conflict.key1 === key && conflict.value1 === value) {
setValueInternal(conflict.key2, conflict.alternate2); setValueInternalIfExists(conflict.key2, conflict.alternate2);
} else if (conflict.key2 === key && conflict.value2 === value) { } else if (conflict.key2 === key && conflict.value2 === value) {
setValueInternal(conflict.key1, conflict.alternate1); setValueInternalIfExists(conflict.key1, conflict.alternate1);
} }
} }
} }