You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Chore: Mobile: Migrate NoteItem and Checkbox to TypeScript (#11094)
				
					
				
			This commit is contained in:
		| @@ -548,6 +548,7 @@ packages/app-mobile/commands/util/showResource.js | ||||
| packages/app-mobile/components/BackButtonDialogBox.js | ||||
| packages/app-mobile/components/BetaChip.js | ||||
| packages/app-mobile/components/CameraView.js | ||||
| packages/app-mobile/components/Checkbox.js | ||||
| packages/app-mobile/components/DialogManager.js | ||||
| packages/app-mobile/components/DismissibleDialog.js | ||||
| packages/app-mobile/components/Dropdown.test.js | ||||
| @@ -611,6 +612,7 @@ packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.test.js | ||||
| packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.js | ||||
| packages/app-mobile/components/NoteEditor/hooks/useKeyboardVisible.js | ||||
| packages/app-mobile/components/NoteEditor/types.js | ||||
| packages/app-mobile/components/NoteItem.js | ||||
| packages/app-mobile/components/NoteList.js | ||||
| packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js | ||||
| packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js | ||||
|   | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -525,6 +525,7 @@ packages/app-mobile/commands/util/showResource.js | ||||
| packages/app-mobile/components/BackButtonDialogBox.js | ||||
| packages/app-mobile/components/BetaChip.js | ||||
| packages/app-mobile/components/CameraView.js | ||||
| packages/app-mobile/components/Checkbox.js | ||||
| packages/app-mobile/components/DialogManager.js | ||||
| packages/app-mobile/components/DismissibleDialog.js | ||||
| packages/app-mobile/components/Dropdown.test.js | ||||
| @@ -588,6 +589,7 @@ packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.test.js | ||||
| packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.js | ||||
| packages/app-mobile/components/NoteEditor/hooks/useKeyboardVisible.js | ||||
| packages/app-mobile/components/NoteEditor/types.js | ||||
| packages/app-mobile/components/NoteItem.js | ||||
| packages/app-mobile/components/NoteList.js | ||||
| packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js | ||||
| packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js | ||||
|   | ||||
							
								
								
									
										67
									
								
								packages/app-mobile/components/Checkbox.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								packages/app-mobile/components/Checkbox.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| import * as React from 'react'; | ||||
| import { useState, useEffect, useCallback, useMemo } from 'react'; | ||||
| import { TouchableHighlight, StyleSheet, TextStyle } from 'react-native'; | ||||
| const Icon = require('react-native-vector-icons/Ionicons').default; | ||||
|  | ||||
| interface Props { | ||||
| 	checked: boolean; | ||||
| 	accessibilityLabel?: string; | ||||
| 	onChange?: (checked: boolean)=> void; | ||||
| 	style?: TextStyle; | ||||
| 	iconStyle?: TextStyle; | ||||
| } | ||||
|  | ||||
| const useStyles = (baseStyles: TextStyle|undefined, iconStyle: TextStyle|undefined) => { | ||||
| 	return useMemo(() => { | ||||
| 		return StyleSheet.create({ | ||||
| 			container: { | ||||
| 				...(baseStyles ?? {}), | ||||
| 				justifyContent: 'center', | ||||
| 				alignItems: 'center', | ||||
| 			}, | ||||
| 			icon: { | ||||
| 				fontSize: 20, | ||||
| 				height: 22, | ||||
| 				color: baseStyles?.color, | ||||
| 				...iconStyle, | ||||
| 			}, | ||||
| 		}); | ||||
| 	}, [baseStyles, iconStyle]); | ||||
| }; | ||||
|  | ||||
| const Checkbox: React.FC<Props> = props => { | ||||
| 	const [checked, setChecked] = useState(props.checked); | ||||
|  | ||||
| 	useEffect(() => { | ||||
| 		setChecked(props.checked); | ||||
| 	}, [props.checked]); | ||||
|  | ||||
| 	const onPress = useCallback(() => { | ||||
| 		setChecked(checked => { | ||||
| 			const newChecked = !checked; | ||||
| 			props.onChange?.(newChecked); | ||||
| 			return newChecked; | ||||
| 		}); | ||||
| 	}, [props.onChange]); | ||||
|  | ||||
| 	const iconName = checked ? 'checkbox-outline' : 'square-outline'; | ||||
| 	const styles = useStyles(props.style, props.iconStyle); | ||||
|  | ||||
| 	const accessibilityState = useMemo(() => ({ | ||||
| 		checked, | ||||
| 	}), [checked]); | ||||
|  | ||||
| 	return ( | ||||
| 		<TouchableHighlight | ||||
| 			onPress={onPress} | ||||
| 			style={styles.container} | ||||
| 			accessibilityRole="checkbox" | ||||
| 			accessibilityState={accessibilityState} | ||||
| 			accessibilityLabel={props.accessibilityLabel ?? ''} | ||||
| 		> | ||||
| 			<Icon name={iconName} style={styles.icon} /> | ||||
| 		</TouchableHighlight> | ||||
| 	); | ||||
| }; | ||||
|  | ||||
| export default Checkbox; | ||||
| @@ -1,34 +1,40 @@ | ||||
| const React = require('react'); | ||||
| const Component = React.Component; | ||||
| const { connect } = require('react-redux'); | ||||
| const { Text, TouchableOpacity, View, StyleSheet } = require('react-native'); | ||||
| const { Checkbox } = require('./checkbox.js'); | ||||
| const Note = require('@joplin/lib/models/Note').default; | ||||
| const time = require('@joplin/lib/time').default; | ||||
| const { themeStyle } = require('./global-style'); | ||||
| const { _ } = require('@joplin/lib/locale'); | ||||
| import * as React from 'react'; | ||||
| import { PureComponent } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { Text, TouchableOpacity, View, StyleSheet, TextStyle, ViewStyle } from 'react-native'; | ||||
| import Checkbox from './Checkbox'; | ||||
| import Note from '@joplin/lib/models/Note'; | ||||
| import time from '@joplin/lib/time'; | ||||
| import { themeStyle } from './global-style'; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import { AppState } from '../utils/types'; | ||||
| import { Dispatch } from 'redux'; | ||||
| import { NoteEntity } from '@joplin/lib/services/database/types'; | ||||
| 
 | ||||
| class NoteItemComponent extends Component { | ||||
| 	constructor() { | ||||
| 		super(); | ||||
| 		this.styles_ = {}; | ||||
| interface Props { | ||||
| 	dispatch: Dispatch; | ||||
| 	themeId: number; | ||||
| 	note: NoteEntity; | ||||
| 	noteSelectionEnabled: boolean; | ||||
| 	selectedNoteIds: string[]; | ||||
| } | ||||
| 
 | ||||
| interface State {} | ||||
| 
 | ||||
| type Styles = Record<string, TextStyle|ViewStyle>; | ||||
| 
 | ||||
| class NoteItemComponent extends PureComponent<Props, State> { | ||||
| 	private styles_: Record<string, Styles> = {}; | ||||
| 	public constructor(props: Props) { | ||||
| 		super(props); | ||||
| 	} | ||||
| 
 | ||||
| 	noteItem_press(noteId) { | ||||
| 		this.props.dispatch({ | ||||
| 			type: 'NAV_GO', | ||||
| 			routeName: 'Note', | ||||
| 			noteId: noteId, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	styles() { | ||||
| 	private styles() { | ||||
| 		const theme = themeStyle(this.props.themeId); | ||||
| 
 | ||||
| 		if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId]; | ||||
| 		this.styles_ = {}; | ||||
| 
 | ||||
| 		const styles = { | ||||
| 		const styles: Record<string, TextStyle|ViewStyle> = { | ||||
| 			listItem: { | ||||
| 				flexDirection: 'row', | ||||
| 				// height: 40,
 | ||||
| @@ -49,6 +55,17 @@ class NoteItemComponent extends Component { | ||||
| 			selectionWrapper: { | ||||
| 				backgroundColor: theme.backgroundColor, | ||||
| 			}, | ||||
| 			checkboxStyle: { | ||||
| 				color: theme.color, | ||||
| 				paddingRight: 10, | ||||
| 				paddingTop: theme.itemMarginTop, | ||||
| 				paddingBottom: theme.itemMarginBottom, | ||||
| 				paddingLeft: theme.marginLeft, | ||||
| 			}, | ||||
| 			checkedOpacityStyle: { | ||||
| 				opacity: 0.4, | ||||
| 			}, | ||||
| 			uncheckedOpacityStyle: { }, | ||||
| 		}; | ||||
| 
 | ||||
| 		styles.listItemWithCheckbox = { ...styles.listItem }; | ||||
| @@ -57,7 +74,7 @@ class NoteItemComponent extends Component { | ||||
| 		delete styles.listItemWithCheckbox.paddingLeft; | ||||
| 
 | ||||
| 		styles.listItemTextWithCheckbox = { ...styles.listItemText }; | ||||
| 		styles.listItemTextWithCheckbox.marginTop = styles.listItem.paddingTop - 1; | ||||
| 		styles.listItemTextWithCheckbox.marginTop = theme.itemMarginTop - 1; | ||||
| 		styles.listItemTextWithCheckbox.marginBottom = styles.listItem.paddingBottom; | ||||
| 
 | ||||
| 		styles.selectionWrapperSelected = { ...styles.selectionWrapper }; | ||||
| @@ -67,7 +84,7 @@ class NoteItemComponent extends Component { | ||||
| 		return this.styles_[this.props.themeId]; | ||||
| 	} | ||||
| 
 | ||||
| 	async todoCheckbox_change(checked) { | ||||
| 	private todoCheckbox_change = async (checked: boolean) => { | ||||
| 		if (!this.props.note) return; | ||||
| 
 | ||||
| 		const newNote = { | ||||
| @@ -77,9 +94,9 @@ class NoteItemComponent extends Component { | ||||
| 		await Note.save(newNote); | ||||
| 
 | ||||
| 		this.props.dispatch({ type: 'NOTE_SORT' }); | ||||
| 	} | ||||
| 	}; | ||||
| 
 | ||||
| 	onPress() { | ||||
| 	private onPress = () => { | ||||
| 		if (!this.props.note) return; | ||||
| 		if (this.props.note.encryption_applied) return; | ||||
| 
 | ||||
| @@ -95,38 +112,26 @@ class NoteItemComponent extends Component { | ||||
| 				noteId: this.props.note.id, | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| 	}; | ||||
| 
 | ||||
| 	onLongPress() { | ||||
| 	private onLongPress = () => { | ||||
| 		if (!this.props.note) return; | ||||
| 
 | ||||
| 		this.props.dispatch({ | ||||
| 			type: this.props.noteSelectionEnabled ? 'NOTE_SELECTION_TOGGLE' : 'NOTE_SELECTION_START', | ||||
| 			id: this.props.note.id, | ||||
| 		}); | ||||
| 	} | ||||
| 	}; | ||||
| 
 | ||||
| 	render() { | ||||
| 	public render() { | ||||
| 		const note = this.props.note ? this.props.note : {}; | ||||
| 		const isTodo = !!Number(note.is_todo); | ||||
| 
 | ||||
| 		const theme = themeStyle(this.props.themeId); | ||||
| 
 | ||||
| 		// IOS: display: none crashes the app
 | ||||
| 		const checkboxStyle = !isTodo ? { display: 'none' } : { color: theme.color }; | ||||
| 
 | ||||
| 		if (isTodo) { | ||||
| 			checkboxStyle.paddingRight = 10; | ||||
| 			checkboxStyle.paddingTop = theme.itemMarginTop; | ||||
| 			checkboxStyle.paddingBottom = theme.itemMarginBottom; | ||||
| 			checkboxStyle.paddingLeft = theme.marginLeft; | ||||
| 		} | ||||
| 
 | ||||
| 		const checkboxChecked = !!Number(note.todo_completed); | ||||
| 
 | ||||
| 		const checkboxStyle = this.styles().checkboxStyle; | ||||
| 		const listItemStyle = isTodo ? this.styles().listItemWithCheckbox : this.styles().listItem; | ||||
| 		const listItemTextStyle = isTodo ? this.styles().listItemTextWithCheckbox : this.styles().listItemText; | ||||
| 		const opacityStyle = isTodo && checkboxChecked ? { opacity: 0.4 } : {}; | ||||
| 		const opacityStyle = isTodo && checkboxChecked ? this.styles().checkedOpacityStyle : this.styles().uncheckedOpacityStyle; | ||||
| 		const isSelected = this.props.noteSelectionEnabled && this.props.selectedNoteIds.indexOf(note.id) >= 0; | ||||
| 
 | ||||
| 		const selectionWrapperStyle = isSelected ? this.styles().selectionWrapperSelected : this.styles().selectionWrapper; | ||||
| @@ -134,16 +139,16 @@ class NoteItemComponent extends Component { | ||||
| 		const noteTitle = Note.displayTitle(note); | ||||
| 
 | ||||
| 		return ( | ||||
| 			<TouchableOpacity onPress={() => this.onPress()} onLongPress={() => this.onLongPress()} activeOpacity={0.5}> | ||||
| 			<TouchableOpacity onPress={this.onPress} onLongPress={this.onLongPress} activeOpacity={0.5}> | ||||
| 				<View style={selectionWrapperStyle}> | ||||
| 					<View style={opacityStyle}> | ||||
| 						<View style={listItemStyle}> | ||||
| 							<Checkbox | ||||
| 							{isTodo ? <Checkbox | ||||
| 								style={checkboxStyle} | ||||
| 								checked={checkboxChecked} | ||||
| 								onChange={checked => this.todoCheckbox_change(checked)} | ||||
| 								onChange={this.todoCheckbox_change} | ||||
| 								accessibilityLabel={_('to-do: %s', noteTitle)} | ||||
| 							/> | ||||
| 							/> : null } | ||||
| 							<Text style={listItemTextStyle}>{noteTitle}</Text> | ||||
| 						</View> | ||||
| 					</View> | ||||
| @@ -153,7 +158,7 @@ class NoteItemComponent extends Component { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| const NoteItem = connect(state => { | ||||
| export default connect((state: AppState) => { | ||||
| 	return { | ||||
| 		themeId: state.settings.theme, | ||||
| 		noteSelectionEnabled: state.noteSelectionEnabled, | ||||
| @@ -161,4 +166,3 @@ const NoteItem = connect(state => { | ||||
| 	}; | ||||
| })(NoteItemComponent); | ||||
| 
 | ||||
| module.exports = { NoteItem }; | ||||
| @@ -10,7 +10,7 @@ import getEmptyFolderMessage from '@joplin/lib/components/shared/NoteList/getEmp | ||||
| import Folder from '@joplin/lib/models/Folder'; | ||||
|  | ||||
| const { _ } = require('@joplin/lib/locale'); | ||||
| const { NoteItem } = require('./note-item.js'); | ||||
| import NoteItem from './NoteItem'; | ||||
| import { themeStyle } from './global-style'; | ||||
|  | ||||
| interface NoteListProps { | ||||
|   | ||||
| @@ -1,77 +0,0 @@ | ||||
| const React = require('react'); | ||||
| const Component = React.Component; | ||||
| const { View, TouchableHighlight } = require('react-native'); | ||||
| const Icon = require('react-native-vector-icons/Ionicons').default; | ||||
|  | ||||
| const styles = { | ||||
| 	checkboxIcon: { | ||||
| 		fontSize: 20, | ||||
| 		height: 22, | ||||
| 		// marginRight: 10, | ||||
| 	}, | ||||
| }; | ||||
|  | ||||
| class Checkbox extends Component { | ||||
| 	constructor() { | ||||
| 		super(); | ||||
| 		this.state = { | ||||
| 			checked: false, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| 	UNSAFE_componentWillMount() { | ||||
| 		this.setState({ checked: this.props.checked }); | ||||
| 	} | ||||
|  | ||||
| 	UNSAFE_componentWillReceiveProps(newProps) { | ||||
| 		if ('checked' in newProps) { | ||||
| 			this.setState({ checked: newProps.checked }); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	onPress() { | ||||
| 		const newChecked = !this.state.checked; | ||||
| 		this.setState({ checked: newChecked }); | ||||
| 		if (this.props.onChange) this.props.onChange(newChecked); | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		const iconName = this.state.checked ? 'checkbox-outline' : 'square-outline'; | ||||
|  | ||||
| 		const style = this.props.style ? { ...this.props.style } : {}; | ||||
| 		style.justifyContent = 'center'; | ||||
| 		style.alignItems = 'center'; | ||||
|  | ||||
| 		const checkboxIconStyle = { ...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> | ||||
| 		); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = { Checkbox }; | ||||
| @@ -26,7 +26,7 @@ import * as mimeUtils from '@joplin/lib/mime-utils'; | ||||
| import ScreenHeader, { MenuOptionType } from '../ScreenHeader'; | ||||
| import NoteTagsDialog from './NoteTagsDialog'; | ||||
| import time from '@joplin/lib/time'; | ||||
| const { Checkbox } = require('../checkbox.js'); | ||||
| import Checkbox from '../Checkbox'; | ||||
| import { _, currentLocale } from '@joplin/lib/locale'; | ||||
| import { reg } from '@joplin/lib/registry'; | ||||
| import ResourceFetcher from '@joplin/lib/services/ResourceFetcher'; | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import ScreenHeader from '../ScreenHeader'; | ||||
| const Icon = require('react-native-vector-icons/Ionicons').default; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import Note from '@joplin/lib/models/Note'; | ||||
| const { NoteItem } = require('../note-item.js'); | ||||
| import NoteItem from '../NoteItem'; | ||||
| const { BaseScreenComponent } = require('../base-screen'); | ||||
| import { themeStyle } from '../global-style'; | ||||
| const DialogBox = require('react-native-dialogbox').default; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user