2018-03-09 21:51:01 +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" ) ;
const BaseModel = require ( "lib/BaseModel.js" ) ;
2019-01-09 19:25:44 +02:00
const Setting = require ( 'lib/models/Setting.js' ) ;
2018-03-09 21:51:01 +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" ) ;
const { themeStyle } = require ( "../theme.js" ) ;
const { bridge } = require ( "electron" ) . remote . require ( "./bridge" ) ;
2017-11-08 23:22:24 +02:00
const Menu = bridge ( ) . Menu ;
const MenuItem = bridge ( ) . MenuItem ;
2018-03-09 21:51:01 +02:00
const InteropServiceHelper = require ( "../InteropServiceHelper.js" ) ;
2019-02-24 13:11:34 +02:00
const { substrWithEllipsis } = require ( 'lib/string-utils' ) ;
2018-10-13 00:44:00 +02:00
const { shim } = require ( 'lib/shim' ) ;
2017-11-06 01:55:01 +02:00
class SideBarComponent extends React . Component {
2018-05-09 10:53:47 +02:00
constructor ( ) {
super ( ) ;
this . onFolderDragStart _ = ( event ) => {
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 ] ) ) ;
} ;
this . onFolderDragOver _ = ( event ) => {
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 ( ) ;
} ;
this . onFolderDrop _ = async ( event ) => {
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.
2018-05-09 10:53:47 +02:00
if ( dt . types . indexOf ( "text/x-jop-note-ids" ) >= 0 ) {
event . preventDefault ( ) ;
2019-01-31 10:02:12 +02:00
if ( ! folderId ) return ;
2018-05-09 10:53:47 +02:00
const noteIds = JSON . parse ( dt . getData ( "text/x-jop-note-ids" ) ) ;
for ( let i = 0 ; i < noteIds . length ; i ++ ) {
await Note . moveToFolder ( noteIds [ i ] , folderId ) ;
}
} else if ( dt . types . indexOf ( "text/x-jop-folder-ids" ) >= 0 ) {
event . preventDefault ( ) ;
const folderIds = JSON . parse ( dt . getData ( "text/x-jop-folder-ids" ) ) ;
for ( let i = 0 ; i < folderIds . length ; i ++ ) {
await Folder . moveToFolder ( folderIds [ i ] , folderId ) ;
}
}
} ;
2018-05-09 11:49:31 +02:00
2018-09-05 12:43:03 +02:00
this . onTagDrop _ = async ( event ) => {
const tagId = event . currentTarget . getAttribute ( 'tagid' ) ;
const dt = event . dataTransfer ;
if ( ! dt ) return ;
if ( dt . types . indexOf ( "text/x-jop-note-ids" ) >= 0 ) {
event . preventDefault ( ) ;
const noteIds = JSON . parse ( dt . getData ( "text/x-jop-note-ids" ) ) ;
for ( let i = 0 ; i < noteIds . length ; i ++ ) {
await Tag . addNote ( tagId , noteIds [ i ] ) ;
}
}
}
2018-05-09 11:49:31 +02:00
this . onFolderToggleClick _ = async ( event ) => {
const folderId = event . currentTarget . getAttribute ( 'folderid' ) ;
this . props . dispatch ( {
type : 'FOLDER_TOGGLE' ,
id : folderId ,
} ) ;
} ;
2019-01-09 19:25:44 +02:00
2019-01-26 20:04:32 +02:00
this . folderItemsOrder _ = [ ] ;
this . tagItemsOrder _ = [ ] ;
this . onKeyDown = this . onKeyDown . bind ( this ) ;
this . rootRef = React . createRef ( ) ;
this . anchorItemRefs = { } ;
2019-01-09 19:25:44 +02:00
this . state = {
tagHeaderIsExpanded : Setting . value ( 'tagHeaderIsExpanded' ) ,
folderHeaderIsExpanded : Setting . value ( 'folderHeaderIsExpanded' )
} ;
2018-05-09 10:53:47 +02:00
}
2017-11-08 19:51:55 +02:00
style ( ) {
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
let style = {
2017-11-09 00:48:19 +02:00
root : {
backgroundColor : theme . backgroundColor2 ,
} ,
2018-05-06 13:11:59 +02:00
listItemContainer : {
boxSizing : "border-box" ,
2017-11-08 19:51:55 +02:00
height : itemHeight ,
2018-05-09 11:49:31 +02:00
// paddingLeft: 14,
2018-05-06 13:11:59 +02:00
display : "flex" ,
alignItems : "stretch" ,
} ,
listItem : {
2017-11-09 00:48:19 +02:00
fontFamily : theme . fontFamily ,
fontSize : theme . fontSize ,
2018-03-09 21:51:01 +02:00
textDecoration : "none" ,
2017-11-09 00:48:19 +02:00
color : theme . color2 ,
2018-03-09 21:51:01 +02:00
cursor : "default" ,
2017-11-12 02:44:26 +02:00
opacity : 0.8 ,
2018-03-09 21:51:01 +02:00
whiteSpace : "nowrap" ,
2018-05-06 13:11:59 +02:00
display : "flex" ,
flex : 1 ,
alignItems : 'center' ,
2017-11-09 00:48:19 +02:00
} ,
listItemSelected : {
backgroundColor : theme . selectedColor2 ,
} ,
2018-05-06 13:11:59 +02:00
listItemExpandIcon : {
color : theme . color2 ,
cursor : "default" ,
opacity : 0.8 ,
2018-05-09 10:53:47 +02:00
// fontFamily: theme.fontFamily,
2018-05-06 13:11:59 +02:00
fontSize : theme . fontSize ,
textDecoration : "none" ,
paddingRight : 5 ,
display : "flex" ,
alignItems : 'center' ,
} ,
2017-11-12 20:57:59 +02:00
conflictFolder : {
color : theme . colorError2 ,
2018-03-09 21:51:01 +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 ,
2018-03-09 21:51:01 +02:00
textDecoration : "none" ,
boxSizing : "border-box" ,
2017-11-09 00:48:19 +02:00
color : theme . color2 ,
paddingLeft : 8 ,
2018-03-09 21:51:01 +02:00
display : "flex" ,
alignItems : "center" ,
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 ,
2018-03-09 21:51:01 +02:00
textDecoration : "none" ,
boxSizing : "border-box" ,
2017-11-09 21:21:10 +02:00
color : theme . color2 ,
2018-03-09 21:51:01 +02:00
display : "flex" ,
alignItems : "center" ,
justifyContent : "center" ,
2017-11-09 21:21:10 +02:00
border : "1px solid rgba(255,255,255,0.2)" ,
marginTop : 10 ,
marginLeft : 5 ,
marginRight : 5 ,
2018-03-09 21:51:01 +02:00
cursor : "default" ,
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 ,
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 ,
2018-03-09 21:51:01 +02:00
wordWrap : "break-word" ,
2017-11-10 21:18:19 +02:00
} ,
2017-11-08 19:51:55 +02:00
} ;
2018-05-09 16:31:42 +02:00
style . tagItem = Object . assign ( { } , style . listItem ) ;
style . tagItem . paddingLeft = 23 ;
style . tagItem . height = itemHeight ;
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 ;
}
}
2019-01-26 20:04:32 +02:00
doCommand ( command ) {
if ( ! command ) return ;
let commandProcessed = true ;
if ( command . name === 'focusElement' && command . target === 'sideBar' ) {
if ( this . props . sidebarVisibility ) {
const item = this . selectedItem ( ) ;
if ( item ) {
const anchorRef = this . anchorItemRefs [ item . type ] [ item . id ] ;
if ( anchorRef ) anchorRef . current . focus ( ) ;
}
}
2019-02-24 13:10:22 +02:00
} else if ( command . name === 'synchronize' ) {
2019-05-06 22:35:29 +02:00
if ( ! this . props . syncStarted ) this . sync _click ( ) ;
2019-01-26 20:04:32 +02:00
} else {
commandProcessed = false ;
}
if ( commandProcessed ) {
this . props . dispatch ( {
type : 'WINDOW_COMMAND' ,
name : null ,
} ) ;
}
}
2018-10-13 00:44:00 +02:00
componentDidUpdate ( prevProps ) {
if ( shim . isLinux ( ) ) {
// For some reason, the UI seems to sleep in some Linux distro during
// sync. Cannot find the reason for it and cannot replicate, so here
2019-01-09 19:25:44 +02:00
// as a test force the update at regular intervals.
2018-10-13 00:44:00 +02:00
// https://github.com/laurent22/joplin/issues/312#issuecomment-429472193
if ( ! prevProps . syncStarted && this . props . syncStarted ) {
this . clearForceUpdateDuringSync ( ) ;
this . forceUpdateDuringSyncIID _ = setInterval ( ( ) => {
this . forceUpdate ( ) ;
} , 2000 ) ;
}
if ( prevProps . syncStarted && ! this . props . syncStarted ) this . clearForceUpdateDuringSync ( ) ;
2019-01-09 19:25:44 +02:00
}
2018-10-13 00:44:00 +02:00
}
componentWillUnmount ( ) {
this . clearForceUpdateDuringSync ( ) ;
}
2017-11-08 19:51:55 +02:00
2019-01-26 20:04:32 +02:00
componentDidUpdate ( prevProps , prevState , snapshot ) {
if ( prevProps . windowCommand !== this . props . windowCommand ) {
this . doCommand ( this . props . windowCommand ) ;
}
}
2019-02-24 13:11:34 +02:00
async itemContextMenu ( event ) {
2018-03-09 21:51:01 +02:00
const itemId = event . target . getAttribute ( "data-id" ) ;
2017-11-12 20:59:54 +02:00
if ( itemId === Folder . conflictFolderId ( ) ) return ;
2017-11-08 23:22:24 +02:00
2018-03-09 21:51:01 +02:00
const itemType = Number ( event . target . getAttribute ( "data-type" ) ) ;
if ( ! itemId || ! itemType ) throw new Error ( "No data on element" ) ;
let deleteMessage = "" ;
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 ) ) ;
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 ) ;
deleteMessage = _ ( 'Remove tag "%s" from all notes?' , substrWithEllipsis ( tag . title , 0 , 32 ) ) ;
2017-11-17 20:57:27 +02:00
} else if ( itemType === BaseModel . TYPE _SEARCH ) {
2018-03-09 21:51:01 +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 ) ;
}
2018-03-09 21:51:01 +02:00
menu . append (
new MenuItem ( {
label : _ ( "Delete" ) ,
click : async ( ) => {
const ok = bridge ( ) . showConfirmMessageBox ( deleteMessage ) ;
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 ( {
type : "SEARCH_DELETE" ,
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 ) {
2018-03-09 21:51:01 +02:00
menu . append (
new MenuItem ( {
label : _ ( "Rename" ) ,
click : async ( ) => {
this . props . dispatch ( {
type : "WINDOW_COMMAND" ,
name : "renameFolder" ,
id : itemId ,
} ) ;
} ,
} )
) ;
2018-05-09 10:53:47 +02:00
// menu.append(
// new MenuItem({
// label: _("Move"),
// click: async () => {
// this.props.dispatch({
// type: "WINDOW_COMMAND",
// name: "renameFolder",
// id: itemId,
// });
// },
// })
// );
2018-03-09 21:51:01 +02:00
menu . append ( new MenuItem ( { type : "separator" } ) ) ;
const InteropService = require ( "lib/services/InteropService.js" ) ;
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 ;
exportMenu . append ( new MenuItem ( { label : module . fullLabel ( ) , click : async ( ) => {
await InteropServiceHelper . export ( this . props . dispatch . bind ( this ) , module , { sourceFolderIds : [ itemId ] } ) ;
} } ) ) ;
}
2018-03-09 21:51:01 +02:00
menu . append (
new MenuItem ( {
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 ) {
2018-05-20 09:39:32 +02:00
menu . append (
new MenuItem ( {
label : _ ( 'Rename' ) ,
click : async ( ) => {
this . props . dispatch ( {
type : "WINDOW_COMMAND" ,
name : "renameTag" ,
id : itemId
} ) ;
} ,
} )
) ;
}
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 ( {
2018-03-09 21:51:01 +02:00
type : "FOLDER_SELECT" ,
2017-11-06 01:55:01 +02:00
id : folder ? folder . id : null ,
} ) ;
}
tagItem _click ( tag ) {
this . props . dispatch ( {
2018-03-09 21:51:01 +02:00
type : "TAG_SELECT" ,
2017-11-06 01:55:01 +02:00
id : tag ? tag . id : null ,
} ) ;
}
2019-01-26 20:04:32 +02:00
// searchItem_click(search) {
// this.props.dispatch({
// type: "SEARCH_SELECT",
// id: search ? search.id : null,
// });
// }
2017-11-17 20:57:27 +02:00
2017-11-06 23:11:15 +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 ) {
let refs = null ;
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 ] ;
}
2018-05-06 13:11:59 +02:00
folderItem ( folder , selected , hasChildren , depth ) {
2017-11-09 00:48:19 +02:00
let style = Object . assign ( { } , this . style ( ) . listItem ) ;
2017-11-12 20:57:59 +02:00
if ( folder . id === Folder . conflictFolderId ( ) ) style = Object . assign ( style , this . style ( ) . conflictFolder ) ;
2017-11-22 21:20:19 +02:00
2017-12-14 22:21:36 +02:00
const itemTitle = Folder . displayTitle ( folder ) ;
2017-12-14 19:58:10 +02:00
2018-05-06 13:11:59 +02:00
let containerStyle = Object . assign ( { } , this . style ( ) . listItemContainer ) ;
if ( selected ) containerStyle = Object . assign ( containerStyle , this . style ( ) . listItemSelected ) ;
2018-05-09 10:53:47 +02:00
let expandLinkStyle = Object . assign ( { } , this . style ( ) . listItemExpandIcon ) ;
let expandIconStyle = {
visibility : hasChildren ? 'visible' : 'hidden' ,
2018-05-09 11:49:31 +02:00
paddingLeft : 8 + depth * 10 ,
2018-05-09 10:53:47 +02:00
}
2018-05-09 11:49:31 +02:00
2018-05-09 13:39:17 +02:00
const iconName = this . props . collapsedFolderIds . indexOf ( folder . id ) >= 0 ? 'fa-plus-square' : 'fa-minus-square' ;
2018-05-09 11:49:31 +02:00
const expandIcon = < i style = { expandIconStyle } className = { "fa " + iconName } > < / i >
const expandLink = hasChildren ? < a style = { expandLinkStyle } href = "#" folderid = { folder . id } onClick = { this . onFolderToggleClick _ } > { expandIcon } < / a > : < span style = { expandLinkStyle } > { expandIcon } < / span >
2018-05-06 13:11:59 +02:00
2019-01-26 20:04:32 +02:00
const anchorRef = this . anchorItemRef ( 'folder' , folder . id ) ;
2018-03-09 21:51:01 +02:00
return (
2018-05-09 11:49:31 +02:00
< div className = "list-item-container" style = { containerStyle } key = { folder . id } onDragStart = { this . onFolderDragStart _ } onDragOver = { this . onFolderDragOver _ } onDrop = { this . onFolderDrop _ } draggable = { true } folderid = { folder . id } >
2018-05-09 10:53:47 +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 = "#"
data - id = { folder . id }
data - type = { BaseModel . TYPE _FOLDER }
onContextMenu = { event => this . itemContextMenu ( event ) }
style = { style }
2018-05-09 11:49:31 +02:00
folderid = { folder . id }
2018-05-06 13:11:59 +02:00
onClick = { ( ) => {
this . folderItem _click ( folder ) ;
} }
2018-05-09 11:49:31 +02:00
onDoubleClick = { this . onFolderToggleClick _ }
2018-05-06 13:11:59 +02:00
>
{ itemTitle }
< / a >
< / div >
2018-03-09 21:51:01 +02:00
) ;
2017-11-06 01:55:01 +02:00
}
tagItem ( tag , selected ) {
2018-05-09 16:31:42 +02:00
let style = Object . assign ( { } , this . style ( ) . tagItem ) ;
2017-11-09 00:48:19 +02:00
if ( selected ) style = Object . assign ( style , this . style ( ) . listItemSelected ) ;
2019-01-26 20:04:32 +02:00
const anchorRef = this . anchorItemRef ( 'tag' , tag . id ) ;
2018-03-09 21:51:01 +02:00
return (
< a
className = "list-item"
href = "#"
2019-01-26 20:04:32 +02:00
ref = { anchorRef }
2018-03-09 21:51:01 +02:00
data - id = { tag . id }
data - type = { BaseModel . TYPE _TAG }
onContextMenu = { event => this . itemContextMenu ( event ) }
2018-09-05 12:43:03 +02:00
tagid = { tag . id }
2018-03-09 21:51:01 +02:00
key = { tag . id }
style = { style }
2018-09-05 12:43:03 +02:00
onDrop = { this . onTagDrop _ }
2018-03-09 21:51:01 +02:00
onClick = { ( ) => {
this . tagItem _click ( tag ) ;
} }
>
{ Tag . displayTitle ( tag ) }
< / a >
) ;
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 ) {
2018-03-09 21:51:01 +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 ;
2019-04-28 15:20:18 +02:00
const icon = < i style = { { fontSize : style . fontSize , marginRight : 5 } } className = { "fa " + iconName } / > ;
2019-01-09 19:25:44 +02:00
if ( extraProps . toggleblock || extraProps . onClick ) {
style . cursor = "pointer" ;
}
let headerClick = extraProps . onClick || null ;
delete extraProps . onClick ;
// check if toggling option is set.
let toggleIcon = null ;
const toggleKey = ` ${ key } IsExpanded ` ;
if ( extraProps . toggleblock ) {
let isExpanded = this . state [ toggleKey ] ;
toggleIcon = < i className = { ` fa ${ isExpanded ? 'fa-chevron-down' : 'fa-chevron-left' } ` } style = { { fontSize : style . fontSize * 0.75 ,
marginRight : 12 , marginLeft : 5 , marginTop : style . fontSize * 0.125 } } > < / i > ;
}
2019-01-26 20:04:32 +02:00
const ref = this . anchorItemRef ( 'headers' , key ) ;
2018-03-09 21:51:01 +02:00
return (
2019-01-26 20:04:32 +02:00
< div ref = { ref } style = { style } key = { key } { ...extraProps } onClick = { ( event ) => {
2019-01-09 19:25:44 +02:00
// if a custom click event is attached, trigger that.
2019-01-09 19:33:52 +02:00
if ( headerClick ) {
headerClick ( key , event ) ;
2019-01-09 19:25:44 +02:00
}
2019-01-09 19:33:52 +02:00
this . onHeaderClick _ ( key , event ) ;
2019-01-09 19:25:44 +02:00
} } >
2018-03-09 21:51:01 +02:00
{ icon }
2019-01-09 19:25:44 +02:00
< span style = { { flex : 1 } } > { label } < / span >
{ 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 } ;
} else if ( this . props . notesParentType === 'Search' && this . props . selectedSearchId ) {
return { type : 'search' , id : this . props . selectedSearchId } ;
}
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
if ( keyCode === 40 || keyCode === 38 ) { // DOWN / UP
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 ] ;
let actionName = focusItem . type . toUpperCase ( ) + '_SELECT' ;
this . props . dispatch ( {
type : actionName ,
id : focusItem . id ,
} ) ;
focusItem . ref . current . focus ( ) ;
}
if ( keyCode === 9 ) { // TAB
event . preventDefault ( ) ;
if ( event . shiftKey ) {
this . props . dispatch ( {
type : 'WINDOW_COMMAND' ,
name : 'focusElement' ,
target : 'noteBody' ,
} ) ;
} else {
this . props . dispatch ( {
type : 'WINDOW_COMMAND' ,
name : 'focusElement' ,
target : 'noteList' ,
} ) ;
}
}
2019-01-26 20:09:09 +02:00
if ( selectedItem && selectedItem . type === 'folder' && keyCode === 32 ) { // SPACE
event . preventDefault ( ) ;
this . props . dispatch ( {
type : 'FOLDER_TOGGLE' ,
id : selectedItem . id ,
} ) ;
}
2019-01-26 20:04:32 +02:00
}
2019-01-09 19:33:52 +02:00
onHeaderClick _ ( key , event ) {
2019-01-09 19:25:44 +02:00
const currentHeader = event . currentTarget ;
const toggleBlock = + currentHeader . getAttribute ( 'toggleblock' ) ;
if ( toggleBlock ) {
const toggleKey = ` ${ key } IsExpanded ` ;
const isExpanded = this . state [ toggleKey ] ;
2019-01-09 19:33:52 +02:00
this . setState ( { [ toggleKey ] : ! isExpanded } ) ;
2019-01-09 19:25:44 +02:00
Setting . setValue ( toggleKey , ! isExpanded ) ;
}
}
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 } ) ;
2019-04-28 15:20:18 +02:00
const iconName = "fa-refresh" ;
2018-03-09 21:51:01 +02:00
const label = type === "sync" ? _ ( "Synchronise" ) : _ ( "Cancel" ) ;
2019-04-28 15:20:18 +02:00
let iconStyle = { fontSize : style . fontSize , marginRight : 5 } ;
if ( type !== 'sync' ) {
iconStyle . animation = 'icon-infinite-rotation 1s linear infinite' ;
}
const icon = < i style = { iconStyle } className = { "fa " + iconName } / > ;
2018-03-09 21:51:01 +02:00
return (
< a
className = "synchronize-button"
style = { style }
href = "#"
key = "sync_button"
onClick = { ( ) => {
this . sync _click ( ) ;
} }
>
{ icon }
{ label }
< / a >
) ;
2017-11-06 01:55:01 +02:00
}
render ( ) {
2017-11-08 19:51:55 +02:00
const theme = themeStyle ( this . props . theme ) ;
2017-11-12 01:13:14 +02:00
const style = Object . assign ( { } , this . style ( ) . root , this . props . style , {
2018-03-09 21:51:01 +02:00
overflowX : "hidden" ,
2019-01-26 20:36:20 +02:00
overflowY : "hidden" ,
display : 'inline-flex' ,
flexDirection : 'column' ,
2017-11-12 01:13:14 +02:00
} ) ;
2017-11-08 19:51:55 +02:00
2017-11-06 01:55:01 +02:00
let items = [ ] ;
2019-02-15 00:27:53 +02:00
items . push ( this . makeHeader ( "folderHeader" , _ ( "Notebooks" ) , "fa-book" , {
2018-05-09 10:53:47 +02:00
onDrop : this . onFolderDrop _ ,
folderid : '' ,
2019-01-09 19:25:44 +02:00
toggleblock : 1
2018-05-09 10:53:47 +02:00
} ) ) ;
2017-11-09 00:48:19 +02:00
2017-11-06 01:55:01 +02:00
if ( this . props . folders . length ) {
2019-01-26 20:04:32 +02:00
const result = shared . renderFolders ( this . props , this . folderItem . bind ( this ) ) ;
const folderItems = result . items ;
this . folderItemsOrder _ = result . order ;
2019-01-09 19:25:44 +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-01-09 19:25:44 +02:00
items . push ( this . makeHeader ( "tagHeader" , _ ( "Tags" ) , "fa-tags" , {
toggleblock : 1
} ) ) ;
2017-11-09 00:48:19 +02:00
2017-11-06 01:55:01 +02:00
if ( this . props . tags . length ) {
2019-01-26 20:04:32 +02:00
const result = shared . renderTags ( this . props , this . tagItem . bind ( this ) ) ;
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-01-09 19:25:44 +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 ) {
resourceFetcherText = _ ( 'Fetching resources: %d' , this . props . resourceFetcher . toFetchCount ) ;
}
2017-11-06 01:55:01 +02:00
let 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 (
< div key = { i } style = { { wordWrap : "break-word" , width : "100%" } } >
{ lines [ i ] }
< / div >
) ;
2017-11-12 19:53:26 +02:00
}
2017-11-06 01:55:01 +02:00
2019-01-26 20:36:20 +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-01-26 20:36:20 +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-02-16 03:12:43 +02:00
< div style = { { flex : 1 , overflowX : 'hidden' , overflowY : 'auto' } } >
2019-01-26 20:36:20 +02:00
{ 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 >
) ;
}
}
2018-03-09 21:51:01 +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 ,
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 ,
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
windowCommand : state . windowCommand ,
sidebarVisibility : state . sidebarVisibility ,
2017-11-06 01:55:01 +02:00
} ;
} ;
const SideBar = connect ( mapStateToProps ) ( SideBarComponent ) ;
2018-03-09 21:51:01 +02:00
module . exports = { SideBar } ;