Add ability to add notes to notebooks

Notes are added to the selected notebook on the left and they
appropriately update the numbers next to the labels.

Notes will also load based on notebook/menu item selection.
This commit is contained in:
Joey Payne 2016-03-11 15:21:50 -07:00
commit 7684e53c47
2 changed files with 263 additions and 98 deletions

View file

@ -7,6 +7,7 @@ import List from 'material-ui/lib/lists/list'
import ListItem from 'material-ui/lib/lists/list-item'
import Description from 'material-ui/lib/svg-icons/action/description'
import Add from 'material-ui/lib/svg-icons/content/add'
import Tag from 'material-ui/lib/svg-icons/maps/local-offer'
import SearchBar from 'SearchBar'
import SelectableList from 'SelectableList'
@ -16,13 +17,19 @@ import uuid from 'node-uuid'
import path from 'path-extra'
import * as utils from 'utils'
import glob from 'glob'
import glob from 'glob'
import moment from 'moment'
import fs from 'fs'
import mkdirp from 'mkdirp'
import jsfile from 'jsonfile'
import rmdir from 'rimraf'
import {
NOTEBOOK_TYPE,
MENU_TYPE
} from '../constants/navigation'
const {
IconButton,
AutoComplete,
@ -37,11 +44,20 @@ const DefaultRawTheme = Styles.LightRawTheme
export default class EntrySelector extends React.Component {
constructor(props){
super(props)
constructor(props, context){
super(props, context)
this.state = {notes: []}
this.loadNotes()
const { store } = this.context
store.subscribe(this.stateChanged)
}
stateChanged = () => {
const { store } = this.context
var selection = store.getState().navigation.selection
this.reloadNotes(selection)
};
static get childContextTypes(){
return {muiTheme: React.PropTypes.object}
}
@ -52,18 +68,146 @@ export default class EntrySelector extends React.Component {
}
}
loadNotes = () => {
var notebook = this.props.navigation.selection
var notes = utils.loadNotes(notebook)
this.state.notes = notes
}
reloadNotes = (selection) => {
var notebook = selection || this.props.navigation.selection
var notes = utils.loadNotes(notebook)
this.setState({notes: notes})
};
blank(){
}
createNotePath = (note, callback) => {
mkdirp(path.join(note.path, 'resources'), (err) => {
if(err){
console.log('There was an error creating the directory '+note.path)
console.log(err)
}
else{
var notes = this.state.notes
notes.splice(0, 0, note)
this.setState({notes: notes}, () => {
this.createNoteFiles(note, callback)
})
}
})
};
createNoteFiles = (note, callback) => {
var noteMeta = {
'created_at': note.created_at,
'updated_at': note.updated_at,
'tags': note.tags,
'title': note.title,
'uuid': note.uuid
}
var content = {
'title': note.title,
'cells': []
}
var metaPath = path.join(note.path, 'meta.json')
var contentPath = path.join(note.path, 'content.json')
jsfile.writeFile(metaPath, noteMeta, (err) => {
if(err){
console.log(err)
}
jsfile.writeFile(contentPath, content, (err) => {
if(err){
console.log(err)
}
if(utils.isFunction(callback)){
callback(note, err)
}
})
})
};
createNewNote = (callback) => {
var notebook = this.props.navigation.selection
var noteUUID = uuid.v4().toUpperCase()
var notePath = utils.getNotePathFromUUID(notebook, noteUUID)
var noteCreation = moment.utc().valueOf()/1000
var noteUpdated = noteCreation
var note = {
'created_at': noteCreation,
'updated_at': noteUpdated,
'tags': [],
'title': '',
'path': notePath,
'uuid': noteUUID,
'summary': '',
'notebook': notebook
}
this.createNotePath(note, callback)
};
addNoteTapped = () => {
console.log(this.props.navigation)
this.createNewNote((note, err)=>{
this.props.refreshNavigation()
})
};
renderNoteTags = (note) => {
if (note.tags.length > 0){
return (
<div className="inline">
<IconButton
className="tag-icon inline"
style={{'zIndex': 1000}}
touch={true}
tooltip={note.tags.join(", ")}
>
<Tag color={colors.grey400}/>
</IconButton>
<div className="tag-list inline">
{utils.trunc(note.tags.join(", "), 30)}
</div>
</div>
)
}
};
renderNoteInfo = (note) => {
let noteInfoStyle = {
position: "absolute",
top: 70,
left: 20,
color: colors.grey400,
fontSize: 14
}
return (<div style={noteInfoStyle}>
<div className="note-date inline">
{moment.unix(note.created_at).format('MMM D, YYYY')}
</div>
{this.renderNoteTags(note)}
</div>)
};
render(){
return (
<Paper id={this.props.id} className={this.props.className+ " noselect"} zDepth={0}>
<SearchBar
id="entry-search-bar"
rightIconButton={
<IconButton
style={{'zIndex': 1000}}
@ -81,21 +225,37 @@ export default class EntrySelector extends React.Component {
id="entry-list"
ref="entryList"
selectedItemStyle={{backgroundColor: colors.grey300}}>
<ListItem
leftIcon={<Description color={colors.grey600}/>}
primaryText="Today's Notes"
secondaryText={
<p>
<span style={{color: colors.darkBlack}}>I did things</span> --
I did so many things today, it's not even funny. You think it's funny?
Well it's not!
</p>
}
secondaryTextLines={2}
/>
{this.state.notes.map((note, i) =>{
return (<ListItem
key={i}
value={i}
leftIcon={<Description color={colors.grey600}/>}
innerDivStyle={{paddingBottom: 40}}
style={{borderBottom: '1px solid #F1F1F1'}}
secondaryText={
<p>
{note.summary}
</p>
}
secondaryTextLines={2}
>
<div>
{note.title || "Untitled Note"}
</div>
{this.renderNoteInfo(note)}
</ListItem>
)
})
}
</SelectableList>
</Paper>
)
}
}
EntrySelector.contextTypes = {
store: React.PropTypes.object
}

View file

@ -23,7 +23,6 @@ import path from 'path-extra'
import * as utils from 'utils'
import glob from 'glob'
import fs from 'fs'
import mkdirp from 'mkdirp'
import jsfile from 'jsonfile'
import rmdir from 'rimraf'
@ -51,49 +50,51 @@ export default class LibraryNav extends React.Component {
super(props, context)
this.state = {
open: true,
navItems: [
{
'name': 'Entries',
'isNotebook': true,
'icon': <img src="images/note.svg"/>,
'clicked': this.props.entriesTapped || this.entriesTapped
},
{
'name': 'Starred',
'notes': 0,
'icon': <ActionGrade color={colors.amberA700}/>,
'clicked': this.props.starredTapped || this.starredTapped
},
{
'name': 'Recents',
'notes': 0,
'icon': <History color="#4BAE4E"/>,
'clicked': this.props.recentsTapped || this.recentsTapped
},
{
'name': 'Trash',
'isNotebook': true,
'icon': <Delete color={colors.grey500}/>,
'clicked': this.props.trashTapped || this.trashTapped
},
{
'name': 'All Notes',
'notes': 0,
'glob': '*.qvnotebook/*.qvnote',
'icon': <Folder color="#FFCC5F" />,
'clicked': this.props.allNotesTapped || this.allNotesTapped
},
],
notebooks: [
]
}
this.navItems = [
{
'name': 'Entries',
'isNotebook': true,
'icon': <img src="images/note.svg"/>,
'clicked': this.props.entriesTapped || this.entriesTapped
},
{
'name': 'Starred',
'notes': 0,
'icon': <ActionGrade color={colors.amberA700}/>,
'clicked': this.props.starredTapped || this.starredTapped
},
{
'name': 'Recents',
'notes': 0,
'icon': <History color="#4BAE4E"/>,
'clicked': this.props.recentsTapped || this.recentsTapped
},
{
'name': 'Trash',
'isNotebook': true,
'icon': <Delete color={colors.grey500}/>,
'clicked': this.props.trashTapped || this.trashTapped
},
{
'name': 'All Notes',
'notes': 0,
'glob': '*.qvnotebook/*.qvnote',
'icon': <Folder color="#FFCC5F" />,
'clicked': this.props.allNotesTapped || this.allNotesTapped
}
]
this.loadDefaultNotebooks()
this.getNotebooks()
const { store } = this.context
store.subscribe(this.stateChanged)
}
loadDefaultNotebooks = () => {
var notebooks = this.state.navItems
var notebooks = this.navItems
for(var i=0; i<notebooks.length; i++){
var nb = notebooks[i]
if(nb.isNotebook){
@ -120,6 +121,10 @@ export default class LibraryNav extends React.Component {
nb.uuid = nb.name
nb.notes = notes.length
}
if(i==0){
this.props.updateSelection(nb)
}
this.props.addMenuItem(nb)
}
};
@ -144,27 +149,9 @@ export default class LibraryNav extends React.Component {
nb.state = 'displaying'
}
this.state.notebooks.push(nb)
}
this.sortNotebooks(true)
};
compareNotebooks = (a, b) => {
let atitle = a.title.toLowerCase()
let btitle = b.title.toLowerCase()
if(atitle > btitle)
return 1
if(atitle < btitle)
return -1
return 0
};
sortNotebooks = (dontSet, func) => {
this.state.notebooks.sort(this.compareNotebooks)
if (!dontSet){
this.setState({notebooks: this.state.notebooks}, func)
this.props.addNotebook(nb)
}
this.props.sortNotebooks(utils.compareNotebooks)
};
static get childContextTypes(){
@ -203,14 +190,17 @@ export default class LibraryNav extends React.Component {
scrollToRenamedNotebook = (original) => {
var newIndex = 0
for (var j = 0; j < this.state.notebooks.length; j++){
var n = this.state.notebooks[j]
for (var j = 0; j < this.props.navigation.notebooks.length; j++){
var n = this.props.navigation.notebooks[j]
if (n == original){
newIndex = j
this.refs.notebookList.setIndex(j, () => {
var nbitem = $(ReactDOM.findDOMNode(this.refs[n.title+newIndex]))
var cont = $('#notebook-list')
var newPos = nbitem.offset().top - cont.offset().top + cont.scrollTop() - cont.height()/2;
var newPos = 0
if(typeof nbitem.offset() != 'undefined'){
newPos = nbitem.offset().top - cont.offset().top + cont.scrollTop() - cont.height()/2;
}
$('#notebook-list').animate({
scrollTop: newPos
})
@ -243,12 +233,8 @@ export default class LibraryNav extends React.Component {
console.log(err)
}
else{
var nbs = this.state.notebooks
nbs.splice(0, 0, notebook)
this.setState({notebooks: nbs}, () => {
this.createNotebookMeta(notebook, callback)
})
this.props.addNotebook(notebook)
this.createNotebookMeta(notebook, callback)
}
})
@ -288,22 +274,24 @@ export default class LibraryNav extends React.Component {
};
newNotebookUnfocus = (i) => {
var nb = this.state.notebooks[i]
var nb = this.props.navigation.notebooks[i]
var tf = this.refs["textField"+i]
var notebookName = tf.getValue()
if (notebookName){
this.refs.mainList.setIndex(-1)
nb.title = notebookName
nb.state = 'displaying'
this.setState({notebooks: this.state.notebooks})
this.sortNotebooks(false, ()=>{
this.scrollToRenamedNotebook(nb)
this.props.updateNotebook(nb, i, () => {
this.props.sortNotebooks(this.compareNotebooks, ()=>{
this.scrollToRenamedNotebook(nb)
})
})
this.createNotebookMeta(nb)
}
else if(nb.title){
nb.state = 'displaying'
this.setState({notebooks: this.state.notebooks})
this.props.updateNotebook(nb, i)
}
};
@ -325,7 +313,7 @@ export default class LibraryNav extends React.Component {
menuItemClicked = (i, ev) => {
this.refs.notebookList.setIndex(-1)
var item = this.state.navItems[i]
var item = this.props.navigation.menuItems[i]
var type = 'leftClick'
var nativeEvent = ev.nativeEvent
if(nativeEvent.button == 2){
@ -335,6 +323,7 @@ export default class LibraryNav extends React.Component {
if (item.isNotebook){
var notebook = utils.loadNotebookByName(item.name)
notebook = utils.updateObject(item, notebook)
this.props.updateSelection(notebook)
}
else if(item.glob){
@ -345,23 +334,23 @@ export default class LibraryNav extends React.Component {
};
renameTapped = (i) => {
var nbs = this.state.notebooks
var nbs = this.props.navigation.notebooks
nbs[i].state = 'editing'
this.setState({notebooks: nbs}, () => {
this.props.updateNotebook(nbs[i], i, () => {
this.refs['textField'+i].focus()
})
this.props.closeContextMenu()
}
deleteTapped = (i, callback) => {
var nbs = this.state.notebooks
var nb = nbs.splice(i, 1)[0]
var nbs = this.props.navigation.notebooks
var nb = nbs.slice(i, 1)[0]
rmdir(nb.path, (err)=>{
if(err){
console.log(err)
}
this.setState({notebooks: nbs}, ()=>{
this.props.removeNotebook(i, () =>{
if(utils.isFunction(callback)){
callback(nb, err)
}
@ -399,7 +388,7 @@ export default class LibraryNav extends React.Component {
this.props.openContextMenu(x, y)
}
else{
this.props.updateSelection(this.state.notebooks[i])
this.props.updateSelection(this.props.navigation.notebooks[i])
}
this.refs.mainList.setIndex(-1)
@ -412,7 +401,7 @@ export default class LibraryNav extends React.Component {
notebookList = () => {
return (<div id="notebook-list">
{this.state.notebooks.map((notebook, i) =>{
{this.props.navigation.notebooks.map((notebook, i) =>{
var l = null
if (notebook.state == 'editing'){
l = <ListItem
@ -448,6 +437,8 @@ export default class LibraryNav extends React.Component {
leftIcon={
<IconButton
onClick={this.preventEventProp}
tooltip={notebook.title}
touch={true}
onTouchTap={this.notebookIconTapped.bind(this, i)}
style={{padding: 0}}
>
@ -470,16 +461,26 @@ export default class LibraryNav extends React.Component {
ev.stopPropagation()
};
stateChanged = () => {
const { store } = this.context
const state = store.getState()
if(utils.isFunction(state.navigation.callback)){
state.navigation.callback(state)
}
};
render(){
return (
<div id={this.props.id} className={this.props.className+" leftnav noselect"} open={this.state.open}>
<SelectableList
ref='mainList'
className="list"
initialIndex={0}
id="main-nav"
selectedItemStyle={{backgroundColor: colors.lightBlue100}}
subheader="Library">
{this.state.navItems.map((item, i) => {
{this.props.navigation.menuItems.map((item, i) => {
return <ListItem
primaryText={item.name}
ref={item.name}
@ -516,6 +517,10 @@ export default class LibraryNav extends React.Component {
}
}
LibraryNav.contextTypes = {
store: React.PropTypes.object
}
LibraryNav.defaultProps = {
closeContextMenu: () => {},
updateSelection: () => {}