1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-21 09:38:01 +02:00

Revert: Mobile: Add note bar (#6772)

Revert commit dfd95f8385
Due to UX issues.
Ref https://discourse.joplinapp.org/t/25775/30
This commit is contained in:
Laurent Cozic 2022-09-30 11:46:26 +01:00
parent afc34b44c8
commit 44e60bdda9
21 changed files with 794 additions and 1144 deletions

View File

@ -954,12 +954,6 @@ packages/app-mobile/components/NoteEditor/SelectionFormatting.js.map
packages/app-mobile/components/NoteEditor/types.d.ts packages/app-mobile/components/NoteEditor/types.d.ts
packages/app-mobile/components/NoteEditor/types.js packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteEditor/types.js.map packages/app-mobile/components/NoteEditor/types.js.map
packages/app-mobile/components/NotesBar.d.ts
packages/app-mobile/components/NotesBar.js
packages/app-mobile/components/NotesBar.js.map
packages/app-mobile/components/NotesBarListItem.d.ts
packages/app-mobile/components/NotesBarListItem.js
packages/app-mobile/components/NotesBarListItem.js.map
packages/app-mobile/components/ScreenHeader.d.ts packages/app-mobile/components/ScreenHeader.d.ts
packages/app-mobile/components/ScreenHeader.js packages/app-mobile/components/ScreenHeader.js
packages/app-mobile/components/ScreenHeader.js.map packages/app-mobile/components/ScreenHeader.js.map
@ -969,18 +963,12 @@ packages/app-mobile/components/SelectDateTimeDialog.js.map
packages/app-mobile/components/SideMenu.d.ts packages/app-mobile/components/SideMenu.d.ts
packages/app-mobile/components/SideMenu.js packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/SideMenu.js.map packages/app-mobile/components/SideMenu.js.map
packages/app-mobile/components/checkbox.d.ts
packages/app-mobile/components/checkbox.js
packages/app-mobile/components/checkbox.js.map
packages/app-mobile/components/getResponsiveValue.d.ts packages/app-mobile/components/getResponsiveValue.d.ts
packages/app-mobile/components/getResponsiveValue.js packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/getResponsiveValue.js.map packages/app-mobile/components/getResponsiveValue.js.map
packages/app-mobile/components/getResponsiveValue.test.d.ts packages/app-mobile/components/getResponsiveValue.test.d.ts
packages/app-mobile/components/getResponsiveValue.test.js packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.test.js.map packages/app-mobile/components/getResponsiveValue.test.js.map
packages/app-mobile/components/global-style.d.ts
packages/app-mobile/components/global-style.js
packages/app-mobile/components/global-style.js.map
packages/app-mobile/components/screens/ConfigScreen.d.ts packages/app-mobile/components/screens/ConfigScreen.d.ts
packages/app-mobile/components/screens/ConfigScreen.js packages/app-mobile/components/screens/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen.js.map packages/app-mobile/components/screens/ConfigScreen.js.map
@ -993,12 +981,6 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
packages/app-mobile/components/screens/encryption-config.d.ts packages/app-mobile/components/screens/encryption-config.d.ts
packages/app-mobile/components/screens/encryption-config.js packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/encryption-config.js.map packages/app-mobile/components/screens/encryption-config.js.map
packages/app-mobile/components/searchNotes.d.ts
packages/app-mobile/components/searchNotes.js
packages/app-mobile/components/searchNotes.js.map
packages/app-mobile/components/useStyles.d.ts
packages/app-mobile/components/useStyles.js
packages/app-mobile/components/useStyles.js.map
packages/app-mobile/gulpfile.d.ts packages/app-mobile/gulpfile.d.ts
packages/app-mobile/gulpfile.js packages/app-mobile/gulpfile.js
packages/app-mobile/gulpfile.js.map packages/app-mobile/gulpfile.js.map

View File

@ -29,7 +29,6 @@ module.exports = {
// React Native variables // React Native variables
'__DEV__': 'readonly', '__DEV__': 'readonly',
'JSX': 'readonly',
// Clipper variables // Clipper variables
'browserSupportsPromises_': true, 'browserSupportsPromises_': true,

18
.gitignore vendored
View File

@ -942,12 +942,6 @@ packages/app-mobile/components/NoteEditor/SelectionFormatting.js.map
packages/app-mobile/components/NoteEditor/types.d.ts packages/app-mobile/components/NoteEditor/types.d.ts
packages/app-mobile/components/NoteEditor/types.js packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteEditor/types.js.map packages/app-mobile/components/NoteEditor/types.js.map
packages/app-mobile/components/NotesBar.d.ts
packages/app-mobile/components/NotesBar.js
packages/app-mobile/components/NotesBar.js.map
packages/app-mobile/components/NotesBarListItem.d.ts
packages/app-mobile/components/NotesBarListItem.js
packages/app-mobile/components/NotesBarListItem.js.map
packages/app-mobile/components/ScreenHeader.d.ts packages/app-mobile/components/ScreenHeader.d.ts
packages/app-mobile/components/ScreenHeader.js packages/app-mobile/components/ScreenHeader.js
packages/app-mobile/components/ScreenHeader.js.map packages/app-mobile/components/ScreenHeader.js.map
@ -957,18 +951,12 @@ packages/app-mobile/components/SelectDateTimeDialog.js.map
packages/app-mobile/components/SideMenu.d.ts packages/app-mobile/components/SideMenu.d.ts
packages/app-mobile/components/SideMenu.js packages/app-mobile/components/SideMenu.js
packages/app-mobile/components/SideMenu.js.map packages/app-mobile/components/SideMenu.js.map
packages/app-mobile/components/checkbox.d.ts
packages/app-mobile/components/checkbox.js
packages/app-mobile/components/checkbox.js.map
packages/app-mobile/components/getResponsiveValue.d.ts packages/app-mobile/components/getResponsiveValue.d.ts
packages/app-mobile/components/getResponsiveValue.js packages/app-mobile/components/getResponsiveValue.js
packages/app-mobile/components/getResponsiveValue.js.map packages/app-mobile/components/getResponsiveValue.js.map
packages/app-mobile/components/getResponsiveValue.test.d.ts packages/app-mobile/components/getResponsiveValue.test.d.ts
packages/app-mobile/components/getResponsiveValue.test.js packages/app-mobile/components/getResponsiveValue.test.js
packages/app-mobile/components/getResponsiveValue.test.js.map packages/app-mobile/components/getResponsiveValue.test.js.map
packages/app-mobile/components/global-style.d.ts
packages/app-mobile/components/global-style.js
packages/app-mobile/components/global-style.js.map
packages/app-mobile/components/screens/ConfigScreen.d.ts packages/app-mobile/components/screens/ConfigScreen.d.ts
packages/app-mobile/components/screens/ConfigScreen.js packages/app-mobile/components/screens/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen.js.map packages/app-mobile/components/screens/ConfigScreen.js.map
@ -981,12 +969,6 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js.map
packages/app-mobile/components/screens/encryption-config.d.ts packages/app-mobile/components/screens/encryption-config.d.ts
packages/app-mobile/components/screens/encryption-config.js packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/encryption-config.js.map packages/app-mobile/components/screens/encryption-config.js.map
packages/app-mobile/components/searchNotes.d.ts
packages/app-mobile/components/searchNotes.js
packages/app-mobile/components/searchNotes.js.map
packages/app-mobile/components/useStyles.d.ts
packages/app-mobile/components/useStyles.js
packages/app-mobile/components/useStyles.js.map
packages/app-mobile/gulpfile.d.ts packages/app-mobile/gulpfile.d.ts
packages/app-mobile/gulpfile.js packages/app-mobile/gulpfile.js
packages/app-mobile/gulpfile.js.map packages/app-mobile/gulpfile.js.map

View File

@ -86,9 +86,5 @@
"node-gyp": "^8.4.1", "node-gyp": "^8.4.1",
"nodemon": "^2.0.9" "nodemon": "^2.0.9"
}, },
"packageManager": "yarn@3.1.1", "packageManager": "yarn@3.1.1"
"resolutions": {
"@types/react": "17.0.14",
"@types/react-dom": "17.0.14"
}
} }

View File

@ -1,293 +0,0 @@
import * as React from 'react';
import { View, Text, TextInput, TouchableOpacity, FlatList, StyleSheet } from 'react-native';
import { State } from '@joplin/lib/reducer';
import { themeStyle } from './global-style';
const { connect } = require('react-redux');
const Icon = require('react-native-vector-icons/Ionicons').default;
const { _ } = require('@joplin/lib/locale');
import { Style } from './global-style';
import Note from '@joplin/lib/models/Note';
import NotesBarListItem from './NotesBarListItem';
import Folder from '@joplin/lib/models/Folder';
import searchNotes from './searchNotes';
interface Props {
themeId: number;
notes: any[];
todoCheckbox_change: (checked: boolean)=> void;
selectedFolderId: string;
activeFolderId: string;
dispatch: any;
selectedNoteId: string;
settings: any;
}
function NotesBarComponent(props: Props) {
const [query, setQuery] = React.useState<string>('');
const [notes, setNotes] = React.useState<any[]>(props.notes);
const theme: Style = React.useMemo(() => themeStyle(props.themeId), [props.themeId]);
const styles = (): Style => {
const styles: Style = {
container: {
flex: 1,
width: '100%',
backgroundColor: theme.backgroundColor3,
},
horizontalFlex: {
flexDirection: 'row',
},
title: {
alignItems: 'center',
},
titleText: {
fontSize: 16,
},
closeIcon: {
fontSize: 30,
paddingTop: 8,
paddingBottom: 8,
paddingRight: theme.marginRight,
paddingLeft: theme.marginLeft,
},
top: {
color: theme.color,
},
topContainer: {
width: '100%',
justifyContent: 'space-between',
paddingLeft: theme.marginLeft,
},
padding: {
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
paddingTop: 12,
paddingBottom: 12,
},
titleIcon: {
fontSize: 22,
marginRight: 4,
},
divider: {
backgroundColor: theme.dividerColor,
height: 1,
width: '100%',
},
nativeInput: {
fontSize: theme.fontSize,
flex: 1,
paddingRight: 8,
},
searchIcon: {
fontSize: 22,
},
searchInput: {
alignItems: 'center',
backgroundColor: theme.backgroundColor,
paddingLeft: 8,
borderRadius: 4,
borderWidth: 1,
borderColor: theme.dividerColor,
height: 42,
flex: 1,
},
button: {
height: 42,
width: 42,
backgroundColor: theme.color4,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 4,
flex: 0.5,
marginLeft: 8,
},
buttonIcon: {
color: theme.backgroundColor,
fontSize: 22,
},
inputGroup: {
justifyContent: 'space-between',
},
};
return StyleSheet.create(styles);
};
const titleComp = (
<View style={[styles().title, styles().horizontalFlex]}>
<Icon name='md-document'style={[styles().top, styles().titleIcon]} />
<Text style={[styles().top, styles().titleText]}>{_('Notes')}</Text>
</View>
);
const dividerComp = (
<View style={styles().divider}></View>
);
const handleNotesBarClose = () => {
props.dispatch({ type: 'NOTES_BAR_CLOSE' });
};
const closeButtonComp = (
<TouchableOpacity
onPress={handleNotesBarClose}
accessibilityLabel={_('Toggle note list')}
accessibilityRole="button"
>
<Icon name="close" style={[styles().top, styles().closeIcon]}/>
</TouchableOpacity>
);
const renderIconButton = (icon: JSX.Element, onPress: ()=> Promise<void>, label: string) => {
return (
<TouchableOpacity
style={styles().button}
activeOpacity={0.8}
onPress={onPress}
accessibilityLabel={label}
accessibilityRole="button"
>
{icon}
</TouchableOpacity>
);
};
const handleNewNote = async (isTodo: boolean) => {
let folderId = props.selectedFolderId !== Folder.conflictFolderId() ? props.selectedFolderId : null;
if (!folderId) folderId = props.activeFolderId;
props.dispatch({
type: 'NAV_BACK',
});
const newNote = await Note.save({
parent_id: folderId,
is_todo: isTodo ? 1 : 0,
}, { provisional: true });
props.dispatch({
type: 'NAV_GO',
routeName: 'Note',
noteId: newNote.id,
});
};
const addNoteButtonComp = renderIconButton(<Icon name='document-text-outline' style={styles().buttonIcon} />, () => handleNewNote(false), _('New note'));
const addTodoButtonComp = renderIconButton(<Icon name='checkbox-outline' style={styles().buttonIcon} />, () => handleNewNote(true), _('New to-do'));
const topComp = (
<View>
<View style={[styles().topContainer, styles().horizontalFlex]}>
{titleComp}
{closeButtonComp}
</View>
{dividerComp}
</View>
);
const refreshSearch = async () => {
const notes = await searchNotes(query, props.settings['db.ftsEnabled'], props.dispatch);
setNotes(notes);
};
const handleQuerySubmit = async () => {
const trimmedQuery = query.trim();
if (!trimmedQuery) {
props.dispatch({
type: 'SEARCH_QUERY',
query: '',
});
} else {
props.dispatch({
type: 'SEARCH_QUERY',
query: trimmedQuery,
});
}
setQuery(trimmedQuery);
await refreshSearch();
};
const searchInputComp = (
<View style={[styles().horizontalFlex, styles().searchInput]}>
<Icon name='search' style={[styles().top, styles().searchIcon]}/>
<TextInput style={[styles().top, styles().nativeInput]} placeholder='Search' onChangeText={setQuery} value={query} onSubmitEditing={handleQuerySubmit} placeholderTextColor={theme.dividerColor} />
</View>
);
const inputGroupComp = (
<View style={{ width: '100%' }}>
<View style={[styles().padding, styles().horizontalFlex, styles().inputGroup]}>
{searchInputComp}
{addNoteButtonComp}
{addTodoButtonComp}
</View>
{dividerComp}
</View>
);
let flatListRef: any = React.useRef(null);
const onRenderItem = React.useCallback(({ item }: { item: any }) => {
if (item.is_todo) {
return <NotesBarListItem note={item} todoCheckbox_change={props.todoCheckbox_change} />;
} else {
return <NotesBarListItem note={item} />;
}
}, [props.todoCheckbox_change]);
const NotesBarListComp = (
<FlatList
data={notes}
renderItem={onRenderItem}
keyExtractor={(item: any) => item.id}
getItemLayout={(data, index) => (
{
length: data.length,
offset: (theme.fontSize + styles().padding.paddingTop + styles().padding.paddingBottom) * index,
viewOffset: (theme.fontSize + styles().padding.paddingTop + styles().padding.paddingBottom),
index,
}
)}
ref={(ref: any) => { flatListRef = ref; }}
/>
);
// Scroll the notesbar to selected note item after rendering
React.useEffect(() => {
const selectedItemIndex = notes.findIndex(item => item.id === props.selectedNoteId);
if (selectedItemIndex >= 0) {
flatListRef.scrollToIndex({ index: selectedItemIndex });
}
});
// Update the notesbar when a note item changes
React.useEffect(() => {
setNotes(props.notes);
}, [props.notes]);
return (
<View style={styles().container}>
{topComp}
{inputGroupComp}
{ NotesBarListComp }
</View>
);
}
const NotesBar = connect((state: State) => {
return {
themeId: state.settings.theme,
notes: state.notes,
activeFolderId: state.settings.activeFolderId,
selectedFolderId: state.selectedFolderId,
selectedNoteId: state.selectedNoteIds[0],
settings: state.settings,
};
})(NotesBarComponent);
export default NotesBar;

View File

@ -1,132 +0,0 @@
import * as React from 'react';
import Checkbox from './checkbox';
import Note from '@joplin/lib/models/Note';
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
import { Style } from './global-style';
import { State } from '@joplin/lib/reducer';
const { connect } = require('react-redux');
const { _ } = require('@joplin/lib/locale');
import shim from '@joplin/lib/shim';
import { themeStyle } from './global-style';
interface NoteListProps {
note: any;
themeId: number;
todoCheckbox_change: (checked: boolean)=> void;
dispatch: Function;
selectedNoteId: string;
}
const NotesBarListItemComponent = function(props: NoteListProps) {
const note = props.note ?? {};
const isTodo = !!Number(note.is_todo);
const styles = (): Style => {
const themeId = props.themeId;
const theme = themeStyle(themeId);
const styles: Style = {
horizontalFlex: {
flexDirection: 'row',
},
padding: {
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
paddingTop: 12,
paddingBottom: 12,
},
button: {
height: 42,
width: 42,
backgroundColor: theme.color4,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 4,
flex: 0.5,
marginLeft: 8,
},
itemText: {
fontSize: theme.fontSize,
color: theme.color,
paddingTop: 12,
paddingBottom: 12,
},
checkbox: {
paddingRight: 10,
paddingLeft: theme.marginLeft,
paddingTop: 12,
paddingBottom: 12,
color: theme.color,
},
selectedItem: props.selectedNoteId === note.id ? {
backgroundColor: theme.dividerColor,
} : null,
item: {
borderBottomWidth: 1,
borderColor: theme.dividerColor,
},
};
return StyleSheet.create(styles);
};
const onTodoCheckboxChange = async (checked: boolean) => {
await props.todoCheckbox_change(checked);
};
const onPress = async () => {
if (!note) return;
if (note.encryption_applied) return;
props.dispatch({
type: 'NAV_BACK',
});
shim.setTimeout(() => {
props.dispatch({
type: 'NAV_GO',
routeName: 'Note',
noteId: note.id,
});
}, 5);
};
const noteTitle = Note.displayTitle(note);
let item;
if (isTodo) {
item = (
<View>
<TouchableOpacity style={[styles().horizontalFlex, styles().item, styles().selectedItem]} onPress={onPress}>
<Checkbox
style={styles().checkbox}
checked={!!Number(note.todo_completed)}
onChange={(checked) => onTodoCheckboxChange(checked)}
accessibilityLabel={_('to-do: %s', noteTitle)}
/>
<Text style={styles().itemText}>{noteTitle}</Text>
</TouchableOpacity>
</View>
);
} else {
item = (
<View>
<TouchableOpacity onPress={onPress} style={[styles().selectedItem, styles().item]}>
<Text style={[styles().itemText, styles().padding]}>{noteTitle}</Text>
</TouchableOpacity>
</View>
);
}
return item;
};
const NotesBarListItem = connect((state: State) => {
return {
themeId: state.settings.theme,
selectedNoteId: state.selectedNoteIds[0],
};
})(NotesBarListItemComponent);
export default NotesBarListItem;

View File

@ -1,93 +0,0 @@
import * as React from 'react';
const Component = React.Component;
const { View, TouchableHighlight } = require('react-native');
const Icon = require('react-native-vector-icons/Ionicons').default;
Icon.loadFont();
interface Style {
[key: string]: any;
}
interface Props {
style: Style;
checked: boolean;
onChange: (checked: boolean)=> void;
accessibilityLabel?: string;
}
interface State {
checked: boolean;
}
const styles: Style = {
checkboxIcon: {
fontSize: 20,
height: 22,
// marginRight: 10,
},
};
class Checkbox extends Component<Props, State> {
public constructor(props: Props) {
super(props);
this.state = {
checked: false,
};
}
public UNSAFE_componentWillMount() {
this.setState({ checked: this.props.checked });
}
public UNSAFE_componentWillReceiveProps(newProps: Props) {
if ('checked' in newProps) {
this.setState({ checked: newProps.checked });
}
}
private onPress() {
const newChecked = !this.state.checked;
this.setState({ checked: newChecked });
if (this.props.onChange) this.props.onChange(newChecked);
}
public render() {
const iconName = this.state.checked ? 'md-checkbox-outline' : 'md-square-outline';
const style: Style = this.props.style ? Object.assign({}, this.props.style) : {};
style.justifyContent = 'center';
style.alignItems = 'center';
const checkboxIconStyle = Object.assign({}, styles.checkboxIcon);
if (style.color) checkboxIconStyle.color = style.color;
if (style.paddingTop) checkboxIconStyle.marginTop = style.paddingTop;
if (style.paddingBottom) checkboxIconStyle.marginBottom = style.paddingBottom;
if (style.paddingLeft) checkboxIconStyle.marginLeft = style.paddingLeft;
if (style.paddingRight) checkboxIconStyle.marginRight = style.paddingRight;
const thStyle = {
justifyContent: 'center',
alignItems: 'center',
};
if (style && style.display === 'none') return <View />;
// if (style.display) thStyle.display = style.display;
return (
<TouchableHighlight
onPress={() => this.onPress()}
style={thStyle}
accessibilityRole="checkbox"
accessibilityState={{
checked: this.state.checked,
}}
accessibilityLabel={this.props.accessibilityLabel ?? ''}>
<Icon name={iconName} style={checkboxIconStyle} />
</TouchableHighlight>
);
}
}
export default Checkbox;

View File

@ -1,115 +0,0 @@
import Setting from '@joplin/lib/models/Setting';
const { Platform } = require('react-native');
import { themeById } from '@joplin/lib/theme';
export interface Style {
[key: string]: any;
}
interface Fonts {
[key: number]: string;
}
interface ThemeCache {
[key: string]: any;
}
const baseStyle = {
appearance: 'light',
fontSize: 16,
noteViewerFontSize: 16,
margin: 15, // No text and no interactive component should be within this margin
itemMarginTop: 10,
itemMarginBottom: 10,
fontSizeSmaller: 14,
disabledOpacity: 0.2,
lineHeight: '1.6em',
};
const themeCache_: ThemeCache = {};
function addExtraStyles(style: Style) {
style.marginRight = style.margin;
style.marginLeft = style.margin;
style.marginTop = style.margin;
style.marginBottom = style.margin;
style.icon = {
color: style.color,
fontSize: 30,
};
style.lineInput = {
color: style.color,
backgroundColor: style.backgroundColor,
borderBottomWidth: 1,
borderColor: style.dividerColor,
paddingBottom: 0,
};
if (Platform.OS === 'ios') {
delete style.lineInput.borderBottomWidth;
delete style.lineInput.borderColor;
}
style.buttonRow = {
flexDirection: 'row',
borderTopWidth: 1,
borderTopColor: style.dividerColor,
paddingTop: 10,
};
style.normalText = {
color: style.color,
fontSize: style.fontSize,
};
style.urlText = {
color: style.urlColor,
fontSize: style.fontSize,
};
style.headerStyle = {
color: style.color,
fontSize: style.fontSize * 1.2,
fontWeight: 'bold',
};
style.headerWrapperStyle = {
backgroundColor: style.headerBackgroundColor,
};
style.keyboardAppearance = style.appearance;
return style;
}
export function editorFont(fontId?: number) {
// IMPORTANT: The font mapping must match the one in Setting.js
const fonts: Fonts = {
[Setting.FONT_DEFAULT]: null,
[Setting.FONT_MENLO]: 'Menlo',
[Setting.FONT_COURIER_NEW]: 'Courier New',
[Setting.FONT_AVENIR]: 'Avenir',
[Setting.FONT_MONOSPACE]: 'monospace',
};
if (!fontId) {
// console.warn('Editor font not set! Falling back to default font."');
fontId = Setting.FONT_DEFAULT;
}
return fonts[fontId];
}
export function themeStyle(theme?: number) {
if (!theme) {
console.warn('Theme not set! Defaulting to Light theme.');
theme = Setting.THEME_LIGHT;
}
const cacheKey = [theme].join('-');
if (themeCache_[cacheKey]) return themeCache_[cacheKey];
const output = Object.assign({}, baseStyle, themeById(theme));
themeCache_[cacheKey] = addExtraStyles(output);
return themeCache_[cacheKey];
}

View File

@ -2,7 +2,7 @@ const React = require('react');
const Component = React.Component; const Component = React.Component;
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { Text, TouchableOpacity, View, StyleSheet } = require('react-native'); const { Text, TouchableOpacity, View, StyleSheet } = require('react-native');
const Checkbox = require('./checkbox.js').default; const { Checkbox } = require('./checkbox.js');
const Note = require('@joplin/lib/models/Note').default; const Note = require('@joplin/lib/models/Note').default;
const time = require('@joplin/lib/time').default; const time = require('@joplin/lib/time').default;
const { themeStyle } = require('./global-style.js'); const { themeStyle } = require('./global-style.js');

View File

@ -3,7 +3,6 @@ import uuid from '@joplin/lib/uuid';
import Setting from '@joplin/lib/models/Setting'; import Setting from '@joplin/lib/models/Setting';
import shim from '@joplin/lib/shim'; import shim from '@joplin/lib/shim';
import UndoRedoService from '@joplin/lib/services/UndoRedoService'; import UndoRedoService from '@joplin/lib/services/UndoRedoService';
import { State } from '@joplin/lib/reducer';
import NoteBodyViewer from '../NoteBodyViewer/NoteBodyViewer'; import NoteBodyViewer from '../NoteBodyViewer/NoteBodyViewer';
import checkPermissions from '../../utils/checkPermissions'; import checkPermissions from '../../utils/checkPermissions';
import NoteEditor from '../NoteEditor/NoteEditor'; import NoteEditor from '../NoteEditor/NoteEditor';
@ -11,7 +10,7 @@ import { ChangeEvent, UndoRedoDepthChangeEvent } from '../NoteEditor/types';
const FileViewer = require('react-native-file-viewer').default; const FileViewer = require('react-native-file-viewer').default;
const React = require('react'); const React = require('react');
import { Platform, Keyboard, View, TextInput, StyleSheet, Linking, Image, Share, PermissionsAndroid, Animated, TouchableOpacity, Dimensions, PanResponder } from 'react-native'; const { Platform, Keyboard, View, TextInput, StyleSheet, Linking, Image, Share, PermissionsAndroid } = require('react-native');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
// const { MarkdownEditor } = require('@joplin/lib/../MarkdownEditor/index.js'); // const { MarkdownEditor } = require('@joplin/lib/../MarkdownEditor/index.js');
const RNFS = require('react-native-fs'); const RNFS = require('react-native-fs');
@ -30,12 +29,12 @@ const mimeUtils = require('@joplin/lib/mime-utils.js').mime;
import ScreenHeader from '../ScreenHeader'; import ScreenHeader from '../ScreenHeader';
const NoteTagsDialog = require('./NoteTagsDialog'); const NoteTagsDialog = require('./NoteTagsDialog');
import time from '@joplin/lib/time'; import time from '@joplin/lib/time';
import Checkbox from '../checkbox'; const { Checkbox } = require('../checkbox.js');
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
import { reg } from '@joplin/lib/registry'; import { reg } from '@joplin/lib/registry';
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher'; import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
const { BaseScreenComponent } = require('../base-screen.js'); const { BaseScreenComponent } = require('../base-screen.js');
import { themeStyle, editorFont } from '../global-style'; const { themeStyle, editorFont } = require('../global-style.js');
const { dialogs } = require('../../utils/dialogs.js'); const { dialogs } = require('../../utils/dialogs.js');
const DialogBox = require('react-native-dialogbox').default; const DialogBox = require('react-native-dialogbox').default;
const DocumentPicker = require('react-native-document-picker').default; const DocumentPicker = require('react-native-document-picker').default;
@ -46,23 +45,20 @@ import { ImagePickerResponse } from 'react-native-image-picker';
import SelectDateTimeDialog from '../SelectDateTimeDialog'; import SelectDateTimeDialog from '../SelectDateTimeDialog';
import ShareExtension from '../../utils/ShareExtension.js'; import ShareExtension from '../../utils/ShareExtension.js';
import CameraView from '../CameraView'; import CameraView from '../CameraView';
import NotesBar from '../NotesBar';
import { NoteEntity } from '@joplin/lib/services/database/types'; import { NoteEntity } from '@joplin/lib/services/database/types';
import Logger from '@joplin/lib/Logger'; import Logger from '@joplin/lib/Logger';
const urlUtils = require('@joplin/lib/urlUtils'); const urlUtils = require('@joplin/lib/urlUtils');
const Icon = require('react-native-vector-icons/Feather').default;
import getResponsiveValue from '../getResponsiveValue';
const emptyArray: any[] = []; const emptyArray: any[] = [];
const logger = Logger.create('screens/Note'); const logger = Logger.create('screens/Note');
class NoteScreenComponent extends BaseScreenComponent { class NoteScreenComponent extends BaseScreenComponent {
public static navigationOptions(): any { static navigationOptions(): any {
return { header: null }; return { header: null };
} }
public constructor() { constructor() {
super(); super();
this.state = { this.state = {
note: Note.new(), note: Note.new(),
@ -91,9 +87,6 @@ class NoteScreenComponent extends BaseScreenComponent {
canUndo: false, canUndo: false,
canRedo: false, canRedo: false,
}, },
notesBarWidth: this.getNotesBarWidth(),
isTablet: Dimensions.get('window').width >= 768,
}; };
this.saveActionQueues_ = {}; this.saveActionQueues_ = {};
@ -205,7 +198,7 @@ class NoteScreenComponent extends BaseScreenComponent {
if (msg.indexOf('file://') === 0) { if (msg.indexOf('file://') === 0) {
throw new Error(_('Links with protocol "%s" are not supported', 'file://')); throw new Error(_('Links with protocol "%s" are not supported', 'file://'));
} else { } else {
await Linking.openURL(msg); Linking.openURL(msg);
} }
} }
} catch (error) { } catch (error) {
@ -246,8 +239,6 @@ class NoteScreenComponent extends BaseScreenComponent {
this.onBodyViewerCheckboxChange = this.onBodyViewerCheckboxChange.bind(this); this.onBodyViewerCheckboxChange = this.onBodyViewerCheckboxChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this); this.onBodyChange = this.onBodyChange.bind(this);
this.onUndoRedoDepthChange = this.onUndoRedoDepthChange.bind(this); this.onUndoRedoDepthChange = this.onUndoRedoDepthChange.bind(this);
this.onNotesBarToggle = this.onNotesBarToggle.bind(this);
this.handleScreenWidthChange_ = this.handleScreenWidthChange_.bind(this);
} }
private useEditorBeta(): boolean { private useEditorBeta(): boolean {
@ -290,27 +281,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}); });
} }
private getNotesBarWidth = () => { screenHeader_undoButtonPress() {
const notesBarWidth = getResponsiveValue({
sm: 250,
md: 260,
lg: 270,
xl: 280,
xxl: 290,
});
return notesBarWidth;
};
// Update state that depends on the screen width when the screen width changes ( the device orientation changess)
private handleScreenWidthChange_() {
this.setState({
notesBarWidth: this.getNotesBarWidth(),
isTablet: Dimensions.get('window').width >= 768,
});
}
private screenHeader_undoButtonPress() {
if (this.useEditorBeta()) { if (this.useEditorBeta()) {
this.editorRef.current.undo(); this.editorRef.current.undo();
} else { } else {
@ -318,7 +289,7 @@ class NoteScreenComponent extends BaseScreenComponent {
} }
} }
private screenHeader_redoButtonPress() { screenHeader_redoButtonPress() {
if (this.useEditorBeta()) { if (this.useEditorBeta()) {
this.editorRef.current.redo(); this.editorRef.current.redo();
} else { } else {
@ -326,13 +297,13 @@ class NoteScreenComponent extends BaseScreenComponent {
} }
} }
private undoState(noteBody: string = null) { undoState(noteBody: string = null) {
return { return {
body: noteBody === null ? this.state.note.body : noteBody, body: noteBody === null ? this.state.note.body : noteBody,
}; };
} }
private styles() { styles() {
const themeId = this.props.themeId; const themeId = this.props.themeId;
const theme = themeStyle(themeId); const theme = themeStyle(themeId);
@ -351,7 +322,6 @@ class NoteScreenComponent extends BaseScreenComponent {
flex: 1, flex: 1,
paddingLeft: theme.marginLeft, paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight, paddingRight: theme.marginRight,
paddingBottom: Platform.OS === 'ios' ? 40 : 0,
// Add extra space to allow scrolling past end of document, and also to fix this: // Add extra space to allow scrolling past end of document, and also to fix this:
// https://github.com/laurent22/joplin/issues/1437 // https://github.com/laurent22/joplin/issues/1437
@ -417,84 +387,17 @@ class NoteScreenComponent extends BaseScreenComponent {
paddingBottom: 10, // Added for iOS (Not needed for Android??) paddingBottom: 10, // Added for iOS (Not needed for Android??)
}; };
styles.noteMainComp = {
flex: 1,
flexDirection: 'row',
position: 'relative',
};
styles.notesBarContainer = {
position: 'relative',
left: this.notesBarPosition,
top: 0,
width: this.state.notesBarWidth,
height: '100%',
};
styles.noteComp = {
position: 'relative',
top: 0,
left: this.notePosition,
width: this.noteWidth,
};
styles.noteActionButton = {
width: 54,
height: 54,
backgroundColor: theme.backgroundColor3,
borderWidth: 1,
borderColor: theme.dividerColor,
alignItems: 'center',
justifyContent: 'center',
};
styles.noteActionButtonActive = {
...styles.noteActionButton,
borderWidth: 0,
backgroundColor: theme.color4,
};
styles.noteActionButton1 = {
borderBottomWidth: 0,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
};
styles.noteActionButton2 = {
// Removing this temporarily till second noteAction is implemented
// borderBottomLeftRadius: 8,
// borderBottomRightRadius: 8,
borderRadius: 8,
};
styles.noteActionButtonIcon = {
fontSize: 30,
color: theme.color,
};
styles.noteActionButtonIconActive = {
...styles.noteActionButtonIcon,
color: theme.backgroundColor,
};
styles.noteActionButtonGroup = {
position: 'absolute',
top: '8%',
right: '3%',
transform: [{ translateY: this.noteActionsPositionY }],
};
if (this.state.HACK_webviewLoadingState === 1) styles.titleTextInput.marginTop = 1; if (this.state.HACK_webviewLoadingState === 1) styles.titleTextInput.marginTop = 1;
this.styles_[cacheKey] = StyleSheet.create(styles); this.styles_[cacheKey] = StyleSheet.create(styles);
return this.styles_[cacheKey]; return this.styles_[cacheKey];
} }
private isModified() { isModified() {
return shared.isModified(this); return shared.isModified(this);
} }
private async requestGeoLocationPermissions() { async requestGeoLocationPermissions() {
if (!Setting.value('trackLocation')) return; if (!Setting.value('trackLocation')) return;
const response = await checkPermissions(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, { const response = await checkPermissions(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, {
@ -511,7 +414,7 @@ class NoteScreenComponent extends BaseScreenComponent {
} }
} }
public async componentDidMount() { async componentDidMount() {
BackButtonService.addHandler(this.backHandler); BackButtonService.addHandler(this.backHandler);
NavService.addHandler(this.navHandler); NavService.addHandler(this.navHandler);
@ -532,75 +435,13 @@ class NoteScreenComponent extends BaseScreenComponent {
// has already been granted, it doesn't slow down opening the note. If it hasn't // has already been granted, it doesn't slow down opening the note. If it hasn't
// been granted, the popup will open anyway. // been granted, the popup will open anyway.
void this.requestGeoLocationPermissions(); void this.requestGeoLocationPermissions();
this.unsubscribeScreenWidthChangeHandler_ = Dimensions.addEventListener('change', this.handleScreenWidthChange_);
} }
private animateNotesBarOpen = () => { onMarkForDownload(event: any) {
Animated.parallel([
Animated.spring(
this.notesBarPosition,
{
toValue: 0,
useNativeDriver: false,
}
),
Animated.spring(
this.notePosition,
{
toValue: 0,
useNativeDriver: false,
}
),
Animated.spring(
this.noteWidth,
{
toValue: Dimensions.get('window').width - this.state.notesBarWidth,
useNativeDriver: false,
}
),
]).start();
};
private animateNotesBarClose = () => {
Animated.parallel([
Animated.spring(
this.notesBarPosition,
{
toValue: -1 * this.state.notesBarWidth,
useNativeDriver: false,
}
),
Animated.spring(
this.notePosition,
{
toValue: -1 * this.state.notesBarWidth,
useNativeDriver: false,
}
),
Animated.spring(
this.noteWidth,
{
toValue: Dimensions.get('window').width,
useNativeDriver: false,
}
),
]).start();
};
private onNotesBarToggle = async () => {
if (this.props.showNotesBar) {
this.props.dispatch({ type: 'NOTES_BAR_CLOSE' });
} else {
this.props.dispatch({ type: 'NOTES_BAR_OPEN' });
}
};
private onMarkForDownload(event: any) {
void ResourceFetcher.instance().markForDownload(event.resourceId); void ResourceFetcher.instance().markForDownload(event.resourceId);
} }
public componentDidUpdate(prevProps: any) { componentDidUpdate(prevProps: any) {
if (this.doFocusUpdate_) { if (this.doFocusUpdate_) {
this.doFocusUpdate_ = false; this.doFocusUpdate_ = false;
this.focusUpdate(); this.focusUpdate();
@ -612,17 +453,9 @@ class NoteScreenComponent extends BaseScreenComponent {
options: this.sideMenuOptions(), options: this.sideMenuOptions(),
}); });
} }
if (this.props.showNotesBar !== prevProps.showNotesBar) {
if (this.props.showNotesBar) {
this.animateNotesBarOpen();
} else {
this.animateNotesBarClose();
}
}
} }
public componentWillUnmount() { componentWillUnmount() {
BackButtonService.removeHandler(this.backHandler); BackButtonService.removeHandler(this.backHandler);
NavService.removeHandler(this.navHandler); NavService.removeHandler(this.navHandler);
@ -637,34 +470,15 @@ class NoteScreenComponent extends BaseScreenComponent {
// It cannot theoretically be undefined, since componentDidMount should always be called before // It cannot theoretically be undefined, since componentDidMount should always be called before
// componentWillUnmount, but with React Native the impossible often becomes possible. // componentWillUnmount, but with React Native the impossible often becomes possible.
if (this.undoRedoService_) this.undoRedoService_.off('stackChange', this.undoRedoService_stackChange); if (this.undoRedoService_) this.undoRedoService_.off('stackChange', this.undoRedoService_stackChange);
if (this.unsubscribeScreenWidthChangeHandler_) {
this.unsubscribeScreenWidthChangeHandler_.remove();
this.unsubscribeScreenWidthChangeHandler_ = null;
}
} }
public componentWillMount() { title_changeText(text: string) {
if (this.props.showNotesBar) {
this.notesBarPosition = new Animated.Value(0);
this.notePosition = new Animated.Value(0);
this.noteWidth = new Animated.Value(Dimensions.get('window').width - 250);
} else {
this.notesBarPosition = new Animated.Value(-1 * this.state.notesBarWidth);
this.notePosition = new Animated.Value(-1 * this.state.notesBarWidth);
this.noteWidth = new Animated.Value(Dimensions.get('window').width);
}
this.noteActionsPositionY = new Animated.Value(0);
}
private title_changeText(text: string) {
shared.noteComponent_change(this, 'title', text); shared.noteComponent_change(this, 'title', text);
this.setState({ newAndNoTitleChangeNoteId: null }); this.setState({ newAndNoTitleChangeNoteId: null });
this.scheduleSave(); this.scheduleSave();
} }
private body_changeText(text: string) { body_changeText(text: string) {
if (!this.undoRedoService_.canUndo) { if (!this.undoRedoService_.canUndo) {
this.undoRedoService_.push(this.undoState()); this.undoRedoService_.push(this.undoState());
} else { } else {
@ -675,7 +489,7 @@ class NoteScreenComponent extends BaseScreenComponent {
this.scheduleSave(); this.scheduleSave();
} }
private body_selectionChange(event: any) { body_selectionChange(event: any) {
if (this.useEditorBeta()) { if (this.useEditorBeta()) {
this.selection = event.selection; this.selection = event.selection;
} else { } else {
@ -683,34 +497,34 @@ class NoteScreenComponent extends BaseScreenComponent {
} }
} }
private makeSaveAction() { makeSaveAction() {
return async () => { return async () => {
return shared.saveNoteButton_press(this); return shared.saveNoteButton_press(this);
}; };
} }
private saveActionQueue(noteId: string) { saveActionQueue(noteId: string) {
if (!this.saveActionQueues_[noteId]) { if (!this.saveActionQueues_[noteId]) {
this.saveActionQueues_[noteId] = new AsyncActionQueue(500); this.saveActionQueues_[noteId] = new AsyncActionQueue(500);
} }
return this.saveActionQueues_[noteId]; return this.saveActionQueues_[noteId];
} }
private scheduleSave() { scheduleSave() {
this.saveActionQueue(this.state.note.id).push(this.makeSaveAction()); this.saveActionQueue(this.state.note.id).push(this.makeSaveAction());
} }
private async saveNoteButton_press(folderId: string = null) { async saveNoteButton_press(folderId: string = null) {
await shared.saveNoteButton_press(this, folderId); await shared.saveNoteButton_press(this, folderId);
Keyboard.dismiss(); Keyboard.dismiss();
} }
private async saveOneProperty(name: string, value: any) { async saveOneProperty(name: string, value: any) {
await shared.saveOneProperty(this, name, value); await shared.saveOneProperty(this, name, value);
} }
private async deleteNote_onPress() { async deleteNote_onPress() {
const note = this.state.note; const note = this.state.note;
if (!note.id) return; if (!note.id) return;
@ -743,7 +557,7 @@ class NoteScreenComponent extends BaseScreenComponent {
} }
} }
private async imageDimensions(uri: string) { async imageDimensions(uri: string) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
Image.getSize( Image.getSize(
uri, uri,
@ -757,7 +571,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}); });
} }
private showImagePicker(options: any) { showImagePicker(options: any) {
return new Promise((resolve) => { return new Promise((resolve) => {
ImagePicker.launchImageLibrary(options, (response: any) => { ImagePicker.launchImageLibrary(options, (response: any) => {
resolve(response); resolve(response);
@ -765,7 +579,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}); });
} }
private async resizeImage(localFilePath: string, targetPath: string, mimeType: string) { async resizeImage(localFilePath: string, targetPath: string, mimeType: string) {
const maxSize = Resource.IMAGE_MAX_DIMENSION; const maxSize = Resource.IMAGE_MAX_DIMENSION;
const dimensions: any = await this.imageDimensions(localFilePath); const dimensions: any = await this.imageDimensions(localFilePath);
@ -814,7 +628,7 @@ class NoteScreenComponent extends BaseScreenComponent {
return true; return true;
} }
private async attachFile(pickerResponse: any, fileType: string) { async attachFile(pickerResponse: any, fileType: string) {
if (!pickerResponse) { if (!pickerResponse) {
// User has cancelled // User has cancelled
return; return;
@ -932,11 +746,11 @@ class NoteScreenComponent extends BaseScreenComponent {
} }
} }
private takePhoto_onPress() { takePhoto_onPress() {
this.setState({ showCamera: true }); this.setState({ showCamera: true });
} }
private cameraView_onPhoto(data: any) { cameraView_onPhoto(data: any) {
void this.attachFile( void this.attachFile(
{ {
uri: data.uri, uri: data.uri,
@ -948,7 +762,7 @@ class NoteScreenComponent extends BaseScreenComponent {
this.setState({ showCamera: false }); this.setState({ showCamera: false });
} }
private cameraView_onCancel() { cameraView_onCancel() {
this.setState({ showCamera: false }); this.setState({ showCamera: false });
} }
@ -959,34 +773,34 @@ class NoteScreenComponent extends BaseScreenComponent {
} }
} }
private toggleIsTodo_onPress() { toggleIsTodo_onPress() {
shared.toggleIsTodo_onPress(this); shared.toggleIsTodo_onPress(this);
this.scheduleSave(); this.scheduleSave();
} }
private tags_onPress() { tags_onPress() {
if (!this.state.note || !this.state.note.id) return; if (!this.state.note || !this.state.note.id) return;
this.setState({ noteTagDialogShown: true }); this.setState({ noteTagDialogShown: true });
} }
private async share_onPress() { async share_onPress() {
await Share.share({ await Share.share({
message: `${this.state.note.title}\n\n${this.state.note.body}`, message: `${this.state.note.title}\n\n${this.state.note.body}`,
title: this.state.note.title, title: this.state.note.title,
}); });
} }
private properties_onPress() { properties_onPress() {
this.props.dispatch({ type: 'SIDE_MENU_OPEN' }); this.props.dispatch({ type: 'SIDE_MENU_OPEN' });
} }
public setAlarm_onPress() { setAlarm_onPress() {
this.setState({ alarmDialogShown: true }); this.setState({ alarmDialogShown: true });
} }
private async onAlarmDialogAccept(date: Date) { async onAlarmDialogAccept(date: Date) {
const newNote = Object.assign({}, this.state.note); const newNote = Object.assign({}, this.state.note);
newNote.todo_due = date ? date.getTime() : 0; newNote.todo_due = date ? date.getTime() : 0;
@ -995,40 +809,40 @@ class NoteScreenComponent extends BaseScreenComponent {
this.setState({ alarmDialogShown: false }); this.setState({ alarmDialogShown: false });
} }
private onAlarmDialogReject() { onAlarmDialogReject() {
this.setState({ alarmDialogShown: false }); this.setState({ alarmDialogShown: false });
} }
private async showOnMap_onPress() { async showOnMap_onPress() {
if (!this.state.note.id) return; if (!this.state.note.id) return;
const note = await Note.load(this.state.note.id); const note = await Note.load(this.state.note.id);
try { try {
const url = Note.geolocationUrl(note); const url = Note.geolocationUrl(note);
await Linking.openURL(url); Linking.openURL(url);
} catch (error) { } catch (error) {
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' }); this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
await dialogs.error(this, error.message); await dialogs.error(this, error.message);
} }
} }
private async showSource_onPress() { async showSource_onPress() {
if (!this.state.note.id) return; if (!this.state.note.id) return;
const note = await Note.load(this.state.note.id); const note = await Note.load(this.state.note.id);
try { try {
await Linking.openURL(note.source_url); Linking.openURL(note.source_url);
} catch (error) { } catch (error) {
await dialogs.error(this, error.message); await dialogs.error(this, error.message);
} }
} }
private copyMarkdownLink_onPress() { copyMarkdownLink_onPress() {
const note = this.state.note; const note = this.state.note;
Clipboard.setString(Note.markdownTag(note)); Clipboard.setString(Note.markdownTag(note));
} }
private sideMenuOptions() { sideMenuOptions() {
const note = this.state.note; const note = this.state.note;
if (!note) return []; if (!note) return [];
@ -1080,7 +894,7 @@ class NoteScreenComponent extends BaseScreenComponent {
if (buttonId === 'attachPhoto') void this.attachPhoto_onPress(); if (buttonId === 'attachPhoto') void this.attachPhoto_onPress();
} }
private menuOptions() { menuOptions() {
const note = this.state.note; const note = this.state.note;
const isTodo = note && !!note.is_todo; const isTodo = note && !!note.is_todo;
const isSaved = note && note.id; const isSaved = note && note.id;
@ -1164,11 +978,11 @@ class NoteScreenComponent extends BaseScreenComponent {
return output; return output;
} }
private async todoCheckbox_change(checked: boolean) { async todoCheckbox_change(checked: boolean) {
await this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0); await this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0);
} }
public scheduleFocusUpdate() { scheduleFocusUpdate() {
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_); if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
this.focusUpdateIID_ = shim.setTimeout(() => { this.focusUpdateIID_ = shim.setTimeout(() => {
@ -1177,7 +991,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}, 100); }, 100);
} }
private focusUpdate() { focusUpdate() {
if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_); if (this.focusUpdateIID_) shim.clearTimeout(this.focusUpdateIID_);
this.focusUpdateIID_ = null; this.focusUpdateIID_ = null;
@ -1195,7 +1009,7 @@ class NoteScreenComponent extends BaseScreenComponent {
// } // }
} }
private async folderPickerOptions_valueChanged(itemValue: any) { async folderPickerOptions_valueChanged(itemValue: any) {
const note = this.state.note; const note = this.state.note;
const isProvisionalNote = this.props.provisionalNoteIds.includes(note.id); const isProvisionalNote = this.props.provisionalNoteIds.includes(note.id);
@ -1216,7 +1030,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}); });
} }
private folderPickerOptions() { folderPickerOptions() {
const options = { const options = {
enabled: true, enabled: true,
selectedFolderId: this.state.folder ? this.state.folder.id : null, selectedFolderId: this.state.folder ? this.state.folder.id : null,
@ -1229,7 +1043,7 @@ class NoteScreenComponent extends BaseScreenComponent {
return this.folderPickerOptions_; return this.folderPickerOptions_;
} }
private onBodyViewerLoadEnd() { onBodyViewerLoadEnd() {
shim.setTimeout(() => { shim.setTimeout(() => {
this.setState({ HACK_webviewLoadingState: 1 }); this.setState({ HACK_webviewLoadingState: 1 });
shim.setTimeout(() => { shim.setTimeout(() => {
@ -1238,11 +1052,11 @@ class NoteScreenComponent extends BaseScreenComponent {
}, 5); }, 5);
} }
private onBodyViewerCheckboxChange(newBody: string) { onBodyViewerCheckboxChange(newBody: string) {
void this.saveOneProperty('body', newBody); void this.saveOneProperty('body', newBody);
} }
public render() { render() {
if (this.state.isLoading) { if (this.state.isLoading) {
return ( return (
<View style={this.styles().screen}> <View style={this.styles().screen}>
@ -1319,6 +1133,7 @@ class NoteScreenComponent extends BaseScreenComponent {
placeholderTextColor={theme.colorFaded} placeholderTextColor={theme.colorFaded}
// need some extra padding for iOS so that the keyboard won't cover last line of the note // need some extra padding for iOS so that the keyboard won't cover last line of the note
// see https://github.com/laurent22/joplin/issues/3607 // see https://github.com/laurent22/joplin/issues/3607
paddingBottom={ Platform.OS === 'ios' ? 40 : 0}
/> />
); );
} else { } else {
@ -1397,65 +1212,6 @@ class NoteScreenComponent extends BaseScreenComponent {
const noteTagDialog = !this.state.noteTagDialogShown ? null : <NoteTagsDialog onCloseRequested={this.noteTagDialog_closeRequested} />; const noteTagDialog = !this.state.noteTagDialogShown ? null : <NoteTagsDialog onCloseRequested={this.noteTagDialog_closeRequested} />;
// Different styles for when the note actions are active or not
const notesBarToggleIconStyle = this.props.showNotesBar ? this.styles().noteActionButtonIconActive : this.styles().noteActionButtonIcon;
const notesBarToggleStyle = this.props.showNotesBar ? this.styles().noteActionButtonActive : this.styles().noteActionButton;
const handleNoteActionsDrag = (gestureState: any) => {
const minY = 160;
const maxY = 0.8 * Dimensions.get('window').height;
let newY = gestureState.moveY;
if (newY < minY) {
newY = minY;
} else if (newY > maxY) {
newY = maxY;
}
this.noteActionsPositionY.setValue(newY - 162);
};
// Pan responder that handles making the note actions draggable.
// The note actions need to be draggable, because they could
// potentially obstruct some portion of a note's content
const noteActionsDragResponder = PanResponder.create({
// Only start dragging after moving at least 10px — this prevents clicks from dragging instead
// of triggering onPress events
onMoveShouldSetPanResponder: (_evt, gestureState) => {
return Math.abs(gestureState.dx) > 10 || Math.abs(gestureState.dy) > 10;
},
onPanResponderMove: (_e: any, gestureState: any) => {
handleNoteActionsDrag(gestureState);
},
});
// Note actions are the notesbar and split layout toggle button
const noteActionButtonGroupComp = (
<Animated.View style={this.styles().noteActionButtonGroup} {...noteActionsDragResponder.panHandlers} >
{/* Temporarily hiding the split layout button till it's implemented */}
{/* <TouchableOpacity style={[this.styles().noteActionButton, this.styles().noteActionButton1]} activeOpacity={0.7}>
<Icon name="columns" style={this.styles().noteActionButtonIcon} />
</TouchableOpacity> */}
<TouchableOpacity style={[notesBarToggleStyle, this.styles().noteActionButton2]} activeOpacity={0.7} onPress={this.onNotesBarToggle}>
<Icon name="list" style={notesBarToggleIconStyle} />
</TouchableOpacity>
</Animated.View>
);
const noteMainComp = (
<View style={this.styles().noteMainComp}>
<Animated.View style={this.styles().notesBarContainer}>
<NotesBar todoCheckbox_change={this.todoCheckbox_change} />
</Animated.View>
<Animated.View style={this.styles().noteComp}>
{titleComp}
{bodyComponent}
</Animated.View>
{ this.state.isTablet && noteActionButtonGroupComp }
</View>
);
return ( return (
<View style={this.rootStyle(this.props.themeId).root}> <View style={this.rootStyle(this.props.themeId).root}>
<ScreenHeader <ScreenHeader
@ -1472,7 +1228,8 @@ class NoteScreenComponent extends BaseScreenComponent {
onUndoButtonPress={this.screenHeader_undoButtonPress} onUndoButtonPress={this.screenHeader_undoButtonPress}
onRedoButtonPress={this.screenHeader_redoButtonPress} onRedoButtonPress={this.screenHeader_redoButtonPress}
/> />
{noteMainComp} {titleComp}
{bodyComponent}
{actionButtonComp} {actionButtonComp}
<SelectDateTimeDialog themeId={this.props.themeId} shown={this.state.alarmDialogShown} date={dueDate} onAccept={this.onAlarmDialogAccept} onReject={this.onAlarmDialogReject} /> <SelectDateTimeDialog themeId={this.props.themeId} shown={this.state.alarmDialogShown} date={dueDate} onAccept={this.onAlarmDialogAccept} onReject={this.onAlarmDialogReject} />
@ -1488,7 +1245,7 @@ class NoteScreenComponent extends BaseScreenComponent {
} }
} }
const NoteScreen = connect((state: State) => { const NoteScreen = connect((state: any) => {
return { return {
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null, noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
noteHash: state.selectedNoteHash, noteHash: state.selectedNoteHash,
@ -1504,7 +1261,6 @@ const NoteScreen = connect((state: State) => {
provisionalNoteIds: state.provisionalNoteIds, provisionalNoteIds: state.provisionalNoteIds,
highlightedWords: state.highlightedWords, highlightedWords: state.highlightedWords,
useEditorBeta: state.settings['editor.beta'], useEditorBeta: state.settings['editor.beta'],
showNotesBar: state.showMobileNotesBar,
}; };
})(NoteScreenComponent); })(NoteScreenComponent);

View File

@ -5,12 +5,13 @@ const { connect } = require('react-redux');
const { ScreenHeader } = require('../ScreenHeader'); const { ScreenHeader } = require('../ScreenHeader');
const Icon = require('react-native-vector-icons/Ionicons').default; const Icon = require('react-native-vector-icons/Ionicons').default;
const { _ } = require('@joplin/lib/locale'); const { _ } = require('@joplin/lib/locale');
const Note = require('@joplin/lib/models/Note').default;
const { NoteItem } = require('../note-item.js'); const { NoteItem } = require('../note-item.js');
const { BaseScreenComponent } = require('../base-screen.js'); const { BaseScreenComponent } = require('../base-screen.js');
const { themeStyle } = require('../global-style.js'); const { themeStyle } = require('../global-style.js');
const DialogBox = require('react-native-dialogbox').default; const DialogBox = require('react-native-dialogbox').default;
const SearchEngineUtils = require('@joplin/lib/services/searchengine/SearchEngineUtils').default;
const SearchEngine = require('@joplin/lib/services/searchengine/SearchEngine').default; const SearchEngine = require('@joplin/lib/services/searchengine/SearchEngine').default;
import searchNotes from '../searchNotes';
Icon.loadFont(); Icon.loadFont();
@ -100,7 +101,25 @@ class SearchScreenComponent extends BaseScreenComponent {
query = query === null ? this.state.query.trim : query.trim(); query = query === null ? this.state.query.trim : query.trim();
const notes = await searchNotes(query, this.props.settings['db.ftsEnabled'], this.props.dispatch); let notes = [];
if (query) {
if (this.props.settings['db.ftsEnabled']) {
notes = await SearchEngineUtils.notesForQuery(query, true);
} else {
const p = query.split(' ');
const temp = [];
for (let i = 0; i < p.length; i++) {
const t = p[i].trim();
if (!t) continue;
temp.push(t);
}
notes = await Note.previews(null, {
anywherePattern: `*${temp.join('*')}*`,
});
}
}
if (!this.isMounted_) return; if (!this.isMounted_) return;

View File

@ -1,42 +0,0 @@
import SearchEngineUtils from '@joplin/lib/services/searchengine/SearchEngineUtils';
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
import Note from '@joplin/lib/models/Note';
import { NoteEntity } from '@joplin/lib/services/database/types';
// Returns notes from a search query and sets highlighted words
// Be sure to use 'await' keyword or the function might not work properly
// Eg. await HandleNoteQuery();
const searchNotes = async (query: string, dbFtsEnabled: boolean, dispatch: (action: Object)=> void): Promise<NoteEntity[]> => {
let notes = [];
if (query) {
if (dbFtsEnabled) {
notes = await SearchEngineUtils.notesForQuery(query, true);
} else {
const p = query.split(' ');
const temp = [];
for (let i = 0; i < p.length; i++) {
const t = p[i].trim();
if (!t) continue;
temp.push(t);
}
notes = await Note.previews(null, {
anywherePattern: `*${temp.join('*')}*`,
});
}
const parsedQuery = await SearchEngine.instance().parseQuery(query);
const highlightedWords = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
dispatch({
type: 'SET_HIGHLIGHTED',
words: highlightedWords,
});
}
return notes;
};
export default searchNotes;

View File

@ -1,12 +0,0 @@
import { StyleSheet } from 'react-native';
import { Style } from './global-style';
import { themeStyle } from './global-style';
const useStyles = (stylingFunction: (theme: Style)=> Style, themeId?: number) => {
const theme = themeStyle(themeId);
const styles = stylingFunction(theme);
return StyleSheet.create(styles);
};
export default useStyles;

View File

@ -229,7 +229,7 @@ PODS:
- React - React
- react-native-get-random-values (1.7.1): - react-native-get-random-values (1.7.1):
- React-Core - React-Core
- react-native-image-picker (2.3.4): - react-native-image-picker (4.10.0):
- React-Core - React-Core
- react-native-image-resizer (1.4.5): - react-native-image-resizer (1.4.5):
- React-Core - React-Core
@ -533,7 +533,7 @@ SPEC CHECKSUMS:
react-native-document-picker: 20f652c2402d3ddc81f396d8167c3bd978add4a2 react-native-document-picker: 20f652c2402d3ddc81f396d8167c3bd978add4a2
react-native-geolocation: c956aeb136625c23e0dce0467664af2c437888c9 react-native-geolocation: c956aeb136625c23e0dce0467664af2c437888c9
react-native-get-random-values: 2c4ff6b44cb71291dabe9a8ae87d3877dcf387da react-native-get-random-values: 2c4ff6b44cb71291dabe9a8ae87d3877dcf387da
react-native-image-picker: c6d75c4ab2cf46f9289f341242b219cb3c1180d3 react-native-image-picker: 4bc9ed38c8be255b515d8c88babbaf74973f91a8
react-native-image-resizer: d9fb629a867335bdc13230ac2a58702bb8c8828f react-native-image-resizer: d9fb629a867335bdc13230ac2a58702bb8c8828f
react-native-netinfo: 3d3769f0d65de15c83a9bf1346f8be71de5a24bf react-native-netinfo: 3d3769f0d65de15c83a9bf1346f8be71de5a24bf
react-native-rsa-native: 1f6bba06dd02f0e652a66a384c75c270f7a0062f react-native-rsa-native: 1f6bba06dd02f0e652a66a384c75c270f7a0062f

View File

@ -15,9 +15,5 @@ module.exports = {
testPathIgnorePatterns: ['<rootDir>/node_modules/'], testPathIgnorePatterns: ['<rootDir>/node_modules/'],
'transformIgnorePatterns': [
'node_modules/(?!@codemirror)/',
],
slowTestThreshold: 40, slowTestThreshold: 40,
}; };

View File

@ -27,14 +27,11 @@
"@react-native-community/netinfo": "^6.0.0", "@react-native-community/netinfo": "^6.0.0",
"@react-native-community/push-notification-ios": "^1.6.0", "@react-native-community/push-notification-ios": "^1.6.0",
"@react-native-community/slider": "^3.0.3", "@react-native-community/slider": "^3.0.3",
"@types/react-test-renderer": "^18.0.0",
"assert-browserify": "^2.0.0", "assert-browserify": "^2.0.0",
"buffer": "^5.0.8", "buffer": "^5.0.8",
"constants-browserify": "^1.0.0", "constants-browserify": "^1.0.0",
"crypto-browserify": "^3.12.0", "crypto-browserify": "^3.12.0",
"events": "^3.2.0", "events": "^3.2.0",
"jest": "^28.1.3",
"jest-environment-jsdom": "^28.1.3",
"joplin-rn-alarm-notification": "^1.0.5", "joplin-rn-alarm-notification": "^1.0.5",
"jsc-android": "241213.1.0", "jsc-android": "241213.1.0",
"md5": "^2.2.1", "md5": "^2.2.1",
@ -92,21 +89,20 @@
"@codemirror/view": "^6.0.0", "@codemirror/view": "^6.0.0",
"@joplin/tools": "~2.9", "@joplin/tools": "~2.9",
"@lezer/highlight": "^1.0.0", "@lezer/highlight": "^1.0.0",
"@testing-library/react-native": "^11.0.0",
"@types/fs-extra": "^9.0.13", "@types/fs-extra": "^9.0.13",
"@types/jest": "^28.1.3", "@types/jest": "^28.1.3",
"@types/node": "^18.7.6",
"@types/react-native": "^0.64.4", "@types/react-native": "^0.64.4",
"@types/react-redux": "^7.1.24", "@types/react-redux": "^7.1.24",
"babel-plugin-module-resolver": "^4.1.0", "babel-plugin-module-resolver": "^4.1.0",
"execa": "^4.0.0", "execa": "^4.0.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"jest": "^28.1.1",
"jest-environment-jsdom": "^28.1.3",
"jetifier": "^1.6.5", "jetifier": "^1.6.5",
"jsdom": "^20.0.0", "jsdom": "^20.0.0",
"metro-react-native-babel-preset": "^0.66.2", "metro-react-native-babel-preset": "^0.66.2",
"nodemon": "^2.0.12", "nodemon": "^2.0.12",
"react-test-renderer": "17.0.2",
"ts-jest": "^28.0.5", "ts-jest": "^28.0.5",
"ts-loader": "^9.3.1", "ts-loader": "^9.3.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",

View File

@ -64,7 +64,7 @@ const { SearchScreen } = require('./components/screens/search.js');
const { OneDriveLoginScreen } = require('./components/screens/onedrive-login.js'); const { OneDriveLoginScreen } = require('./components/screens/onedrive-login.js');
import EncryptionConfigScreen from './components/screens/encryption-config'; import EncryptionConfigScreen from './components/screens/encryption-config';
const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js'); const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js');
import { MenuProvider } from 'react-native-popup-menu'; const { MenuContext } = require('react-native-popup-menu');
import SideMenu from './components/SideMenu'; import SideMenu from './components/SideMenu';
const { SideMenuContent } = require('./components/side-menu-content.js'); const { SideMenuContent } = require('./components/side-menu-content.js');
const { SideMenuContentNote } = require('./components/side-menu-content-note.js'); const { SideMenuContentNote } = require('./components/side-menu-content-note.js');
@ -75,7 +75,7 @@ const { FileApiDriverLocal } = require('@joplin/lib/file-api-driver-local');
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher'; import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine'; import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
const WelcomeUtils = require('@joplin/lib/WelcomeUtils'); const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
import { themeStyle } from './components/global-style'; const { themeStyle } = require('./components/global-style.js');
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js'); const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js');
const SyncTargetNextcloud = require('@joplin/lib/SyncTargetNextcloud.js'); const SyncTargetNextcloud = require('@joplin/lib/SyncTargetNextcloud.js');
@ -378,17 +378,6 @@ const appReducer = (state = appDefaultState, action: any) => {
newState.isOnMobileData = action.isOnMobileData; newState.isOnMobileData = action.isOnMobileData;
break; break;
case 'NOTES_BAR_OPEN':
newState = Object.assign({}, state);
newState.showMobileNotesBar = true;
break;
case 'NOTES_BAR_CLOSE':
newState = Object.assign({}, state);
newState.showMobileNotesBar = false;
break;
} }
} catch (error) { } catch (error) {
error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`; error.message = `In reducer: ${error.message} Action: ${JSON.stringify(action)}`;
@ -930,7 +919,7 @@ class AppComponent extends React.Component {
}} }}
> >
<StatusBar barStyle={statusBarStyle} /> <StatusBar barStyle={statusBarStyle} />
<MenuProvider style={{ flex: 1 }}> <MenuContext style={{ flex: 1 }}>
<SafeAreaView style={{ flex: 0, backgroundColor: theme.backgroundColor2 }}/> <SafeAreaView style={{ flex: 0, backgroundColor: theme.backgroundColor2 }}/>
<SafeAreaView style={{ flex: 1 }}> <SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}> <View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
@ -939,7 +928,7 @@ class AppComponent extends React.Component {
<DropdownAlert ref={(ref: any) => this.dropdownAlert_ = ref} tapToCloseEnabled={true} /> <DropdownAlert ref={(ref: any) => this.dropdownAlert_ = ref} tapToCloseEnabled={true} />
<Animated.View pointerEvents='none' style={{ position: 'absolute', backgroundColor: 'black', opacity: this.state.sideMenuContentOpacity, width: '100%', height: '120%' }}/> <Animated.View pointerEvents='none' style={{ position: 'absolute', backgroundColor: 'black', opacity: this.state.sideMenuContentOpacity, width: '100%', height: '120%' }}/>
</SafeAreaView> </SafeAreaView>
</MenuProvider> </MenuContext>
</SideMenu> </SideMenu>
</View> </View>
); );

View File

@ -13,7 +13,6 @@
"tools/*.ts", "tools/*.ts",
], ],
"compilerOptions": { "compilerOptions": {
"types": ["jest", "node", "react-test-renderer"], "types": ["jest", "node"]
"allowSyntheticDefaultImports": true
} }
} }

View File

@ -96,7 +96,6 @@ export interface State {
hasEncryptedItems: boolean; hasEncryptedItems: boolean;
needApiAuth: boolean; needApiAuth: boolean;
profileConfig: ProfileConfig; profileConfig: ProfileConfig;
showMobileNotesBar: boolean;
// Extra reducer keys go here: // Extra reducer keys go here:
pluginService: PluginServiceState; pluginService: PluginServiceState;
@ -157,7 +156,6 @@ export const defaultState: State = {
}, },
backwardHistoryNotes: [], backwardHistoryNotes: [],
forwardHistoryNotes: [], forwardHistoryNotes: [],
showMobileNotesBar: false,
// pluginsLegacy is the original plugin system, which eventually was used only for GotoAnything. // pluginsLegacy is the original plugin system, which eventually was used only for GotoAnything.
// GotoAnything should be refactored to part of core and when it's done the pluginsLegacy key can // GotoAnything should be refactored to part of core and when it's done the pluginsLegacy key can
// be removed. It was originally named "plugins", then renamed "pluginsLegacy" so as not to conflict // be removed. It was originally named "plugins", then renamed "pluginsLegacy" so as not to conflict

View File

@ -23,7 +23,7 @@ const themes: any = {
[Setting.THEME_OLED_DARK]: theme_oledDark, [Setting.THEME_OLED_DARK]: theme_oledDark,
}; };
export function themeById(themeId: number) { export function themeById(themeId: string) {
if (!themes[themeId]) throw new Error(`Invalid theme ID: ${themeId}`); if (!themes[themeId]) throw new Error(`Invalid theme ID: ${themeId}`);
const output = Object.assign({}, themes[themeId]); const output = Object.assign({}, themes[themeId]);

781
yarn.lock

File diff suppressed because it is too large Load Diff