конфиг выделить изи апп
parent
13ca4c403b
commit
066a30ff7d
2
go.mod
2
go.mod
|
@ -1,3 +1,5 @@
|
||||||
module main
|
module main
|
||||||
|
|
||||||
go 1.23.2
|
go 1.23.2
|
||||||
|
|
||||||
|
require github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
|
||||||
|
|
|
@ -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=
|
8
main.go
8
main.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"main/mvc/controllers"
|
"main/mvc/controllers"
|
||||||
controllers_pages "main/mvc/controllers/pages"
|
controllers_pages "main/mvc/controllers/pages"
|
||||||
"main/mvc/models"
|
"main/mvc/models"
|
||||||
|
models_pages "main/mvc/models/pages"
|
||||||
"main/tools"
|
"main/tools"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
@ -52,7 +53,7 @@ func setupRoutes(a *models.App) *http.ServeMux {
|
||||||
m := controllers.MiddlewaresChain
|
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("/", m(controllers_pages.MainPageHandler(a)))
|
||||||
// Обработка страницы со списком постов
|
// Обработка страницы со списком постов
|
||||||
router.Handle("/posts/", m(controllers_pages.PostsPageHandler(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
|
return router
|
||||||
|
|
|
@ -2,6 +2,7 @@ package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"main/mvc/models"
|
"main/mvc/models"
|
||||||
models_pages "main/mvc/models/pages"
|
models_pages "main/mvc/models/pages"
|
||||||
"main/tools"
|
"main/tools"
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AssetsPath string
|
PostsDir string
|
||||||
TemplatesPath string
|
AssetsDir string
|
||||||
TemplatesExt string
|
TemplatesDir string
|
||||||
LocalIP string
|
TemplatesExt string
|
||||||
LocalPort string
|
LocalIP string
|
||||||
ServerIP string
|
LocalPort string
|
||||||
ServerPort string
|
ServerIP string
|
||||||
ServerDomain string
|
ServerPort string
|
||||||
Port string
|
ServerDomain string
|
||||||
|
Port string
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConfigInit() *Config {
|
func ConfigInit() *Config {
|
||||||
|
|
|
@ -1,36 +1,55 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"html/template"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PostName string
|
type PostLink string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Имя соответствующего шаблона
|
||||||
|
PostPageTmplName = "post.gohtml"
|
||||||
|
)
|
||||||
|
|
||||||
type Post struct {
|
type Post struct {
|
||||||
Name PostName // имя поста
|
Link PostLink
|
||||||
Link string // ссылка на пост
|
Preview template.HTML
|
||||||
Preview string // превью поста
|
Data template.HTML
|
||||||
Data string // содержание
|
CreateTimestamp int64
|
||||||
CreateTimestamp int64 // время создания
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPost создает новый пост
|
func RenderPostPage(templates *template.Template, version int64, data template.HTML) ([]byte, error) {
|
||||||
func NewPost(name, link, data string) *Post {
|
var pageData bytes.Buffer
|
||||||
// preview - первые 500 символов содержания
|
|
||||||
var preview string
|
context := map[string]any{
|
||||||
if len(data) > 500 {
|
"version": version,
|
||||||
preview = data[:500] + "..."
|
"renderingTimestamp": time.Now().Unix(),
|
||||||
} else {
|
"data": data,
|
||||||
preview = data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
post := &Post{
|
if err := templates.ExecuteTemplate(&pageData, PostPageTmplName, context); err != nil {
|
||||||
Name: PostName(name),
|
return nil, err
|
||||||
Link: link,
|
}
|
||||||
Preview: preview,
|
|
||||||
Data: data,
|
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(),
|
CreateTimestamp: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return post
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,13 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"main/tools"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -12,28 +18,53 @@ const (
|
||||||
PostsPageTmplName = "posts.gohtml"
|
PostsPageTmplName = "posts.gohtml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Posts map[PostName]Post
|
type Posts map[PostLink]*Post
|
||||||
|
|
||||||
var (
|
var (
|
||||||
posts = Posts{
|
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),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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) {
|
func RenderPostsPage(templates *template.Template, version int64) ([]byte, error) {
|
||||||
var pageData bytes.Buffer
|
var pageData bytes.Buffer
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
{{ template "head" . }}
|
||||||
|
<body>
|
||||||
|
{{ template "header" . }}
|
||||||
|
<main>
|
||||||
|
<div>
|
||||||
|
{{ .data }}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{{ template "footer" . }}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -7,9 +7,6 @@
|
||||||
|
|
||||||
{{ range $key, $post := .posts }}
|
{{ range $key, $post := .posts }}
|
||||||
<div>
|
<div>
|
||||||
<h1>
|
|
||||||
{{ $post.Name }}
|
|
||||||
</h1>
|
|
||||||
<p>
|
<p>
|
||||||
{{ $post.Preview }}
|
{{ $post.Preview }}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Это тестовый пост
|
||||||
|
Этот пост был написан в файле формата .md
|
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue