[static]: simple html framework

This commit is contained in:
Denis Zheleztsov 2021-03-26 11:11:14 +03:00
parent 5d507d2810
commit 400e8e2562
Signed by: Difrex
GPG Key ID: D423378A747E202E
17 changed files with 258 additions and 57 deletions

1
go.sum
View File

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

View File

@ -232,6 +232,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)

View File

@ -366,6 +366,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)))

View File

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

View File

@ -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)
}
}

View File

@ -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
View 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
}

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

View File

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

View File

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

View File

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

View File

@ -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
View 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 }}

View 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" . }}