mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-27 10:32:58 +02:00
294 lines
7.2 KiB
TypeScript
294 lines
7.2 KiB
TypeScript
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;
|