feat: added dynamic heading generation.
This commit is contained in:
parent
243bd2b9e9
commit
cd2b166ff5
14 changed files with 341 additions and 174 deletions
110
server/heading.go
Normal file
110
server/heading.go
Normal 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()
|
||||
}
|
129
server/main.go
129
server/main.go
|
@ -3,7 +3,6 @@ package server
|
|||
import (
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/gomarkdown/markdown/ast"
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
"vultras.su/core/bond"
|
||||
"vultras.su/core/bond/contents"
|
||||
|
@ -12,7 +11,7 @@ import (
|
|||
"path"
|
||||
"os"
|
||||
"fmt"
|
||||
"strings"
|
||||
//"strings"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
|
@ -20,6 +19,7 @@ const htmlHead = `
|
|||
<!doctype html>
|
||||
<html><head>
|
||||
<title>Title</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="/web/main.css">
|
||||
</head><body>
|
||||
`
|
||||
|
@ -90,113 +90,6 @@ func (srv *Server) pageFooter() string {
|
|||
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) {
|
||||
var b bytes.Buffer
|
||||
doc := srv.makeMdParser().Parse(bts)
|
||||
|
@ -217,7 +110,7 @@ func (srv *Server) ProcessToHtml(urlPath, filePath string, bts []byte) ([]byte,
|
|||
&b, "<a href=\"..\"><<<</a><a href=\".\">%s</a>",
|
||||
srv.makeFileName(fileDirPath, "index"+srv.options.WikiExt),
|
||||
)
|
||||
fmt.Fprint(&b, "</nav><nav class=\"dirs\">")
|
||||
fmt.Fprint(&b, "<nav class=\"dirs\">")
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
|
@ -228,7 +121,11 @@ func (srv *Server) ProcessToHtml(urlPath, filePath string, bts []byte) ([]byte,
|
|||
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 {
|
||||
if entry.IsDir() || entry.Name() == "index" + srv.options.WikiExt{
|
||||
continue
|
||||
|
@ -239,20 +136,20 @@ func (srv *Server) ProcessToHtml(urlPath, filePath string, bts []byte) ([]byte,
|
|||
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)
|
||||
trees := makeHeadingTrees(headings)
|
||||
docNav := RenderHeadingTrees(trees, true)
|
||||
fmt.Fprint(&b, docNav)
|
||||
|
||||
|
||||
fmt.Fprint(&b, "</header>")
|
||||
}
|
||||
|
||||
fmt.Fprint(&b, "<main>")
|
||||
fmt.Fprintf(&b, string(main_section))
|
||||
fmt.Fprintf(&b, "</main>")
|
||||
fmt.Fprintf(&b, "</div>")
|
||||
fmt.Fprint(&b, srv.pageFooter())
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
|
BIN
web/img/atruim1.webp
Normal file
BIN
web/img/atruim1.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
99
web/main.css
99
web/main.css
|
@ -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 {
|
||||
padding: .5rem;
|
||||
}
|
||||
nav a {
|
||||
padding: 1rem;
|
||||
padding-left: 1rem;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
p {
|
||||
border-style: solid;
|
||||
border-width: 0.15rem;
|
||||
border-color: black;
|
||||
main {
|
||||
/*margin-left: calc(var(--header-width) + 1rem);*/
|
||||
padding-left: 1rem;
|
||||
flex-shrink: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
?>
|
||||
|
|
@ -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
4
wiki/rpg/chars/index.pmd
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Персонажи
|
||||
|
||||
Хранилище персонажей для моих миров.
|
||||
|
8
wiki/rpg/chars/j.pmd
Normal file
8
wiki/rpg/chars/j.pmd
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Джей(J) Наёмник
|
||||
|
||||
Наёмник из Атриума, прошедший многие войны.
|
||||
|
||||
## Биография
|
||||
|
||||
Родился там-то там-то
|
||||
|
8
wiki/rpg/index.pmd
Normal file
8
wiki/rpg/index.pmd
Normal file
|
@ -0,0 +1,8 @@
|
|||
# RPG
|
||||
|
||||
Этот раздел отвечает за разрабатываемые мной игровые аспекты для НРИ:
|
||||
|
||||
* Лор
|
||||
* Персонажи
|
||||
* Локации
|
||||
* etc
|
57
wiki/rpg/worlds/atrium.pmd
Normal file
57
wiki/rpg/worlds/atrium.pmd
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Атриум
|
||||
|
||||
## История
|
||||
|
||||
Бесконечный, абсолютный хаос, то, с чего начинается всё,
|
||||
в том числе и история Атриума, места, где соединилось всё:
|
||||
боль и удовольствие, дружба и вражда, рабы и повелители, жизнь и смерть, хаос и порядок.
|
||||
|
||||
Могущественная раса Древних, существ,
|
||||
что познали суть хаоса и открыли многие из его секретов,
|
||||
способы его применения себе на благо и во вред.
|
||||
|
||||
Одним из этих секретов был способ создания прорывов между
|
||||
близлежащими измерениями, открытый в спешке в целях решения
|
||||
проблемы перенаселения, вызванной крайне высоким уровнем жизни
|
||||
и полным объединением в одно государство,
|
||||
что избавило их от ещё одного способа контроля популяции.
|
||||
|
||||
В мирах, что они начинали колонизировать,
|
||||
также присутствовали другие разумные расы,
|
||||
но на более ранних этапах развития.
|
||||
Большая часть из них была порабощена либо ассимилирована.
|
||||
Независимо от их взаимодействия со временем их крови смешались,
|
||||
порождая гибриды, что также имели способность к манипуляции хаосом.
|
||||
Во многих случаях такие гибриды имели меньшие способности, чем Древние,
|
||||
но зачастую позволяли им делать то, чего Древние не могли,
|
||||
раскрывая свой потенциал связи с Планами.
|
||||
|
||||
## Магия
|
||||
|
||||
Магия в мире Атриума является способом взаимодействия с Планами и
|
||||
энергией Хаоса в частности, который был изобретён Древними.
|
||||
|
||||
Планы представляют из себя базовые составляющие для любого измерения и
|
||||
определяют его законы, т. к. измерения являются лишь пересечениями
|
||||
планов таким образом, что оно может содержать материю.
|
||||
Из-за того что измерения могут не иметь в себе
|
||||
некоторых планов в мирах могут отсутствовать некоторые
|
||||
явления или вещества, например огонь или электричество, вода или железо, например.
|
||||
|
||||
Способности в манипуляции хаосом также исходят из этих планов и связи мага с ними.
|
||||
Маги могут влиять именно на планы, усиливая,
|
||||
ослабляя или прорывая их,
|
||||
что находит своё отражение уже в самом измерении.
|
||||
|
||||
Планы включают в себя Подпланы, отражающие более конкретные аспекты измерений.
|
||||
Маги ограничены своей связью с планами для воплощения заклинаний.
|
||||
|
||||
### Рефлексы
|
||||
|
||||
Магия не всегда работает осознанно. Зачастую это происходит рефлекторно,
|
||||
когда связь с некоторым(и) планом(ами) становится достаточно прочной по каким-то причинам:
|
||||
навык, вера, какое-то знание, и это позволяет преодолевать свои пределы.
|
||||
Например опытный ассасин, достигший вершин возможностей в скрытности,
|
||||
может в один момент научиться становиться сначала неполностью прозрачным
|
||||
и гасить звук передвижения, а потом и становиться полностью невидимым и бесшумным.
|
||||
|
5
wiki/rpg/worlds/index.pmd
Normal file
5
wiki/rpg/worlds/index.pmd
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Миры
|
||||
|
||||
Описания миров
|
||||
|
||||
* [Атриум](atrium)
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
# checking shit
|
||||
|
||||
another shit
|
|
@ -1,3 +0,0 @@
|
|||
# sub dir index
|
||||
|
||||
hetaushetusahet
|
Loading…
Reference in a new issue