mirror of https://github.com/laurent22/joplin.git
parent
a754a8d772
commit
41fdc0d44d
|
@ -411,6 +411,7 @@ packages/app-mobile/components/NoteEditor/MarkdownToolbar/Toolbar.js
|
||||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarButton.js
|
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarButton.js
|
||||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarOverflowRows.js
|
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarOverflowRows.js
|
||||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/types.js
|
packages/app-mobile/components/NoteEditor/MarkdownToolbar/types.js
|
||||||
|
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
|
||||||
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
||||||
packages/app-mobile/components/NoteEditor/SearchPanel.js
|
packages/app-mobile/components/NoteEditor/SearchPanel.js
|
||||||
packages/app-mobile/components/NoteEditor/SelectionFormatting.js
|
packages/app-mobile/components/NoteEditor/SelectionFormatting.js
|
||||||
|
|
|
@ -397,6 +397,7 @@ packages/app-mobile/components/NoteEditor/MarkdownToolbar/Toolbar.js
|
||||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarButton.js
|
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarButton.js
|
||||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarOverflowRows.js
|
packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToolbarOverflowRows.js
|
||||||
packages/app-mobile/components/NoteEditor/MarkdownToolbar/types.js
|
packages/app-mobile/components/NoteEditor/MarkdownToolbar/types.js
|
||||||
|
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
|
||||||
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
||||||
packages/app-mobile/components/NoteEditor/SearchPanel.js
|
packages/app-mobile/components/NoteEditor/SearchPanel.js
|
||||||
packages/app-mobile/components/NoteEditor/SelectionFormatting.js
|
packages/app-mobile/components/NoteEditor/SelectionFormatting.js
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
import { ReactElement, useCallback, useState } from 'react';
|
import { ReactElement, useCallback, useMemo, useState } from 'react';
|
||||||
import { LayoutChangeEvent, ScrollView, View, ViewStyle } from 'react-native';
|
import { LayoutChangeEvent, ScrollView, View, ViewStyle } from 'react-native';
|
||||||
import ToggleOverflowButton from './ToggleOverflowButton';
|
import ToggleOverflowButton from './ToggleOverflowButton';
|
||||||
import ToolbarButton, { buttonSize } from './ToolbarButton';
|
import ToolbarButton, { buttonSize } from './ToolbarButton';
|
||||||
|
@ -18,19 +18,22 @@ const Toolbar = (props: ToolbarProps) => {
|
||||||
const [overflowButtonsVisible, setOverflowPopupVisible] = useState(false);
|
const [overflowButtonsVisible, setOverflowPopupVisible] = useState(false);
|
||||||
const [maxButtonsEachSide, setMaxButtonsEachSide] = useState(0);
|
const [maxButtonsEachSide, setMaxButtonsEachSide] = useState(0);
|
||||||
|
|
||||||
const allButtonSpecs = props.buttons.reduce((accumulator: ButtonSpec[], current: ButtonGroup) => {
|
const allButtonSpecs = useMemo(() => {
|
||||||
const newItems: ButtonSpec[] = [];
|
const buttons = props.buttons.reduce((accumulator: ButtonSpec[], current: ButtonGroup) => {
|
||||||
for (const item of current.items) {
|
const newItems: ButtonSpec[] = [];
|
||||||
if (item.visible ?? true) {
|
for (const item of current.items) {
|
||||||
newItems.push(item);
|
if (item.visible ?? true) {
|
||||||
|
newItems.push(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return accumulator.concat(...newItems);
|
return accumulator.concat(...newItems);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Sort from highest priority to lowest
|
// Sort from highest priority to lowest
|
||||||
allButtonSpecs.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
buttons.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
||||||
|
return buttons;
|
||||||
|
}, [props.buttons]);
|
||||||
|
|
||||||
const allButtonComponents: ReactElement[] = [];
|
const allButtonComponents: ReactElement[] = [];
|
||||||
let key = 0;
|
let key = 0;
|
||||||
|
@ -67,7 +70,9 @@ const Toolbar = (props: ToolbarProps) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const mainButtons: ReactElement[] = [];
|
const mainButtons: ReactElement[] = [];
|
||||||
if (maxButtonsEachSide < allButtonComponents.length) {
|
if (maxButtonsEachSide >= allButtonComponents.length) {
|
||||||
|
mainButtons.push(...allButtonComponents);
|
||||||
|
} else if (maxButtonsEachSide > 0) {
|
||||||
// We want the menu to look something like this:
|
// We want the menu to look something like this:
|
||||||
// B I (…) 🔍 ⌨
|
// B I (…) 🔍 ⌨
|
||||||
// where (…) shows/hides overflow.
|
// where (…) shows/hides overflow.
|
||||||
|
@ -77,7 +82,7 @@ const Toolbar = (props: ToolbarProps) => {
|
||||||
mainButtons.push(toggleOverflowButton);
|
mainButtons.push(toggleOverflowButton);
|
||||||
mainButtons.push(...allButtonComponents.slice(-maxButtonsEachSide));
|
mainButtons.push(...allButtonComponents.slice(-maxButtonsEachSide));
|
||||||
} else {
|
} else {
|
||||||
mainButtons.push(...allButtonComponents);
|
mainButtons.push(toggleOverflowButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = props.styleSheet.styles;
|
const styles = props.styleSheet.styles;
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { describe, it, expect, beforeEach } from '@jest/globals';
|
||||||
|
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react-native';
|
||||||
|
import '@testing-library/jest-native';
|
||||||
|
|
||||||
|
import NoteEditor from './NoteEditor';
|
||||||
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import { MenuProvider } from 'react-native-popup-menu';
|
||||||
|
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
|
||||||
|
|
||||||
|
describe('NoteEditor', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Required to use ExtendedWebView
|
||||||
|
await setupDatabaseAndSynchronizer(0);
|
||||||
|
await switchClient(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide the markdown toolbar when the window is small', async () => {
|
||||||
|
const wrappedNoteEditor = render(
|
||||||
|
<MenuProvider>
|
||||||
|
<NoteEditor
|
||||||
|
themeId={Setting.THEME_ARITIM_DARK}
|
||||||
|
initialText='Testing...'
|
||||||
|
style={{}}
|
||||||
|
toolbarEnabled={true}
|
||||||
|
readOnly={false}
|
||||||
|
onChange={()=>{}}
|
||||||
|
onSelectionChange={()=>{}}
|
||||||
|
onUndoRedoDepthChange={()=>{}}
|
||||||
|
onAttach={()=>{}}
|
||||||
|
/>
|
||||||
|
</MenuProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Maps from screen height to whether the markdown toolbar should be visible.
|
||||||
|
const testCases: [number, boolean][] = [
|
||||||
|
[10, false],
|
||||||
|
[1000, true],
|
||||||
|
[100, false],
|
||||||
|
[80, false],
|
||||||
|
[600, true],
|
||||||
|
];
|
||||||
|
|
||||||
|
const noteEditorRoot = await wrappedNoteEditor.findByTestId('note-editor-root');
|
||||||
|
|
||||||
|
const setRootHeight = (height: number) => {
|
||||||
|
act(() => {
|
||||||
|
// See https://stackoverflow.com/a/61774123
|
||||||
|
fireEvent(noteEditorRoot, 'layout', {
|
||||||
|
nativeEvent: {
|
||||||
|
layout: { height },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [height, visible] of testCases) {
|
||||||
|
setRootHeight(height);
|
||||||
|
|
||||||
|
await waitFor(async () => {
|
||||||
|
const showMoreButton = await screen.queryByLabelText(_('Show more actions'));
|
||||||
|
if (visible) {
|
||||||
|
expect(showMoreButton).not.toBeNull();
|
||||||
|
} else {
|
||||||
|
expect(showMoreButton).toBeNull();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -8,7 +8,7 @@ import ExtendedWebView from '../ExtendedWebView';
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
import { forwardRef, RefObject, useImperativeHandle } from 'react';
|
import { forwardRef, RefObject, useImperativeHandle } from 'react';
|
||||||
import { useEffect, useMemo, useState, useCallback, useRef } from 'react';
|
import { useEffect, useMemo, useState, useCallback, useRef } from 'react';
|
||||||
import { View, ViewStyle } from 'react-native';
|
import { LayoutChangeEvent, View, ViewStyle } from 'react-native';
|
||||||
const { editorFont } = require('../global-style');
|
const { editorFont } = require('../global-style');
|
||||||
|
|
||||||
import SelectionFormatting from './SelectionFormatting';
|
import SelectionFormatting from './SelectionFormatting';
|
||||||
|
@ -368,6 +368,19 @@ function NoteEditor(props: Props, ref: any) {
|
||||||
console.error('NoteEditor: webview error');
|
console.error('NoteEditor: webview error');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
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 toolbar = <MarkdownToolbar
|
const toolbar = <MarkdownToolbar
|
||||||
style={{
|
style={{
|
||||||
// Don't show the markdown toolbar if there isn't enough space
|
// Don't show the markdown toolbar if there isn't enough space
|
||||||
|
@ -385,10 +398,14 @@ function NoteEditor(props: Props, ref: any) {
|
||||||
// - `scrollEnabled` prevents iOS from scrolling the document (has no effect on Android)
|
// - `scrollEnabled` prevents iOS from scrolling the document (has no effect on Android)
|
||||||
// when an editable region (e.g. a the full-screen NoteEditor) is focused.
|
// when an editable region (e.g. a the full-screen NoteEditor) is focused.
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View
|
||||||
...props.style,
|
testID='note-editor-root'
|
||||||
flexDirection: 'column',
|
onLayout={onContainerLayout}
|
||||||
}}>
|
style={{
|
||||||
|
...props.style,
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<EditLinkDialog
|
<EditLinkDialog
|
||||||
visible={linkDialogVisible}
|
visible={linkDialogVisible}
|
||||||
themeId={props.themeId}
|
themeId={props.themeId}
|
||||||
|
@ -419,7 +436,7 @@ function NoteEditor(props: Props, ref: any) {
|
||||||
searchState={searchState}
|
searchState={searchState}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{props.toolbarEnabled ? toolbar : null}
|
{toolbarEnabled ? toolbar : null}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,21 @@ document.createRange = () => {
|
||||||
|
|
||||||
shimInit({ nodeSqlite: sqlite3 });
|
shimInit({ nodeSqlite: sqlite3 });
|
||||||
|
|
||||||
|
// This library has the following error when running within Jest:
|
||||||
|
// Invariant Violation: `new NativeEventEmitter()` requires a non-null argument.
|
||||||
|
jest.mock('react-native-device-info', () => {
|
||||||
|
return {
|
||||||
|
hasNotch: () => false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// react-native-webview expects native iOS/Android code so needs to be mocked.
|
||||||
|
jest.mock('react-native-webview', () => {
|
||||||
|
const { View } = require('react-native');
|
||||||
|
return {
|
||||||
|
WebView: View,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// react-native-fs's CachesDirectoryPath export doesn't work in a testing environment.
|
// react-native-fs's CachesDirectoryPath export doesn't work in a testing environment.
|
||||||
// Use a temporary folder instead.
|
// Use a temporary folder instead.
|
||||||
|
|
Loading…
Reference in New Issue