import { h, Component, createRef } from 'preact' import htm from 'htm' import { signal } from 'preact/signals' const html = htm.bind(h) export class NodeUI extends Component { constructor() {//{{{ super() this.menu = signal(false) this.tree = signal(null) this.node = signal(null) this.nodeContent = createRef() window.addEventListener('popstate', evt=>{ if(evt.state && evt.state.hasOwnProperty('nodeID')) this.goToNode(evt.state.nodeID, true) else this.goToNode(0, true) }) window.addEventListener('keydown', evt=>this.keyHandler(evt)) }//}}} render() {//{{{ if(this.node.value === null) return let node = this.node.value let tree = this.tree.value let treeHTML = html`Tree` if(tree !== null) treeHTML = this.renderTree(tree) let crumbs = [ html`
this.goToNode(0)}>Start
` ] crumbs = crumbs.concat(node.Crumbs.slice(0).map(node=> html`
this.goToNode(node.ID)}>${node.Name}
` ).reverse()) let children = node.Children.sort((a,b)=>{ if(a.Name.toLowerCase() > b.Name.toLowerCase()) return 1; if(a.Name.toLowerCase() < b.Name.toLowerCase()) return -1; return 0 }).map(child=>html`
this.goToNode(child.ID)}>${child.Name}
`) let modified = '' if(this.props.app.nodeModified.value) modified = 'modified'; return html`
this.saveNode()}>
Notes
this.createNode(evt)}>+
${crumbs} ${children.length > 0 ? html`
${children}
` : html``} ${node.ID > 0 ? html`
${node.Name}
<${NodeContent} key=${node.ID} content=${node.Content} ref=${this.nodeContent} /> ` : html``} ` }//}}} componentDidMount() {//{{{ let urlParams = new URLSearchParams(window.location.search) let nodeID = urlParams.get('node') let root = new Node(this.props.app, nodeID ? parseInt(nodeID) : 0) root.retrieve(node=>{ this.node.value = node }) }//}}} keyHandler(evt) {//{{{ let handled = true switch(evt.key.toUpperCase()) { case 'S': if(!evt.ctrlKey) { handled = false break } this.saveNode() break case 'N': if(!evt.ctrlKey && !evt.AltKey) { handled = false break } this.createNode() 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}`) let node = new Node(this.props.app, nodeID) node.retrieve(node=>{ this.props.app.nodeModified.value = false this.node.value = node }) }//}}} createNode(evt) {//{{{ evt.stopPropagation() let name = prompt("Name") if(!name) return this.props.app.request('/node/create', { Name: name.trim(), ParentID: this.node.value.ID, }) .then(res=>{ this.goToNode(res.Node.ID) }) .catch(this.props.app.responseError) }//}}} saveNode() {//{{{ let content = this.nodeContent.current.contentDiv.current.value this.props.app.request('/node/update', { NodeID: this.node.value.ID, Content: content, }) .then(res=>{ this.props.app.nodeModified.value = false }) .catch(this.props.app.responseError) }//}}} renameNode() {//{{{ let name = prompt("New name") if(!name) return this.props.app.request('/node/rename', { Name: name.trim(), NodeID: this.node.value.ID, }) .then(_=>{ this.goToNode(this.node.value.ID) this.menu.value = false }) .catch(this.props.app.responseError) }//}}} deleteNode() {//{{{ if(!confirm("Do you want to delete this note and all sub-notes?")) return this.props.app.request('/node/delete', { NodeID: this.node.value.ID, }) .then(_=>{ this.goToNode(this.node.value.ParentID) this.menu.value = false }) .catch(this.props.app.responseError) }//}}} retrieveTree() {//{{{ this.props.app.request('/node/tree', { StartNodeID: this.node.value.ID }) .then(res=>{ this.tree.value = res.Nodes }) .catch(this.props.app.responseError) }//}}} renderTree(tree) {//{{{ return tree.map(node=>html`
${node.Name}
`) }//}}} } class NodeContent extends Component { constructor(props) {//{{{ super(props) this.contentDiv = createRef() this.state = { modified: false, //content: props.content, } }//}}} render({ content }) {//{{{ return html` ` }//}}} componentDidMount() {//{{{ this.resize() }//}}} componentDidUpdate() {//{{{ this.resize() }//}}} contentChanged() {//{{{ window._app.current.nodeModified.value = true this.resize() }//}}} resize() {//{{{ let textarea = this.contentDiv.current; textarea.style.height = "auto"; textarea.style.height = textarea.scrollHeight + 16 + "px"; }//}}} } class Node { constructor(app, nodeID) {//{{{ this.app = app this.ID = nodeID this.ParentID = 0 this.UserID = 0 this.Name = '' this.Content = '' this.Children = [] this.Crumbs = [] }//}}} retrieve(callback) {//{{{ this.app.request('/node/retrieve', { ID: this.ID }) .then(res=>{ this.ParentID = res.Node.ParentID this.UserID = res.Node.UserID this.Name = res.Node.Name this.Content = res.Node.Content this.Children = res.Node.Children this.Crumbs = res.Node.Crumbs callback(this) }) .catch(this.app.responseError) }//}}} } // vim: foldmethod=marker