${page}
`
}//}}}
async componentDidMount() {//{{{
// When rendered and fetching the node, keys could be needed in order to
// decrypt the content.
await this.retrieveKeys()
this.props.app.startNode.retrieve(node=>{
this.node.value = node
// The tree isn't guaranteed to have loaded yet. This is also run from
// the tree code, in case the node hasn't loaded.
this.props.app.tree.crumbsUpdateNodes(node)
})
}//}}}
keyHandler(evt) {//{{{
let handled = true
// All keybindings is Alt+Shift, since the popular browsers at the time (2023) allows to override thees.
// Ctrl+S is the exception to using Alt+Shift, since it is overridable and in such widespread use for saving.
// Thus, the exception is acceptable to consequent use of alt+shift.
if(!(evt.shiftKey && evt.altKey) && !(evt.key.toUpperCase() == 'S' && evt.ctrlKey))
return
switch(evt.key.toUpperCase()) {
case 'E':
this.showPage('keys')
break
case 'N':
this.createNode()
break
case 'P':
this.showPage('node-properties')
break
case 'S':
this.saveNode()
break
case 'U':
this.showPage('upload')
break
case 'F':
this.showPage('search')
break
default:
handled = false
}
if(handled) {
evt.preventDefault()
evt.stopPropagation()
}
}//}}}
showMenu(evt) {//{{{
evt.stopPropagation()
this.menu.value = true
}//}}}
logout() {//{{{
window.localStorage.removeItem('session.UUID')
location.href = '/'
}//}}}
goToNode(nodeID, dontPush) {//{{{
if(this.props.app.nodeModified.value) {
if(!confirm("Changes not saved. Do you want to discard changes?"))
return
}
if(!dontPush)
history.pushState({ nodeID }, '', `/?node=${nodeID}`)
// New node is fetched in order to retrieve content and files.
// Such data is unnecessary to transfer for tree/navigational purposes.
let node = new Node(this.props.app, nodeID)
node.retrieve(node=>{
this.props.app.nodeModified.value = false
this.node.value = node
this.showPage('node')
// Tree needs to know another node is selected, in order to render any
// previously selected node not selected.
this.props.app.tree.setSelected(node)
// Hide tree toggle, as this would be the next natural action to do manually anyway.
// At least in mobile mode.
document.getElementById('app').classList.remove('toggle-tree')
})
}//}}}
createNode(evt) {//{{{
if(evt)
evt.stopPropagation()
let name = prompt("Name")
if(!name)
return
this.node.value.create(name, nodeID=>this.goToNode(nodeID))
}//}}}
saveNode() {//{{{
let content = this.nodeContent.current.contentDiv.current.value
this.node.value.setContent(content)
this.node.value.save(()=>this.props.app.nodeModified.value = false)
}//}}}
renameNode() {//{{{
let name = prompt("New name")
if(!name)
return
this.node.value.rename(name, ()=>{
this.goToNode(this.node.value.ID)
this.menu.value = false
})
}//}}}
deleteNode() {//{{{
if(!confirm("Do you want to delete this note and all sub-notes?"))
return
this.node.value.delete(()=>{
this.goToNode(this.node.value.ParentID)
this.menu.value = false
})
}//}}}
async retrieveKeys() {//{{{
return new Promise((resolve, reject)=>{
this.props.app.request('/key/retrieve', {})
.then(res=>{
this.keys.value = res.Keys.map(keyData=>new Key(keyData, this.keyCounter))
resolve(this.keys.value)
})
.catch(reject)
})
}//}}}
keyCounter() {//{{{
return window._app.current.request('/key/counter', {})
.then(res=>BigInt(res.Counter))
.catch(window._app.current.responseError)
}//}}}
getKey(id) {//{{{
let keys = this.keys.value
for(let i = 0; i < keys.length; i++)
if(keys[i].ID == id)
return keys[i]
return null
}//}}}
showPage(pg) {//{{{
this.page.value = pg
}//}}}
}
class NodeContent extends Component {
constructor(props) {//{{{
super(props)
this.contentDiv = createRef()
this.state = {
modified: false,
}
}//}}}
render({ node }) {//{{{
let content = ''
try {
content = node.content()
} catch(err) {
return html`
`
}//}}}
async save() {//{{{
let nodeui = this.props.nodeui
let node = nodeui.node.value
// Find the actual key object used for encryption
let new_key = nodeui.getKey(this.selected_key_id)
let current_key = nodeui.getKey(node.CryptoKeyID)
if(current_key && current_key.status() == 'locked') {
alert("Decryption key is locked and can not be used.")
return
}
if(new_key && new_key.status() == 'locked') {
alert("Key is locked and can not be used.")
return
}
await node.setCryptoKey(new_key)
node.save(()=>this.props.nodeui.showPage('node'))
}//}}}
}
class Search extends Component {
constructor() {//{{{
super()
this.state = {
matches: [],
results_returned: false,
}
}//}}}
render({ nodeui }, { matches, results_returned }) {//{{{
let match_elements = [
html`