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> |
