[static]: simple html framework
This commit is contained in:
parent
76039ada7e
commit
14802942c6
1
go.sum
1
go.sum
@ -2,6 +2,7 @@ gitea.difrex.ru/Umbrella/fetcher v0.0.0-20200723122826-e8bbdd12256b h1:K0vLl90b8
|
||||
gitea.difrex.ru/Umbrella/fetcher v0.0.0-20200723122826-e8bbdd12256b/go.mod h1:rcNfqAtzWqj1MsvxDuqTuqTNiJ7r6f1reQvsuUaiHYY=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/elastic/go-elasticsearch/v6 v6.8.10/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
|
@ -227,6 +227,7 @@ func Serve(listen string, es ESConf) {
|
||||
// Simple and clean SSR UI
|
||||
ssr := newSSR("./templates", es)
|
||||
r.HandleFunc("/", ssr.ssrRootHandler)
|
||||
r.HandleFunc("/forum", ssr.ssrForumHandler)
|
||||
|
||||
http.Handle("/", r)
|
||||
|
||||
|
@ -371,6 +371,42 @@ func (es ESConf) GetXC(echoes string) []string {
|
||||
return counts
|
||||
}
|
||||
|
||||
type ThreadBucket struct {
|
||||
DocCount int64 `json:"doc_count"`
|
||||
Key string `json:"key"`
|
||||
Post Hits
|
||||
}
|
||||
|
||||
func (es ESConf) GetThreads(echoes ...string) (posts []i2es.ESDoc) {
|
||||
query := `{"sort":[{"date":{"order":"desc"}}],"aggs":{"topics":{"terms":{"field":"topicid.keyword","size":100},"aggs":{"post":{"top_hits":{"size":1,"sort":[{"date":{"order":"desc"}}],"_source":{"include":["subg","author","date","echo","topicid","address"]}}}}}},"query":{"bool":{"must":[{"range":{"date":{"from":"now-30d","to":"now-0d"}}},{"constant_score":{"filter":{"terms":{"echo.keyword":["idec.talks","pipe.2032","linux.14","develop.16","dynamic.local","std.club","std.hugeping","oldpc.51t.ru","difrex.blog","ii.test.14"]}}}}]}}}`
|
||||
req, err := http.NewRequest("POST", es.searchURI(), bytes.NewReader([]byte(query)))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var data ESAggsResp
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
for _, bucket := range data.Aggregations.Topics.Buckets {
|
||||
for _, post := range bucket.Post.Hits.Hits {
|
||||
posts = append(posts, post.Source)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (es ESConf) GetLatestPosts(sum int) []i2es.ESDoc {
|
||||
query := fmt.Sprintf(`{"sort": [{"date": {"order": "desc"}}, {"_score": {"order": "desc" }}], "size": %d}`, sum)
|
||||
req, err := http.NewRequest("POST", es.searchURI(), bytes.NewBuffer([]byte(query)))
|
||||
|
@ -3,7 +3,6 @@ gitea.difrex.ru/Umbrella/fetcher v0.0.0-20200723122826-e8bbdd12256b/go.mod h1:rc
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@ -27,7 +26,6 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
53
node/ssr.go
53
node/ssr.go
@ -1,9 +1,7 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"gitea.difrex.ru/Umbrella/fetcher/i2es"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -21,47 +19,40 @@ func newSSR(templatesDir string, es ESConf) *ssr {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ssr) templatePath(name string) string {
|
||||
return s.templatesDir + string(os.PathSeparator) + name
|
||||
type PageData struct {
|
||||
Echoes []echo
|
||||
CurrentPage string
|
||||
Posts []i2es.ESDoc
|
||||
}
|
||||
|
||||
func (s *ssr) getTemplate(name string) (*template.Template, error) {
|
||||
t := template.New("root.html")
|
||||
|
||||
tpl, err := t.ParseFiles(
|
||||
s.templatePath(name+".html"),
|
||||
s.templatePath("style.html"),
|
||||
s.templatePath("echoes.html"),
|
||||
s.templatePath("post.html"),
|
||||
s.templatePath("latest_posts.html"),
|
||||
s.templatePath("header.html"),
|
||||
s.templatePath("footer.html"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (s *ssr) newPageData(page string, posts []i2es.ESDoc) *PageData {
|
||||
return &PageData{
|
||||
Echoes: s.es.GetEchoesList(),
|
||||
Posts: posts,
|
||||
CurrentPage: page,
|
||||
}
|
||||
|
||||
return tpl, nil
|
||||
}
|
||||
|
||||
func (s *ssr) ssrRootHandler(w http.ResponseWriter, r *http.Request) {
|
||||
LogRequest(r)
|
||||
|
||||
tpl, err := s.getTemplate("root")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
var root struct {
|
||||
Echoes []echo
|
||||
CurrentPage string
|
||||
Posts []i2es.ESDoc
|
||||
}
|
||||
root.Echoes = s.es.GetEchoesList()
|
||||
root.Posts = s.es.GetLatestPosts(100)
|
||||
|
||||
if err := tpl.Execute(w, root); err != nil {
|
||||
if err := tpl.Execute(w, s.newPageData("", s.es.GetLatestPosts(50))); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ssr) ssrForumHandler(w http.ResponseWriter, r *http.Request) {
|
||||
tpl, err := s.getTemplate("forum")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := tpl.Execute(w, s.newPageData("", s.es.GetThreads())); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,20 @@ type ESSearchResp struct {
|
||||
Hits `json:"hits"`
|
||||
}
|
||||
|
||||
type ESAggsResp struct {
|
||||
ESSearchResp
|
||||
Aggregations struct {
|
||||
Topics struct {
|
||||
Buckets []struct {
|
||||
Bucket
|
||||
Post struct {
|
||||
Hits Hits `json:"hits"`
|
||||
} `json:"post"`
|
||||
} `json:"buckets"`
|
||||
} `json:"topics"`
|
||||
} `json:"aggregations"`
|
||||
}
|
||||
|
||||
type Hits struct {
|
||||
Total int64 `json:"total"`
|
||||
MaxScore float64 `json:"max_score"`
|
||||
|
98
node/templates.go
Normal file
98
node/templates.go
Normal file
@ -0,0 +1,98 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// Common templates
|
||||
CommonTplDir = "common"
|
||||
|
||||
// Reusable components
|
||||
ComponentsTplDir = "components"
|
||||
|
||||
// Something like component of components
|
||||
MetaTplDir = "meta"
|
||||
|
||||
// Main pages
|
||||
ViewsTplDir = "views"
|
||||
)
|
||||
|
||||
func (s *ssr) tplDir(name string) string {
|
||||
return strings.Join([]string{s.templatesDir, name}, string(os.PathSeparator))
|
||||
}
|
||||
|
||||
func (s *ssr) readTplDir(name string) ([]string, error) {
|
||||
var paths []string
|
||||
|
||||
dir, err := os.ReadDir(s.templatesDir + string(os.PathSeparator) + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range dir {
|
||||
if strings.HasSuffix(entry.Name(), ".html") {
|
||||
paths = append(paths, strings.Join(
|
||||
[]string{s.templatesDir, name, entry.Name()},
|
||||
string(os.PathSeparator)))
|
||||
}
|
||||
}
|
||||
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func (s *ssr) tplsPaths() ([]string, error) {
|
||||
var paths []string
|
||||
// Read common dir
|
||||
commonTpls, err := s.readTplDir(CommonTplDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, commonTpls...)
|
||||
|
||||
// Read components dir
|
||||
compTpls, err := s.readTplDir(ComponentsTplDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, compTpls...)
|
||||
|
||||
// Read meta dir
|
||||
metaTpls, err := s.readTplDir(MetaTplDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, metaTpls...)
|
||||
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func (s *ssr) componentsForTemplate(name string) []string {
|
||||
paths := []string{s.tplDir(ViewsTplDir) + string(os.PathSeparator) + name + ".html"}
|
||||
tpls, err := s.tplsPaths()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return paths
|
||||
}
|
||||
|
||||
return append(paths, tpls...)
|
||||
}
|
||||
|
||||
func (s *ssr) templatePath(name string) string {
|
||||
return s.templatesDir + string(os.PathSeparator) + name
|
||||
}
|
||||
|
||||
func (s *ssr) getTemplate(name string) (*template.Template, error) {
|
||||
t := template.New(name + ".html")
|
||||
|
||||
tpl, err := t.ParseFiles(s.componentsForTemplate(name)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tpl, nil
|
||||
}
|
12
templates/common/footer.html
Normal file
12
templates/common/footer.html
Normal file
@ -0,0 +1,12 @@
|
||||
{{ define "footer" }}
|
||||
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script> -->
|
||||
<p class="text-center">
|
||||
copyleft 2021 difrex at lessmore dot pw;
|
||||
|
||||
<a href="#">source code</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
{{ end }}
|
@ -6,27 +6,37 @@
|
||||
<body class="dynamic-bg">
|
||||
|
||||
<!-- Panel -->
|
||||
<nav class="navbar navbar-expand-lg position-static navbar-dark dynamic-panel mb-2">
|
||||
<nav class="navbar navbar-expand-lg position-static navbar-dark dynamic-panel mb-2 shadow">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">static | more</a>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
menu
|
||||
<div class="nav-item">
|
||||
<a class="nav-link"
|
||||
href="#"
|
||||
role="button">
|
||||
feed
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<li><a class="dropdown-item" href="#">Action</a></li>
|
||||
<li><a class="dropdown-item" href="#">Another action</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#">Something else here</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a class="nav-link"
|
||||
href="/forum"
|
||||
role="button">
|
||||
forum
|
||||
</a>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<a class="nav-link"
|
||||
href="#"
|
||||
role="button">
|
||||
new
|
||||
</a>
|
||||
</div>
|
||||
</ul>
|
||||
<form class="d-flex">
|
||||
<input class="form-control me-2 bg-dark text-black-50 border-0" type="search" placeholder='search query' aria-label="Search">
|
||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
||||
<button class="btn btn-outline-light" type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -3,7 +3,7 @@
|
||||
<div class="echoList">
|
||||
{{ range .Echoes }}
|
||||
|
||||
<div class="card dynamic-echo mb-1">
|
||||
<div class="card dynamic-echo dynamic-opacity-95 mb-1">
|
||||
<div class="card-header text-info container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-9">
|
@ -6,7 +6,9 @@
|
||||
{ template "post" .Posts[0] }
|
||||
-->
|
||||
|
||||
<div class="card container dynamic-post dynamic-opacity-95 p-3 mb-3">
|
||||
<div
|
||||
id="{{ .MsgID }}"
|
||||
class="card container dynamic-post dynamic-opacity-95 p-3 mb-3">
|
||||
<div class="card-body p-3">
|
||||
<h3 class="card-title">
|
||||
<a class="dynamic-post-title" href="/ssr#{{ .TopicID }}">{{ .Subg }}</a>
|
||||
@ -18,7 +20,7 @@
|
||||
{{ .Date }}
|
||||
@<a
|
||||
title="{{ .Author }} posts"
|
||||
href="/ssr#/author/{{ .Author }}">{{ .Author }}</a> ->
|
||||
href="#/author/{{ .Author }}">{{ .Author }}</a> ->
|
||||
{{ if .Repto }}<a href="/ssr#{{.Repto}}">{{ .To }}</a>
|
||||
{{ else }}{{ .To }}{{ end }}
|
||||
</p>
|
||||
@ -28,7 +30,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-white-50">
|
||||
[<a href="/ssr#{{ .MsgID }}">#</a>] [<a href="/ssr#{{ .MsgID }}">reply</a>]
|
||||
[<a href="#{{ .MsgID }}">#</a>] [<a href="/ssr#{{ .MsgID }}">reply</a>]
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,8 +0,0 @@
|
||||
{{ define "footer" }}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
{{ end }}
|
29
templates/meta/forum.html
Normal file
29
templates/meta/forum.html
Normal file
@ -0,0 +1,29 @@
|
||||
{{ define "forum" }}
|
||||
|
||||
<div class="container">
|
||||
<div class="row container-fluid">
|
||||
<table class="table table-sm border-0 dynamic-bg dynamic-post-title dynamic-opacity-95 shadow">
|
||||
<caption>latest threads</caption>
|
||||
<thead>
|
||||
<th scope="col">echo</th>
|
||||
<th scope="col">thread</th>
|
||||
<th scope="col">latest</th>
|
||||
<th scope="col">address</th>
|
||||
<th scope="col">date</th>
|
||||
<!-- <th scope="col">comments</th> -->
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .Posts }}
|
||||
<tr>
|
||||
<td>{{ .Echo }}</td>
|
||||
<td>{{ .Subg }}</td>
|
||||
<td>{{ .Author }}</td>
|
||||
<td>{{ .Address }}</td>
|
||||
<td>{{ .Date }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
17
templates/views/forum.html
Normal file
17
templates/views/forum.html
Normal file
@ -0,0 +1,17 @@
|
||||
{{ template "header" . }}
|
||||
|
||||
{{ template "style" }}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
{{ template "echoes" . }}
|
||||
</div>
|
||||
|
||||
<div class="col-9 text-primary">
|
||||
{{ template "forum" . }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "footer" . }}
|
Loading…
Reference in New Issue
Block a user