2022-04-14 17:50:42 +02:00
import * as React from 'react' ;
import { useMemo , useEffect , useState , useRef , useCallback } from 'react' ;
2021-09-04 19:11:29 +02:00
import { AppState } from '../../app.reducer' ;
2020-11-07 17:59:37 +02:00
import eventManager from '@joplin/lib/eventManager' ;
2020-10-09 19:35:46 +02:00
import NoteListUtils from '../utils/NoteListUtils' ;
2020-11-07 17:59:37 +02:00
import { _ } from '@joplin/lib/locale' ;
2020-11-17 13:50:46 +02:00
import time from '@joplin/lib/time' ;
2023-07-16 18:42:42 +02:00
import BaseModel , { ModelType } from '@joplin/lib/BaseModel' ;
2020-11-17 13:50:46 +02:00
import bridge from '../../services/bridge' ;
import Setting from '@joplin/lib/models/Setting' ;
import NoteListItem from '../NoteListItem' ;
2021-01-22 19:41:11 +02:00
import CommandService from '@joplin/lib/services/CommandService' ;
2020-11-17 13:50:46 +02:00
import shim from '@joplin/lib/shim' ;
import styled from 'styled-components' ;
import { themeStyle } from '@joplin/lib/theme' ;
2023-01-19 19:19:06 +02:00
import ItemList from '../ItemList' ;
2017-11-05 02:17:48 +02:00
const { connect } = require ( 'react-redux' ) ;
2021-01-22 19:41:11 +02:00
import Note from '@joplin/lib/models/Note' ;
import Folder from '@joplin/lib/models/Folder' ;
2023-08-21 17:01:20 +02:00
import { Props } from './utils/types' ;
2022-04-14 17:50:42 +02:00
import usePrevious from '../hooks/usePrevious' ;
2023-07-16 18:42:42 +02:00
import { itemIsReadOnlySync , ItemSlice } from '@joplin/lib/models/utils/readOnly' ;
import { FolderEntity } from '@joplin/lib/services/database/types' ;
import ItemChange from '@joplin/lib/models/ItemChange' ;
2020-07-03 23:32:39 +02:00
const commands = [
require ( './commands/focusElementNoteList' ) ,
] ;
2017-11-04 18:40:34 +02:00
2020-09-15 15:01:07 +02:00
const StyledRoot = styled . div `
width : 100 % ;
height : 100 % ;
2020-11-12 21:13:28 +02:00
background - color : $ { ( props : any ) = > props . theme . backgroundColor3 } ;
border - right : 1px solid $ { ( props : any ) = > props . theme . dividerColor } ;
2020-09-15 15:01:07 +02:00
` ;
2022-04-14 17:50:42 +02:00
const itemAnchorRefs_ : any = {
current : { } ,
} ;
2018-11-21 21:50:50 +02:00
2022-04-14 17:50:42 +02:00
export const itemAnchorRef = ( itemId : string ) = > {
if ( itemAnchorRefs_ . current [ itemId ] && itemAnchorRefs_ . current [ itemId ] . current ) return itemAnchorRefs_ . current [ itemId ] . current ;
return null ;
} ;
2020-07-03 23:32:39 +02:00
2022-04-14 17:50:42 +02:00
const NoteListComponent = ( props : Props ) = > {
const [ dragOverTargetNoteIndex , setDragOverTargetNoteIndex ] = useState ( null ) ;
const [ width , setWidth ] = useState ( 0 ) ;
const [ , setHeight ] = useState ( 0 ) ;
2020-05-27 18:21:46 +02:00
2022-04-14 17:50:42 +02:00
useEffect ( ( ) = > {
itemAnchorRefs_ . current = { } ;
CommandService . instance ( ) . registerCommands ( commands ) ;
return ( ) = > {
itemAnchorRefs_ . current = { } ;
CommandService . instance ( ) . unregisterCommands ( commands ) ;
2020-05-27 18:21:46 +02:00
} ;
2022-04-14 17:50:42 +02:00
} , [ ] ) ;
2022-06-08 11:33:06 +02:00
const [ itemHeight , setItemHeight ] = useState ( 34 ) ;
2020-05-27 18:21:46 +02:00
2022-04-14 17:50:42 +02:00
const focusItemIID_ = useRef < any > ( null ) ;
const noteListRef = useRef ( null ) ;
const itemListRef = useRef ( null ) ;
let globalDragEndEventRegistered_ = false ;
const style = useMemo ( ( ) = > {
const theme = themeStyle ( props . themeId ) ;
return {
2017-11-09 21:21:10 +02:00
root : {
backgroundColor : theme.backgroundColor ,
} ,
listItem : {
2020-05-27 18:21:46 +02:00
maxWidth : '100%' ,
2022-04-14 17:50:42 +02:00
height : itemHeight ,
2017-11-09 21:21:10 +02:00
boxSizing : 'border-box' ,
display : 'flex' ,
2017-11-10 22:11:48 +02:00
alignItems : 'stretch' ,
2017-11-09 21:21:10 +02:00
backgroundColor : theme.backgroundColor ,
2019-09-19 23:51:18 +02:00
borderBottom : ` 1px solid ${ theme . dividerColor } ` ,
2017-11-09 21:21:10 +02:00
} ,
listItemSelected : {
backgroundColor : theme.selectedColor ,
} ,
2017-11-10 22:11:48 +02:00
listItemTitle : {
fontFamily : theme.fontFamily ,
fontSize : theme.fontSize ,
textDecoration : 'none' ,
color : theme.color ,
cursor : 'default' ,
whiteSpace : 'nowrap' ,
flex : 1 ,
display : 'flex' ,
alignItems : 'center' ,
overflow : 'hidden' ,
} ,
2017-11-10 22:12:38 +02:00
listItemTitleCompleted : {
opacity : 0.5 ,
textDecoration : 'line-through' ,
} ,
2017-11-09 21:21:10 +02:00
} ;
2022-04-14 17:50:42 +02:00
} , [ props . themeId , itemHeight ] ) ;
2017-11-09 21:21:10 +02:00
2022-04-14 17:50:42 +02:00
const itemContextMenu = useCallback ( ( event : any ) = > {
2018-01-09 22:16:09 +02:00
const currentItemId = event . currentTarget . getAttribute ( 'data-id' ) ;
if ( ! currentItemId ) return ;
let noteIds = [ ] ;
2022-04-14 17:50:42 +02:00
if ( props . selectedNoteIds . indexOf ( currentItemId ) < 0 ) {
2018-01-09 22:16:09 +02:00
noteIds = [ currentItemId ] ;
} else {
2022-04-14 17:50:42 +02:00
noteIds = props . selectedNoteIds ;
2018-01-09 22:16:09 +02:00
}
2017-11-22 20:35:31 +02:00
if ( ! noteIds . length ) return ;
2017-11-08 19:51:55 +02:00
2019-01-29 20:02:34 +02:00
const menu = NoteListUtils . makeContextMenu ( noteIds , {
2022-04-14 17:50:42 +02:00
notes : props.notes ,
dispatch : props.dispatch ,
watchedNoteFiles : props.watchedNoteFiles ,
plugins : props.plugins ,
inConflictFolder : props.selectedFolderId === Folder . conflictFolderId ( ) ,
customCss : props.customCss ,
2019-01-29 20:02:34 +02:00
} ) ;
2017-11-10 22:34:36 +02:00
2023-02-22 20:15:21 +02:00
menu . popup ( { window : bridge ( ) . window ( ) } ) ;
2023-01-11 20:37:22 +02:00
} , [ props . selectedNoteIds , props . notes , props . dispatch , props . watchedNoteFiles , props . plugins , props . selectedFolderId , props . customCss ] ) ;
2017-11-08 19:51:55 +02:00
2022-04-14 17:50:42 +02:00
const onGlobalDrop_ = ( ) = > {
unregisterGlobalDragEndEvent_ ( ) ;
setDragOverTargetNoteIndex ( null ) ;
} ;
2018-11-21 21:50:50 +02:00
2022-04-14 17:50:42 +02:00
const registerGlobalDragEndEvent_ = ( ) = > {
if ( globalDragEndEventRegistered_ ) return ;
globalDragEndEventRegistered_ = true ;
document . addEventListener ( 'dragend' , onGlobalDrop_ ) ;
} ;
2017-11-05 01:27:13 +02:00
2022-04-14 17:50:42 +02:00
const unregisterGlobalDragEndEvent_ = ( ) = > {
globalDragEndEventRegistered_ = false ;
document . removeEventListener ( 'dragend' , onGlobalDrop_ ) ;
} ;
2018-06-10 02:27:20 +02:00
2022-04-14 17:50:42 +02:00
const dragTargetNoteIndex_ = ( event : any ) = > {
return Math . abs ( Math . round ( ( event . clientY - itemListRef . current . offsetTop ( ) + itemListRef . current . offsetScroll ( ) ) / itemHeight ) ) ;
} ;
2018-06-10 02:27:20 +02:00
2022-04-14 17:50:42 +02:00
const noteItem_noteDragOver = ( event : any ) = > {
if ( props . notesParentType !== 'Folder' ) return ;
2019-07-29 14:13:23 +02:00
2020-05-27 18:21:46 +02:00
const dt = event . dataTransfer ;
2017-11-22 21:20:19 +02:00
2020-05-27 18:21:46 +02:00
if ( dt . types . indexOf ( 'text/x-jop-note-ids' ) >= 0 ) {
event . preventDefault ( ) ;
2022-04-14 17:50:42 +02:00
const newIndex = dragTargetNoteIndex_ ( event ) ;
if ( dragOverTargetNoteIndex === newIndex ) return ;
registerGlobalDragEndEvent_ ( ) ;
setDragOverTargetNoteIndex ( newIndex ) ;
2020-05-27 18:21:46 +02:00
}
2022-04-14 17:50:42 +02:00
} ;
2017-11-10 22:11:48 +02:00
2023-02-21 12:55:17 +02:00
const canManuallySortNotes = async ( ) = > {
if ( props . notesParentType !== 'Folder' ) return false ;
2017-11-10 22:11:48 +02:00
2022-04-14 17:50:42 +02:00
if ( props . noteSortOrder !== 'order' ) {
2020-05-27 18:21:46 +02:00
const doIt = await bridge ( ) . showConfirmMessageBox ( _ ( 'To manually sort the notes, the sort order must be changed to "%s" in the menu "%s" > "%s"' , _ ( 'Custom order' ) , _ ( 'View' ) , _ ( 'Sort notes by' ) ) , {
buttons : [ _ ( 'Do it now' ) , _ ( 'Cancel' ) ] ,
} ) ;
2023-02-21 12:55:17 +02:00
if ( ! doIt ) return false ;
2020-05-27 18:21:46 +02:00
Setting . setValue ( 'notes.sortOrder.field' , 'order' ) ;
2023-02-21 12:55:17 +02:00
return false ;
2018-03-20 01:04:48 +02:00
}
2023-02-21 12:55:17 +02:00
return true ;
} ;
2018-03-20 01:04:48 +02:00
2023-02-21 12:55:17 +02:00
const noteItem_noteDrop = async ( event : any ) = > {
2018-01-12 21:58:01 +02:00
2023-02-21 12:55:17 +02:00
// TODO: check that parent type is folder
if ( ! canManuallySortNotes ( ) ) {
return ;
}
2020-05-27 18:21:46 +02:00
const dt = event . dataTransfer ;
2022-04-14 17:50:42 +02:00
unregisterGlobalDragEndEvent_ ( ) ;
setDragOverTargetNoteIndex ( null ) ;
2017-11-08 19:51:55 +02:00
2022-04-14 17:50:42 +02:00
const targetNoteIndex = dragTargetNoteIndex_ ( event ) ;
2023-07-16 18:42:42 +02:00
const noteIds : string [ ] = JSON . parse ( dt . getData ( 'text/x-jop-note-ids' ) ) ;
2018-12-14 00:57:14 +02:00
2023-02-20 15:23:26 +02:00
void Note . insertNotesAt ( props . selectedFolderId , noteIds , targetNoteIndex , props . uncompletedTodosOnTop , props . showCompletedTodos ) ;
2022-04-14 17:50:42 +02:00
} ;
2018-12-14 00:57:14 +02:00
2022-04-14 17:50:42 +02:00
const noteItem_checkboxClick = async ( event : any , item : any ) = > {
2020-05-27 18:21:46 +02:00
const checked = event . target . checked ;
const newNote = {
id : item.id ,
todo_completed : checked ? time . unixMs ( ) : 0 ,
} ;
await Note . save ( newNote , { userSideValidation : true } ) ;
eventManager . emit ( 'todoToggle' , { noteId : item.id , note : newNote } ) ;
2022-04-14 17:50:42 +02:00
} ;
2020-05-27 18:21:46 +02:00
2022-04-14 17:50:42 +02:00
const noteItem_titleClick = async ( event : any , item : any ) = > {
2020-05-27 18:21:46 +02:00
if ( event . ctrlKey || event . metaKey ) {
event . preventDefault ( ) ;
2022-04-14 17:50:42 +02:00
props . dispatch ( {
2020-05-27 18:21:46 +02:00
type : 'NOTE_SELECT_TOGGLE' ,
id : item.id ,
} ) ;
} else if ( event . shiftKey ) {
event . preventDefault ( ) ;
2022-04-14 17:50:42 +02:00
props . dispatch ( {
2020-05-27 18:21:46 +02:00
type : 'NOTE_SELECT_EXTEND' ,
id : item.id ,
} ) ;
} else {
2022-04-14 17:50:42 +02:00
props . dispatch ( {
2020-05-27 18:21:46 +02:00
type : 'NOTE_SELECT' ,
id : item.id ,
} ) ;
}
2022-04-14 17:50:42 +02:00
} ;
2020-05-27 18:21:46 +02:00
2023-07-16 18:42:42 +02:00
const noteItem_dragStart = useCallback ( ( event : any ) = > {
if ( props . parentFolderIsReadOnly ) return false ;
2020-05-27 18:21:46 +02:00
let noteIds = [ ] ;
2018-03-20 01:04:48 +02:00
2020-05-27 18:21:46 +02:00
// Here there is two cases:
// - If multiple notes are selected, we drag the group
// - If only one note is selected, we drag the note that was clicked on (which might be different from the currently selected note)
2022-04-14 17:50:42 +02:00
if ( props . selectedNoteIds . length >= 2 ) {
noteIds = props . selectedNoteIds ;
2018-03-20 01:04:48 +02:00
} else {
2020-05-27 18:21:46 +02:00
const clickedNoteId = event . currentTarget . getAttribute ( 'data-id' ) ;
if ( clickedNoteId ) noteIds . push ( clickedNoteId ) ;
2018-03-20 01:04:48 +02:00
}
2023-07-16 18:42:42 +02:00
if ( ! noteIds . length ) return false ;
2020-05-27 18:21:46 +02:00
event . dataTransfer . setDragImage ( new Image ( ) , 1 , 1 ) ;
event . dataTransfer . clearData ( ) ;
event . dataTransfer . setData ( 'text/x-jop-note-ids' , JSON . stringify ( noteIds ) ) ;
2023-07-29 17:32:52 +02:00
// While setting
// event.dataTransfer.effectAllowed = 'move';
// causes the drag cursor to have a "move", rather than an "add", icon,
// this breaks note drag and drop into the markdown editor.
2023-07-16 18:42:42 +02:00
return true ;
} , [ props . parentFolderIsReadOnly , props . selectedNoteIds ] ) ;
2020-05-27 18:21:46 +02:00
2022-04-14 17:50:42 +02:00
const renderItem = useCallback ( ( item : any , index : number ) = > {
2020-05-27 18:21:46 +02:00
const highlightedWords = ( ) = > {
2022-04-14 17:50:42 +02:00
if ( props . notesParentType === 'Search' ) {
const query = BaseModel . byId ( props . searches , props . selectedSearchId ) ;
2020-05-27 18:21:46 +02:00
if ( query ) {
2022-04-14 17:50:42 +02:00
return props . highlightedWords ;
2020-05-27 18:21:46 +02:00
}
}
return [ ] ;
2018-11-21 21:50:50 +02:00
} ;
2022-04-14 17:50:42 +02:00
if ( ! itemAnchorRefs_ . current [ item . id ] ) itemAnchorRefs_ . current [ item . id ] = React . createRef ( ) ;
const ref = itemAnchorRefs_ . current [ item . id ] ;
2019-01-25 21:59:36 +02:00
2020-05-27 18:21:46 +02:00
return < NoteListItem
ref = { ref }
key = { item . id }
2022-04-14 17:50:42 +02:00
style = { style }
2020-05-27 18:21:46 +02:00
item = { item }
index = { index }
2022-04-14 17:50:42 +02:00
themeId = { props . themeId }
width = { width }
height = { itemHeight }
dragItemIndex = { dragOverTargetNoteIndex }
2020-05-27 18:21:46 +02:00
highlightedWords = { highlightedWords ( ) }
2022-04-14 17:50:42 +02:00
isProvisional = { props . provisionalNoteIds . includes ( item . id ) }
isSelected = { props . selectedNoteIds . indexOf ( item . id ) >= 0 }
isWatched = { props . watchedNoteFiles . indexOf ( item . id ) < 0 }
itemCount = { props . notes . length }
onCheckboxClick = { noteItem_checkboxClick }
onDragStart = { noteItem_dragStart }
onNoteDragOver = { noteItem_noteDragOver }
onTitleClick = { noteItem_titleClick }
onContextMenu = { itemContextMenu }
2023-07-16 18:42:42 +02:00
draggable = { ! props . parentFolderIsReadOnly }
2020-05-27 18:21:46 +02:00
/ > ;
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2022-04-14 17:50:42 +02:00
} , [ style , props . themeId , width , itemHeight , dragOverTargetNoteIndex , props . provisionalNoteIds , props . selectedNoteIds , props . watchedNoteFiles ,
props . notes ,
props . notesParentType ,
props . searches ,
props . selectedSearchId ,
props . highlightedWords ,
2023-07-16 18:42:42 +02:00
props . parentFolderIsReadOnly ,
2022-04-14 17:50:42 +02:00
] ) ;
const previousSelectedNoteIds = usePrevious ( props . selectedNoteIds , [ ] ) ;
const previousNotes = usePrevious ( props . notes , [ ] ) ;
const previousVisible = usePrevious ( props . visible , false ) ;
useEffect ( ( ) = > {
if ( previousSelectedNoteIds !== props . selectedNoteIds && props . selectedNoteIds . length === 1 ) {
const id = props . selectedNoteIds [ 0 ] ;
2023-03-13 14:18:06 +02:00
const doRefocus = props . notes . length < previousNotes . length && ! props . focusedField ;
2022-04-14 17:50:42 +02:00
for ( let i = 0 ; i < props . notes . length ; i ++ ) {
if ( props . notes [ i ] . id === id ) {
itemListRef . current . makeItemIndexVisible ( i ) ;
2020-03-11 19:08:35 +02:00
if ( doRefocus ) {
2022-04-14 17:50:42 +02:00
const ref = itemAnchorRef ( id ) ;
2020-03-11 19:08:35 +02:00
if ( ref ) ref . focus ( ) ;
}
2019-01-29 20:32:52 +02:00
break ;
2019-07-29 14:13:23 +02:00
}
2019-01-29 20:32:52 +02:00
}
}
2020-09-15 15:01:07 +02:00
2022-04-14 17:50:42 +02:00
if ( previousVisible !== props . visible ) {
updateSizeState ( ) ;
2020-09-15 15:01:07 +02:00
}
2023-03-13 14:18:06 +02:00
} , [ previousSelectedNoteIds , previousNotes , previousVisible , props . selectedNoteIds , props . notes , props . focusedField , props . visible ] ) ;
2019-01-26 20:04:32 +02:00
2022-04-14 17:50:42 +02:00
const scrollNoteIndex_ = ( keyCode : any , ctrlKey : any , metaKey : any , noteIndex : any ) = > {
2020-02-06 11:38:33 +02:00
if ( keyCode === 33 ) {
// Page Up
2022-04-14 17:50:42 +02:00
noteIndex -= ( itemListRef . current . visibleItemCount ( ) - 1 ) ;
2020-02-06 11:38:33 +02:00
} else if ( keyCode === 34 ) {
// Page Down
2022-04-14 17:50:42 +02:00
noteIndex += ( itemListRef . current . visibleItemCount ( ) - 1 ) ;
2020-02-06 11:38:33 +02:00
} else if ( ( keyCode === 35 && ctrlKey ) || ( keyCode === 40 && metaKey ) ) {
// CTRL+End, CMD+Down
2022-04-14 17:50:42 +02:00
noteIndex = props . notes . length - 1 ;
2020-02-06 11:38:33 +02:00
} else if ( ( keyCode === 36 && ctrlKey ) || ( keyCode === 38 && metaKey ) ) {
// CTRL+Home, CMD+Up
noteIndex = 0 ;
} else if ( keyCode === 38 && ! metaKey ) {
// Up
noteIndex -= 1 ;
} else if ( keyCode === 40 && ! metaKey ) {
// Down
noteIndex += 1 ;
}
if ( noteIndex < 0 ) noteIndex = 0 ;
2022-04-14 17:50:42 +02:00
if ( noteIndex > props . notes . length - 1 ) noteIndex = props . notes . length - 1 ;
2020-02-06 11:38:33 +02:00
return noteIndex ;
2022-04-14 17:50:42 +02:00
} ;
2020-02-06 11:38:33 +02:00
2023-02-21 12:55:17 +02:00
const noteItem_noteMove = async ( direction : number ) = > {
if ( ! canManuallySortNotes ( ) ) {
return ;
}
const noteIds = props . selectedNoteIds ;
const noteId = noteIds [ 0 ] ;
let targetNoteIndex = BaseModel . modelIndexById ( props . notes , noteId ) ;
if ( ( direction === 1 ) ) {
targetNoteIndex += 2 ;
}
if ( ( direction === - 1 ) ) {
targetNoteIndex -= 1 ;
}
2023-02-21 17:28:00 +02:00
void Note . insertNotesAt ( props . selectedFolderId , noteIds , targetNoteIndex , props . uncompletedTodosOnTop , props . showCompletedTodos ) ;
2023-02-21 12:55:17 +02:00
} ;
2022-04-14 17:50:42 +02:00
const onKeyDown = async ( event : any ) = > {
2019-01-25 21:59:36 +02:00
const keyCode = event . keyCode ;
2022-04-14 17:50:42 +02:00
const noteIds = props . selectedNoteIds ;
2019-01-26 17:15:16 +02:00
2023-02-21 12:55:17 +02:00
if ( ( keyCode === 40 || keyCode === 38 ) && event . altKey ) {
// (DOWN / UP) & ALT
2023-02-21 17:28:00 +02:00
await noteItem_noteMove ( keyCode === 40 ? 1 : - 1 ) ;
2023-02-21 12:55:17 +02:00
event . preventDefault ( ) ;
} else if ( noteIds . length > 0 && ( keyCode === 40 || keyCode === 38 || keyCode === 33 || keyCode === 34 || keyCode === 35 || keyCode === 36 ) ) {
2020-02-06 11:38:33 +02:00
// DOWN / UP / PAGEDOWN / PAGEUP / END / HOME
2019-01-25 21:59:36 +02:00
const noteId = noteIds [ 0 ] ;
2022-04-14 17:50:42 +02:00
let noteIndex = BaseModel . modelIndexById ( props . notes , noteId ) ;
2019-01-25 21:59:36 +02:00
2022-04-14 17:50:42 +02:00
noteIndex = scrollNoteIndex_ ( keyCode , event . ctrlKey , event . metaKey , noteIndex ) ;
2019-01-25 21:59:36 +02:00
2022-04-14 17:50:42 +02:00
const newSelectedNote = props . notes [ noteIndex ] ;
2019-01-25 21:59:36 +02:00
2022-04-14 17:50:42 +02:00
props . dispatch ( {
2019-01-25 21:59:36 +02:00
type : 'NOTE_SELECT' ,
id : newSelectedNote.id ,
} ) ;
2022-04-14 17:50:42 +02:00
itemListRef . current . makeItemIndexVisible ( noteIndex ) ;
2019-01-25 21:59:36 +02:00
2022-04-14 17:50:42 +02:00
focusNoteId_ ( newSelectedNote . id ) ;
2019-01-25 21:59:36 +02:00
event . preventDefault ( ) ;
}
2019-01-26 17:15:16 +02:00
2019-07-29 14:13:23 +02:00
if ( noteIds . length && ( keyCode === 46 || ( keyCode === 8 && event . metaKey ) ) ) {
// DELETE / CMD+Backspace
2019-01-26 17:15:16 +02:00
event . preventDefault ( ) ;
2023-07-16 18:42:42 +02:00
void CommandService . instance ( ) . execute ( 'deleteNote' , noteIds ) ;
// await NoteListUtils.confirmDeleteNotes(noteIds);
2019-01-26 17:15:16 +02:00
}
2019-01-26 17:33:45 +02:00
2019-07-29 14:13:23 +02:00
if ( noteIds . length && keyCode === 32 ) {
// SPACE
2019-01-26 17:33:45 +02:00
event . preventDefault ( ) ;
2022-04-14 17:50:42 +02:00
const notes = BaseModel . modelsByIds ( props . notes , noteIds ) ;
2020-11-12 21:13:28 +02:00
const todos = notes . filter ( ( n : any ) = > ! ! n . is_todo ) ;
2019-01-26 17:33:45 +02:00
if ( ! todos . length ) return ;
for ( let i = 0 ; i < todos . length ; i ++ ) {
const toggledTodo = Note . toggleTodoCompleted ( todos [ i ] ) ;
await Note . save ( toggledTodo ) ;
}
2022-04-14 17:50:42 +02:00
focusNoteId_ ( todos [ 0 ] . id ) ;
2019-01-26 17:33:45 +02:00
}
2019-01-26 20:04:32 +02:00
2019-07-29 14:13:23 +02:00
if ( keyCode === 9 ) {
// TAB
2019-01-26 20:04:32 +02:00
event . preventDefault ( ) ;
if ( event . shiftKey ) {
2020-11-25 16:40:25 +02:00
void CommandService . instance ( ) . execute ( 'focusElement' , 'sideBar' ) ;
2019-01-26 20:04:32 +02:00
} else {
2020-11-25 16:40:25 +02:00
void CommandService . instance ( ) . execute ( 'focusElement' , 'noteTitle' ) ;
2019-01-26 20:04:32 +02:00
}
}
2020-02-04 23:55:06 +02:00
if ( event . keyCode === 65 && ( event . ctrlKey || event . metaKey ) ) {
// Ctrl+A key
event . preventDefault ( ) ;
2022-04-14 17:50:42 +02:00
props . dispatch ( {
2020-02-04 23:55:06 +02:00
type : 'NOTE_SELECT_ALL' ,
} ) ;
}
2022-04-14 17:50:42 +02:00
} ;
2019-01-26 17:33:45 +02:00
2022-04-14 17:50:42 +02:00
const focusNoteId_ = ( noteId : string ) = > {
2019-01-26 17:33:45 +02:00
// - We need to focus the item manually otherwise focus might be lost when the
// list is scrolled and items within it are being rebuilt.
// - We need to use an interval because when leaving the arrow pressed, the rendering
// of items might lag behind and so the ref is not yet available at this point.
2022-04-14 17:50:42 +02:00
if ( ! itemAnchorRef ( noteId ) ) {
if ( focusItemIID_ . current ) shim . clearInterval ( focusItemIID_ . current ) ;
focusItemIID_ . current = shim . setInterval ( ( ) = > {
if ( itemAnchorRef ( noteId ) ) {
itemAnchorRef ( noteId ) . focus ( ) ;
shim . clearInterval ( focusItemIID_ . current ) ;
focusItemIID_ . current = null ;
2019-01-26 17:33:45 +02:00
}
} , 10 ) ;
} else {
2022-04-14 17:50:42 +02:00
itemAnchorRef ( noteId ) . focus ( ) ;
2019-01-26 17:33:45 +02:00
}
2022-04-14 17:50:42 +02:00
} ;
2020-09-15 15:01:07 +02:00
2022-04-14 17:50:42 +02:00
const updateSizeState = ( ) = > {
setWidth ( noteListRef . current . clientWidth ) ;
setHeight ( noteListRef . current . clientHeight ) ;
} ;
2020-09-15 15:01:07 +02:00
2022-04-14 17:50:42 +02:00
const resizableLayout_resize = ( ) = > {
updateSizeState ( ) ;
} ;
2020-09-15 15:01:07 +02:00
2022-04-14 17:50:42 +02:00
useEffect ( ( ) = > {
props . resizableLayoutEventEmitter . on ( 'resize' , resizableLayout_resize ) ;
return ( ) = > {
props . resizableLayoutEventEmitter . off ( 'resize' , resizableLayout_resize ) ;
} ;
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2022-04-14 17:50:42 +02:00
} , [ props . resizableLayoutEventEmitter ] ) ;
2020-07-03 23:32:39 +02:00
2022-04-14 17:50:42 +02:00
useEffect ( ( ) = > {
updateSizeState ( ) ;
2020-09-15 15:01:07 +02:00
2022-04-14 17:50:42 +02:00
return ( ) = > {
if ( focusItemIID_ . current ) {
shim . clearInterval ( focusItemIID_ . current ) ;
focusItemIID_ . current = null ;
}
CommandService . instance ( ) . componentUnregisterCommands ( commands ) ;
} ;
} , [ ] ) ;
2019-01-25 21:59:36 +02:00
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2022-06-08 11:33:06 +02:00
useEffect ( ( ) = > {
// When a note list item is styled by userchrome.css, its height is reflected.
// Ref. https://github.com/laurent22/joplin/pull/6542
2022-07-10 16:10:08 +02:00
if ( dragOverTargetNoteIndex !== null ) {
// When dragged, its height should not be considered.
// Ref. https://github.com/laurent22/joplin/issues/6639
return ;
}
2022-06-08 11:33:06 +02:00
const noteItem = Object . values < any > ( itemAnchorRefs_ . current ) [ 0 ] ? . current ;
const actualItemHeight = noteItem ? . getHeight ( ) ? ? 0 ;
if ( actualItemHeight >= 8 ) { // To avoid generating too many narrow items
setItemHeight ( actualItemHeight ) ;
}
} ) ;
2022-04-14 17:50:42 +02:00
const renderEmptyList = ( ) = > {
if ( props . notes . length ) return null ;
2020-09-15 15:01:07 +02:00
2022-04-14 17:50:42 +02:00
const theme = themeStyle ( props . themeId ) ;
2020-09-15 15:01:07 +02:00
const padding = 10 ;
const emptyDivStyle = {
padding : ` ${ padding } px ` ,
fontSize : theme.fontSize ,
color : theme.color ,
backgroundColor : theme.backgroundColor ,
fontFamily : theme.fontFamily ,
} ;
2022-04-14 17:50:42 +02:00
return < div style = { emptyDivStyle } > { props . folders . length ? _ ( 'No notes in here. Create one by clicking on "New note".' ) : _ ( 'There is currently no notebook. Create one by clicking on "New notebook".' ) } < / div > ;
} ;
2017-11-08 19:51:55 +02:00
2022-04-14 17:50:42 +02:00
const renderItemList = ( ) = > {
if ( ! props . notes . length ) return null ;
2020-09-15 15:01:07 +02:00
return (
< ItemList
2022-04-14 17:50:42 +02:00
ref = { itemListRef }
disabled = { props . isInsertingNotes }
itemHeight = { style . listItem . height }
2020-09-15 15:01:07 +02:00
className = { 'note-list' }
2022-04-14 17:50:42 +02:00
items = { props . notes }
style = { props . size }
itemRenderer = { renderItem }
onKeyDown = { onKeyDown }
2023-02-26 17:40:13 +02:00
onNoteDrop = { noteItem_noteDrop }
2020-09-15 15:01:07 +02:00
/ >
) ;
2022-04-14 17:50:42 +02:00
} ;
2020-09-15 15:01:07 +02:00
2022-04-14 17:50:42 +02:00
if ( ! props . size ) throw new Error ( 'props.size is required' ) ;
2020-09-15 15:01:07 +02:00
2022-04-14 17:50:42 +02:00
return (
< StyledRoot ref = { noteListRef } >
{ renderEmptyList ( ) }
{ renderItemList ( ) }
< / StyledRoot >
) ;
} ;
2017-11-04 18:40:34 +02:00
2020-11-12 21:13:28 +02:00
const mapStateToProps = ( state : AppState ) = > {
2023-07-16 18:42:42 +02:00
const selectedFolder : FolderEntity = state . notesParentType === 'Folder' ? BaseModel . byId ( state . folders , state . selectedFolderId ) : null ;
const userId = state . settings [ 'sync.userId' ] ;
2017-11-04 18:40:34 +02:00
return {
2017-11-05 01:27:13 +02:00
notes : state.notes ,
2017-12-07 15:16:38 +02:00
folders : state.folders ,
2017-11-22 20:35:31 +02:00
selectedNoteIds : state.selectedNoteIds ,
2020-05-27 18:21:46 +02:00
selectedFolderId : state.selectedFolderId ,
2020-09-15 15:01:07 +02:00
themeId : state.settings.theme ,
2018-03-20 01:04:48 +02:00
notesParentType : state.notesParentType ,
searches : state.searches ,
selectedSearchId : state.selectedSearchId ,
2018-11-21 21:50:50 +02:00
watchedNoteFiles : state.watchedNoteFiles ,
2020-02-29 14:39:15 +02:00
provisionalNoteIds : state.provisionalNoteIds ,
2020-05-27 18:21:46 +02:00
isInsertingNotes : state.isInsertingNotes ,
noteSortOrder : state.settings [ 'notes.sortOrder.field' ] ,
2023-02-20 15:23:26 +02:00
uncompletedTodosOnTop : state.settings.uncompletedTodosOnTop ,
showCompletedTodos : state.settings.showCompletedTodos ,
2020-09-06 14:07:00 +02:00
highlightedWords : state.highlightedWords ,
2020-10-09 19:35:46 +02:00
plugins : state.pluginService.plugins ,
2021-05-19 15:00:16 +02:00
customCss : state.customCss ,
2023-03-13 14:18:06 +02:00
focusedField : state.focusedField ,
2023-07-16 18:42:42 +02:00
parentFolderIsReadOnly : state.notesParentType === 'Folder' && selectedFolder ? itemIsReadOnlySync ( ModelType . Folder , ItemChange . SOURCE_UNSPECIFIED , selectedFolder as ItemSlice , userId , state . shareService ) : false ,
2017-11-04 18:40:34 +02:00
} ;
} ;
2020-09-15 15:01:07 +02:00
export default connect ( mapStateToProps ) ( NoteListComponent ) ;