diff --git a/assets/css/styles.css b/assets/css/styles.css index c4a0919..6aca1f1 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -41,6 +41,14 @@ div { overflow: hidden; } +.pagi { + text-align: center; + background-color: transparent; + box-shadow: none; + border: none; + font-size: 1.5em; +} + h1 { text-align: center; padding: 30px; diff --git a/main.go b/main.go index 7ef1ddd..3197b2a 100644 --- a/main.go +++ b/main.go @@ -63,12 +63,18 @@ func setupRoutes(app *models.App) *http.ServeMux { { // Обработка главной страницы 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) router.Handle(postLink, m(controllers_pages.PostPageHandler(app))) } diff --git a/main~ b/main~ new file mode 100644 index 0000000..810d7bd Binary files /dev/null and b/main~ differ diff --git a/mvc/controllers/controllers_pages/post.go b/mvc/controllers/controllers_pages/post.go index c77aa94..1ef9ea6 100644 --- a/mvc/controllers/controllers_pages/post.go +++ b/mvc/controllers/controllers_pages/post.go @@ -4,7 +4,6 @@ import ( "main/mvc/models" "main/mvc/models/models_pages" "net/http" - "strings" ) // Обработчик главной страницы @@ -12,27 +11,21 @@ func PostPageHandler(app *models.App) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var err error - postLink := r.URL.Path - - // Ссылки на посты имеют вид postLink = /link/, а если прилетело что то типо /link/123123, - // то надо оставить только часть /link/ - secondSlash := strings.IndexByte(postLink[1:], '/') - if secondSlash != -1 { - postLink = postLink[:secondSlash+2] - } + // Ссылка вида /postname + link := r.URL.Path // Страничка рендерится только если ее нет в кэше - pageData, ok := app.PagesCache.Get(postLink) + pageData, ok := app.PagesCache.Get(link) if !ok { - post := app.Posts[models_pages.PostLink(postLink)] + post := app.PostsMap[models_pages.PostLink(link)] pageData, err = post.RenderPostPage(app.Templates, app.Version) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - app.PagesCache.Set(postLink, pageData) + app.PagesCache.Set(link, pageData) } sendPostPage(w, pageData) diff --git a/mvc/controllers/controllers_pages/posts.go b/mvc/controllers/controllers_pages/posts.go index 76d98ec..395e6e3 100644 --- a/mvc/controllers/controllers_pages/posts.go +++ b/mvc/controllers/controllers_pages/posts.go @@ -4,22 +4,39 @@ import ( "main/mvc/models" "main/mvc/models/models_pages" "net/http" + "path" + "strconv" ) -// Обработчик главной страницы +// Обработчик странички со списком постов func PostsPageHandler(app *models.App) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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 { - 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 { http.Error(w, err.Error(), http.StatusInternalServerError) return } - app.PagesCache.Set(models_pages.PostsPageTmplName, pageData) + app.PagesCache.Set(link, pageData) } sendPostsPage(w, pageData) diff --git a/mvc/models/app.go b/mvc/models/app.go index 9c38268..53c5b47 100644 --- a/mvc/models/app.go +++ b/mvc/models/app.go @@ -7,12 +7,12 @@ import ( ) type App struct { - Cfg *Config // Сонфиг - Posts models_pages.Posts // Посты - Templates *template.Template // Шаблоны страниц - PagesCache *Cache // Кэш (отрендеренные странички) - LastfmLastTrack string // Последний трек, полученный с ластфм - Version int64 // Время запуска + Cfg *Config // Сонфиг + PostsMap models_pages.PostsMap // Посты + Templates *template.Template // Шаблоны страниц + PagesCache *Cache // Кэш (отрендеренные странички) + LastfmLastTrack string // Последний трек, полученный с ластфм + Version int64 // Время запуска } // Инициализирует приложение @@ -28,7 +28,7 @@ func InitApp() (*App, error) { 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 } // Загрузка шаблонов diff --git a/mvc/models/config.go b/mvc/models/config.go index 9134f97..0925532 100644 --- a/mvc/models/config.go +++ b/mvc/models/config.go @@ -12,6 +12,7 @@ const ( type Config struct { PostsDir string + PostsMaxCountOnPage int AssetsDir string TemplatesDir string TemplatesExt string diff --git a/mvc/models/models_pages/post.go b/mvc/models/models_pages/post.go index c0ecb04..39cc4ca 100644 --- a/mvc/models/models_pages/post.go +++ b/mvc/models/models_pages/post.go @@ -37,7 +37,7 @@ func (p *Post) RenderPostPage(templates *template.Template, version int64) ([]by 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) if len(data) > 500 { diff --git a/mvc/models/models_pages/posts.go b/mvc/models/models_pages/posts.go index e47e7c1..16416f3 100644 --- a/mvc/models/models_pages/posts.go +++ b/mvc/models/models_pages/posts.go @@ -4,10 +4,12 @@ import ( "bytes" "fmt" "html/template" + "log" "main/tools" "os" "path/filepath" "sort" + "strconv" "strings" "time" ) @@ -17,11 +19,16 @@ const ( 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 { if err != nil { @@ -35,9 +42,24 @@ func LoadPosts(dir string) (Posts, error) { } html := tools.MdToHTML(md) - link := fmt.Sprintf("/%s/", strings.TrimSuffix(filepath.Base(path), ".md")) - timestamp := f.ModTime().Unix() - posts[PostLink(link)] = newPost(link, html, timestamp) + + name := filepath.Base(path) + 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 }) @@ -49,23 +71,65 @@ func LoadPosts(dir string) (Posts, error) { return posts, nil } -func (p *Posts) RenderPostsPage(templates *template.Template, version int64) ([]byte, error) { - var pageData bytes.Buffer - +// Получение из мапы постов списка постов, отсортированного по ModTimestamp (новые сначала) +func (p *PostsMap) PostsList() []*Post { postsSlice := make([]*Post, 0, len(*p)) for _, post := range *p { postsSlice = append(postsSlice, post) } - // Сортирую по ModTimestamp (новые сначала) sort.Slice(postsSlice, func(i, j int) bool { 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( + `<- ->`, + prevPageNumber, + nextPageNumber, + ) + context := map[string]any{ + "pagi": template.HTML(pagi), "version": version, "renderingTimestamp": time.Now().Unix(), - "posts": postsSlice, + "posts": p.PostsListOnPage, } if err := templates.ExecuteTemplate(&pageData, PostsPageTmplName, context); err != nil { diff --git a/mvc/views/blocks/header.gohtml b/mvc/views/blocks/header.gohtml index c71fe81..2916265 100644 --- a/mvc/views/blocks/header.gohtml +++ b/mvc/views/blocks/header.gohtml @@ -19,7 +19,7 @@
- see main page or go to posts section + see main page or go to posts section