сделал пагинацию /posts
parent
4327a14052
commit
380cec2c5a
|
@ -41,6 +41,14 @@ div {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pagi {
|
||||||
|
text-align: center;
|
||||||
|
background-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
|
|
12
main.go
12
main.go
|
@ -63,12 +63,18 @@ func setupRoutes(app *models.App) *http.ServeMux {
|
||||||
{
|
{
|
||||||
// Обработка главной страницы
|
// Обработка главной страницы
|
||||||
router.Handle("/", m(controllers_pages.MainPageHandler(app)))
|
router.Handle("/", m(controllers_pages.MainPageHandler(app)))
|
||||||
// Обработка страницы со списком постов
|
}
|
||||||
router.Handle("/posts/", m(controllers_pages.PostsPageHandler(app)))
|
|
||||||
|
// Странички со списками постов
|
||||||
|
postsPagesCount := (len(app.PostsMap) + app.Cfg.PostsMaxCountOnPage - 1) / app.Cfg.PostsMaxCountOnPage
|
||||||
|
for i := range postsPagesCount {
|
||||||
|
router.Handle(
|
||||||
|
fmt.Sprintf("/posts/%d", i),
|
||||||
|
m(controllers_pages.PostsPageHandler(app)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработка страничек постов
|
// Обработка страничек постов
|
||||||
for key := range app.Posts {
|
for key := range app.PostsMap {
|
||||||
postLink := string(key)
|
postLink := string(key)
|
||||||
router.Handle(postLink, m(controllers_pages.PostPageHandler(app)))
|
router.Handle(postLink, m(controllers_pages.PostPageHandler(app)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"main/mvc/models"
|
"main/mvc/models"
|
||||||
"main/mvc/models/models_pages"
|
"main/mvc/models/models_pages"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Обработчик главной страницы
|
// Обработчик главной страницы
|
||||||
|
@ -12,27 +11,21 @@ func PostPageHandler(app *models.App) http.HandlerFunc {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
postLink := r.URL.Path
|
// Ссылка вида /postname
|
||||||
|
link := r.URL.Path
|
||||||
// Ссылки на посты имеют вид postLink = /link/, а если прилетело что то типо /link/123123,
|
|
||||||
// то надо оставить только часть /link/
|
|
||||||
secondSlash := strings.IndexByte(postLink[1:], '/')
|
|
||||||
if secondSlash != -1 {
|
|
||||||
postLink = postLink[:secondSlash+2]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Страничка рендерится только если ее нет в кэше
|
// Страничка рендерится только если ее нет в кэше
|
||||||
pageData, ok := app.PagesCache.Get(postLink)
|
pageData, ok := app.PagesCache.Get(link)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
||||||
post := app.Posts[models_pages.PostLink(postLink)]
|
post := app.PostsMap[models_pages.PostLink(link)]
|
||||||
|
|
||||||
pageData, err = post.RenderPostPage(app.Templates, app.Version)
|
pageData, err = post.RenderPostPage(app.Templates, app.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.PagesCache.Set(postLink, pageData)
|
app.PagesCache.Set(link, pageData)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPostPage(w, pageData)
|
sendPostPage(w, pageData)
|
||||||
|
|
|
@ -4,22 +4,39 @@ import (
|
||||||
"main/mvc/models"
|
"main/mvc/models"
|
||||||
"main/mvc/models/models_pages"
|
"main/mvc/models/models_pages"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Обработчик главной страницы
|
// Обработчик странички со списком постов
|
||||||
func PostsPageHandler(app *models.App) http.HandlerFunc {
|
func PostsPageHandler(app *models.App) http.HandlerFunc {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// Ссылка вида /posts/0
|
||||||
|
link := r.URL.Path
|
||||||
|
|
||||||
// Страничка рендерится только если ее нет в кэше
|
// Страничка рендерится только если ее нет в кэше
|
||||||
pageData, ok := app.PagesCache.Get(models_pages.PostsPageTmplName)
|
pageData, ok := app.PagesCache.Get(link)
|
||||||
if !ok {
|
if !ok {
|
||||||
pageData, err = app.Posts.RenderPostsPage(app.Templates, app.Version)
|
|
||||||
|
// Из мапы формирую отсортированный список всех постов (сначала новые)
|
||||||
|
postsList := app.PostsMap.PostsList()
|
||||||
|
|
||||||
|
// Ошибки тут быть не может, так как этот обработчик настроен
|
||||||
|
// на только существующие реально pageNumber и в случае
|
||||||
|
// какого то не существующего номера страницы он просто
|
||||||
|
// не сработает (в главном main настроены маршруты)
|
||||||
|
pageNumber, _ := strconv.Atoi(path.Base(link))
|
||||||
|
|
||||||
|
page := models_pages.CreatePage(postsList, pageNumber, app.Cfg.PostsMaxCountOnPage)
|
||||||
|
|
||||||
|
pageData, err = page.RenderPostsPage(app.Templates, app.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.PagesCache.Set(models_pages.PostsPageTmplName, pageData)
|
app.PagesCache.Set(link, pageData)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPostsPage(w, pageData)
|
sendPostsPage(w, pageData)
|
||||||
|
|
|
@ -7,12 +7,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
Cfg *Config // Сонфиг
|
Cfg *Config // Сонфиг
|
||||||
Posts models_pages.Posts // Посты
|
PostsMap models_pages.PostsMap // Посты
|
||||||
Templates *template.Template // Шаблоны страниц
|
Templates *template.Template // Шаблоны страниц
|
||||||
PagesCache *Cache // Кэш (отрендеренные странички)
|
PagesCache *Cache // Кэш (отрендеренные странички)
|
||||||
LastfmLastTrack string // Последний трек, полученный с ластфм
|
LastfmLastTrack string // Последний трек, полученный с ластфм
|
||||||
Version int64 // Время запуска
|
Version int64 // Время запуска
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализирует приложение
|
// Инициализирует приложение
|
||||||
|
@ -28,7 +28,7 @@ func InitApp() (*App, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Загрузка постов
|
// Загрузка постов
|
||||||
if app.Posts, err = models_pages.LoadPosts(app.Cfg.PostsDir); err != nil {
|
if app.PostsMap, err = models_pages.LoadPosts(app.Cfg.PostsDir); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Загрузка шаблонов
|
// Загрузка шаблонов
|
||||||
|
|
|
@ -12,6 +12,7 @@ const (
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
PostsDir string
|
PostsDir string
|
||||||
|
PostsMaxCountOnPage int
|
||||||
AssetsDir string
|
AssetsDir string
|
||||||
TemplatesDir string
|
TemplatesDir string
|
||||||
TemplatesExt string
|
TemplatesExt string
|
||||||
|
|
|
@ -37,7 +37,7 @@ func (p *Post) RenderPostPage(templates *template.Template, version int64) ([]by
|
||||||
return pageData.Bytes(), nil
|
return pageData.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPost(link string, data []byte, timestamp int64) *Post {
|
func createPost(link string, data []byte, timestamp int64) *Post {
|
||||||
previewBuf := make([]byte, 0, 503)
|
previewBuf := make([]byte, 0, 503)
|
||||||
|
|
||||||
if len(data) > 500 {
|
if len(data) > 500 {
|
||||||
|
|
|
@ -4,10 +4,12 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"log"
|
||||||
"main/tools"
|
"main/tools"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -17,11 +19,16 @@ const (
|
||||||
PostsPageTmplName = "posts.gohtml"
|
PostsPageTmplName = "posts.gohtml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Posts map[PostLink]*Post
|
type PostsMap map[PostLink]*Post
|
||||||
|
type PostsPage struct {
|
||||||
|
PostsListOnPage []*Post // список постов на странице
|
||||||
|
PageNumber int // номер страницы
|
||||||
|
PagesCount int // общее количество страниц
|
||||||
|
}
|
||||||
|
|
||||||
func LoadPosts(dir string) (Posts, error) {
|
func LoadPosts(dir string) (PostsMap, error) {
|
||||||
|
|
||||||
posts := Posts{}
|
posts := PostsMap{}
|
||||||
|
|
||||||
err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
|
err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -35,9 +42,24 @@ func LoadPosts(dir string) (Posts, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
html := tools.MdToHTML(md)
|
html := tools.MdToHTML(md)
|
||||||
link := fmt.Sprintf("/%s/", strings.TrimSuffix(filepath.Base(path), ".md"))
|
|
||||||
timestamp := f.ModTime().Unix()
|
name := filepath.Base(path)
|
||||||
posts[PostLink(link)] = newPost(link, html, timestamp)
|
lowLineIndex := strings.Index(name, "_")
|
||||||
|
|
||||||
|
if lowLineIndex == -1 {
|
||||||
|
// Обработка случая, если "_" нет в имени
|
||||||
|
log.Fatal(`post name parse error`)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestampStr := name[:lowLineIndex]
|
||||||
|
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
// Ошибка парсинга timestamp
|
||||||
|
log.Fatal(`post name parse error`)
|
||||||
|
}
|
||||||
|
|
||||||
|
link := fmt.Sprintf("/%s", name[lowLineIndex+1:])
|
||||||
|
posts[PostLink(link)] = createPost(link, html, timestamp)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -49,23 +71,65 @@ func LoadPosts(dir string) (Posts, error) {
|
||||||
return posts, nil
|
return posts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Posts) RenderPostsPage(templates *template.Template, version int64) ([]byte, error) {
|
// Получение из мапы постов списка постов, отсортированного по ModTimestamp (новые сначала)
|
||||||
var pageData bytes.Buffer
|
func (p *PostsMap) PostsList() []*Post {
|
||||||
|
|
||||||
postsSlice := make([]*Post, 0, len(*p))
|
postsSlice := make([]*Post, 0, len(*p))
|
||||||
for _, post := range *p {
|
for _, post := range *p {
|
||||||
postsSlice = append(postsSlice, post)
|
postsSlice = append(postsSlice, post)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сортирую по ModTimestamp (новые сначала)
|
|
||||||
sort.Slice(postsSlice, func(i, j int) bool {
|
sort.Slice(postsSlice, func(i, j int) bool {
|
||||||
return postsSlice[i].Timestamp > postsSlice[j].Timestamp
|
return postsSlice[i].Timestamp > postsSlice[j].Timestamp
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return postsSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreatePage(postsList []*Post, pageNumber, postsMaxCountOnPage int) *PostsPage {
|
||||||
|
// Общее количество страниц
|
||||||
|
pagesCount := (len(postsList) + postsMaxCountOnPage - 1) / postsMaxCountOnPage
|
||||||
|
|
||||||
|
// Ошибки тут быть не может, так как этот обработчик настроен
|
||||||
|
// на только существующие реально pageNumber и в случае
|
||||||
|
// какого то не существующего номера страницы он просто
|
||||||
|
// не сработает (в главном main настроены маршруты)
|
||||||
|
|
||||||
|
startIndex := pageNumber * postsMaxCountOnPage
|
||||||
|
endIndex := startIndex + postsMaxCountOnPage
|
||||||
|
endIndex = min(endIndex, len(postsList))
|
||||||
|
postsSublistForPageNumber := postsList[startIndex:endIndex]
|
||||||
|
|
||||||
|
return &PostsPage{
|
||||||
|
PostsListOnPage: postsSublistForPageNumber,
|
||||||
|
PageNumber: pageNumber,
|
||||||
|
PagesCount: pagesCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PostsPage) RenderPostsPage(templates *template.Template, version int64) ([]byte, error) {
|
||||||
|
var pageData bytes.Buffer
|
||||||
|
|
||||||
|
var prevPageNumber, nextPageNumber int
|
||||||
|
|
||||||
|
if p.PageNumber == 0 {
|
||||||
|
prevPageNumber = p.PagesCount - 1
|
||||||
|
} else {
|
||||||
|
prevPageNumber = p.PageNumber - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPageNumber = (p.PageNumber + 1) % p.PagesCount
|
||||||
|
|
||||||
|
pagi := fmt.Sprintf(
|
||||||
|
`<a href="/posts/%d"><-</a> <a href="/posts/%d">-></a>`,
|
||||||
|
prevPageNumber,
|
||||||
|
nextPageNumber,
|
||||||
|
)
|
||||||
|
|
||||||
context := map[string]any{
|
context := map[string]any{
|
||||||
|
"pagi": template.HTML(pagi),
|
||||||
"version": version,
|
"version": version,
|
||||||
"renderingTimestamp": time.Now().Unix(),
|
"renderingTimestamp": time.Now().Unix(),
|
||||||
"posts": postsSlice,
|
"posts": p.PostsListOnPage,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := templates.ExecuteTemplate(&pageData, PostsPageTmplName, context); err != nil {
|
if err := templates.ExecuteTemplate(&pageData, PostsPageTmplName, context); err != nil {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
see <a href="/">main page</a> or go to <a href="/posts/">posts section</a>
|
see <a href="/">main page</a> or go to <a href="/posts/0">posts section</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
<div class="pagi">{{ .pagi }}</div>
|
||||||
</main>
|
</main>
|
||||||
{{ template "footer" . }}
|
{{ template "footer" . }}
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
# 123
|
Loading…
Reference in New Issue