From 066a30ff7d89218f25186ca0654e4d34d68c67b1 Mon Sep 17 00:00:00 2001 From: serr Date: Thu, 10 Apr 2025 21:25:50 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B4=D0=B5=D0=BB=D0=B8=D1=82=D1=8C=20=D0=B8=D0=B7=D0=B8?= =?UTF-8?q?=20=D0=B0=D0=BF=D0=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 ++ go.sum | 2 ++ main.go | 8 ++++- mvc/controllers/pages/main.go | 1 + mvc/controllers/pages/post.go | 39 ++++++++++++++++++++ mvc/models/app.go | 2 +- mvc/models/config.go | 19 +++++----- mvc/models/pages/post.go | 61 ++++++++++++++++++++----------- mvc/models/pages/posts.go | 67 +++++++++++++++++++++++++---------- mvc/views/pages/post.gohtml | 13 +++++++ mvc/views/pages/posts.gohtml | 3 -- posts/test.md | 2 ++ tools/parse.go | 22 ++++++++++++ 13 files changed, 188 insertions(+), 53 deletions(-) create mode 100644 go.sum create mode 100644 mvc/controllers/pages/post.go create mode 100644 mvc/views/pages/post.gohtml create mode 100644 posts/test.md create mode 100644 tools/parse.go diff --git a/go.mod b/go.mod index c8beda6..9566803 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module main go 1.23.2 + +require github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..148941c --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= +github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= diff --git a/main.go b/main.go index 015f956..000e992 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "main/mvc/controllers" controllers_pages "main/mvc/controllers/pages" "main/mvc/models" + models_pages "main/mvc/models/pages" "main/tools" "net/http" ) @@ -52,7 +53,7 @@ func setupRoutes(a *models.App) *http.ServeMux { m := controllers.MiddlewaresChain // Обработка статических файлов - router.Handle(a.Config.AssetsPath, m(controllers.StaticHandler())) + router.Handle(a.Config.AssetsDir, m(controllers.StaticHandler())) // Главные странички { @@ -62,6 +63,11 @@ func setupRoutes(a *models.App) *http.ServeMux { router.Handle("/", m(controllers_pages.MainPageHandler(a))) // Обработка страницы со списком постов router.Handle("/posts/", m(controllers_pages.PostsPageHandler(a))) + // Обработка страничек постов + for key := range models_pages.GetPosts() { + postLink := string(key) + router.Handle(postLink, m(controllers_pages.PostPageHandler(a))) + } } return router diff --git a/mvc/controllers/pages/main.go b/mvc/controllers/pages/main.go index 890a169..a7f051d 100644 --- a/mvc/controllers/pages/main.go +++ b/mvc/controllers/pages/main.go @@ -2,6 +2,7 @@ package controllers import ( "log" + "main/mvc/models" models_pages "main/mvc/models/pages" "main/tools" diff --git a/mvc/controllers/pages/post.go b/mvc/controllers/pages/post.go new file mode 100644 index 0000000..7633ffd --- /dev/null +++ b/mvc/controllers/pages/post.go @@ -0,0 +1,39 @@ +package controllers + +import ( + "main/mvc/models" + models_pages "main/mvc/models/pages" + "net/http" +) + +// Обработчик главной страницы +func PostPageHandler(a *models.App) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var err error + + posts := models_pages.GetPosts() + + // Страничка рендерится только если ее нет в кэше + pageData, ok := a.Cache.Get(models_pages.PostPageTmplName) + if !ok { + + post := posts[models_pages.PostLink(r.URL.Path)] + + pageData, err = models_pages.RenderPostPage(a.Templates, a.Version, post.Data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + a.Cache.Set(models_pages.PostPageTmplName, pageData) + } + + SendPostPage(w, pageData.([]byte)) + }) +} + +// Отправляет страницу +func SendPostPage(w http.ResponseWriter, data []byte) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(data) +} diff --git a/mvc/models/app.go b/mvc/models/app.go index 6638f97..6317191 100644 --- a/mvc/models/app.go +++ b/mvc/models/app.go @@ -32,7 +32,7 @@ func AppInit(configPath string) (*App, error) { } // Загрузка шаблонов - if err := a.loadTemplates(a.Config.TemplatesPath, a.Config.TemplatesExt); err != nil { + if err := a.loadTemplates(a.Config.TemplatesDir, a.Config.TemplatesExt); err != nil { log.Fatal(err) } diff --git a/mvc/models/config.go b/mvc/models/config.go index e74c438..ca7c284 100644 --- a/mvc/models/config.go +++ b/mvc/models/config.go @@ -6,15 +6,16 @@ import ( ) type Config struct { - AssetsPath string - TemplatesPath string - TemplatesExt string - LocalIP string - LocalPort string - ServerIP string - ServerPort string - ServerDomain string - Port string + PostsDir string + AssetsDir string + TemplatesDir string + TemplatesExt string + LocalIP string + LocalPort string + ServerIP string + ServerPort string + ServerDomain string + Port string } func ConfigInit() *Config { diff --git a/mvc/models/pages/post.go b/mvc/models/pages/post.go index 2d69cca..d35f0b1 100644 --- a/mvc/models/pages/post.go +++ b/mvc/models/pages/post.go @@ -1,36 +1,55 @@ package models import ( + "bytes" + "html/template" "time" ) -type PostName string +type PostLink string + +const ( + // Имя соответствующего шаблона + PostPageTmplName = "post.gohtml" +) type Post struct { - Name PostName // имя поста - Link string // ссылка на пост - Preview string // превью поста - Data string // содержание - CreateTimestamp int64 // время создания + Link PostLink + Preview template.HTML + Data template.HTML + CreateTimestamp int64 } -// NewPost создает новый пост -func NewPost(name, link, data string) *Post { - // preview - первые 500 символов содержания - var preview string - if len(data) > 500 { - preview = data[:500] + "..." - } else { - preview = data +func RenderPostPage(templates *template.Template, version int64, data template.HTML) ([]byte, error) { + var pageData bytes.Buffer + + context := map[string]any{ + "version": version, + "renderingTimestamp": time.Now().Unix(), + "data": data, } - post := &Post{ - Name: PostName(name), - Link: link, - Preview: preview, - Data: data, + if err := templates.ExecuteTemplate(&pageData, PostPageTmplName, context); err != nil { + return nil, err + } + + return pageData.Bytes(), nil +} + +func NewPost(link string, data []byte) *Post { + previewBuf := make([]byte, 0, 503) + + if len(data) > 500 { + previewBuf = append(previewBuf, data[:500]...) + previewBuf = append(previewBuf, '.', '.', '.') + } else { + previewBuf = append(previewBuf, data...) + } + + return &Post{ + Link: PostLink(link), + Preview: template.HTML(previewBuf), + Data: template.HTML(data), CreateTimestamp: time.Now().Unix(), } - - return post } diff --git a/mvc/models/pages/posts.go b/mvc/models/pages/posts.go index daa7507..b159404 100644 --- a/mvc/models/pages/posts.go +++ b/mvc/models/pages/posts.go @@ -2,7 +2,13 @@ package models import ( "bytes" + "fmt" "html/template" + "io" + "log" + "main/tools" + "os" + "path/filepath" "strings" "time" ) @@ -12,28 +18,53 @@ const ( PostsPageTmplName = "posts.gohtml" ) -type Posts map[PostName]Post +type Posts map[PostLink]*Post var ( - posts = Posts{ - "post 1": *NewPost( - "post 1", - "/post-1/", - "full content 1 with more than 100 characters Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam euismod...", - ), - "post 2": *NewPost( - "post 2", - "/post-2/", - "full content 2", - ), - "post 3": *NewPost( - "post 3", - "/post-3/", - strings.Repeat("This is post 3 content. ", 30), - ), - } + posts = Posts{} ) +func init() { + if err := LoadPosts("posts/"); err != nil { + log.Fatalf("%v", err) + } +} + +func GetPosts() Posts { + return posts +} + +func LoadPosts(dir string) error { + err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { + if err != nil { + return err + } + if !f.IsDir() && strings.HasSuffix(f.Name(), ".md") { + + file, err := os.Open(path) + if err != nil { + return err + } + + md, err := io.ReadAll(file) + if err != nil { + return err + } + + html := tools.MdToHTML(md) + link := fmt.Sprintf("/%s/", strings.TrimSuffix(filepath.Base(path), ".md")) + posts[PostLink(link)] = NewPost(link, html) + } + return nil + }) + + if err != nil { + return err + } + + return nil +} + func RenderPostsPage(templates *template.Template, version int64) ([]byte, error) { var pageData bytes.Buffer diff --git a/mvc/views/pages/post.gohtml b/mvc/views/pages/post.gohtml new file mode 100644 index 0000000..1f78e9e --- /dev/null +++ b/mvc/views/pages/post.gohtml @@ -0,0 +1,13 @@ + + +{{ template "head" . }} + + {{ template "header" . }} +
+
+ {{ .data }} +
+
+ {{ template "footer" . }} + + \ No newline at end of file diff --git a/mvc/views/pages/posts.gohtml b/mvc/views/pages/posts.gohtml index e4ebf67..2b29249 100644 --- a/mvc/views/pages/posts.gohtml +++ b/mvc/views/pages/posts.gohtml @@ -7,9 +7,6 @@ {{ range $key, $post := .posts }}
-

- {{ $post.Name }} -

{{ $post.Preview }}

diff --git a/posts/test.md b/posts/test.md new file mode 100644 index 0000000..4bc1270 --- /dev/null +++ b/posts/test.md @@ -0,0 +1,2 @@ +# Это тестовый пост +Этот пост был написан в файле формата .md \ No newline at end of file diff --git a/tools/parse.go b/tools/parse.go new file mode 100644 index 0000000..a36a4a1 --- /dev/null +++ b/tools/parse.go @@ -0,0 +1,22 @@ +package tools + +import ( + "github.com/gomarkdown/markdown" + "github.com/gomarkdown/markdown/html" + "github.com/gomarkdown/markdown/parser" +) + +// Принимает байты .md, отдает .html +func MdToHTML(md []byte) []byte { + // create markdown parser with extensions + extensions := parser.CommonExtensions | parser.NoEmptyLineBeforeBlock + p := parser.NewWithExtensions(extensions) + doc := p.Parse(md) + + // create HTML renderer with extensions + htmlFlags := html.CommonFlags | html.HrefTargetBlank + opts := html.RendererOptions{Flags: htmlFlags} + renderer := html.NewRenderer(opts) + + return markdown.Render(doc, renderer) +}