diff options
-rw-r--r-- | assets/css/list.css | 5 | ||||
-rw-r--r-- | assets/js/search.js | 31 | ||||
-rw-r--r-- | main.go | 2 | ||||
-rw-r--r-- | modules/search/search.go | 81 | ||||
-rw-r--r-- | views/instant_search_results.html | 9 | ||||
-rw-r--r-- | views/list.html | 92 |
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 @@ | |||
1 | var strokeTimeout = null; | ||
2 | |||
3 | function instantSearch() { | ||
4 | value = document.getElementById("search").value; | ||
5 | |||
6 | if (strokeTimeout) { | ||
7 | clearTimeout(strokeTimeout); | ||
8 | } | ||
9 | strokeTimeout = setTimeout(doSearch, 150, value); | ||
10 | } | ||
11 | |||
12 | function 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 | ||
@@ -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 @@ | |||
1 | package search | ||
2 | |||
3 | import ( | ||
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 | |||
14 | type Module struct { | ||
15 | g *grilist.Grilist | ||
16 | grils *grils.GrilsModule | ||
17 | } | ||
18 | |||
19 | func New() *Module { | ||
20 | return &Module{} | ||
21 | } | ||
22 | |||
23 | func (m *Module) Name() string { | ||
24 | return "Search" | ||
25 | } | ||
26 | |||
27 | func (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 | |||
38 | func (m *Module) Interface() interface{} { | ||
39 | return m | ||
40 | } | ||
41 | |||
42 | func (m *Module) ProvideDashboardData(user *charakterin.User) []grilist.DashboardCategory { | ||
43 | return make([]grilist.DashboardCategory, 0) | ||
44 | } | ||
45 | |||
46 | func (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> |