From cb9d95bcb2289064552fa5bdc287fe3b2398aa8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 19 Jun 2023 15:02:52 +0200 Subject: [PATCH] Static dir configuration and tree view of notes --- config.go | 4 +++ main.go | 31 ++++++++++++++++++-- node.go | 66 ++++++++++++++++++++++++++++++++++++++++++- static/css/main.css | 3 ++ static/js/node.mjs | 20 +++++++++++++ static/less/main.less | 4 +++ 6 files changed, 125 insertions(+), 3 deletions(-) diff --git a/config.go b/config.go index 6f70288..aec9c2b 100644 --- a/config.go +++ b/config.go @@ -23,6 +23,10 @@ type Config struct { Password string } + Application struct { + StaticDir string + } + Session struct { DaysValid int } diff --git a/main.go b/main.go index 56b875d..b32e672 100644 --- a/main.go +++ b/main.go @@ -59,11 +59,12 @@ func main() {// {{{ connectionManager = NewConnectionManager() go connectionManager.BroadcastLoop() - static = http.FileServer(http.Dir("./static")) + static = http.FileServer(http.Dir(config.Application.StaticDir)) http.HandleFunc("/css_updated", cssUpdateHandler) http.HandleFunc("/session/create", sessionCreate) http.HandleFunc("/session/retrieve", sessionRetrieve) http.HandleFunc("/session/authenticate", sessionAuthenticate) + http.HandleFunc("/node/tree", nodeTree) http.HandleFunc("/node/retrieve", nodeRetrieve) http.HandleFunc("/node/create", nodeCreate) http.HandleFunc("/node/update", nodeUpdate) @@ -185,6 +186,32 @@ func sessionAuthenticate(w http.ResponseWriter, r *http.Request) {// {{{ }) }// }}} +func nodeTree(w http.ResponseWriter, r *http.Request) {// {{{ + var err error + var session Session + + if session, _, err = ValidateSession(r, true); err != nil { + responseError(w, err) + return + } + + req := struct { StartNodeID int }{} + if err = parseRequest(r, &req); err != nil { + responseError(w, err) + return + } + + nodes, err := session.NodeTree(req.StartNodeID) + if err != nil { + responseError(w, err) + return + } + + responseData(w, map[string]interface{}{ + "OK": true, + "Nodes": nodes, + }) +}// }}} func nodeRetrieve(w http.ResponseWriter, r *http.Request) {// {{{ log.Println("/node/retrieve") var err error @@ -332,7 +359,7 @@ func newTemplate(requestPath string) (tmpl *template.Template, err error) {// {{ if p[len(p)-1] == '/' { p += "index.html" } - p = "static" + p + p = config.Application.StaticDir + p base := path.Base(p) if tmpl, err = template.New(base).ParseFiles(p); err != nil { return } diff --git a/node.go b/node.go index 9799108..50f69f8 100644 --- a/node.go +++ b/node.go @@ -5,7 +5,7 @@ import ( "github.com/jmoiron/sqlx" // Standard - + "time" ) type Node struct { @@ -14,11 +14,75 @@ type Node struct { ParentID int `db:"parent_id"` Name string Content string + Updated time.Time Children []Node Crumbs []Node Complete bool + Level int } +func (session Session) NodeTree(startNodeID int) (nodes []Node, err error) {// {{{ + var rows *sqlx.Rows + rows, err = db.Queryx(` + WITH RECURSIVE nodetree AS ( + SELECT + *, + array[name::text] AS path, + 0 AS level + FROM node + WHERE + user_id = $1 AND + CASE $2::int + WHEN 0 THEN parent_id IS NULL + ELSE parent_id = $2 + END + + UNION ALL + + SELECT + n.*, + path||n.name::text AS path, + nt.level + 1 AS level + FROM node n + INNER JOIN nodetree nt ON n.parent_id = nt.id + ) + + SELECT + id, + user_id, + COALESCE(parent_id, 0) AS parent_id, + name, + updated, + level + FROM nodetree + ORDER BY + path ASC + `, + session.UserID, + startNodeID, + ) + if err != nil { + return + } + defer rows.Close() + + type resultRow struct { + Node + Level int + } + + nodes = []Node{} + for rows.Next() { + node := Node{} + node.Complete = false + if err = rows.StructScan(&node); err != nil { + return + } + nodes = append(nodes, node) + } + + return +}// }}} func (session Session) RootNode() (node Node, err error) {// {{{ var rows *sqlx.Rows rows, err = db.Queryx(` diff --git a/static/css/main.css b/static/css/main.css index b4439e7..52dca9e 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -158,6 +158,9 @@ header .menu { border-radius: 8px; height: 48px; } +.tree { + padding: 16px; +} @media only screen and (max-width: 100ex) { .node-content { width: 100%; diff --git a/static/js/node.mjs b/static/js/node.mjs index d19ab12..9287eaa 100644 --- a/static/js/node.mjs +++ b/static/js/node.mjs @@ -7,6 +7,7 @@ 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=>{ @@ -21,6 +22,11 @@ export class NodeUI extends Component { 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
` @@ -58,6 +64,10 @@ export class NodeUI extends Component {
${crumbs} ${children.length > 0 ? html`
${children}
` : html``} +
this.retrieveTree()} style="color: #000"> +
Start
+ ${treeHTML} +
${node.ID > 0 ? html`
${node.Name}
@@ -146,6 +156,16 @@ export class NodeUI extends Component { }) .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 { diff --git a/static/less/main.less b/static/less/main.less index 70ef22c..4e25746 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -188,6 +188,10 @@ header { } } +.tree { + padding: 16px; +} + @media only screen and (max-width: 100ex) { .node-content { width: 100%;