Feature - Components refactor (#10)

* feat: header component

* feat: captcha component

* feat: rename all html files

* feat: update github link

* feat: all templates in subfolders

* feat: separate thread card

* feat: separate pagination

* feat: update pagination

* feat: separate post

* lint
This commit is contained in:
Yanislav Igonin 2022-01-31 18:56:21 +02:00 committed by GitHub
parent 21c82b47e9
commit fbe39f45a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 284 additions and 255 deletions

View File

@ -15,7 +15,7 @@ func GetCaptcha(c *gin.Context) {
err := captcha.WriteImage(&content, ID, captcha.StdWidth, captcha.StdHeight)
if err != nil {
log.Println("error:", err)
c.HTML(http.StatusInternalServerError, "500.html", nil)
c.HTML(http.StatusInternalServerError, "pages/500.html", nil)
return
}

View File

@ -55,8 +55,10 @@ func GetThreads(c *gin.Context) {
captchaID := captcha.New()
htmlData := Repositories.GetThreadsHtmlData{
Threads: threads,
Pagination: Repositories.HtmlPaginationData{
PagesCount: pagesCount,
Page: page,
},
FormData: Repositories.HtmlFormData{
CaptchaID: captchaID,
IsCaptchaActive: Config.App.IsCaptchaActive,

View File

@ -60,7 +60,7 @@ func main() {
return Items
},
})
router.LoadHTMLGlob("templates/*.html")
router.LoadHTMLGlob("templates/**/*")
router.ForwardedByClientIP = true
if Config.App.IsRateLimiterEnabled {
router.Use(middleware)

View File

@ -19,6 +19,13 @@ type Post struct {
UpdatedAt time.Time `json:"-"`
}
func (p *Post) getThreadID() int {
if p.IsParent {
return p.ID
}
return p.ParentID
}
type File struct {
ID int `json:"-"`
PostID int `json:"-"`
@ -39,6 +46,12 @@ type HtmlFormData struct {
IsCaptchaActive bool
}
// index.html
type HtmlPaginationData struct {
PagesCount int
Page int
}
// thread.html
type GetThreadHtmlData struct {
Thread []Post
@ -48,8 +61,7 @@ type GetThreadHtmlData struct {
// index.html
type GetThreadsHtmlData struct {
Threads []Post `json:"threads"`
PagesCount int `json:"pagesCount"`
Page int `json:"page"`
Pagination HtmlPaginationData
FormData HtmlFormData
}

View File

@ -0,0 +1,7 @@
{{ define "captcha" }}
<div class="captcha-container text-center">
<img src="/captcha/{{ . }}" alt="Captcha">
</div>
<input class="form-control" type="hidden" name="captchaId" value="{{ . }}">
<input class="form-control" id="postCaptcha" type="tel" pattern="\d+" placeholder="Captcha" name="captcha">
{{ end }}

View File

@ -0,0 +1,9 @@
{{ define "footer" }}
<footer class="py-4">
<div class="container text-center">
<a href="https://github.com/yanislav-igonin/micrach" target="blank">
<img src="/static/images/github-icon.png" alt="GitHub icon">
</a>
</div>
</footer>
{{ end }}

View File

@ -0,0 +1,17 @@
{{ define "header" }}
{{ if . }}
<a href="/" class="thread-title-link">
<h1 class="display-1 text-center thread-title">
{{ if ne . "" }}
{{ . }}
{{ else }}
Welcome to Micrach
{{ end }}
</h1>
</a>
{{ else }}
<h1 class="display-1 text-center">Welcome to Micrach</h1>
{{ end }}
{{ end }}

View File

@ -0,0 +1,12 @@
{{ define "pagiantion" }}
<nav aria-label="Page navigation">
<ul id="pagesList" class="pagination justify-content-center">
{{ $ActivePage := .Page }}
{{ range $i := Iterate .PagesCount }}
<li class="page-item {{ if (eq $i $ActivePage) }} active {{ end }}">
<a class="page-link" href="/?page={{ $i }}">{{ $i }}</a>
</li>
{{ end }}
</ul>
</nav>
{{ end }}

View File

@ -0,0 +1,46 @@
{{ define "post-form" }}
<div class="d-flex justify-content-center">
<div class="col col-lg-6 col-md-8">
{{ if eq .FirstPostID 0 }}
<form id="postForm" action="/" method="POST" enctype="multipart/form-data">
{{ else }}
<form id="postForm" action="/{{ .FirstPostID }}" method="POST" enctype="multipart/form-data">
{{ end }}
{{ if eq .FirstPostID 0 }}
<input class="form-control" id="postTitle" placeholder="Title" name="title">
{{ end }}
<textarea class="form-control" id="postText" rows="5" placeholder="Text" name="text"></textarea>
<input class="form-control" type="file" id="postFiles" multiple name="files">
{{ if .IsCaptchaActive }}
{{ template "captcha" .CaptchaID }}
{{ end }}
<div class="row">
{{ if ne .FirstPostID 0 }}
<div class="col">
<input class="form-check-input" type="checkbox" value="on" id="postSage" name="sage">
<label class="form-check-label" for="postSage">
Sage
</label>
</div>
{{ end }}
<div class="col text-end">
<button class="col btn btn-outline-primary" type="submit" >
{{ if ne .FirstPostID 0 }}
Send
{{ else }}
Create thread
{{ end }}
</button>
</div>
</div>
</form>
</div>
</div>
{{ end }}

View File

@ -0,0 +1,20 @@
{{ define "post" }}
<div class="post-container mb-4">
{{ $filesLength := len .Files }}
{{ if gt $filesLength 0 }}
<div class="files-container">
{{ range $File := .Files }}
<div class="file-container">
<a href="/uploads/{{.getThreadID}}/o/{{$File.ID}}.{{$File.Ext}}" target="blank">
<img src="/uploads/{{.getThreadID}}/t/{{$File.ID}}.{{$File.Ext}}" class="file" alt="Uploaded picture">
</a>
</div>
{{ end }}
</div>
{{ end }}
<div class="p-2">
<p class="card-text line-break text-break">{{.Text}}</p>
</div>
</div>
{{ end }}

View File

@ -0,0 +1,26 @@
{{ define "thread-card" }}
<div class="col col-sm-6 col-md-4 col-lg-3">
<div class="card">
{{ $length := len .Files }}
{{ if gt $length 0 }}
{{ $FirstFile := index .Files 0 }}
<img src="/uploads/{{.ID}}/t/{{$FirstFile.ID}}.{{$FirstFile.Ext}}" class="card-img-top" alt="Uploaded picture">
{{ end }}
<div class="card-body">
{{ if ne .Title "" }}
<h5 class="card-title">{{.Title}}</h5>
{{ end }}
{{ $textLength := len .Text }}
{{ if gt $textLength 300 }}
<p class="card-text line-break text-break">{{ slice .Text 0 300 }} ...</p>
{{ else }}
<p class="card-text line-break text-break">{{.Text}}</p>
{{ end }}
<a href="/{{.ID}}" class="btn btn-outline-primary">Open</a>
</div>
</div>
</div>
{{ end }}

View File

@ -1,9 +0,0 @@
{{ define "footer" }}
<footer class="py-4">
<div class="container text-center">
<a href="https://github.com/yanislav-igonin/micrach-go" target="blank">
<img src="/static/images/github-icon.png" alt="GitHub icon">
</a>
</div>
</footer>
{{ end }}

View File

@ -0,0 +1,31 @@
{{ define "meta-tags-dynamic" }}
{{ $SeoImageUrl := "" }}
{{ if .Files }}
{{ $FirstFile := index .Files 0 }}
{{ $SeoImageUrl = printf "https://micrach.igonin.dev/uploads/%d/t/%d.%s" .ID $FirstFile.ID $FirstFile.Ext }}
{{ else }}
{{ $SeoImageUrl = "https://memepedia.ru/wp-content/uploads/2018/03/ebanyy-rot-etogo-kazino.png" }}
{{ end }}
<title>{{ .Title }}</title>
<!-- Primary Meta Tags -->
<meta charset="UTF-8"/>
<meta name="title" content="{{ .Title }}">
<meta name="description" content="{{ .Text }}">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://micrach.igonin.dev/{{ .ID }}">
<meta property="og:title" content="{{ .Title }}">
<meta property="og:description" content="{{ .Text }}">
<meta property="og:image" content="{{ $SeoImageUrl }}">
<!-- Twitter -->
<meta property="twitter:card" content="{{ $SeoImageUrl }}">
<meta property="twitter:url" content="https://micrach.igonin.dev/{{ .ID }}">
<meta property="twitter:title" content="{{ .Title }}">
<meta property="twitter:description" content="{{ .Text }}">
<meta property="twitter:image" content="{{ $SeoImageUrl }}">
{{ end }}

View File

@ -0,0 +1,22 @@
{{ define "meta-tags-static" }}
<title>Micrach</title>
<!-- Primary Meta Tags -->
<meta charset="UTF-8"/>
<meta name="title" content="Micrach">
<meta name="description" content="Single board imageboard.">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://micrach.igonin.dev/">
<meta property="og:title" content="Micrach">
<meta property="og:description" content="Single board imageboard.">
<meta property="og:image" content="https://memepedia.ru/wp-content/uploads/2018/03/ebanyy-rot-etogo-kazino.png">
<!-- Twitter -->
<meta property="twitter:card" content="https://memepedia.ru/wp-content/uploads/2018/03/ebanyy-rot-etogo-kazino.png">
<meta property="twitter:url" content="https://micrach.igonin.dev/">
<meta property="twitter:title" content="Micrach">
<meta property="twitter:description" content="Single board imageboard.">
<meta property="twitter:image" content="https://memepedia.ru/wp-content/uploads/2018/03/ebanyy-rot-etogo-kazino.png">
{{ end }}

View File

@ -0,0 +1,16 @@
{{ define "static" }}
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We"
crossorigin="anonymous"
>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj"
crossorigin="anonymous">
</script>
<link rel="icon" href="/static/favicon.ico"/>
<link href="/static/styles/utils.css" rel="stylesheet">
<link href="/static/styles/post-form.css" rel="stylesheet">
{{ end }}

View File

@ -1,64 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
{{ template "static" }}
<link href="/static/styles/index.css" rel="stylesheet">
{{ template "meta-tags-static" }}
</head>
<body>
<h1 class="display-1 text-center">Welcome to Micrach</h1>
<div class="container">
{{ template "post-form" .FormData }}
<div id="" class="row row-cols-auto gy-4 mb-4 justify-content-center">
{{ range $Post := .Threads }}
<div class="col col-sm-6 col-md-4 col-lg-3">
<div class="card">
{{ $length := len $Post.Files }}
{{ if gt $length 0 }}
{{ $FirstFile := index $Post.Files 0 }}
<img src="/uploads/{{$Post.ID}}/t/{{$FirstFile.ID}}.{{$FirstFile.Ext}}" class="card-img-top"
alt="Uploaded picture">
{{ end }}
<div class="card-body">
{{ if ne $Post.Title "" }}
<h5 class="card-title">{{$Post.Title}}</h5>
{{ end }}
{{ $textLength := len $Post.Text }}
{{ if gt $textLength 300 }}
<p class="card-text line-break text-break">{{ slice $Post.Text 0 300 }} ...</p>
{{ else }}
<p class="card-text line-break text-break">{{$Post.Text}}</p>
{{ end }}
<a href="/{{$Post.ID}}" class="btn btn-outline-primary">Open</a>
</div>
</div>
</div>
{{ end }}
</div>
<div class="col col-12">
<nav aria-label="Page navigation">
<ul id="pagesList" class="pagination justify-content-center">
{{ $ActivePage := .Page }}
{{ range $i := Iterate .PagesCount }}
<li class="page-item {{ if (eq $i $ActivePage) }} active {{ end }}">
<a class="page-link" href="/?page={{ $i }}">{{ $i }}</a>
</li>
{{ end }}
</ul>
</nav>
</div>
</div>
{{ template "footer" }}
</body>
</html>

View File

@ -1,31 +0,0 @@
{{ define "meta-tags-dynamic" }}
{{ $SeoImageUrl := "" }}
{{ if .Files }}
{{ $FirstFile := index .Files 0 }}
{{ $SeoImageUrl = printf "https://micrach.igonin.dev/uploads/%d/t/%d.%s" .ID $FirstFile.ID $FirstFile.Ext }}
{{ else }}
{{ $SeoImageUrl = "https://memepedia.ru/wp-content/uploads/2018/03/ebanyy-rot-etogo-kazino.png" }}
{{ end }}
<title>{{ .Title }}</title>
<!-- Primary Meta Tags -->
<meta charset="UTF-8"/>
<meta name="title" content="{{ .Title }}">
<meta name="description" content="{{ .Text }}">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://micrach.igonin.dev/{{ .ID }}">
<meta property="og:title" content="{{ .Title }}">
<meta property="og:description" content="{{ .Text }}">
<meta property="og:image" content="{{ $SeoImageUrl }}">
<!-- Twitter -->
<meta property="twitter:card" content="{{ $SeoImageUrl }}">
<meta property="twitter:url" content="https://micrach.igonin.dev/{{ .ID }}">
<meta property="twitter:title" content="{{ .Title }}">
<meta property="twitter:description" content="{{ .Text }}">
<meta property="twitter:image" content="{{ $SeoImageUrl }}">
{{ end }}

View File

@ -1,22 +0,0 @@
{{ define "meta-tags-static" }}
<title>Micrach</title>
<!-- Primary Meta Tags -->
<meta charset="UTF-8"/>
<meta name="title" content="Micrach">
<meta name="description" content="Single board imageboard.">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://micrach.igonin.dev/">
<meta property="og:title" content="Micrach">
<meta property="og:description" content="Single board imageboard.">
<meta property="og:image" content="https://memepedia.ru/wp-content/uploads/2018/03/ebanyy-rot-etogo-kazino.png">
<!-- Twitter -->
<meta property="twitter:card" content="https://memepedia.ru/wp-content/uploads/2018/03/ebanyy-rot-etogo-kazino.png">
<meta property="twitter:url" content="https://micrach.igonin.dev/">
<meta property="twitter:title" content="Micrach">
<meta property="twitter:description" content="Single board imageboard.">
<meta property="twitter:image" content="https://memepedia.ru/wp-content/uploads/2018/03/ebanyy-rot-etogo-kazino.png">
{{ end }}

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
{{ template "static" }}
<link href="/static/styles/index.css" rel="stylesheet">
{{ template "meta-tags-static" }}
</head>
<body>
{{ template "header" }}
<div class="container">
{{ template "post-form" .FormData }}
<div id="" class="row row-cols-auto gy-4 mb-4 justify-content-center">
{{ range $Post := .Threads }}
{{ template "thread-card" $Post }}
{{ end }}
</div>
{{ template "pagiantion" .Pagination }}
</div>
{{ template "footer" }}
</body>
</html>

View File

@ -0,0 +1,29 @@
{{ $FirstPost:= index .Thread 0 }}
<!DOCTYPE html>
<html lang="en">
<head>
{{ template "static" }}
{{ template "meta-tags-dynamic" $FirstPost }}
<link href="/static/styles/thread.css" rel="stylesheet">
<link href="/static/styles/post.css" rel="stylesheet">
</head>
<body>
{{ template "header" $FirstPost.Title }}
<div class="container">
{{ template "post-form" .FormData }}
<div id="postsСontainer">
{{ range $Post := .Thread }}
{{ template "post" $Post }}
{{ end }}
</div>
</div>
{{ template "footer" }}
</body>
</html>

View File

@ -1,50 +0,0 @@
{{ define "post-form" }}
<div class="d-flex justify-content-center">
<div class="col col-lg-6 col-md-8">
{{ if eq .FirstPostID 0 }}
<form id="postForm" action="/" method="POST" enctype="multipart/form-data">
{{ else }}
<form id="postForm" action="/{{ .FirstPostID }}" method="POST" enctype="multipart/form-data">
{{ end }}
{{ if eq .FirstPostID 0 }}
<input class="form-control" id="postTitle" placeholder="Title" name="title">
{{ end }}
<textarea class="form-control" id="postText" rows="5" placeholder="Text" name="text"></textarea>
<input class="form-control" type="file" id="postFiles" multiple name="files">
{{ if .IsCaptchaActive }}
<div class="captcha-container text-center">
<img src="/captcha/{{ .CaptchaID }}" alt="Captcha">
</div>
<input class="form-control" type="hidden" name="captchaId" value="{{ .CaptchaID }}">
<input class="form-control" id="postCaptcha" type="tel" pattern="\d+" placeholder="Captcha" name="captcha">
{{ end }}
<div class="row">
{{ if ne .FirstPostID 0 }}
<div class="col">
<input class="form-check-input" type="checkbox" value="on" id="postSage" name="sage">
<label class="form-check-label" for="postSage">
Sage
</label>
</div>
{{ end }}
<div class="col text-end">
<button class="col btn btn-outline-primary" type="submit" >
{{ if ne .FirstPostID 0 }}
Send
{{ else }}
Create thread
{{ end }}
</button>
</div>
</div>
</form>
</div>
</div>
{{ end }}

View File

@ -1,16 +0,0 @@
{{ define "static" }}
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We"
crossorigin="anonymous"
>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj"
crossorigin="anonymous">
</script>
<link rel="icon" href="/static/favicon.ico"/>
<link href="/static/styles/utils.css" rel="stylesheet">
<link href="/static/styles/post-form.css" rel="stylesheet">
{{ end }}

View File

@ -1,56 +0,0 @@
{{ $FirstPost:= index .Thread 0 }}
<!DOCTYPE html>
<html lang="en">
<head>
{{ template "static" }}
{{ template "meta-tags-dynamic" $FirstPost }}
<link href="/static/styles/thread.css" rel="stylesheet">
<link href="/static/styles/post.css" rel="stylesheet">
</head>
<body>
<a href="/" class="thread-title-link">
<h1 class="display-1 text-center thread-title">
{{ if ne $FirstPost.Title "" }}
{{$FirstPost.Title}}
{{ else }}
Welcome to Micrach
{{ end }}
</h1>
</a>
<div class="container">
{{ template "post-form" .FormData }}
<div id="postsСontainer">
{{ range $Post := .Thread }}
<div class="post-container mb-4">
{{ $filesLength := len $Post.Files }}
{{ if gt $filesLength 0 }}
<div class="files-container">
{{ range $File := $Post.Files }}
<div class="file-container">
<a href="/uploads/{{$FirstPost.ID}}/o/{{$File.ID}}.{{$File.Ext}}" target="blank">
<img src="/uploads/{{$FirstPost.ID}}/t/{{$File.ID}}.{{$File.Ext}}" class="file" alt="Uploaded picture">
</a>
</div>
{{ end }}
</div>
{{ end }}
<div class="p-2">
<p class="card-text line-break text-break">{{$Post.Text}}</p>
</div>
</div>
{{end}}
</div>
</div>
{{ template "footer" }}
</body>
</html>