add pagination
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
{{ define "content" }}
|
||||
|
||||
<div class="content max-w-screen-lg mx-auto">
|
||||
<div class="content max-w-screen-lg flex flex-col mx-auto">
|
||||
|
||||
{{ range . }}
|
||||
{{ range .ArticleVMs }}
|
||||
<div tabindex="0" class="collapse bg-base-200 shadow mb-4">
|
||||
<div class="collapse-title font-medium">{{ .Title }}</div>
|
||||
<div class="collapse-content">
|
||||
@@ -19,5 +19,7 @@
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ template "pagination" .Paginations }}
|
||||
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
10
src/assets/templates/components/pagination.html
Normal file
10
src/assets/templates/components/pagination.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{{ define "pagination" }}
|
||||
|
||||
<div class="join pagination p-5 mx-auto">
|
||||
{{ range . }}
|
||||
<a class="join-item btn btn-sm {{ if .Active }}btn-active{{ end }} {{ if .Disabled }}btn-disabled{{end}}" up-follow up-target=".content"
|
||||
{{ if .Disabled }}{{else}}href="/page/{{ .Content }}" tabindex="0"{{end}}>{{ .Content }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
{{ end }}
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
{{/* Logo with navigation */}}
|
||||
<div class="flex-1">
|
||||
<a href="/" tabindex="0" class="btn btn-ghost text-xl">crowsnest</a>
|
||||
<a href="/" tabindex="0" class="btn btn-ghost text-xl" up-follow up-target=".body">crowsnest</a>
|
||||
<ul class="menu menu-horizontal hidden sm:flex">
|
||||
<li><a tabindex="0" class="active">Artikel</a></li>
|
||||
<li><a tabindex="0" {{ if .SelectedNavItemArticle }} class="active" {{ end }}>Artikel</a></li>
|
||||
<li><a tabindex="0">Themen</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -4,17 +4,36 @@ import (
|
||||
"crowsnest/internal/model"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// List the latest articles using the base template.
|
||||
func (app *App) Index(w http.ResponseWriter, req *http.Request) {
|
||||
const pageSize = 15
|
||||
var limit, offset, pageId uint64 = pageSize, 0, 0
|
||||
var err error
|
||||
|
||||
// get page number
|
||||
if pageId, err = strconv.ParseUint(req.PathValue("id"), 10, 32); err == nil {
|
||||
pageId--
|
||||
offset = pageId * pageSize
|
||||
}
|
||||
|
||||
// get articles
|
||||
articles, err := app.articles.All(30)
|
||||
articles, err := app.articles.All(int(limit), int(offset))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// get count of total articles
|
||||
totalCount, err := app.articles.CountAll()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
totalCount /= pageSize
|
||||
|
||||
// convert to viewmodel
|
||||
articleVMs := make([]*model.ArticleViewModel, 0, len(articles))
|
||||
for _, a := range articles {
|
||||
@@ -22,8 +41,17 @@ func (app *App) Index(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
// render template
|
||||
t := template.Must(template.ParseFiles("assets/templates/article.html", "assets/templates/layout.html"))
|
||||
err = t.ExecuteTemplate(w, "base", articleVMs)
|
||||
t := template.Must(template.ParseFiles(
|
||||
"assets/templates/article.html",
|
||||
"assets/templates/layout.html",
|
||||
"assets/templates/components/pagination.html"))
|
||||
|
||||
data := map[string]interface{}{
|
||||
"SelectedNavItemArticle": true,
|
||||
"ArticleVMs": &articleVMs,
|
||||
"Paginations": model.NewPaginationViewModel(uint(pageId+1), totalCount+1),
|
||||
}
|
||||
err = t.ExecuteTemplate(w, "base", data)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to render template", http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -23,6 +23,7 @@ func (app *App) routes() http.Handler {
|
||||
|
||||
// dynamic routes
|
||||
mux.Handle("GET /", LoggingMiddleware(http.HandlerFunc(app.Index)))
|
||||
mux.Handle("GET /page/{id}", LoggingMiddleware(http.HandlerFunc(app.Index)))
|
||||
mux.Handle("POST /up/search", LoggingMiddleware(http.HandlerFunc(app.UpSearch)))
|
||||
|
||||
// serve files from the "static" directory
|
||||
|
||||
@@ -11,14 +11,14 @@ type ArticleModel struct {
|
||||
|
||||
// Gets all the article objects from the database. This may throw an error if
|
||||
// the connection to the database fails.
|
||||
func (m *ArticleModel) All(limit int) ([]model.Article, error) {
|
||||
func (m *ArticleModel) All(limit int, offset int) ([]model.Article, error) {
|
||||
stmt := `
|
||||
SELECT id, title, sourceUrl, content, publishDate, fetchDate, aisummary
|
||||
FROM articles
|
||||
ORDER BY publishDate DESC
|
||||
LIMIT $1
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
rows, err := m.DB.Query(stmt, limit)
|
||||
rows, err := m.DB.Query(stmt, limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -41,6 +41,21 @@ func (m *ArticleModel) All(limit int) ([]model.Article, error) {
|
||||
return articles, nil
|
||||
}
|
||||
|
||||
// Counts all articles in the database. This may throw an error if the
|
||||
// connection to the database fails.
|
||||
func (m *ArticleModel) CountAll() (uint, error) {
|
||||
stmt := `SELECT count(id) FROM articles `
|
||||
|
||||
rows := m.DB.QueryRow(stmt)
|
||||
|
||||
count := uint(0)
|
||||
if err := rows.Scan(&count); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Will use the full-text search features of the underlying database to search
|
||||
// articles for a given search query. This may fail if the connection to the
|
||||
// database fails.
|
||||
|
||||
35
src/internal/model/pagination.go
Normal file
35
src/internal/model/pagination.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type PageButton struct {
|
||||
Content string
|
||||
Active bool
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
type PaginationViewModel []PageButton
|
||||
|
||||
func NewPaginationViewModel(currentPage uint, totalPages uint) *PaginationViewModel {
|
||||
pagVM := make(PaginationViewModel, 0)
|
||||
|
||||
if totalPages > 1 {
|
||||
pagVM = append(pagVM, PageButton{"1", currentPage == 1, false})
|
||||
}
|
||||
if currentPage > 3 {
|
||||
pagVM = append(pagVM, PageButton{"...", false, true})
|
||||
}
|
||||
for i := max(2, currentPage-1); i <= min(totalPages-1, currentPage+1); i++ {
|
||||
pagVM = append(pagVM, PageButton{strconv.Itoa(int(i)), i == currentPage, false})
|
||||
}
|
||||
if currentPage < totalPages-2 {
|
||||
pagVM = append(pagVM, PageButton{"...", false, true})
|
||||
}
|
||||
if totalPages > 1 {
|
||||
pagVM = append(pagVM, PageButton{strconv.Itoa(int(totalPages)), totalPages == currentPage, false})
|
||||
}
|
||||
|
||||
return &pagVM
|
||||
}
|
||||
Reference in New Issue
Block a user