This commit is contained in:
parent
37cb244ffb
commit
c946b9daba
10 changed files with 126 additions and 89 deletions
26
README.md
26
README.md
|
@ -5,36 +5,16 @@
|
|||
My capsule in Gemini space. Built with zord-tree support
|
||||
|
||||
|
||||
## URL plan
|
||||
|
||||
* / - redirect to /sgs
|
||||
* /sgs or /en - index template by language. Links to other language, about, search, root categories, all tags, last 10 post, link to all posts
|
||||
* /[sgs|en]/a - about page
|
||||
* /[sgs|en]/s - search
|
||||
* /[sgs|en]/f/<category/path> - display files, subcategories, subtags. If exists index.gmi - display its text
|
||||
* /[sgs|en]/f/<category/path>/<id>/<name.gmi> - display category path as:
|
||||
```
|
||||
=> /sgs/f/c1 c1
|
||||
=> /sgs/f/c1/c2 └─ c2
|
||||
=> /sgs/f/c1/c2/c3 , └─ c3
|
||||
---
|
||||
> c1
|
||||
> └─ c2
|
||||
> , └─ c3
|
||||
```
|
||||
display tags, date, copyright
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
* add category view
|
||||
* add tag view
|
||||
* add file view
|
||||
* add image or other file not `gmi` download
|
||||
* feed
|
||||
* security
|
||||
* citata from some file
|
||||
__DONE__
|
||||
* add category view
|
||||
* add tag view
|
||||
* add file view
|
||||
* about
|
||||
* migrate to gRPC
|
||||
* routing, not found
|
||||
|
|
12
formatter.go
12
formatter.go
|
@ -20,6 +20,10 @@ func (file *TreeFile) GmiName() string {
|
|||
return strings.Replace(file.Name, ".md", ".gmi", 1)
|
||||
}
|
||||
|
||||
func (file *TreeFile) CategoryPath() string {
|
||||
return "/" + strings.Join(file.Category, "/")
|
||||
}
|
||||
|
||||
func (tree *Tree) GetIndexFile() (*TreeFile, error) {
|
||||
for _, file := range tree.RootFiles {
|
||||
if file.Name == "index.md" {
|
||||
|
@ -29,14 +33,6 @@ func (tree *Tree) GetIndexFile() (*TreeFile, error) {
|
|||
|
||||
return nil, errors.New("index file not found")
|
||||
}
|
||||
func (tree *Tree) HasIndexFile() bool {
|
||||
_, err := tree.GetIndexFile()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func GetLastFiles(files []*TreeFile) []*TreeFile {
|
||||
sortingFiles := make(map[string]*TreeFile)
|
||||
|
|
101
main.go
101
main.go
|
@ -100,6 +100,9 @@ func process(_ context.Context, w gemini.ResponseWriter, r *gemini.Request) {
|
|||
renderFile(lang, w, r, client)
|
||||
case regexp.MustCompile(`^/(sgs|en)/f/[\p{L}\d_+.]+(/[\p{L}\d_+.]+)*/?$`).MatchString(r.URL.Path):
|
||||
renderCategory(lang, w, r, client)
|
||||
case regexp.MustCompile(`^/(sgs|en)/t/[\p{L}\d_+.]+/?$`).MatchString(r.URL.Path):
|
||||
renderTag(lang, w, r, client)
|
||||
// case abruozdelee
|
||||
default:
|
||||
w.WriteHeader(gemini.StatusNotFound, "Out of space")
|
||||
}
|
||||
|
@ -214,7 +217,6 @@ func renderCategory(lang string, w gemini.ResponseWriter, r *gemini.Request, cli
|
|||
|
||||
w.SetMediaType("text/gemini")
|
||||
tpl := pongo2.Must(pongo2.FromFile("templates/category.gmi"))
|
||||
fmt.Println(file, &file)
|
||||
page, err := tpl.Execute(pongo2.Context{"lang": lang, "tree": tree, "path": path, "indexFile": file})
|
||||
if err != nil {
|
||||
log.Fatalf("template failed: %v", err)
|
||||
|
@ -240,29 +242,19 @@ func renderFile(lang string, w gemini.ResponseWriter, r *gemini.Request, client
|
|||
return
|
||||
}
|
||||
|
||||
category := ""
|
||||
for i, cat := range file.File.Category {
|
||||
category = category + fmt.Sprintf("=> /%s/f/%s .%s└─ %s\n",
|
||||
lang,
|
||||
strings.Join(file.File.Category[:i+1], "/"),
|
||||
strings.Repeat(" ", i),
|
||||
cat,
|
||||
)
|
||||
w.SetMediaType("text/gemini")
|
||||
tpl := pongo2.Must(pongo2.FromFile("templates/page.gmi"))
|
||||
page, err := tpl.Execute(pongo2.Context{"lang": lang, "file": file})
|
||||
if err != nil {
|
||||
log.Fatalf("template failed: %v", err)
|
||||
return
|
||||
}
|
||||
content := fmt.Sprintf(
|
||||
"# %s\n\n"+
|
||||
"%s\n\n"+
|
||||
"%s\n%s\n\n"+
|
||||
"%s\n\n[ %s ]",
|
||||
file.File.Description,
|
||||
file.Content,
|
||||
file.File.Created,
|
||||
file.File.Copyright,
|
||||
category,
|
||||
strings.Join(file.File.Tags, " "),
|
||||
)
|
||||
|
||||
w.Write([]byte(content))
|
||||
_, err = w.Write([]byte(page))
|
||||
if err != nil {
|
||||
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func renderAllFiles(lang string, w gemini.ResponseWriter, client TreeManagerClient) {
|
||||
|
@ -274,40 +266,51 @@ func renderAllFiles(lang string, w gemini.ResponseWriter, client TreeManagerClie
|
|||
|
||||
tree, err := client.GetSummery(context.Background(), &tr)
|
||||
if err != nil {
|
||||
log.Fatalf("client.GetSummery failed: %v", err)
|
||||
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
||||
return
|
||||
}
|
||||
|
||||
content := "# Arnas alkierios :: "
|
||||
if lang == "sgs" {
|
||||
content = content + "vėsė tekstā\n\n"
|
||||
} else {
|
||||
content = content + "all texts\n\n"
|
||||
w.SetMediaType("text/gemini")
|
||||
tpl := pongo2.Must(pongo2.FromFile("templates/all_texts.gmi"))
|
||||
page, err := tpl.Execute(pongo2.Context{"lang": lang, "tree": tree})
|
||||
if err != nil {
|
||||
log.Fatalf("template failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, f := range tree.Files {
|
||||
content = appendFileLink(lang, content, f)
|
||||
_, err = w.Write([]byte(page))
|
||||
if err != nil {
|
||||
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
||||
return
|
||||
}
|
||||
|
||||
if lang == "sgs" {
|
||||
content = content + "\n=> /sgs ← grīžtė"
|
||||
} else {
|
||||
content = content + "\n=> /en ← back"
|
||||
}
|
||||
|
||||
w.Write([]byte(content))
|
||||
}
|
||||
|
||||
func appendFileLink(lang string, content string, f *TreeFile) string {
|
||||
content = content + fmt.Sprintf(
|
||||
"=> /%s/f/%s/%s/%s %s (%s)\n",
|
||||
lang,
|
||||
strings.Join(f.Category, "/"),
|
||||
f.Id,
|
||||
strings.Replace(f.Name, ".md", ".gmi", 1),
|
||||
f.Description,
|
||||
f.Created,
|
||||
)
|
||||
return content
|
||||
func renderTag(lang string, w gemini.ResponseWriter, r *gemini.Request, client TreeManagerClient) {
|
||||
urlParts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
|
||||
tag := urlParts[len(urlParts)-1]
|
||||
tagFilter := TreeRequest_Filter{Key: "tag", Value: tag}
|
||||
filters := []*TreeRequest_Filter{&tagFilter}
|
||||
|
||||
path := ""
|
||||
tr := TreeRequest{Path: &path, Filter: filters}
|
||||
|
||||
tree, err := client.GetSummery(context.Background(), &tr)
|
||||
if err != nil {
|
||||
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
||||
return
|
||||
}
|
||||
|
||||
w.SetMediaType("text/gemini")
|
||||
tpl := pongo2.Must(pongo2.FromFile("templates/tag.gmi"))
|
||||
page, err := tpl.Execute(pongo2.Context{"lang": lang, "tree": tree, "tag": tag})
|
||||
if err != nil {
|
||||
log.Fatalf("template failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte(page))
|
||||
if err != nil {
|
||||
w.WriteHeader(gemini.StatusTemporaryFailure, "Internal server error")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
7
templates/all_texts.gmi
Normal file
7
templates/all_texts.gmi
Normal file
|
@ -0,0 +1,7 @@
|
|||
{% import "macros.tpl" category_url, tag_url, text_url %}
|
||||
# Arna alkierios :: {% if lang == "sgs" %}Vėsė tekstā{% else %}All texts{% endif %}
|
||||
|
||||
{% for file in tree.Files %}{{ text_url(lang, file) }}
|
||||
{% endfor %}
|
||||
|
||||
=> {% if lang == "sgs" %}/sgs 🏠 grīžtė{% else %}/en ← back{% endif %}
|
|
@ -1,10 +1,18 @@
|
|||
{% import "macros.tpl" category_url, text_url %}
|
||||
{% import "macros.tpl" category_url, tag_url, text_url %}
|
||||
# Arna alkierios :: {{ path }}
|
||||
|
||||
{% if indexFile %}{{ indexFile.Content }}{% endif %}
|
||||
{% if indexFile %}{{ indexFile.Content }}
|
||||
|
||||
{% for file in tree.RootFiles %}{{ text_url(lang, file) }} {% endfor %}
|
||||
🌙 {{ indexFile.File.Created}}
|
||||
{{ indexFile.File.Copyright }}{% endif %}
|
||||
|
||||
{% for file in tree.RootFiles %}{{ text_url(lang, file) }}
|
||||
{% endfor %}
|
||||
{% if tree.Categories %}### {% if lang == "sgs" %}Kateguorėjės{% else %}Categories{% endif %}
|
||||
{% for cat, count in tree.Categories %}{{ category_url(lang, cat, cat, count) }}
|
||||
{% for cat, count in tree.Categories %}{{ category_url(lang, tree.Path|add:"/"|add:cat, cat, count) }}
|
||||
{% endfor %} {% endif %}
|
||||
{% if tree.Tags %}### {% if lang == "sgs" %}Žīmas{% else %}Tags{% endif %}
|
||||
{% for tag, count in tree.Tags %}{{ tag_url(lang, tag, count) }}
|
||||
{% endfor %} {% endif %}
|
||||
{% if tree.RootPath %}{{ category_url(lang, tree.RootPath, tree.RootPath, 0) }}{% endif %}
|
||||
=> {% if lang == "sgs" %}/sgs 🏠 nomėi{% else %}/en 🏠 home{% endif %}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% macro category_url(lang, path, category, count) export %}=> /{{ lang }}/f/{{ path }} {{ category }} ({{ count }}) {% endmacro %}
|
||||
{% macro category_url(lang, path, category, count) export %}=> /{{ lang }}/f/{{ path }} 🌿 {{ category }}{% if count %} ({{ count }}){% endif %} {% endmacro %}
|
||||
|
||||
{% macro tag_url(lang, tag, count) export %}=> /{{ lang }}/t/{{ tag }} {{ tag }} ({{ count }}) {% endmacro %}
|
||||
{% macro tag_url(lang, tag, count) export %}=> /{{ lang }}/t/{{ tag }} 🏷 {{ tag }} ({{ count }}) {% endmacro %}
|
||||
|
||||
{% macro text_url(lang, file) export %}=> /{{ lang }}/f/{{ file.CategoriesAsUrl() }}/{{ file.Id }}/{{ file.GmiName() }} {{ file.Description }} ({{ file.Created }}) {% endmacro %}
|
||||
{% macro text_url(lang, file) export %}=> /{{ lang }}/f/{{ file.CategoriesAsUrl() }}/{{ file.Id }}/{{ file.GmiName() }} ✒ {{ file.Description }} ({{ file.Created }}) {% endmacro %}
|
||||
|
|
13
templates/page.gmi
Normal file
13
templates/page.gmi
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% import "macros.tpl" category_url, tag_url, text_url %}
|
||||
# Arna alkierios :: {{ file.File.Description }}
|
||||
|
||||
{{ file.Content }}
|
||||
|
||||
🌙 {{ file.File.Created}}
|
||||
{{ file.File.Copyright }}
|
||||
|
||||
{% if file.File.Tags %}### {% if lang == "sgs" %}Žīmas{% else %}Tags{% endif %}
|
||||
{% for tag in file.File.Tags %}{{ tag_url(lang, tag, 0) }}
|
||||
{% endfor %} {% endif %}
|
||||
{{ category_url(lang, file.File.CategoryPath, file.File.Category|last, 0) }}
|
||||
=> {% if lang == "sgs" %}/sgs 🏠 nomėi{% else %}/en 🏠 home{% endif %}
|
9
templates/tag.gmi
Normal file
9
templates/tag.gmi
Normal file
|
@ -0,0 +1,9 @@
|
|||
{% import "macros.tpl" category_url, tag_url, text_url %}
|
||||
# Arna alkierios :: [{{ tag }}]
|
||||
|
||||
{% for file in tree.Files %}{{ text_url(lang, file) }}
|
||||
{% endfor %}
|
||||
{% if tree.Tags %}### {% if lang == "sgs" %}Žīmas{% else %}Tags{% endif %}
|
||||
{% for tag, count in tree.Tags %}{{ tag_url(lang, tag, count) }}
|
||||
{% endfor %} {% endif %}
|
||||
=> {% if lang == "sgs" %}/sgs 🏠 nomėi{% else %}/en 🏠 home{% endif %}
|
23
tree.pb.go
23
tree.pb.go
|
@ -234,6 +234,8 @@ type Tree struct {
|
|||
RootFiles []*TreeFile `protobuf:"bytes,4,rep,name=rootFiles,proto3" json:"rootFiles,omitempty"`
|
||||
Tags map[string]int32 `protobuf:"bytes,2,rep,name=tags,proto3" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
|
||||
Categories map[string]int32 `protobuf:"bytes,3,rep,name=categories,proto3" json:"categories,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
|
||||
Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"`
|
||||
RootPath string `protobuf:"bytes,6,opt,name=rootPath,proto3" json:"rootPath,omitempty"`
|
||||
}
|
||||
|
||||
func (x *Tree) Reset() {
|
||||
|
@ -296,6 +298,20 @@ func (x *Tree) GetCategories() map[string]int32 {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *Tree) GetPath() string {
|
||||
if x != nil {
|
||||
return x.Path
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Tree) GetRootPath() string {
|
||||
if x != nil {
|
||||
return x.RootPath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type FileContent struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
@ -434,7 +450,7 @@ var file_tree_proto_rawDesc = []byte{
|
|||
0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73,
|
||||
0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x18, 0x0a, 0x07,
|
||||
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0xb8, 0x02, 0x0a, 0x04, 0x54, 0x72, 0x65, 0x65, 0x12,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0xe8, 0x02, 0x0a, 0x04, 0x54, 0x72, 0x65, 0x65, 0x12,
|
||||
0x24, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e,
|
||||
0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05,
|
||||
0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x74, 0x46, 0x69, 0x6c,
|
||||
|
@ -446,7 +462,10 @@ var file_tree_proto_rawDesc = []byte{
|
|||
0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x2e, 0x43, 0x61,
|
||||
0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x63,
|
||||
0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, 0x67,
|
||||
0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74,
|
||||
0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x72, 0x6f, 0x6f, 0x74, 0x50, 0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x72, 0x6f, 0x6f, 0x74, 0x50, 0x61, 0x74, 0x68, 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, 0x67,
|
||||
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
|
||||
|
|
|
@ -42,6 +42,8 @@ message Tree {
|
|||
repeated TreeFile rootFiles = 4;
|
||||
map<string, int32> tags = 2;
|
||||
map<string, int32> categories = 3;
|
||||
string path = 5;
|
||||
string rootPath = 6;
|
||||
}
|
||||
|
||||
message FileContent {
|
||||
|
|
Loading…
Reference in a new issue