feat: added dynamic heading generation.

This commit is contained in:
Andrey Parhomenko 2024-03-03 06:59:29 +05:00
parent 243bd2b9e9
commit cd2b166ff5
14 changed files with 341 additions and 174 deletions

110
server/heading.go Normal file
View file

@ -0,0 +1,110 @@
package server
import (
"github.com/gomarkdown/markdown/ast"
"strings"
"fmt"
)
type Heading struct {
Id string
Level int
Text string
}
func getDocumentHeadings(doc ast.Node) []Heading {
ret := []Heading{}
ast.WalkFunc(doc, func(node ast.Node, entering bool) ast.WalkStatus {
heading, ok := node.(*ast.Heading)
if !ok {
return ast.GoToNext
}
children := node.GetChildren()
if len(children) > 0 && !entering {
leaf := children[0].AsLeaf()
if leaf == nil {
return ast.GoToNext
}
ret = append(ret, Heading{
Id: heading.HeadingID,
Level: heading.Level,
Text: string(leaf.Literal),
})
return ast.SkipChildren
}
return ast.GoToNext
})
return ret
}
type HeadingTree struct {
Heading Heading
Children HeadingTrees
}
type HeadingTrees []*HeadingTree
func makeHeadingTrees(hs []Heading) HeadingTrees {
if len(hs) == 0 {
return nil
}
if len(hs) == 1 {
return HeadingTrees{
&HeadingTree{
Heading: hs[0],
},
}
}
var (
lasti int
found bool
)
first := hs[0]
rest := hs[1:]
for i, h := range rest {
if first.Level >= h.Level {
lasti = i+1
found = true
break
}
}
if !found {
return HeadingTrees{
&HeadingTree{
Heading: first,
Children: makeHeadingTrees(rest),
},
}
}
return append(
makeHeadingTrees(hs[:lasti]),
makeHeadingTrees(hs[lasti:])... ,
)
}
func RenderHeadingTrees(trees HeadingTrees, first bool) string {
var b strings.Builder
fmt.Fprint(&b, "<nav")
if first {
fmt.Fprint(&b, " class=\"document\"")
}
fmt.Fprint(&b, ">")
for _, tree := range trees {
fmt.Fprintf(
&b, "<a href=\"#%s\">%s</a>",
tree.Heading.Id, tree.Heading.Text,
)
if len(tree.Children) > 0 {
fmt.Fprint(&b, RenderHeadingTrees(tree.Children, false))
}
}
fmt.Fprint(&b, "</nav>")
return b.String()
}

View file

@ -3,7 +3,6 @@ package server
import ( import (
"github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/ast"
"github.com/gomarkdown/markdown/parser" "github.com/gomarkdown/markdown/parser"
"vultras.su/core/bond" "vultras.su/core/bond"
"vultras.su/core/bond/contents" "vultras.su/core/bond/contents"
@ -12,7 +11,7 @@ import (
"path" "path"
"os" "os"
"fmt" "fmt"
"strings" //"strings"
"bytes" "bytes"
) )
@ -20,6 +19,7 @@ const htmlHead = `
<!doctype html> <!doctype html>
<html><head> <html><head>
<title>Title</title> <title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/web/main.css"> <link rel="stylesheet" href="/web/main.css">
</head><body> </head><body>
` `
@ -90,113 +90,6 @@ func (srv *Server) pageFooter() string {
return htmlFooter return htmlFooter
} }
type Heading struct {
Id string
Level int
Text string
}
func getDocumentHeadings(doc ast.Node) []Heading {
ret := []Heading{}
ast.WalkFunc(doc, func(node ast.Node, entering bool) ast.WalkStatus {
heading, ok := node.(*ast.Heading)
if !ok {
return ast.GoToNext
}
children := node.GetChildren()
if len(children) > 0 && !entering {
leaf := children[0].AsLeaf()
if leaf == nil {
return ast.GoToNext
}
ret = append(ret, Heading{
Id: heading.HeadingID,
Level: heading.Level,
Text: string(leaf.Literal),
})
return ast.SkipChildren
}
return ast.GoToNext
})
return ret
}
type HeadingTree struct {
Heading Heading
Children HeadingTrees
}
type HeadingTrees []*HeadingTree
func makeHeadingTrees(hs []Heading) HeadingTrees {
fmt.Println("fooooooooooooooouuuuuuuuund", len(hs), hs)
if len(hs) == 0 {
return nil
}
if len(hs) == 1 {
return HeadingTrees{
&HeadingTree{
Heading: hs[0],
},
}
}
var (
lasti int
found bool
)
first := hs[0]
rest := hs[1:]
for i, h := range rest {
if first.Level >= h.Level {
fmt.Println("thefound: ", first, h)
lasti = i+1
found = true
break
}
}
if !found {
fmt.Println("in not found")
return HeadingTrees{
&HeadingTree{
Heading: first,
Children: makeHeadingTrees(rest),
},
}
}
fmt.Println("through", lasti)
return append(
makeHeadingTrees(hs[:lasti]),
makeHeadingTrees(hs[lasti:])... ,
)
}
func RenderHeadingTrees(trees HeadingTrees, first bool) string {
var b strings.Builder
fmt.Fprint(&b, "<nav")
if first {
fmt.Fprint(&b, " class=\"document\"")
}
fmt.Fprint(&b, ">")
for _, tree := range trees {
fmt.Fprintf(
&b, "<a href=\"#%s\">%s</a>",
tree.Heading.Id, tree.Heading.Text,
)
if len(tree.Children) > 0 {
fmt.Fprint(&b, RenderHeadingTrees(tree.Children, false))
}
}
fmt.Fprint(&b, "</nav>")
return b.String()
}
func (srv *Server) ProcessToHtml(urlPath, filePath string, bts []byte) ([]byte, error) { func (srv *Server) ProcessToHtml(urlPath, filePath string, bts []byte) ([]byte, error) {
var b bytes.Buffer var b bytes.Buffer
doc := srv.makeMdParser().Parse(bts) doc := srv.makeMdParser().Parse(bts)
@ -217,7 +110,7 @@ func (srv *Server) ProcessToHtml(urlPath, filePath string, bts []byte) ([]byte,
&b, "<a href=\"..\">&lt&lt&lt</a><a href=\".\">%s</a>", &b, "<a href=\"..\">&lt&lt&lt</a><a href=\".\">%s</a>",
srv.makeFileName(fileDirPath, "index"+srv.options.WikiExt), srv.makeFileName(fileDirPath, "index"+srv.options.WikiExt),
) )
fmt.Fprint(&b, "</nav><nav class=\"dirs\">") fmt.Fprint(&b, "<nav class=\"dirs\">")
for _, entry := range entries { for _, entry := range entries {
if !entry.IsDir() { if !entry.IsDir() {
continue continue
@ -228,7 +121,11 @@ func (srv *Server) ProcessToHtml(urlPath, filePath string, bts []byte) ([]byte,
srv.makeFileName(fileDirPath, entry.Name()), srv.makeFileName(fileDirPath, entry.Name()),
) )
} }
fmt.Fprint(&b, "</nav><nav class=\"files\">")
fmt.Fprint(&b, "</nav>")
fmt.Fprint(&b, "</nav>")
fmt.Fprint(&b, "<nav class=\"files\">")
for _, entry := range entries { for _, entry := range entries {
if entry.IsDir() || entry.Name() == "index" + srv.options.WikiExt{ if entry.IsDir() || entry.Name() == "index" + srv.options.WikiExt{
continue continue
@ -239,20 +136,20 @@ func (srv *Server) ProcessToHtml(urlPath, filePath string, bts []byte) ([]byte,
srv.makeFileName(fileDirPath, entry.Name()), srv.makeFileName(fileDirPath, entry.Name()),
) )
} }
fmt.Fprint(&b, "</nav></header>") fmt.Fprint(&b, "</nav>")
}
fmt.Fprint(&b, "<div class=\"content\">")
if srv.options.AddDocNavigation {
headings := getDocumentHeadings(doc) headings := getDocumentHeadings(doc)
trees := makeHeadingTrees(headings) trees := makeHeadingTrees(headings)
docNav := RenderHeadingTrees(trees, true) docNav := RenderHeadingTrees(trees, true)
fmt.Fprint(&b, docNav) fmt.Fprint(&b, docNav)
fmt.Fprint(&b, "</header>")
} }
fmt.Fprint(&b, "<main>") fmt.Fprint(&b, "<main>")
fmt.Fprintf(&b, string(main_section)) fmt.Fprintf(&b, string(main_section))
fmt.Fprintf(&b, "</main>") fmt.Fprintf(&b, "</main>")
fmt.Fprintf(&b, "</div>")
fmt.Fprint(&b, srv.pageFooter()) fmt.Fprint(&b, srv.pageFooter())
return b.Bytes(), nil return b.Bytes(), nil
} }

BIN
web/img/atruim1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View file

@ -1,13 +1,96 @@
.full-width {
width: 100%;
}
.border {
border: .15rem solid var(--bd-color);
}
.float-left {
float: left;
}
.float-right {
float: right ;
}
table {
border-collapse: collapse;
}
table, th, td {
border: var(--bd-width) var(--bd-style) var(--bd-color);
}
h1:after, h2:after, h3:after, h4:after {
display : block ;
content: '';
margin-top: 0.2rem;
border-bottom: var(--bd-width) var(--bd-style) var(--bd-color);
}
:root {
--bg-color: ghostwhite;
--fg-color: black;
--bd-color: gray;
--bd-width: .15rem;
--bd-style: solid;
--hv-fg-color: white;
--hv-bg-color: silver;
--header-width: 6rem;
}
html {
background: var(--bg-color);
color: var(--fg-color);
list-style-position: inside;
}
body {
top: 0;
left: 0;
margin: 0;
display: flex;
position: fixed;
width: 100%;
height: 100%;
/*gap: 1rem;*/
}
header {
width: fit-content;
display: flex;
flex-direction: column;
height: 100%;
border-right: var(--bd-width) var(--bd-style) var(--bd-color);
overflow-y: auto;
overflow-x: hidden;
flex-shrink: 0;
padding-right: 1rem;
}
header > nav {
padding-left: .25rem;
padding-top: 1rem;
padding-bottom: 1rem;
border-bottom: var(--bd-width) var(--bd-style) var(--bd-color);
}
nav { nav {
padding: .5rem; padding-left: 1rem;
} width: fit-content;
nav a { display: flex;
padding: 1rem; flex-direction: column;
} }
p { main {
border-style: solid; /*margin-left: calc(var(--header-width) + 1rem);*/
border-width: 0.15rem; padding-left: 1rem;
border-color: black; flex-shrink: 1;
overflow-y: auto;
} }

View file

@ -1,20 +0,0 @@
# Header
*Body* and the **content**
## Checking links
A [root link](/)
## Checking dynamic content
135731 + 531753 = <? print(135731 + 531753) ?>
Loop:
<?
for i:=1 ; i<=20 ; i++ {
printf(" * %d\n", i)
}
?>

View file

@ -1,6 +1,48 @@
# The index file. # surdeus
* [some other file](/file) Мой сайт для хранения заметок и документов.
Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Сайт построен полностью на Golang, для рендера сначала
происходит препроцессинг Markdown с помощью написанного мной [pp](https://vultras.su/util/pp) и встроенного
в него [tengo](https://github.com/d5/tengo),
Вот пример динмаческого контента:
<?
for i:=1 ; i<=50 ;i++ {
print(i)
if i < 50 {
print(", ")
}
}
?>
Да, поверь, мне было бы слишком лень писать 50 цифр. Не веришь? Вот тебе динамическое время: <?
times := import("times")
print(times.now())
?>
А затем, серверно привязанные данные подставляются самим Go и рендерятся в
HTML с помощью [gomarkdown](https://github.com/gomarkdown/markdown).
Всё это написано в Neovim запущено через терминал, скорее всего Konsole из KDE.
## О себе
Андрей Пархоменко из Тюмени (surdeus). Да, я не шифруюсь.
* [Разработчик](https://vultras.su/surdeus)
* Этот сайт
* [vultras.su](https://vultras.su/) - сервис для моих репозиториев
* Музыкант/Аудио-инженер
* Дизайнер
## Контакты
* [Telegram](https://t.me/surdeus)
## Разделы
* [RPG(НРИ)](/rpg)

4
wiki/rpg/chars/index.pmd Normal file
View file

@ -0,0 +1,4 @@
# Персонажи
Хранилище персонажей для моих миров.

8
wiki/rpg/chars/j.pmd Normal file
View file

@ -0,0 +1,8 @@
# Джей(J) Наёмник
Наёмник из Атриума, прошедший многие войны.
## Биография
Родился там-то там-то

8
wiki/rpg/index.pmd Normal file
View file

@ -0,0 +1,8 @@
# RPG
Этот раздел отвечает за разрабатываемые мной игровые аспекты для НРИ:
* Лор
* Персонажи
* Локации
* etc

View file

@ -0,0 +1,57 @@
# Атриум
## История
Бесконечный, абсолютный хаос, то, с чего начинается всё,
в том числе и история Атриума, места, где соединилось всё:
боль и удовольствие, дружба и вражда, рабы и повелители, жизнь и смерть, хаос и порядок.
Могущественная раса Древних, существ,
что познали суть хаоса и открыли многие из его секретов,
способы его применения себе на благо и во вред.
Одним из этих секретов был способ создания прорывов между
близлежащими измерениями, открытый в спешке в целях решения
проблемы перенаселения, вызванной крайне высоким уровнем жизни
и полным объединением в одно государство,
что избавило их от ещё одного способа контроля популяции.
В мирах, что они начинали колонизировать,
также присутствовали другие разумные расы,
но на более ранних этапах развития.
Большая часть из них была порабощена либо ассимилирована.
Независимо от их взаимодействия со временем их крови смешались,
порождая гибриды, что также имели способность к манипуляции хаосом.
Во многих случаях такие гибриды имели меньшие способности, чем Древние,
но зачастую позволяли им делать то, чего Древние не могли,
раскрывая свой потенциал связи с Планами.
## Магия
Магия в мире Атриума является способом взаимодействия с Планами и
энергией Хаоса в частности, который был изобретён Древними.
Планы представляют из себя базовые составляющие для любого измерения и
определяют его законы, т. к. измерения являются лишь пересечениями
планов таким образом, что оно может содержать материю.
Из-за того что измерения могут не иметь в себе
некоторых планов в мирах могут отсутствовать некоторые
явления или вещества, например огонь или электричество, вода или железо, например.
Способности в манипуляции хаосом также исходят из этих планов и связи мага с ними.
Маги могут влиять именно на планы, усиливая,
ослабляя или прорывая их,
что находит своё отражение уже в самом измерении.
Планы включают в себя Подпланы, отражающие более конкретные аспекты измерений.
Маги ограничены своей связью с планами для воплощения заклинаний.
### Рефлексы
Магия не всегда работает осознанно. Зачастую это происходит рефлекторно,
когда связь с некоторым(и) планом(ами) становится достаточно прочной по каким-то причинам:
навык, вера, какое-то знание, и это позволяет преодолевать свои пределы.
Например опытный ассасин, достигший вершин возможностей в скрытности,
может в один момент научиться становиться сначала неполностью прозрачным
и гасить звук передвижения, а потом и становиться полностью невидимым и бесшумным.

View file

@ -0,0 +1,5 @@
# Миры
Описания миров
* [Атриум](atrium)

View file

@ -1,21 +0,0 @@
{#shit .class1 .class2}
# Header
shit
teahuste| ehtua | shit
--------|-------------|--------------
cock| dick|die
* [X] check
```go
func main() {
}
```
## Second header shit
## another dick
### third level header

View file

@ -1,3 +0,0 @@
# checking shit
another shit

View file

@ -1,3 +0,0 @@
# sub dir index
hetaushetusahet