2019-07-29 14:13:23 +02:00
const React = require ( 'react' ) ;
const { connect } = require ( 'react-redux' ) ;
const shared = require ( 'lib/components/shared/side-menu-shared.js' ) ;
const { Synchronizer } = require ( 'lib/synchronizer.js' ) ;
2020-07-03 23:32:39 +02:00
const CommandService = require ( 'lib/services/CommandService.js' ) . default ;
2019-07-29 14:13:23 +02:00
const BaseModel = require ( 'lib/BaseModel.js' ) ;
2019-01-09 19:25:44 +02:00
const Setting = require ( 'lib/models/Setting.js' ) ;
2019-07-29 14:13:23 +02:00
const Folder = require ( 'lib/models/Folder.js' ) ;
const Note = require ( 'lib/models/Note.js' ) ;
const Tag = require ( 'lib/models/Tag.js' ) ;
const { _ } = require ( 'lib/locale.js' ) ;
2020-06-10 23:08:59 +02:00
const { themeStyle } = require ( 'lib/theme' ) ;
2019-07-29 14:13:23 +02:00
const { bridge } = require ( 'electron' ) . remote . require ( './bridge' ) ;
2017-11-08 23:22:24 +02:00
const Menu = bridge ( ) . Menu ;
const MenuItem = bridge ( ) . MenuItem ;
2020-07-03 23:32:39 +02:00
const InteropServiceHelper = require ( '../../InteropServiceHelper.js' ) ;
2020-07-12 19:09:07 +02:00
const { substrWithEllipsis , substrStartWithEllipsis } = require ( 'lib/string-utils' ) ;
2020-02-22 13:25:16 +02:00
const { ALL _NOTES _FILTER _ID } = require ( 'lib/reserved-ids' ) ;
2017-11-06 01:55:01 +02:00
2020-07-03 23:32:39 +02:00
const commands = [
require ( './commands/focusElementSideBar' ) ,
] ;
2017-11-06 01:55:01 +02:00
class SideBarComponent extends React . Component {
2018-05-09 10:53:47 +02:00
constructor ( ) {
super ( ) ;
2020-07-03 23:32:39 +02:00
CommandService . instance ( ) . componentRegisterCommands ( this , commands ) ;
2020-05-21 10:14:33 +02:00
this . onFolderDragStart _ = event => {
2018-05-09 10:53:47 +02:00
const folderId = event . currentTarget . getAttribute ( 'folderid' ) ;
if ( ! folderId ) return ;
2019-01-09 19:25:44 +02:00
2018-05-09 10:53:47 +02:00
event . dataTransfer . setDragImage ( new Image ( ) , 1 , 1 ) ;
event . dataTransfer . clearData ( ) ;
event . dataTransfer . setData ( 'text/x-jop-folder-ids' , JSON . stringify ( [ folderId ] ) ) ;
} ;
2020-05-21 10:14:33 +02:00
this . onFolderDragOver _ = event => {
2019-07-29 14:13:23 +02:00
if ( event . dataTransfer . types . indexOf ( 'text/x-jop-note-ids' ) >= 0 ) event . preventDefault ( ) ;
if ( event . dataTransfer . types . indexOf ( 'text/x-jop-folder-ids' ) >= 0 ) event . preventDefault ( ) ;
2018-05-09 10:53:47 +02:00
} ;
2020-05-21 10:14:33 +02:00
this . onFolderDrop _ = async event => {
2018-05-09 10:53:47 +02:00
const folderId = event . currentTarget . getAttribute ( 'folderid' ) ;
const dt = event . dataTransfer ;
if ( ! dt ) return ;
2019-01-31 10:02:12 +02:00
// folderId can be NULL when dropping on the sidebar Notebook header. In that case, it's used
// to put the dropped folder at the root. But for notes, folderId needs to always be defined
// since there's no such thing as a root note.
2019-07-29 14:13:23 +02:00
if ( dt . types . indexOf ( 'text/x-jop-note-ids' ) >= 0 ) {
2018-05-09 10:53:47 +02:00
event . preventDefault ( ) ;
2019-01-31 10:02:12 +02:00
if ( ! folderId ) return ;
2019-07-29 14:13:23 +02:00
const noteIds = JSON . parse ( dt . getData ( 'text/x-jop-note-ids' ) ) ;
2018-05-09 10:53:47 +02:00
for ( let i = 0 ; i < noteIds . length ; i ++ ) {
await Note . moveToFolder ( noteIds [ i ] , folderId ) ;
}
2019-07-29 14:13:23 +02:00
} else if ( dt . types . indexOf ( 'text/x-jop-folder-ids' ) >= 0 ) {
2018-05-09 10:53:47 +02:00
event . preventDefault ( ) ;
2019-07-29 14:13:23 +02:00
const folderIds = JSON . parse ( dt . getData ( 'text/x-jop-folder-ids' ) ) ;
2018-05-09 10:53:47 +02:00
for ( let i = 0 ; i < folderIds . length ; i ++ ) {
2020-07-12 19:09:07 +02:00
if ( folderId === folderIds [ i ] ) continue ;
2018-05-09 10:53:47 +02:00
await Folder . moveToFolder ( folderIds [ i ] , folderId ) ;
}
}
} ;
2018-05-09 11:49:31 +02:00
2020-07-12 19:09:07 +02:00
this . onTagDragStart _ = event => {
const tagId = event . currentTarget . getAttribute ( 'tagid' ) ;
if ( ! tagId ) return ;
event . dataTransfer . setDragImage ( new Image ( ) , 1 , 1 ) ;
event . dataTransfer . clearData ( ) ;
event . dataTransfer . setData ( 'text/x-jop-tag-ids' , JSON . stringify ( [ tagId ] ) ) ;
} ;
this . onTagDragOver _ = event => {
if ( event . dataTransfer . types . indexOf ( 'text/x-jop-note-ids' ) >= 0 ) event . preventDefault ( ) ;
if ( event . dataTransfer . types . indexOf ( 'text/x-jop-tag-ids' ) >= 0 ) event . preventDefault ( ) ;
} ;
2020-05-21 10:14:33 +02:00
this . onTagDrop _ = async event => {
2018-09-05 12:43:03 +02:00
const tagId = event . currentTarget . getAttribute ( 'tagid' ) ;
const dt = event . dataTransfer ;
if ( ! dt ) return ;
2019-07-29 14:13:23 +02:00
if ( dt . types . indexOf ( 'text/x-jop-note-ids' ) >= 0 ) {
2018-09-05 12:43:03 +02:00
event . preventDefault ( ) ;
2019-07-29 14:13:23 +02:00
const noteIds = JSON . parse ( dt . getData ( 'text/x-jop-note-ids' ) ) ;
2018-09-05 12:43:03 +02:00
for ( let i = 0 ; i < noteIds . length ; i ++ ) {
await Tag . addNote ( tagId , noteIds [ i ] ) ;
}
2020-07-12 19:09:07 +02:00
} else if ( dt . types . indexOf ( 'text/x-jop-tag-ids' ) >= 0 ) {
event . preventDefault ( ) ;
const tagIds = JSON . parse ( dt . getData ( 'text/x-jop-tag-ids' ) ) ;
try {
for ( let i = 0 ; i < tagIds . length ; i ++ ) {
if ( tagId === tagIds [ i ] ) continue ;
await Tag . moveTag ( tagIds [ i ] , tagId ) ;
}
} catch ( error ) {
bridge ( ) . showErrorMessageBox ( error . message ) ;
}
2018-09-05 12:43:03 +02:00
}
2019-07-29 14:13:23 +02:00
} ;
2018-09-05 12:43:03 +02:00
2020-05-21 10:14:33 +02:00
this . onFolderToggleClick _ = async event => {
2018-05-09 11:49:31 +02:00
const folderId = event . currentTarget . getAttribute ( 'folderid' ) ;
this . props . dispatch ( {
type : 'FOLDER_TOGGLE' ,
id : folderId ,
} ) ;
} ;
2019-01-09 19:25:44 +02:00
2020-07-12 19:09:07 +02:00
this . onTagToggleClick _ = async event => {
const tagId = event . currentTarget . getAttribute ( 'tagid' ) ;
this . props . dispatch ( {
type : 'TAG_TOGGLE' ,
id : tagId ,
} ) ;
} ;
2019-01-26 20:04:32 +02:00
this . folderItemsOrder _ = [ ] ;
this . tagItemsOrder _ = [ ] ;
this . onKeyDown = this . onKeyDown . bind ( this ) ;
2020-02-22 13:25:16 +02:00
this . onAllNotesClick _ = this . onAllNotesClick _ . bind ( this ) ;
2019-01-26 20:04:32 +02:00
this . rootRef = React . createRef ( ) ;
this . anchorItemRefs = { } ;
2019-01-09 19:25:44 +02:00
this . state = {
tagHeaderIsExpanded : Setting . value ( 'tagHeaderIsExpanded' ) ,
2019-07-29 14:13:23 +02:00
folderHeaderIsExpanded : Setting . value ( 'folderHeaderIsExpanded' ) ,
2019-01-09 19:25:44 +02:00
} ;
2018-05-09 10:53:47 +02:00
}
2020-05-02 18:17:15 +02:00
style ( ) {
2017-11-08 19:51:55 +02:00
const theme = themeStyle ( this . props . theme ) ;
2017-11-09 00:48:19 +02:00
const itemHeight = 25 ;
2017-11-08 19:51:55 +02:00
2020-03-14 01:46:14 +02:00
const style = {
2017-11-09 00:48:19 +02:00
root : {
backgroundColor : theme . backgroundColor2 ,
} ,
2018-05-06 13:11:59 +02:00
listItemContainer : {
2019-07-29 14:13:23 +02:00
boxSizing : 'border-box' ,
2017-11-08 19:51:55 +02:00
height : itemHeight ,
2019-07-29 14:13:23 +02:00
display : 'flex' ,
2020-05-02 18:17:15 +02:00
flexDirection : 'row' ,
2018-05-06 13:11:59 +02:00
} ,
listItem : {
2017-11-09 00:48:19 +02:00
fontFamily : theme . fontFamily ,
fontSize : theme . fontSize ,
2019-07-29 14:13:23 +02:00
textDecoration : 'none' ,
2017-11-09 00:48:19 +02:00
color : theme . color2 ,
2019-07-29 14:13:23 +02:00
cursor : 'default' ,
2017-11-12 02:44:26 +02:00
opacity : 0.8 ,
2019-07-29 14:13:23 +02:00
whiteSpace : 'nowrap' ,
display : 'flex' ,
2018-05-06 13:11:59 +02:00
flex : 1 ,
alignItems : 'center' ,
2020-04-08 12:12:25 +02:00
userSelect : 'none' ,
2017-11-09 00:48:19 +02:00
} ,
listItemSelected : {
backgroundColor : theme . selectedColor2 ,
} ,
2018-05-06 13:11:59 +02:00
listItemExpandIcon : {
color : theme . color2 ,
2019-07-29 14:13:23 +02:00
cursor : 'default' ,
2018-05-06 13:11:59 +02:00
opacity : 0.8 ,
fontSize : theme . fontSize ,
2019-07-29 14:13:23 +02:00
textDecoration : 'none' ,
2018-05-06 13:11:59 +02:00
paddingRight : 5 ,
2019-07-29 14:13:23 +02:00
display : 'flex' ,
2018-05-06 13:11:59 +02:00
alignItems : 'center' ,
2020-05-02 18:17:15 +02:00
width : 12 ,
2018-05-06 13:11:59 +02:00
} ,
2017-11-12 20:57:59 +02:00
conflictFolder : {
color : theme . colorError2 ,
2019-07-29 14:13:23 +02:00
fontWeight : 'bold' ,
2017-11-12 20:57:59 +02:00
} ,
2017-11-09 00:48:19 +02:00
header : {
height : itemHeight * 1.8 ,
fontFamily : theme . fontFamily ,
2019-04-28 15:20:18 +02:00
fontSize : theme . fontSize * 1.16 ,
2019-07-29 14:13:23 +02:00
textDecoration : 'none' ,
boxSizing : 'border-box' ,
2017-11-09 00:48:19 +02:00
color : theme . color2 ,
paddingLeft : 8 ,
2019-07-29 14:13:23 +02:00
display : 'flex' ,
alignItems : 'center' ,
2020-04-08 12:12:25 +02:00
userSelect : 'none' ,
2017-11-08 19:51:55 +02:00
} ,
2017-11-09 21:21:10 +02:00
button : {
padding : 6 ,
fontFamily : theme . fontFamily ,
fontSize : theme . fontSize ,
2019-07-29 14:13:23 +02:00
textDecoration : 'none' ,
boxSizing : 'border-box' ,
2017-11-09 21:21:10 +02:00
color : theme . color2 ,
2019-07-29 14:13:23 +02:00
display : 'flex' ,
alignItems : 'center' ,
justifyContent : 'center' ,
border : '1px solid rgba(255,255,255,0.2)' ,
2017-11-09 21:21:10 +02:00
marginTop : 10 ,
marginLeft : 5 ,
marginRight : 5 ,
2019-07-29 14:13:23 +02:00
cursor : 'default' ,
2020-04-08 12:12:25 +02:00
userSelect : 'none' ,
2017-11-09 21:21:10 +02:00
} ,
2017-11-10 21:18:19 +02:00
syncReport : {
fontFamily : theme . fontFamily ,
2018-03-09 21:51:01 +02:00
fontSize : Math . round ( theme . fontSize * 0.9 ) ,
2017-11-10 21:18:19 +02:00
color : theme . color2 ,
2018-03-09 21:51:01 +02:00
opacity : 0.5 ,
2019-07-29 14:13:23 +02:00
display : 'flex' ,
alignItems : 'left' ,
justifyContent : 'top' ,
flexDirection : 'column' ,
2017-11-10 21:18:19 +02:00
marginTop : 10 ,
marginLeft : 5 ,
marginRight : 5 ,
2019-01-26 20:36:20 +02:00
marginBottom : 10 ,
2019-07-29 14:13:23 +02:00
wordWrap : 'break-word' ,
2017-11-10 21:18:19 +02:00
} ,
2019-11-11 08:14:56 +02:00
noteCount : {
paddingLeft : 5 ,
opacity : 0.5 ,
2020-04-08 12:12:25 +02:00
userSelect : 'none' ,
2019-11-11 08:14:56 +02:00
} ,
2017-11-08 19:51:55 +02:00
} ;
return style ;
}
2018-10-13 00:44:00 +02:00
clearForceUpdateDuringSync ( ) {
if ( this . forceUpdateDuringSyncIID _ ) {
clearInterval ( this . forceUpdateDuringSyncIID _ ) ;
this . forceUpdateDuringSyncIID _ = null ;
}
}
componentWillUnmount ( ) {
this . clearForceUpdateDuringSync ( ) ;
2019-07-29 14:13:23 +02:00
2020-07-03 23:32:39 +02:00
CommandService . instance ( ) . componentUnregisterCommands ( commands ) ;
2019-01-26 20:04:32 +02:00
}
2019-02-24 13:11:34 +02:00
async itemContextMenu ( event ) {
2019-11-11 08:14:56 +02:00
const itemId = event . currentTarget . getAttribute ( 'data-id' ) ;
2017-11-12 20:59:54 +02:00
if ( itemId === Folder . conflictFolderId ( ) ) return ;
2017-11-08 23:22:24 +02:00
2019-11-11 08:14:56 +02:00
const itemType = Number ( event . currentTarget . getAttribute ( 'data-type' ) ) ;
2019-07-29 14:13:23 +02:00
if ( ! itemId || ! itemType ) throw new Error ( 'No data on element' ) ;
2018-03-09 21:51:01 +02:00
2019-07-29 14:13:23 +02:00
let deleteMessage = '' ;
2019-10-03 23:33:49 +02:00
let buttonLabel = _ ( 'Remove' ) ;
2017-11-08 23:22:24 +02:00
if ( itemType === BaseModel . TYPE _FOLDER ) {
2019-02-24 13:11:34 +02:00
const folder = await Folder . load ( itemId ) ;
deleteMessage = _ ( 'Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.' , substrWithEllipsis ( folder . title , 0 , 32 ) ) ;
2019-10-03 23:33:49 +02:00
buttonLabel = _ ( 'Delete' ) ;
2017-11-08 23:22:24 +02:00
} else if ( itemType === BaseModel . TYPE _TAG ) {
2019-02-24 13:11:34 +02:00
const tag = await Tag . load ( itemId ) ;
2020-07-12 19:09:07 +02:00
deleteMessage = _ ( 'Remove tag "%s" and its descendant tags from all notes?' , substrStartWithEllipsis ( Tag . getCachedFullTitle ( tag . id ) , - 32 , 32 ) ) ;
2017-11-17 20:57:27 +02:00
} else if ( itemType === BaseModel . TYPE _SEARCH ) {
2019-07-29 14:13:23 +02:00
deleteMessage = _ ( 'Remove this search from the sidebar?' ) ;
2017-11-08 23:22:24 +02:00
}
const menu = new Menu ( ) ;
2017-12-14 22:21:36 +02:00
let item = null ;
if ( itemType === BaseModel . TYPE _FOLDER ) {
item = BaseModel . byId ( this . props . folders , itemId ) ;
}
2019-10-17 22:41:13 +02:00
if ( itemType === BaseModel . TYPE _FOLDER && ! item . encryption _applied ) {
menu . append (
2020-07-14 20:56:02 +02:00
new MenuItem ( CommandService . instance ( ) . commandToMenuItem ( 'newNotebook' , null , { parentId : itemId } ) ) ,
2019-10-17 22:41:13 +02:00
) ;
}
2018-03-09 21:51:01 +02:00
menu . append (
new MenuItem ( {
2019-10-03 23:33:49 +02:00
label : buttonLabel ,
2018-03-09 21:51:01 +02:00
click : async ( ) => {
2019-10-03 23:33:49 +02:00
const ok = bridge ( ) . showConfirmMessageBox ( deleteMessage , {
buttons : [ buttonLabel , _ ( 'Cancel' ) ] ,
defaultId : 1 ,
} ) ;
2018-03-09 21:51:01 +02:00
if ( ! ok ) return ;
if ( itemType === BaseModel . TYPE _FOLDER ) {
await Folder . delete ( itemId ) ;
} else if ( itemType === BaseModel . TYPE _TAG ) {
await Tag . untagAll ( itemId ) ;
} else if ( itemType === BaseModel . TYPE _SEARCH ) {
this . props . dispatch ( {
2019-07-29 14:13:23 +02:00
type : 'SEARCH_DELETE' ,
2018-03-09 21:51:01 +02:00
id : itemId ,
} ) ;
}
} ,
} )
) ;
2017-11-08 23:22:24 +02:00
2017-12-14 22:21:36 +02:00
if ( itemType === BaseModel . TYPE _FOLDER && ! item . encryption _applied ) {
2020-07-03 23:32:39 +02:00
menu . append ( new MenuItem ( CommandService . instance ( ) . commandToMenuItem ( 'renameFolder' , null , { folderId : itemId } ) ) ) ;
2018-05-09 10:53:47 +02:00
2019-07-29 14:13:23 +02:00
menu . append ( new MenuItem ( { type : 'separator' } ) ) ;
2018-03-09 21:51:01 +02:00
2019-07-29 14:13:23 +02:00
const InteropService = require ( 'lib/services/InteropService.js' ) ;
2018-03-09 21:51:01 +02:00
2018-09-04 12:59:09 +02:00
const exportMenu = new Menu ( ) ;
const ioService = new InteropService ( ) ;
const ioModules = ioService . modules ( ) ;
for ( let i = 0 ; i < ioModules . length ; i ++ ) {
const module = ioModules [ i ] ;
if ( module . type !== 'exporter' ) continue ;
2019-07-29 14:13:23 +02:00
exportMenu . append (
new MenuItem ( {
label : module . fullLabel ( ) ,
click : async ( ) => {
await InteropServiceHelper . export ( this . props . dispatch . bind ( this ) , module , { sourceFolderIds : [ itemId ] } ) ;
} ,
} )
) ;
2018-09-04 12:59:09 +02:00
}
2018-03-09 21:51:01 +02:00
menu . append (
new MenuItem ( {
2019-07-29 14:13:23 +02:00
label : _ ( 'Export' ) ,
2018-09-04 12:59:09 +02:00
submenu : exportMenu ,
2018-03-09 21:51:01 +02:00
} )
) ;
2017-11-17 20:57:27 +02:00
}
2017-11-16 20:51:11 +02:00
2019-01-09 19:25:44 +02:00
if ( itemType === BaseModel . TYPE _TAG ) {
2020-07-03 23:32:39 +02:00
menu . append ( new MenuItem (
CommandService . instance ( ) . commandToMenuItem ( 'renameTag' , null , { tagId : itemId } )
) ) ;
2018-05-20 09:39:32 +02:00
}
2017-11-08 23:22:24 +02:00
menu . popup ( bridge ( ) . window ( ) ) ;
}
2017-11-06 01:55:01 +02:00
folderItem _click ( folder ) {
this . props . dispatch ( {
2019-07-29 14:13:23 +02:00
type : 'FOLDER_SELECT' ,
2017-11-06 01:55:01 +02:00
id : folder ? folder . id : null ,
} ) ;
}
tagItem _click ( tag ) {
this . props . dispatch ( {
2019-07-29 14:13:23 +02:00
type : 'TAG_SELECT' ,
2017-11-06 01:55:01 +02:00
id : tag ? tag . id : null ,
} ) ;
}
2020-07-03 23:32:39 +02:00
// async sync_click() {
// await shared.synchronize_press(this);
// }
2017-11-06 20:35:04 +02:00
2019-01-26 20:04:32 +02:00
anchorItemRef ( type , id ) {
if ( ! this . anchorItemRefs [ type ] ) this . anchorItemRefs [ type ] = { } ;
if ( this . anchorItemRefs [ type ] [ id ] ) return this . anchorItemRefs [ type ] [ id ] ;
this . anchorItemRefs [ type ] [ id ] = React . createRef ( ) ;
return this . anchorItemRefs [ type ] [ id ] ;
}
2019-12-18 13:16:13 +02:00
firstAnchorItemRef ( type ) {
const refs = this . anchorItemRefs [ type ] ;
if ( ! refs ) return null ;
const n = ` ${ type } s ` ;
const item = this . props [ n ] && this . props [ n ] . length ? this . props [ n ] [ 0 ] : null ;
console . info ( 'props' , this . props [ n ] , item ) ;
if ( ! item ) return null ;
return refs [ item . id ] ;
}
2019-11-11 08:14:56 +02:00
noteCountElement ( count ) {
return < div style = { this . style ( ) . noteCount } > ( { count } ) < / div > ;
}
2020-07-12 19:09:07 +02:00
renderItem ( itemType , item , selected , hasChildren , depth ) {
let itemTitle = '' ;
let collapsedIds = null ;
const jsxItemIdAttribute = { } ;
let anchorRef = null ;
let noteCount = '' ;
let onDragStart = null ;
let onDragOver = null ;
let onDrop = null ;
let onItemClick = null ;
let onItemToggleClick = null ;
if ( itemType === BaseModel . TYPE _FOLDER ) {
itemTitle = Folder . displayTitle ( item ) ;
collapsedIds = this . props . collapsedFolderIds ;
jsxItemIdAttribute . folderid = item . id ;
anchorRef = this . anchorItemRef ( 'folder' , item . id ) ;
noteCount = item . note _count ? this . noteCountElement ( item . note _count ) : '' ;
onDragStart = this . onFolderDragStart _ ;
onDragOver = this . onFolderDragOver _ ;
onDrop = this . onFolderDrop _ ;
onItemClick = this . folderItem _click . bind ( this ) ;
onItemToggleClick = this . onFolderToggleClick _ ;
} else {
itemTitle = Tag . displayTitle ( item ) ;
collapsedIds = this . props . collapsedTagIds ;
jsxItemIdAttribute . tagid = item . id ;
anchorRef = this . anchorItemRef ( 'tag' , item . id ) ;
noteCount = Setting . value ( 'showNoteCounts' ) ? this . noteCountElement ( Tag . getCachedNoteCount ( item . id ) ) : '' ;
onDragStart = this . onTagDragStart _ ;
onDragOver = this . onTagDragOver _ ;
onDrop = this . onTagDrop _ ;
onItemClick = this . tagItem _click . bind ( this ) ;
onItemToggleClick = this . onTagToggleClick _ ;
}
2017-11-22 21:20:19 +02:00
2020-07-12 19:09:07 +02:00
let style = Object . assign ( { } , this . style ( ) . listItem ) ;
if ( item . id === Folder . conflictFolderId ( ) ) style = Object . assign ( style , this . style ( ) . conflictFolder ) ;
2017-12-14 19:58:10 +02:00
2020-05-02 18:17:15 +02:00
let containerStyle = Object . assign ( { } , this . style ( ) . listItemContainer ) ;
2018-05-06 13:11:59 +02:00
if ( selected ) containerStyle = Object . assign ( containerStyle , this . style ( ) . listItemSelected ) ;
2020-05-02 18:17:15 +02:00
containerStyle . paddingLeft = 8 + depth * 15 ;
2020-03-14 01:46:14 +02:00
const expandLinkStyle = Object . assign ( { } , this . style ( ) . listItemExpandIcon ) ;
const expandIconStyle = {
2018-05-09 10:53:47 +02:00
visibility : hasChildren ? 'visible' : 'hidden' ,
2019-07-29 14:13:23 +02:00
} ;
2018-05-09 11:49:31 +02:00
2020-07-12 19:09:07 +02:00
const iconName = collapsedIds . indexOf ( item . id ) >= 0 ? 'fa-chevron-right' : 'fa-chevron-down' ;
2020-05-17 16:34:42 +02:00
const expandIcon = < i style = { expandIconStyle } className = { ` fas ${ iconName } ` } > < / i > ;
2019-07-29 14:13:23 +02:00
const expandLink = hasChildren ? (
2020-07-12 19:09:07 +02:00
< a style = { expandLinkStyle } href = "#" { ...jsxItemIdAttribute } onClick = { onItemToggleClick } >
2019-07-29 14:13:23 +02:00
{ expandIcon }
< / a >
) : (
< span style = { expandLinkStyle } > { expandIcon } < / span >
) ;
2018-05-06 13:11:59 +02:00
2018-03-09 21:51:01 +02:00
return (
2020-07-12 19:09:07 +02:00
< div className = { ` list-item-container list-item-depth- ${ depth } ` } style = { containerStyle } key = { item . id } onDragStart = { onDragStart } onDragOver = { onDragOver } onDrop = { onDrop } draggable = { true } { ...jsxItemIdAttribute } >
2019-07-29 14:13:23 +02:00
{ expandLink }
2018-05-06 13:11:59 +02:00
< a
2019-01-26 20:04:32 +02:00
ref = { anchorRef }
2018-05-06 13:11:59 +02:00
className = "list-item"
href = "#"
2020-07-12 19:09:07 +02:00
data - id = { item . id }
data - type = { itemType }
2020-05-21 10:14:33 +02:00
onContextMenu = { event => this . itemContextMenu ( event ) }
2018-05-06 13:11:59 +02:00
style = { style }
2020-07-12 19:09:07 +02:00
{ ... jsxItemIdAttribute }
2018-05-06 13:11:59 +02:00
onClick = { ( ) => {
2020-07-12 19:09:07 +02:00
onItemClick ( item ) ;
2018-05-06 13:11:59 +02:00
} }
2020-07-12 19:09:07 +02:00
onDoubleClick = { onItemToggleClick }
2018-05-06 13:11:59 +02:00
>
2019-11-11 08:14:56 +02:00
{ itemTitle } { noteCount }
2018-05-06 13:11:59 +02:00
< / a >
< / div >
2018-03-09 21:51:01 +02:00
) ;
2017-11-06 01:55:01 +02:00
}
2019-01-26 20:04:32 +02:00
// searchItem(search, selected) {
// let style = Object.assign({}, this.style().listItem);
// if (selected) style = Object.assign(style, this.style().listItemSelected);
// return (
// <a
// className="list-item"
// href="#"
// data-id={search.id}
// data-type={BaseModel.TYPE_SEARCH}
// onContextMenu={event => this.itemContextMenu(event)}
// key={search.id}
// style={style}
// onClick={() => {
// this.searchItem_click(search);
// }}
// >
// {search.title}
// </a>
// );
// }
2017-11-17 20:57:27 +02:00
2017-11-06 01:55:01 +02:00
makeDivider ( key ) {
2019-07-29 14:13:23 +02:00
return < div style = { { height : 2 , backgroundColor : 'blue' } } key = { key } / > ;
2017-11-06 01:55:01 +02:00
}
2018-05-09 10:53:47 +02:00
makeHeader ( key , label , iconName , extraProps = { } ) {
2017-11-09 00:48:19 +02:00
const style = this . style ( ) . header ;
2020-05-17 16:34:42 +02:00
const icon = < i style = { { fontSize : style . fontSize , marginRight : 5 } } className = { ` fas ${ iconName } ` } / > ;
2019-01-09 19:25:44 +02:00
if ( extraProps . toggleblock || extraProps . onClick ) {
2019-07-29 14:13:23 +02:00
style . cursor = 'pointer' ;
2019-01-09 19:25:44 +02:00
}
2020-03-14 01:46:14 +02:00
const headerClick = extraProps . onClick || null ;
2019-01-09 19:25:44 +02:00
delete extraProps . onClick ;
// check if toggling option is set.
let toggleIcon = null ;
const toggleKey = ` ${ key } IsExpanded ` ;
if ( extraProps . toggleblock ) {
2020-03-14 01:46:14 +02:00
const isExpanded = this . state [ toggleKey ] ;
2020-05-20 02:21:18 +02:00
toggleIcon = < i className = { ` fas ${ isExpanded ? 'fa-chevron-down' : 'fa-chevron-right' } ` } style = { { fontSize : style . fontSize * 0.75 , marginRight : 12 , marginLeft : 5 , marginTop : style . fontSize * 0.125 } } > < / i > ;
2019-01-09 19:25:44 +02:00
}
2020-02-22 13:25:16 +02:00
if ( extraProps . selected ) {
2020-03-14 01:46:14 +02:00
style . backgroundColor = this . style ( ) . listItemSelected . backgroundColor ;
2020-02-22 13:25:16 +02:00
}
2019-01-26 20:04:32 +02:00
const ref = this . anchorItemRef ( 'headers' , key ) ;
2018-03-09 21:51:01 +02:00
return (
2019-07-29 14:13:23 +02:00
< div
ref = { ref }
style = { style }
key = { key }
{ ... extraProps }
2020-05-21 10:14:33 +02:00
onClick = { event => {
2019-07-29 14:13:23 +02:00
// if a custom click event is attached, trigger that.
2019-01-09 19:33:52 +02:00
if ( headerClick ) {
2019-07-29 14:13:23 +02:00
headerClick ( key , event ) ;
2019-01-09 19:25:44 +02:00
}
2019-07-29 14:13:23 +02:00
this . onHeaderClick _ ( key , event ) ;
} }
>
2018-03-09 21:51:01 +02:00
{ icon }
2019-07-29 14:13:23 +02:00
< span style = { { flex : 1 } } > { label } < / span >
2019-01-09 19:25:44 +02:00
{ toggleIcon }
2018-03-09 21:51:01 +02:00
< / div >
) ;
2017-11-09 00:48:19 +02:00
}
2019-01-26 20:04:32 +02:00
selectedItem ( ) {
if ( this . props . notesParentType === 'Folder' && this . props . selectedFolderId ) {
return { type : 'folder' , id : this . props . selectedFolderId } ;
} else if ( this . props . notesParentType === 'Tag' && this . props . selectedTagId ) {
return { type : 'tag' , id : this . props . selectedTagId } ;
}
return null ;
}
onKeyDown ( event ) {
const keyCode = event . keyCode ;
2019-01-26 20:09:09 +02:00
const selectedItem = this . selectedItem ( ) ;
2019-01-26 20:04:32 +02:00
2019-07-29 14:13:23 +02:00
if ( keyCode === 40 || keyCode === 38 ) {
// DOWN / UP
2019-01-26 20:04:32 +02:00
event . preventDefault ( ) ;
const focusItems = [ ] ;
for ( let i = 0 ; i < this . folderItemsOrder _ . length ; i ++ ) {
const id = this . folderItemsOrder _ [ i ] ;
focusItems . push ( { id : id , ref : this . anchorItemRefs [ 'folder' ] [ id ] , type : 'folder' } ) ;
}
for ( let i = 0 ; i < this . tagItemsOrder _ . length ; i ++ ) {
const id = this . tagItemsOrder _ [ i ] ;
focusItems . push ( { id : id , ref : this . anchorItemRefs [ 'tag' ] [ id ] , type : 'tag' } ) ;
}
let currentIndex = 0 ;
for ( let i = 0 ; i < focusItems . length ; i ++ ) {
if ( ! selectedItem || focusItems [ i ] . id === selectedItem . id ) {
currentIndex = i ;
break ;
}
}
const inc = keyCode === 38 ? - 1 : + 1 ;
let newIndex = currentIndex + inc ;
if ( newIndex < 0 ) newIndex = 0 ;
if ( newIndex > focusItems . length - 1 ) newIndex = focusItems . length - 1 ;
const focusItem = focusItems [ newIndex ] ;
2020-03-14 01:46:14 +02:00
const actionName = ` ${ focusItem . type . toUpperCase ( ) } _SELECT ` ;
2019-01-26 20:04:32 +02:00
this . props . dispatch ( {
type : actionName ,
id : focusItem . id ,
} ) ;
focusItem . ref . current . focus ( ) ;
}
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-07-03 23:32:39 +02:00
CommandService . instance ( ) . execute ( 'focusElement' , { target : 'noteBody' } ) ;
2019-01-26 20:04:32 +02:00
} else {
2020-07-03 23:32:39 +02:00
CommandService . instance ( ) . execute ( 'focusElement' , { target : 'noteList' } ) ;
2019-01-26 20:04:32 +02:00
}
}
2019-01-26 20:09:09 +02:00
2019-07-29 14:13:23 +02:00
if ( selectedItem && selectedItem . type === 'folder' && keyCode === 32 ) {
// SPACE
2019-01-26 20:09:09 +02:00
event . preventDefault ( ) ;
this . props . dispatch ( {
type : 'FOLDER_TOGGLE' ,
id : selectedItem . id ,
} ) ;
}
2020-02-04 23:55:06 +02:00
if ( keyCode === 65 && ( event . ctrlKey || event . metaKey ) ) {
// Ctrl+A key
event . preventDefault ( ) ;
}
2019-01-26 20:04:32 +02:00
}
2019-07-29 14:13:23 +02:00
onHeaderClick _ ( key , event ) {
2019-01-09 19:25:44 +02:00
const currentHeader = event . currentTarget ;
const toggleBlock = + currentHeader . getAttribute ( 'toggleblock' ) ;
if ( toggleBlock ) {
2019-07-29 14:13:23 +02:00
const toggleKey = ` ${ key } IsExpanded ` ;
const isExpanded = this . state [ toggleKey ] ;
this . setState ( { [ toggleKey ] : ! isExpanded } ) ;
Setting . setValue ( toggleKey , ! isExpanded ) ;
2019-01-09 19:25:44 +02:00
}
}
2020-02-22 13:25:16 +02:00
onAllNotesClick _ ( ) {
this . props . dispatch ( {
type : 'SMART_FILTER_SELECT' ,
id : ALL _NOTES _FILTER _ID ,
} ) ;
}
2017-11-20 20:44:56 +02:00
synchronizeButton ( type ) {
2019-01-26 20:36:20 +02:00
const style = Object . assign ( { } , this . style ( ) . button , { marginBottom : 5 } ) ;
2020-05-17 16:34:42 +02:00
const iconName = 'fa-sync-alt' ;
2019-07-29 14:13:23 +02:00
const label = type === 'sync' ? _ ( 'Synchronise' ) : _ ( 'Cancel' ) ;
2020-03-14 01:46:14 +02:00
const iconStyle = { fontSize : style . fontSize , marginRight : 5 } ;
2019-04-28 15:20:18 +02:00
2019-07-29 14:13:23 +02:00
if ( type !== 'sync' ) {
2019-04-28 15:20:18 +02:00
iconStyle . animation = 'icon-infinite-rotation 1s linear infinite' ;
}
2020-05-17 16:34:42 +02:00
const icon = < i style = { iconStyle } className = { ` fas ${ iconName } ` } / > ;
2018-03-09 21:51:01 +02:00
return (
< a
className = "synchronize-button"
style = { style }
href = "#"
key = "sync_button"
onClick = { ( ) => {
2020-07-03 23:32:39 +02:00
CommandService . instance ( ) . execute ( 'synchronize' ) ;
// this.sync_click();
2018-03-09 21:51:01 +02:00
} }
>
{ icon }
{ label }
< / a >
) ;
2017-11-06 01:55:01 +02:00
}
render ( ) {
2017-11-12 01:13:14 +02:00
const style = Object . assign ( { } , this . style ( ) . root , this . props . style , {
2019-07-29 14:13:23 +02:00
overflowX : 'hidden' ,
overflowY : 'hidden' ,
2019-01-26 20:36:20 +02:00
display : 'inline-flex' ,
flexDirection : 'column' ,
2017-11-12 01:13:14 +02:00
} ) ;
2017-11-08 19:51:55 +02:00
2020-03-14 01:46:14 +02:00
const items = [ ] ;
2020-02-22 13:25:16 +02:00
items . push (
this . makeHeader ( 'allNotesHeader' , _ ( 'All notes' ) , 'fa-clone' , {
onClick : this . onAllNotesClick _ ,
selected : this . props . notesParentType === 'SmartFilter' && this . props . selectedSmartFilterId === ALL _NOTES _FILTER _ID ,
} )
) ;
2019-07-29 14:13:23 +02:00
items . push (
this . makeHeader ( 'folderHeader' , _ ( 'Notebooks' ) , 'fa-book' , {
onDrop : this . onFolderDrop _ ,
folderid : '' ,
toggleblock : 1 ,
} )
) ;
2017-11-09 00:48:19 +02:00
2017-11-06 01:55:01 +02:00
if ( this . props . folders . length ) {
2020-07-12 19:09:07 +02:00
const result = shared . renderFolders ( this . props , this . renderItem . bind ( this , BaseModel . TYPE _FOLDER ) ) ;
2019-01-26 20:04:32 +02:00
const folderItems = result . items ;
this . folderItemsOrder _ = result . order ;
2019-07-29 14:13:23 +02:00
items . push (
< div className = "folders" key = "folder_items" style = { { display : this . state . folderHeaderIsExpanded ? 'block' : 'none' } } >
{ folderItems }
< / div >
) ;
2017-11-06 01:55:01 +02:00
}
2019-07-29 14:13:23 +02:00
items . push (
this . makeHeader ( 'tagHeader' , _ ( 'Tags' ) , 'fa-tags' , {
toggleblock : 1 ,
2020-07-12 19:09:07 +02:00
onDrop : this . onTagDrop _ ,
2019-07-29 14:13:23 +02:00
} )
) ;
2017-11-09 00:48:19 +02:00
2017-11-06 01:55:01 +02:00
if ( this . props . tags . length ) {
2020-07-12 19:09:07 +02:00
const result = shared . renderTags ( this . props , this . renderItem . bind ( this , BaseModel . TYPE _TAG ) ) ;
2019-01-26 20:04:32 +02:00
const tagItems = result . items ;
this . tagItemsOrder _ = result . order ;
2017-11-06 01:55:01 +02:00
2018-03-09 21:51:01 +02:00
items . push (
2019-07-29 14:13:23 +02:00
< div className = "tags" key = "tag_items" style = { { display : this . state . tagHeaderIsExpanded ? 'block' : 'none' } } >
2018-03-09 21:51:01 +02:00
{ tagItems }
< / div >
) ;
2017-11-06 01:55:01 +02:00
}
2018-10-08 20:11:53 +02:00
let decryptionReportText = '' ;
if ( this . props . decryptionWorker && this . props . decryptionWorker . state !== 'idle' && this . props . decryptionWorker . itemCount ) {
decryptionReportText = _ ( 'Decrypting items: %d/%d' , this . props . decryptionWorker . itemIndex + 1 , this . props . decryptionWorker . itemCount ) ;
}
2018-11-14 00:25:23 +02:00
let resourceFetcherText = '' ;
if ( this . props . resourceFetcher && this . props . resourceFetcher . toFetchCount ) {
2019-05-22 16:56:07 +02:00
resourceFetcherText = _ ( 'Fetching resources: %d/%d' , this . props . resourceFetcher . fetchingCount , this . props . resourceFetcher . toFetchCount ) ;
2018-11-14 00:25:23 +02:00
}
2020-03-14 01:46:14 +02:00
const lines = Synchronizer . reportToLines ( this . props . syncReport ) ;
2018-11-14 00:25:23 +02:00
if ( resourceFetcherText ) lines . push ( resourceFetcherText ) ;
2018-10-08 20:11:53 +02:00
if ( decryptionReportText ) lines . push ( decryptionReportText ) ;
2017-11-12 19:53:26 +02:00
const syncReportText = [ ] ;
for ( let i = 0 ; i < lines . length ; i ++ ) {
2018-03-09 21:51:01 +02:00
syncReportText . push (
2019-07-29 14:13:23 +02:00
< div key = { i } style = { { wordWrap : 'break-word' , width : '100%' } } >
2018-03-09 21:51:01 +02:00
{ lines [ i ] }
< / div >
) ;
2017-11-12 19:53:26 +02:00
}
2017-11-06 01:55:01 +02:00
2019-07-29 14:13:23 +02:00
const syncButton = this . synchronizeButton ( this . props . syncStarted ? 'cancel' : 'sync' ) ;
2017-11-06 01:55:01 +02:00
2019-01-26 20:36:20 +02:00
const syncReportComp = ! syncReportText . length ? null : (
2018-03-09 21:51:01 +02:00
< div style = { this . style ( ) . syncReport } key = "sync_report" >
2019-07-29 14:13:23 +02:00
{ syncReportText }
< / div >
) ;
2017-11-06 01:55:01 +02:00
return (
2019-01-26 20:04:32 +02:00
< div ref = { this . rootRef } onKeyDown = { this . onKeyDown } className = "side-bar" style = { style } >
2019-07-29 14:13:23 +02:00
< div style = { { flex : 1 , overflowX : 'hidden' , overflowY : 'auto' } } > { items } < / div >
< div style = { { flex : 0 } } >
2019-04-28 15:20:18 +02:00
{ syncReportComp }
2019-01-26 20:36:20 +02:00
{ syncButton }
< / div >
2017-11-06 01:55:01 +02:00
< / div >
) ;
}
}
2020-05-21 10:14:33 +02:00
const mapStateToProps = state => {
2017-11-06 01:55:01 +02:00
return {
folders : state . folders ,
tags : state . tags ,
2017-11-17 20:57:27 +02:00
searches : state . searches ,
2017-11-06 01:55:01 +02:00
syncStarted : state . syncStarted ,
syncReport : state . syncReport ,
selectedFolderId : state . selectedFolderId ,
selectedTagId : state . selectedTagId ,
2017-11-17 20:57:27 +02:00
selectedSearchId : state . selectedSearchId ,
2020-02-22 13:25:16 +02:00
selectedSmartFilterId : state . selectedSmartFilterId ,
2017-11-06 01:55:01 +02:00
notesParentType : state . notesParentType ,
locale : state . settings . locale ,
theme : state . settings . theme ,
2018-05-09 11:49:31 +02:00
collapsedFolderIds : state . collapsedFolderIds ,
2020-07-12 19:09:07 +02:00
collapsedTagIds : state . collapsedTagIds ,
2018-10-08 20:11:53 +02:00
decryptionWorker : state . decryptionWorker ,
2018-11-14 00:25:23 +02:00
resourceFetcher : state . resourceFetcher ,
2019-01-26 20:04:32 +02:00
sidebarVisibility : state . sidebarVisibility ,
2019-10-30 11:40:34 +02:00
noteListVisibility : state . noteListVisibility ,
2017-11-06 01:55:01 +02:00
} ;
} ;
const SideBar = connect ( mapStateToProps ) ( SideBarComponent ) ;
2018-03-09 21:51:01 +02:00
module . exports = { SideBar } ;