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' ) ;
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' ) ;
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 ;
2019-07-29 14:13:23 +02:00
const InteropServiceHelper = require ( '../InteropServiceHelper.js' ) ;
2019-02-24 13:11:34 +02:00
const { substrWithEllipsis } = 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
class SideBarComponent extends React . Component {
2018-05-09 10:53:47 +02:00
constructor ( ) {
super ( ) ;
2019-07-29 14:13:23 +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 ] ) ) ;
} ;
2019-07-29 14:13:23 +02:00
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 ( ) ;
2018-05-09 10:53:47 +02:00
} ;
2019-07-29 14:13:23 +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 ++ ) {
await Folder . moveToFolder ( folderIds [ i ] , folderId ) ;
}
}
} ;
2018-05-09 11:49:31 +02:00
2019-07-29 14:13:23 +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 ] ) ;
}
}
2019-07-29 14:13:23 +02:00
} ;
2018-09-05 12:43:03 +02:00
2019-07-29 14:13:23 +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
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
}
2019-06-11 00:44:51 +02:00
style ( depth ) {
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 ,
2018-05-09 11:49:31 +02:00
// paddingLeft: 14,
2019-07-29 14:13:23 +02:00
display : 'flex' ,
alignItems : 'stretch' ,
2019-06-11 00:44:51 +02:00
// Allow 3 levels of color depth
2019-07-29 12:05:58 +02:00
backgroundColor : theme . depthColor . replace ( 'OPACITY' , Math . min ( depth * 0.1 , 0.3 ) ) ,
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' ,
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 ,
2018-05-09 10:53:47 +02:00
// fontFamily: theme.fontFamily,
2018-05-06 13:11:59 +02:00
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' ,
} ,
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' ,
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' ,
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 ,
} ,
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-12-18 13:16:13 +02:00
} else {
const anchorRef = this . firstAnchorItemRef ( 'folder' ) ;
console . info ( 'anchorRef' , anchorRef ) ;
if ( anchorRef ) anchorRef . current . focus ( ) ;
2019-01-26 20:04:32 +02:00
}
}
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
componentWillUnmount ( ) {
this . clearForceUpdateDuringSync ( ) ;
}
2017-11-08 19:51:55 +02:00
2019-07-29 14:13:23 +02:00
componentDidUpdate ( prevProps ) {
2019-01-26 20:04:32 +02:00
if ( prevProps . windowCommand !== this . props . windowCommand ) {
this . doCommand ( this . props . windowCommand ) ;
}
2019-07-29 14:13:23 +02:00
// 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
// // as a test force the update at regular intervals.
// // 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-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 ) ;
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 ) {
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 (
new MenuItem ( {
label : _ ( 'New sub-notebook' ) ,
click : ( ) => {
this . props . dispatch ( {
type : 'WINDOW_COMMAND' ,
name : 'newSubNotebook' ,
activeFolderId : itemId ,
} ) ;
} ,
} )
) ;
}
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 ) {
2018-03-09 21:51:01 +02:00
menu . append (
new MenuItem ( {
2019-07-29 14:13:23 +02:00
label : _ ( 'Rename' ) ,
2018-03-09 21:51:01 +02:00
click : async ( ) => {
this . props . dispatch ( {
2019-07-29 14:13:23 +02:00
type : 'WINDOW_COMMAND' ,
name : 'renameFolder' ,
2018-03-09 21:51:01 +02:00
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,
// });
// },
// })
// );
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 ) {
2018-05-20 09:39:32 +02:00
menu . append (
new MenuItem ( {
label : _ ( 'Rename' ) ,
click : async ( ) => {
this . props . dispatch ( {
2019-07-29 14:13:23 +02:00
type : 'WINDOW_COMMAND' ,
name : 'renameTag' ,
id : 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 ,
2020-03-15 11:40:57 +02:00
historyAction : 'goto' ,
2017-11-06 01:55:01 +02:00
} ) ;
}
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 ,
} ) ;
}
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 ) {
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 > ;
}
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
2019-06-11 00:44:51 +02:00
let containerStyle = Object . assign ( { } , this . style ( depth ) . listItemContainer ) ;
2018-05-06 13:11:59 +02:00
if ( selected ) containerStyle = Object . assign ( containerStyle , this . style ( ) . listItemSelected ) ;
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' ,
2018-05-09 11:49:31 +02:00
paddingLeft : 8 + depth * 10 ,
2019-07-29 14:13:23 +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' ;
2019-09-19 23:51:18 +02:00
const expandIcon = < i style = { expandIconStyle } className = { ` fa ${ iconName } ` } > < / i > ;
2019-07-29 14:13:23 +02:00
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 ) ;
2019-11-11 08:14:56 +02:00
const noteCount = folder . note _count ? this . noteCountElement ( folder . note _count ) : '' ;
2019-01-26 20:04:32 +02:00
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 } >
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 = "#"
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
>
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
}
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 ) ;
2019-11-11 08:14:56 +02:00
const noteCount = Setting . value ( 'showNoteCounts' ) ? this . noteCountElement ( tag . note _count ) : '' ;
2019-01-26 20:04:32 +02:00
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 ) ;
} }
>
2019-11-11 08:14:56 +02:00
{ Tag . displayTitle ( tag ) } { noteCount }
2018-03-09 21:51:01 +02:00
< / 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 ) {
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 ;
2019-09-19 23:51: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 ) {
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 ] ;
2019-07-29 14:13:23 +02:00
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-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 }
onClick = { event => {
// 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 ) {
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
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 } ) ;
2019-07-29 14:13:23 +02:00
const iconName = 'fa-refresh' ;
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' ;
}
2019-09-19 23:51:18 +02:00
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-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 ) {
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-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 ,
} )
) ;
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-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 >
) ;
}
}
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 ,
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 ,
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 ,
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 } ;