Added basic search.

This commit is contained in:
Magnus Åhall 2023-07-19 10:00:36 +02:00
parent 57180e986e
commit c58a1b988b
6 changed files with 211 additions and 21 deletions

31
main.go
View File

@ -76,6 +76,7 @@ func main() {// {{{
http.HandleFunc("/node/delete", nodeDelete) http.HandleFunc("/node/delete", nodeDelete)
http.HandleFunc("/node/upload", nodeUpload) http.HandleFunc("/node/upload", nodeUpload)
http.HandleFunc("/node/download", nodeDownload) http.HandleFunc("/node/download", nodeDownload)
http.HandleFunc("/node/search", nodeSearch)
http.HandleFunc("/key/retrieve", keyRetrieve) http.HandleFunc("/key/retrieve", keyRetrieve)
http.HandleFunc("/key/create", keyCreate) http.HandleFunc("/key/create", keyCreate)
http.HandleFunc("/key/counter", keyCounter) http.HandleFunc("/key/counter", keyCounter)
@ -549,6 +550,36 @@ func nodeFiles(w http.ResponseWriter, r *http.Request) {// {{{
"Files": files, "Files": files,
}) })
}// }}} }// }}}
func nodeSearch(w http.ResponseWriter, r *http.Request) {// {{{
log.Println("/node/search")
var err error
var session Session
var nodes []Node
if session, _, err = ValidateSession(r, true); err != nil {
responseError(w, err)
return
}
req := struct {
Search string
}{}
if err = parseRequest(r, &req); err != nil {
responseError(w, err)
return
}
nodes, err = session.SearchNodes(req.Search)
if err != nil {
responseError(w, err)
return
}
responseData(w, map[string]interface{}{
"OK": true,
"Nodes": nodes,
})
}// }}}
func keyRetrieve(w http.ResponseWriter, r *http.Request) {// {{{ func keyRetrieve(w http.ResponseWriter, r *http.Request) {// {{{
log.Println("/key/retrieve") log.Println("/key/retrieve")

33
node.go
View File

@ -373,5 +373,38 @@ func (session Session) DeleteNode(nodeID int) (err error) {// {{{
) )
return return
}// }}} }// }}}
func (session Session) SearchNodes(search string) (nodes []Node, err error) {// {{{
nodes = []Node{}
var rows *sqlx.Rows
rows, err = db.Queryx(`
SELECT
id,
user_id,
COALESCE(parent_id, 0) AS parent_id,
name,
updated
FROM node
WHERE
crypto_key_id IS NULL AND
content ~* $1
ORDER BY
updated DESC
`, search)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
node := Node{}
node.Complete = false
if err = rows.StructScan(&node); err != nil {
return
}
nodes = append(nodes, node)
}
return
}// }}}
// vim: foldmethod=marker // vim: foldmethod=marker

29
static/css/search.css Normal file
View File

@ -0,0 +1,29 @@
/*
@theme_gradient: linear-gradient(to right, #009fff, #ec2f4b);
@theme_gradient: linear-gradient(to right, #f5af19, #f12711);
@theme_gradient: linear-gradient(to right, #fdc830, #f37335);
@theme_gradient: linear-gradient(to right, #8a2387, #e94057, #f27121);
@theme_gradient: linear-gradient(to right, #659999, #f4791f);
*/
#search {
padding: 16px;
color: #333;
}
#search h2 {
margin-bottom: 8px;
}
#search input[type=text] {
font-size: 1em;
}
#search button {
display: block;
margin-top: 8px;
}
#search .matches .matched-node {
cursor: pointer;
margin-top: 6px;
}
#search .matches .matched-node:before {
content: "•";
margin-right: 6px;
}

View File

@ -5,6 +5,7 @@
<meta name="viewport" content="initial-scale=1.0, user-scalable=yes" /> <meta name="viewport" content="initial-scale=1.0, user-scalable=yes" />
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/main.css"> <link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/main.css">
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/login.css"> <link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/login.css">
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/search.css">
<script type="importmap"> <script type="importmap">
{ {
"imports": { "imports": {

View File

@ -85,6 +85,10 @@ export class NodeUI extends Component {
case 'keys': case 'keys':
page = html`<${Keys} nodeui=${this} />` page = html`<${Keys} nodeui=${this} />`
break break
case 'search':
page = html`<${Search} nodeui=${this} />`
break
} }
let menu = '' let menu = ''
@ -124,41 +128,37 @@ export class NodeUI extends Component {
keyHandler(evt) {//{{{ keyHandler(evt) {//{{{
let handled = true 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()) { switch(evt.key.toUpperCase()) {
case 'E': case 'E':
if(evt.shiftKey && evt.altKey) this.showPage('keys')
this.showPage('keys')
else
handled = false
break break
case 'N': case 'N':
if(evt.shiftKey && evt.altKey) this.createNode()
this.createNode()
else
handled = false
break break
case 'P': case 'P':
if(evt.shiftKey && evt.altKey) this.showPage('node-properties')
this.showPage('node-properties')
else
handled = false
break break
case 'S': case 'S':
if(evt.ctrlKey || (evt.shiftKey && evt.altKey)) this.saveNode()
this.saveNode()
else
handled = false
break break
case 'U': case 'U':
if(evt.shiftKey && evt.altKey) this.showPage('upload')
this.showPage('upload') break
else
handled = false case 'F':
this.showPage('search')
break
default: default:
handled = false handled = false
@ -723,4 +723,69 @@ class NodeProperties extends Component {
}//}}} }//}}}
} }
class Search extends Component {
constructor() {//{{{
super()
this.state = {
matches: [],
results_returned: false,
}
}//}}}
render({ nodeui }, { matches, results_returned }) {//{{{
let match_elements = [
html`<h2>Results</h2>`,
]
let matched_nodes = matches.map(node=>html`
<div class="matched-node" onclick=${()=>nodeui.goToNode(node.ID)}>
${node.Name}
</div>
`)
match_elements.push(html`<div class="matches">${matched_nodes}</div>`)
return html`
<div id="search">
<h1>Search</h1>
<input type="text" id="search-for" placeholder="Search for" onkeydown=${evt=>this.keyHandler(evt)} />
<button onclick=${()=>this.search()}>Search</button>
${results_returned ? match_elements : ''}
</div>`
}//}}}
componentDidMount() {//{{{
document.getElementById('search-for').focus()
}//}}}
keyHandler(evt) {//{{{
let handled = true
switch(evt.key.toUpperCase()) {
case 'ENTER':
this.search()
break
default:
handled = false
}
if(handled) {
evt.preventDefault()
evt.stopPropagation()
}
}//}}}
search() {//{{{
let Search = document.getElementById('search-for').value
window._app.current.request('/node/search', { Search })
.then(res=>{
this.setState({
matches: res.Nodes,
results_returned: true,
})
})
.catch(window._app.current.responseError)
}//}}}
}
// vim: foldmethod=marker // vim: foldmethod=marker

31
static/less/search.less Normal file
View File

@ -0,0 +1,31 @@
@import "theme.less";
#search {
padding: 16px;
color: #333;
h2 {
margin-bottom: 8px;
}
input[type=text] {
font-size: 1em;
}
button {
display: block;
margin-top: 8px;
}
.matches {
.matched-node {
cursor: pointer;
margin-top: 6px;
&:before {
content: "•";
margin-right: 6px;
}
}
}
}