Added basic search.
This commit is contained in:
parent
57180e986e
commit
c58a1b988b
31
main.go
31
main.go
@ -76,6 +76,7 @@ func main() {// {{{
|
||||
http.HandleFunc("/node/delete", nodeDelete)
|
||||
http.HandleFunc("/node/upload", nodeUpload)
|
||||
http.HandleFunc("/node/download", nodeDownload)
|
||||
http.HandleFunc("/node/search", nodeSearch)
|
||||
http.HandleFunc("/key/retrieve", keyRetrieve)
|
||||
http.HandleFunc("/key/create", keyCreate)
|
||||
http.HandleFunc("/key/counter", keyCounter)
|
||||
@ -549,6 +550,36 @@ func nodeFiles(w http.ResponseWriter, r *http.Request) {// {{{
|
||||
"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) {// {{{
|
||||
log.Println("/key/retrieve")
|
||||
|
33
node.go
33
node.go
@ -373,5 +373,38 @@ func (session Session) DeleteNode(nodeID int) (err error) {// {{{
|
||||
)
|
||||
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
|
||||
|
29
static/css/search.css
Normal file
29
static/css/search.css
Normal 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;
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
<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 }}/login.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/search.css">
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
|
@ -85,6 +85,10 @@ export class NodeUI extends Component {
|
||||
case 'keys':
|
||||
page = html`<${Keys} nodeui=${this} />`
|
||||
break
|
||||
|
||||
case 'search':
|
||||
page = html`<${Search} nodeui=${this} />`
|
||||
break
|
||||
}
|
||||
|
||||
let menu = ''
|
||||
@ -124,41 +128,37 @@ export class NodeUI extends Component {
|
||||
|
||||
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':
|
||||
if(evt.shiftKey && evt.altKey)
|
||||
this.showPage('keys')
|
||||
else
|
||||
handled = false
|
||||
this.showPage('keys')
|
||||
break
|
||||
|
||||
case 'N':
|
||||
if(evt.shiftKey && evt.altKey)
|
||||
this.createNode()
|
||||
else
|
||||
handled = false
|
||||
this.createNode()
|
||||
break
|
||||
|
||||
case 'P':
|
||||
if(evt.shiftKey && evt.altKey)
|
||||
this.showPage('node-properties')
|
||||
else
|
||||
handled = false
|
||||
this.showPage('node-properties')
|
||||
break
|
||||
|
||||
case 'S':
|
||||
if(evt.ctrlKey || (evt.shiftKey && evt.altKey))
|
||||
this.saveNode()
|
||||
else
|
||||
handled = false
|
||||
this.saveNode()
|
||||
break
|
||||
|
||||
|
||||
case 'U':
|
||||
if(evt.shiftKey && evt.altKey)
|
||||
this.showPage('upload')
|
||||
else
|
||||
handled = false
|
||||
this.showPage('upload')
|
||||
break
|
||||
|
||||
case 'F':
|
||||
this.showPage('search')
|
||||
break
|
||||
|
||||
default:
|
||||
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
|
||||
|
31
static/less/search.less
Normal file
31
static/less/search.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user