aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--assets/css/list.css5
-rw-r--r--assets/js/search.js31
-rw-r--r--main.go2
-rw-r--r--modules/search/search.go81
-rw-r--r--views/instant_search_results.html9
-rw-r--r--views/list.html92
6 files changed, 184 insertions, 36 deletions
diff --git a/assets/css/list.css b/assets/css/list.css
index 7e6dde9..601f69b 100644
--- a/assets/css/list.css
+++ b/assets/css/list.css
@@ -24,4 +24,9 @@
24 background-size: cover; 24 background-size: cover;
25 width: 64px; 25 width: 64px;
26 height: 64px; 26 height: 64px;
27}
28
29.search-results {
30 top: -50px;
31 z-index: 2;
27} \ No newline at end of file 32} \ No newline at end of file
diff --git a/assets/js/search.js b/assets/js/search.js
new file mode 100644
index 0000000..0f544ab
--- /dev/null
+++ b/assets/js/search.js
@@ -0,0 +1,31 @@
1var strokeTimeout = null;
2
3function instantSearch() {
4 value = document.getElementById("search").value;
5
6 if (strokeTimeout) {
7 clearTimeout(strokeTimeout);
8 }
9 strokeTimeout = setTimeout(doSearch, 150, value);
10}
11
12function doSearch(value) {
13 if (value == "") {
14 strokeTimeout = null;
15 return;
16 }
17 var xhr = new XMLHttpRequest();
18 xhr.onreadystatechange = function() {
19 if (xhr.readyState == XMLHttpRequest.DONE) {
20 if (xhr.status === 404 || xhr.status === 400 || typeof xhr.response === 'undefined') {
21 console.log(xhr.status);
22 document.getElementById("search-results").innerHTML = '';
23 }
24 document.getElementById("search-results").innerHTML = xhr.response;
25 }
26 }
27 console.log(value);
28 xhr.open('GET', '/search/gril_instant/' + value, true);
29 xhr.send(null);
30 strokeTimeout = null;
31} \ No newline at end of file
diff --git a/main.go b/main.go
index 850f0f9..10003cf 100644
--- a/main.go
+++ b/main.go
@@ -11,6 +11,7 @@ import (
11 "fagott.pw/grilist/grilist" 11 "fagott.pw/grilist/grilist"
12 "fagott.pw/grilist/modules/grils" 12 "fagott.pw/grilist/modules/grils"
13 "fagott.pw/grilist/modules/lists" 13 "fagott.pw/grilist/modules/lists"
14 "fagott.pw/grilist/modules/search"
14 "fagott.pw/grilist/modules/tags" 15 "fagott.pw/grilist/modules/tags"
15 16
16 "github.com/julienschmidt/httprouter" 17 "github.com/julienschmidt/httprouter"
@@ -79,6 +80,7 @@ func main() {
79 loadModule(grils.New()) 80 loadModule(grils.New())
80 loadModule(lists.New()) 81 loadModule(lists.New())
81 loadModule(tags.New()) 82 loadModule(tags.New())
83 loadModule(search.New())
82 84
83 log.Fatal(http.ListenAndServe(":8080", nil)) 85 log.Fatal(http.ListenAndServe(":8080", nil))
84} 86}
diff --git a/modules/search/search.go b/modules/search/search.go
new file mode 100644
index 0000000..da6d126
--- /dev/null
+++ b/modules/search/search.go
@@ -0,0 +1,81 @@
1package search
2
3import (
4 "log"
5 "net/http"
6
7 "fagott.pw/charakterin"
8 "fagott.pw/grilist/grilist"
9 "fagott.pw/grilist/modules/grils"
10
11 "github.com/julienschmidt/httprouter"
12)
13
14type Module struct {
15 g *grilist.Grilist
16 grils *grils.GrilsModule
17}
18
19func New() *Module {
20 return &Module{}
21}
22
23func (m *Module) Name() string {
24 return "Search"
25}
26
27func (m *Module) Init(g *grilist.Grilist) {
28 m.g = g
29 gm, ok := g.Modules["Grils"]
30 if !ok {
31 log.Fatal("search: grils module not found")
32 }
33 m.grils = gm.(*grils.GrilsModule)
34
35 m.g.Router.GET("/search/gril_instant/*name", m.instantSearchGril)
36}
37
38func (m *Module) Interface() interface{} {
39 return m
40}
41
42func (m *Module) ProvideDashboardData(user *charakterin.User) []grilist.DashboardCategory {
43 return make([]grilist.DashboardCategory, 0)
44}
45
46func (m *Module) instantSearchGril(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
47 name := p.ByName("name")[1:]
48 if len(name) < 2 {
49 http.Error(w, "Bad Request", 400)
50 return
51 }
52
53 rows, err := m.g.DB.Query(`SELECT id FROM grilist.search_grils($1)`, name)
54 if err != nil {
55 log.Println("error in instant gril search:", err)
56 return
57 }
58
59 defer rows.Close()
60
61 var ids []int
62 for rows.Next() {
63 var id int
64 if err := rows.Scan(&id); err != nil {
65 log.Println("error scanning in instant gril search", err)
66 continue
67 }
68
69 ids = append(ids, id)
70 }
71
72 grils, err := m.grils.FromIDs(ids)
73 if err != nil {
74 log.Println("error acquiring instant grils:", err)
75 return
76 }
77
78 data := make(map[string]interface{})
79 data["results"] = grils
80 m.g.Renderer.RenderPage("instant_search_results", w, data)
81}
diff --git a/views/instant_search_results.html b/views/instant_search_results.html
new file mode 100644
index 0000000..e05c17a
--- /dev/null
+++ b/views/instant_search_results.html
@@ -0,0 +1,9 @@
1{{ define "instant_search_results" }}
2{{ range .results }}
3<li class="collection-item avatar hoverable">
4 <img src="{{ .ImagePath true }}" alt="" class="circle">
5 <span class="title">{{ .RomajiName }}</span>
6 <p>{{ .KanjiName }}</p>
7</li>
8{{ end }}
9{{ end }} \ No newline at end of file
diff --git a/views/list.html b/views/list.html
index 0393d19..5114ab9 100644
--- a/views/list.html
+++ b/views/list.html
@@ -7,55 +7,75 @@
7 <title>grilist</title> 7 <title>grilist</title>
8 <link rel="stylesheet" href="/assets/css/list.css" /> 8 <link rel="stylesheet" href="/assets/css/list.css" />
9 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> 9 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
10 <script src="/assets/js/search.js"></script>
10 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> 11 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
11 </head> 12 </head>
12 <body> 13 <body>
13 {{ template "navbar" . }} 14 {{ template "navbar" . }}
14 <div class="container"> 15 <div class="container">
15 <h1>{{ $list.Name }}<small>von {{ $list.Owner.GetName }}</small></h1><br /> 16 <h1>{{ $list.Name }}<small>von {{ $list.Owner.GetName }}</small></h1><br />
16 <ul class="gril-list"> 17 <div class="row">
17 {{ range $index, $lg := $list.Grils }} 18 <div class="col s12 {{ if ($user) and eq $user.ID $list.Owner.ID }}l8{{ end }}">
18 <li id="{{ $index }}" class="col s12 m8 offset-m2 l6 offset-l3"> 19 <ul class="gril-list">
19 <div class="card-panel hoverable list-entry"> 20 {{ range $index, $lg := $list.Grils }}
20 <div class="row valign-wrapper"> 21 <li id="{{ $index }}">
21 <div class="col s1 rank-text"> 22 <div class="card-panel hoverable list-entry">
22 {{ $index }} 23 <div class="row valign-wrapper">
23 </div> 24 <div class="col s3 m2">
24 <div class="col s3 m2"> 25 <div class="circle gril-img" style="background-image: url(/{{ $lg.Gril.ImagePath true }})"> </div>
25 <div class="circle gril-img" style="background-image: url(/{{ $lg.Gril.ImagePath true }})"> </div> 26 </div>
26 </div> 27 <div class="col s6 m6">
27 <div class="col s6 m6"> 28 <span><a href="/gril/{{ $lg.Gril.Slug }}">{{ $lg.Gril.RomajiName }}</a><br />
28 <span><a href="/gril/{{ $lg.Gril.Slug }}">{{ $lg.Gril.RomajiName }}</a><br /> 29 <span class="jap-name">{{ $lg.Gril.KanjiName }}</span>
29 <span class="jap-name">{{ $lg.Gril.KanjiName }}</span> 30 </span>
30 </span> 31 </div>
31 </div> 32 {{ if ($user) and eq $user.ID $list.Owner.ID }}
32 {{ if ($user) and eq $user.ID $list.Owner.ID }} 33 <div class="col s3 m5">
33 <div class="col s2 m3"> 34 <div class="hide-on-med-and-up">
34 <div class="hide-on-med-and-up"> 35 <div class="row list-controls valign-wrapper">
35 <div class="row list-controls valign-wrapper"> 36 <div class="col s6 left-align">
36 <div class="col s6 left-align"> 37 <i class="material-icons grey-text" onClick="">keyboard_arrow_up</i>
37 <i class="material-icons grey-text" onClick="">keyboard_arrow_up</i> 38 <i class="material-icons grey-text" onClick="">keyboard_arrow_down</i>
38 <i class="material-icons grey-text" onClick="">keyboard_arrow_down</i> 39 </div>
40 <div class="col s4 left-align">
41 <i class="hide-on-med-and-up material-icons delete-icon grey-text" onClick="">delete</i>
42 </div>
43 </div>
39 </div> 44 </div>
40 <div class="col s4 left-align"> 45 <div class="hide-on-small-only">
41 <i class="hide-on-med-and-up material-icons delete-icon grey-text" onClick="">delete</i> 46 <div class="valign-wrapper">
47 <i class="medium material-icons grey-text" onClick="">keyboard_arrow_up</i>
48 <i class="medium material-icons grey-text" onClick="">keyboard_arrow_down</i>
49 <i class="material-icons grey-text" onClick="">delete</i>
42 </div> 50 </div>
43 </div> 51 </div>
44 </div> 52 </div>
45 <div class="hide-on-small-only"> 53 {{ end }}
46 <div class="valign-wrapper">
47 <i class="medium material-icons grey-text" onClick="">keyboard_arrow_up</i>
48 <i class="medium material-icons grey-text" onClick="">keyboard_arrow_down</i>
49 <i class="material-icons grey-text" onClick="">delete</i>
50 </div>
51 </div> 54 </div>
52 </div> 55 </div>
53 {{ end }} 56 </li>
54 </div> 57 {{ end }}
58 </ul>
59 </div>
60 {{ if ($user) and eq $user.ID $list.Owner.ID }}
61 <div class="col s12 l4">
62 <div class="card-panel" style="margin-top: 14px">
63 <span style="font-size: 130%">Gril hinzufügen</span><br />
64 <form>
65 <div class="input-field">
66 <input id="search" type="search" onKeyDown="instantSearch()" required>
67 <label for="search"><i class="material-icons">search</i></label>
68 <i class="material-icons">close</i>
69 </div>
70 </form>
71 </div>
72 <div class="col s10 offset-s1">
73 <ul class="collection search-results" id="search-results">
74 </ul>
55 </div> 75 </div>
56 </li> 76 </div>
57 {{ end }} 77 {{ end }}
58 </ul> 78 </div>
59 </div> 79 </div>
60 </body> 80 </body>
61</html> 81</html>