сделал ajax с использованием htmx

design
serr 2025-06-07 23:42:10 +03:00
parent 0540bdf1d2
commit 2d4a6c77a5
21 changed files with 275 additions and 195 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
config.json
hikan.ru
hikan.ru
*.server*

View File

@ -69,6 +69,7 @@ h1::before { top: 0; }
h1::after { bottom: 0; }
a {
cursor: pointer;
background-color: #77abbb;
color: black;
text-decoration: none;

1
assets/scripts/htmx.js Normal file

File diff suppressed because one or more lines are too long

2
eye.sh
View File

@ -4,7 +4,7 @@ stty -echoctl # Отключает вывод управляющих симво
# НАСТРОЙКА СКРИПТА ТУТ ###########################################################
DURATION=1 # Задержка между проверками в секундах
WATCH_TARGETS=("assets" "mvc" "posts" "tools" "main.go" "config.json") # Массив целей для наблюдения (директории и файлы)
BINARY_PATH="./main" # Путь до бинарного файла
BINARY_PATH="./main.server" # Путь до бинарного файла
BUILD_CMD="go build -o $BINARY_PATH main.go" # Команда для сборки
###################################################################################

View File

@ -14,7 +14,10 @@ import (
func MainPageHandler(app *models.App) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
var (
pageData []byte
err error
)
// Количество запросов, обработанных сервером за 24ч
if r.Method == "COUNT" {
@ -38,17 +41,24 @@ func MainPageHandler(app *models.App) http.HandlerFunc {
return
}
// Страничка рендерится только если ее нет в кэше
pageData, ok := app.PagesCache.Get(models_pages.MainPageTmplName)
if !ok {
pageData, err = models_pages.RenderMainPage(app.Templates, app.Version)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
app.PagesCache.Set(models_pages.MainPageTmplName, pageData)
cacheKey := models_pages.MainPageTmplName
ajax := r.URL.Query().Get("ajax") == "true"
if ajax {
cacheKey += "?ajax=true"
}
if pageData, ok := app.PagesCache.Get(cacheKey); ok {
sendMainPage(w, pageData)
return
}
pageData, err = models_pages.RenderMainPage(app.Templates, app.Version, ajax)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
app.PagesCache.Set(cacheKey, pageData)
sendMainPage(w, pageData)
})
}

View File

@ -6,28 +6,35 @@ import (
"net/http"
)
// Обработчик главной страницы
// Обработчик страницы поста
func PostPageHandler(app *models.App) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
// Ссылка вида /postname
link := r.URL.Path
// Страничка рендерится только если ее нет в кэше
pageData, ok := app.PagesCache.Get(link)
if !ok {
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(link, pageData)
cacheKey := link
ajax := r.URL.Query().Get("ajax") == "true"
if ajax {
cacheKey += "?ajax=true"
}
if pageData, ok := app.PagesCache.Get(cacheKey); ok {
sendPostPage(w, pageData)
return
}
post := app.PostsMap[models_pages.PostLink(link)]
var (
pageData []byte
err error
)
pageData, err = post.RenderPostPage(app.Templates, app.Version, ajax)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
app.PagesCache.Set(cacheKey, pageData)
sendPostPage(w, pageData)
})
}

View File

@ -10,37 +10,39 @@ import (
// Обработчик странички со списком постов
func PostsPageHandler(app *models.App) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
// Ссылка вида /posts/0
return func(w http.ResponseWriter, r *http.Request) {
link := r.URL.Path
cacheKey := link
// Страничка рендерится только если ее нет в кэше
pageData, ok := app.PagesCache.Get(link)
if !ok {
// Из мапы формирую отсортированный список всех постов (сначала новые)
postsList := app.PostsMap.PostsList()
// Ошибки тут быть не может, так как этот обработчик настроен
// на только существующие реально pageNumber и в случае
// какого то не существующего номера страницы он просто
// не сработает (в главном main настроены маршруты)
pageNumber, _ := strconv.Atoi(path.Base(link))
page := models_pages.CreatePostsPage(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(link, pageData)
ajax := r.URL.Query().Get("ajax") == "true"
if ajax {
cacheKey += "?ajax=true"
}
if pageData, ok := app.PagesCache.Get(cacheKey); ok {
sendPostsPage(w, pageData)
return
}
postsList := app.PostsMap.PostsList()
pageNumber, _ := strconv.Atoi(path.Base(link))
page := models_pages.CreatePostsPage(postsList, pageNumber, app.Cfg.PostsMaxCountOnPage)
var (
pageData []byte
err error
)
pageData, err = page.RenderPostsPage(app.Templates, app.Version, ajax)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
app.PagesCache.Set(cacheKey, pageData)
sendPostsPage(w, pageData)
})
}
}
// Отправляет страницу

View File

@ -8,10 +8,11 @@ import (
const (
// Имя соответствующего шаблона
MainPageTmplName = "main.gohtml"
MainPageTmplName = "main.gohtml"
MainPageTmplNameAjax = "main_ajax.gohtml"
)
func RenderMainPage(templates *template.Template, version int64) ([]byte, error) {
func RenderMainPage(templates *template.Template, version int64, ajax bool) ([]byte, error) {
var pageData bytes.Buffer
context := map[string]any{
@ -19,7 +20,12 @@ func RenderMainPage(templates *template.Template, version int64) ([]byte, error)
"renderingTimestamp": time.Now().Unix(),
}
if err := templates.ExecuteTemplate(&pageData, MainPageTmplName, context); err != nil {
templateName := MainPageTmplName
if ajax {
templateName = MainPageTmplNameAjax
}
if err := templates.ExecuteTemplate(&pageData, templateName, context); err != nil {
return nil, err
}

View File

@ -8,7 +8,8 @@ import (
const (
// Имя соответствующего шаблона
PostPageTmplName = "post.gohtml"
PostPageTmplName = "post.gohtml"
PostPageTmplNameAjax = "post_ajax.gohtml"
)
type PostLink string
@ -19,7 +20,7 @@ type Post struct {
Timestamp int64
}
func (p *Post) RenderPostPage(templates *template.Template, version int64) ([]byte, error) {
func (p *Post) RenderPostPage(templates *template.Template, version int64, ajax bool) ([]byte, error) {
var pageData bytes.Buffer
context := map[string]any{
@ -29,7 +30,12 @@ func (p *Post) RenderPostPage(templates *template.Template, version int64) ([]by
"modTimestamp": p.Timestamp,
}
if err := templates.ExecuteTemplate(&pageData, PostPageTmplName, context); err != nil {
templateName := PostPageTmplName
if ajax {
templateName = PostPageTmplNameAjax
}
if err := templates.ExecuteTemplate(&pageData, templateName, context); err != nil {
return nil, err
}

View File

@ -16,7 +16,8 @@ import (
const (
// Имя соответствующего шаблона
PostsPageTmplName = "posts.gohtml"
PostsPageTmplName = "posts.gohtml"
PostsPageTmplNameAjax = "posts_ajax.gohtml"
)
type PostsMap map[PostLink]*Post
@ -101,7 +102,7 @@ func CreatePostsPage(postsList PostsList, pageNumber, postsMaxCountOnPage int) *
}
}
func (p *PostsPage) RenderPostsPage(templates *template.Template, version int64) ([]byte, error) {
func (p *PostsPage) RenderPostsPage(templates *template.Template, version int64, ajax bool) ([]byte, error) {
var pageData bytes.Buffer
var prevPageNumber, nextPageNumber int
@ -114,22 +115,22 @@ func (p *PostsPage) RenderPostsPage(templates *template.Template, version int64)
nextPageNumber = (p.PageNumber + 1) % p.PagesCount
pagi := fmt.Sprintf(
`<a href="/posts/%d"><-</a> %d/%d <a href="/posts/%d">-></a>`,
prevPageNumber,
p.PageNumber+1,
p.PagesCount,
nextPageNumber,
)
context := map[string]any{
"pagi": template.HTML(pagi),
"prevPageNumber": prevPageNumber,
"pageNumberInc": p.PageNumber + 1,
"pagesCount": p.PagesCount,
"nextPageNumber": nextPageNumber,
"version": version,
"renderingTimestamp": time.Now().Unix(),
"posts": p.PostsListOnPage,
}
if err := templates.ExecuteTemplate(&pageData, PostsPageTmplName, context); err != nil {
templateName := PostsPageTmplName
if ajax {
templateName = PostsPageTmplNameAjax
}
if err := templates.ExecuteTemplate(&pageData, templateName, context); err != nil {
return nil, err
}

View File

@ -5,5 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/assets/pic/favicon.ico?v={{ .version }}" type="image/x-icon">
<link rel="stylesheet" href="/assets/css/styles.css?v={{ .version }}" type="text/css">
<script src="/assets/scripts/htmx.js?v={{ .version }}"></script>
<script>document.addEventListener('htmx:afterSwap', function() {window.scrollTo(0, 0);});</script>
</head>
{{ end }}

View File

@ -19,7 +19,19 @@
</div>
<div>
<p>
see <a href="/">main page</a> or go to <a href="/posts/0">posts section</a>
see <a href="/"
hx-get="/?ajax=true"
hx-target="main"
hx-swap="outerHTML"
hx-push-url="/">
main page</a> or go to
<a href="/posts/0"
hx-get="/posts/0?ajax=true"
hx-target="main"
hx-swap="outerHTML"
hx-push-url="/posts/0">
posts section
</a>
</p>
</div>
<div>

View File

@ -1,76 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{ template "head" . }}
<body>
{{ template "header" . }}
<main>
<div>
<p>
<code>welcome to my little site</code>
</p>
</div>
<div>
<h1>
$whoami
</h1>
<p>
my name is serr (you can easily guess my real name if you speak Russian :d), and i didn't come up with that nickname, i just started being called it
</p>
<p>
i was born in 2003, i'm currently a cybersecurity major at university
</p>
<p>
<code>pronouns: he/him</code>
</p>
</div>
<div>
<h1>
what do i do?
</h1>
<p>
programming is my lifestyle
</p>
<p>
i love growing in all areas of programming - i am literally interested in everything: web development, low-level programming, formal grammars and a lot more!
</p>
<p>
i like the idea of <a href="https://en.wikipedia.org/wiki/Symbolic_execution" target="_blank">symbolic</a>/concolic execution and virtual code execution in general
</p>
</div>
<div>
<h1>
things i love
</h1>
<p>
<strong>> coffee</strong>. i REALLY love coffee. almost any. and a lot of<br/>
<strong>> movies and TV series</strong> (especially TV series). i watch something almost every day<br/>
<strong>> true crime</strong>. i'm obsessed with serial killer cases, mysterious disappearances, unsolved murders - all that dark stuff<br/>
<strong>> russian underground rap</strong> like Slava KPSS, Zamay, MB Packet, Ovsyankin etc.<br/>
<strong>> simple and extensible code</strong>. i think if your code is overly complex, it means you are doing something wrong. most things are simpler than they seem
</p>
</div>
<div>
<h1>
projects
</h1>
<p>
> <a href="https://git.hikan.ru/serr/candycache" target="_blank">git.hikan.ru/serr/candycache</a> - simple and efficient Go cache<br/>
> <a href="https://git.hikan.ru/serr/eye-hot-reloader" target="_blank">git.hikan.ru/serr/eye-hot-reloader</a> - lightweight directories monitor with automatic rebuild and restart functionality for any project type<br/>
> <del>telegram bot with schedule for SPBPU - <a href="https://t.me/polysched_bot" target="_blank">polysched_bot</a></del> (transferred to a more proactive owner)<br/>
> <del>telegram bot with schedule for SPMI - <a href="https://t.me/gornischedule_bot" target="_blank">gornischedule_bot</a></del> (closed)<br/>
</p>
</div>
<div>
<h1>
nice links
</h1>
<p>
> huge collection of Xakep issues - <a href="https://図書館.きく.コム/" target="_blank">図書館.きく.コム</a><br/>
> i like to surf here - <a href="https://neocities.org/browse" target="_blank">neocities.org/browse</a><br/>
> very atmospheric forum about black metal - <a href="https://www.lycanthropia.net/" target="_blank">lycanthropia</a><br/>
</p>
</div>
</main>
{{ template "footer" . }}
</body>
</html>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
{{ template "head" . }}
<body>
{{ template "header" . }}
{{ template "main_ajax" . }}
{{ template "footer" . }}
</body>
</html>

View File

@ -0,0 +1,72 @@
{{ template "main_ajax" . }}
{{ define "main_ajax" }}
<main>
<div>
<p>
<code>welcome to my little site</code>
</p>
</div>
<div>
<h1>
$whoami
</h1>
<p>
my name is serr (you can easily guess my real name if you speak Russian :d), and i didn't come up with that nickname, i just started being called it
</p>
<p>
i was born in 2003, i'm currently a cybersecurity major at university
</p>
<p>
<code>pronouns: he/him</code>
</p>
</div>
<div>
<h1>
what do i do?
</h1>
<p>
programming is my lifestyle
</p>
<p>
i love growing in all areas of programming - i am literally interested in everything: web development, low-level programming, formal grammars and a lot more!
</p>
<p>
i like the idea of <a href="https://en.wikipedia.org/wiki/Symbolic_execution" target="_blank">symbolic</a>/concolic execution and virtual code execution in general
</p>
</div>
<div>
<h1>
things i love
</h1>
<p>
<strong>> coffee</strong>. i REALLY love coffee. almost any. and a lot of<br/>
<strong>> movies and TV series</strong> (especially TV series). i watch something almost every day<br/>
<strong>> true crime</strong>. i'm obsessed with serial killer cases, mysterious disappearances, unsolved murders - all that dark stuff<br/>
<strong>> russian underground rap</strong> like Slava KPSS, Zamay, MB Packet, Ovsyankin etc.<br/>
<strong>> simple and extensible code</strong>. i think if your code is overly complex, it means you are doing something wrong. most things are simpler than they seem
</p>
</div>
<div>
<h1>
projects
</h1>
<p>
> <a href="https://git.hikan.ru/serr/candycache" target="_blank">git.hikan.ru/serr/candycache</a> - simple and efficient Go cache<br/>
> <a href="https://git.hikan.ru/serr/eye-hot-reloader" target="_blank">git.hikan.ru/serr/eye-hot-reloader</a> - lightweight directories monitor with automatic rebuild and restart functionality for any project type<br/>
> <del>telegram bot with schedule for SPBPU - <a href="https://t.me/polysched_bot" target="_blank">polysched_bot</a></del> (transferred to a more proactive owner)<br/>
> <del>telegram bot with schedule for SPMI - <a href="https://t.me/gornischedule_bot" target="_blank">gornischedule_bot</a></del> (closed)<br/>
</p>
</div>
<div>
<h1>
nice links
</h1>
<p>
> huge collection of Xakep issues - <a href="https://図書館.きく.コム/" target="_blank">図書館.きく.コム</a><br/>
> i like to surf here - <a href="https://neocities.org/browse" target="_blank">neocities.org/browse</a><br/>
> very atmospheric forum about black metal - <a href="https://www.lycanthropia.net/" target="_blank">lycanthropia</a><br/>
</p>
</div>
</main>
{{ end }}

View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{ template "head" . }}
<body>
{{ template "header" . }}
<main>
<div>
{{ .data }}
<p>
<code>create time: {{ .modTimestamp }}</code>
</p>
</div>
</main>
{{ template "footer" . }}
</body>
</html>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
{{ template "head" . }}
<body>
{{ template "header" . }}
{{ template "post_ajax" . }}
{{ template "footer" . }}
</body>
</html>

View File

@ -0,0 +1,12 @@
{{ template "post_ajax" . }}
{{ define "post_ajax" }}
<main>
<div>
{{ .data }}
<p>
<code>create time: {{ .modTimestamp }}</code>
</p>
</div>
</main>
{{ end }}

View File

@ -1,29 +0,0 @@
<!DOCTYPE html>
<html lang="en">
{{ template "head" . }}
<body>
{{ template "header" . }}
<main>
<div>
<p>
<code>posts sorted by create time</code>
</p>
</div>
{{ range $ind, $post := .posts }}
<div>
<p>
{{ $post.Preview }}
</p>
<p>
<a href="{{ $post.Link }}">read more</a>
</p>
<p>
<code>create time: {{ $post.Timestamp }}</code>
</p>
</div>
{{ end }}
<div class="pagi">{{ .pagi }}</div>
</main>
{{ template "footer" . }}
</body>
</html>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
{{ template "head" . }}
<body>
{{ template "header" . }}
{{ template "posts_ajax" . }}
{{ template "footer" . }}
</body>
</html>

View File

@ -0,0 +1,41 @@
{{ template "posts_ajax" . }}
{{ define "posts_ajax" }}
<main>
<div>
<p>
<code>posts sorted by create time</code>
</p>
</div>
{{ range $ind, $post := .posts }}
<div>
<p>
{{ $post.Preview }}
</p>
<p>
<a href="{{ $post.Link }}"
hx-get="{{ $post.Link }}?ajax=true"
hx-target="main"
hx-swap="outerHTML"
hx-push-url="{{ $post.Link }}">read more</a>
</p>
<p>
<code>create time: {{ $post.Timestamp }}</code>
</p>
</div>
{{ end }}
<div class="pagi">
<a href="/posts/{{ .prevPageNumber }}"
hx-get="/posts/{{ .prevPageNumber }}?ajax=true"
hx-target="main"
hx-swap="outerHTML"
hx-push-url="/posts/{{ .prevPageNumber }}"><-</a>
{{ .pageNumberInc }}/{{ .pagesCount }}
<a href="/posts/{{ .nextPageNumber }}"
hx-get="/posts/{{ .nextPageNumber }}?ajax=true"
hx-target="main"
hx-swap="outerHTML"
hx-push-url="/posts/{{ .nextPageNumber }}">-></a>
</div>
</main>
{{ end }}