diff --git a/templates/asset.qtpl.go b/templates/asset.qtpl.go
index af57b53..9f26407 100644
--- a/templates/asset.qtpl.go
+++ b/templates/asset.qtpl.go
@@ -39,7 +39,6 @@ header { width: 100%; margin-bottom: 1rem; }
.hypha-tabs__selection { display: inline-block; padding: .25rem; font-weight: bold; }
.relative-hyphae { margin-top: .5rem; }
-.relative-hyphae > ul { margin: 0; padding: .5rem 1rem; }
.relative-hyphae li { list-style-type: none; }
@media screen and (max-width: 800px) {
@@ -164,6 +163,19 @@ table { border: #ddd 1px solid; border-radius: .25rem; min-width: 4rem; }
td { padding: .25rem; }
caption { caption-side: top; font-size: small; }
+.navitree { padding: 0; margin: 0; }
+.navitree__trunk ul { padding-left: 1rem; }
+.navitree > .navitree__trunk > ul { padding-left: 2rem; }
+.navitree__entry { }
+.navitree > .navitree__entry > a::before { display: inline-block; width: .5rem; color: #999; margin: 0 .25rem; }
+.navitree > .navitree__entry_infertile > a::before { content: " "} /* nbsp, careful */
+.navitree > .navitree__entry_fertile > a::before { content: "▸"}
+.navitree__trunk { border-left: 1px #999 solid; }
+.navitree > .navitree__trunk { border-left: none; }
+.navitree > .navitree__trunk > a { font-weight: bold; }
+.navitree__link { text-decoration: none; display: block; padding: .25rem; }
+.navitree__link:hover { background-color: #eee; }
+
/* Color stuff */
/* Lighter stuff #eee */
article code,
@@ -210,10 +222,6 @@ blockquote { border-left: 4px black solid; }
.upload-amnt { border: #eee 1px solid; }
td { border: #ddd 1px solid; }
-.navitree__node { padding-left: 1rem; }
-.navitree__link { text-decoration: none; display: block; }
-.navitree__link:hover { background-color: #eee; }
-
/* Dark theme! */
@media (prefers-color-scheme: dark) {
html { background: #222; color: #ddd; }
diff --git a/templates/default.css b/templates/default.css
index c73c5a1..6ab2d95 100644
--- a/templates/default.css
+++ b/templates/default.css
@@ -14,7 +14,6 @@ header { width: 100%; margin-bottom: 1rem; }
.hypha-tabs__selection { display: inline-block; padding: .25rem; font-weight: bold; }
.relative-hyphae { margin-top: .5rem; }
-.relative-hyphae > ul { margin: 0; padding: .5rem 1rem; }
.relative-hyphae li { list-style-type: none; }
@media screen and (max-width: 800px) {
@@ -139,6 +138,19 @@ table { border: #ddd 1px solid; border-radius: .25rem; min-width: 4rem; }
td { padding: .25rem; }
caption { caption-side: top; font-size: small; }
+.navitree { padding: 0; margin: 0; }
+.navitree__trunk ul { padding-left: 1rem; }
+.navitree > .navitree__trunk > ul { padding-left: 2rem; }
+.navitree__entry { }
+.navitree > .navitree__entry > a::before { display: inline-block; width: .5rem; color: #999; margin: 0 .25rem; }
+.navitree > .navitree__entry_infertile > a::before { content: " "} /* nbsp, careful */
+.navitree > .navitree__entry_fertile > a::before { content: "▸"}
+.navitree__trunk { border-left: 1px #999 solid; }
+.navitree > .navitree__trunk { border-left: none; }
+.navitree > .navitree__trunk > a { font-weight: bold; }
+.navitree__link { text-decoration: none; display: block; padding: .25rem; }
+.navitree__link:hover { background-color: #eee; }
+
/* Color stuff */
/* Lighter stuff #eee */
article code,
@@ -185,10 +197,6 @@ blockquote { border-left: 4px black solid; }
.upload-amnt { border: #eee 1px solid; }
td { border: #ddd 1px solid; }
-.navitree__node { padding-left: 1rem; }
-.navitree__link { text-decoration: none; display: block; }
-.navitree__link:hover { background-color: #eee; }
-
/* Dark theme! */
@media (prefers-color-scheme: dark) {
html { background: #222; color: #ddd; }
diff --git a/templates/readers.qtpl b/templates/readers.qtpl
index 8c06d2f..57319b3 100644
--- a/templates/readers.qtpl
+++ b/templates/readers.qtpl
@@ -1,6 +1,7 @@
{% import "net/http" %}
{% import "path" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
+{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% func HistoryHTML(rq *http.Request, hyphaName, list string) %}
{%= navHTML(rq, hyphaName, "history") %}
@@ -43,10 +44,10 @@ If `contents` == "", a helpful message is shown instead.
{% if u := user.FromRequest(rq); !user.AuthUsed || u.Group != "anon" %}
diff --git a/templates/readers.qtpl.go b/templates/readers.qtpl.go
index ab43ebe..812e65b 100644
--- a/templates/readers.qtpl.go
+++ b/templates/readers.qtpl.go
@@ -13,246 +13,249 @@ import "path"
//line templates/readers.qtpl:3
import "github.com/bouncepaw/mycorrhiza/user"
-//line templates/readers.qtpl:5
+//line templates/readers.qtpl:4
+import "github.com/bouncepaw/mycorrhiza/util"
+
+//line templates/readers.qtpl:6
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
-//line templates/readers.qtpl:5
+//line templates/readers.qtpl:6
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
-//line templates/readers.qtpl:5
+//line templates/readers.qtpl:6
func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, list string) {
-//line templates/readers.qtpl:5
+//line templates/readers.qtpl:6
qw422016.N().S(`
`)
-//line templates/readers.qtpl:6
+//line templates/readers.qtpl:7
streamnavHTML(qw422016, rq, hyphaName, "history")
-//line templates/readers.qtpl:6
+//line templates/readers.qtpl:7
qw422016.N().S(`
History of `)
-//line templates/readers.qtpl:10
+//line templates/readers.qtpl:11
qw422016.E().S(hyphaName)
-//line templates/readers.qtpl:10
+//line templates/readers.qtpl:11
qw422016.N().S(`
`)
-//line templates/readers.qtpl:11
+//line templates/readers.qtpl:12
qw422016.N().S(list)
-//line templates/readers.qtpl:11
+//line templates/readers.qtpl:12
qw422016.N().S(`
`)
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
}
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, list string) {
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
qw422016 := qt422016.AcquireWriter(qq422016)
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
StreamHistoryHTML(qw422016, rq, hyphaName, list)
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
qt422016.ReleaseWriter(qw422016)
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
}
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
func HistoryHTML(rq *http.Request, hyphaName, list string) string {
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
qb422016 := qt422016.AcquireByteBuffer()
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
WriteHistoryHTML(qb422016, rq, hyphaName, list)
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
qs422016 := string(qb422016.B)
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
qt422016.ReleaseByteBuffer(qb422016)
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
return qs422016
-//line templates/readers.qtpl:15
+//line templates/readers.qtpl:16
}
-//line templates/readers.qtpl:17
+//line templates/readers.qtpl:18
func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, relatives, revHash string) {
-//line templates/readers.qtpl:17
+//line templates/readers.qtpl:18
qw422016.N().S(`
`)
-//line templates/readers.qtpl:18
+//line templates/readers.qtpl:19
streamnavHTML(qw422016, rq, hyphaName, "revision", revHash)
-//line templates/readers.qtpl:18
+//line templates/readers.qtpl:19
qw422016.N().S(`
Please note that viewing binary parts of hyphae is not supported in history for now.
`)
-//line templates/readers.qtpl:23
+//line templates/readers.qtpl:24
qw422016.N().S(naviTitle)
-//line templates/readers.qtpl:23
+//line templates/readers.qtpl:24
qw422016.N().S(`
`)
-//line templates/readers.qtpl:24
+//line templates/readers.qtpl:25
qw422016.N().S(contents)
-//line templates/readers.qtpl:24
+//line templates/readers.qtpl:25
qw422016.N().S(`
`)
-//line templates/readers.qtpl:27
+//line templates/readers.qtpl:28
streamrelativeHyphae(qw422016, relatives)
-//line templates/readers.qtpl:27
+//line templates/readers.qtpl:28
qw422016.N().S(`
`)
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
}
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, relatives, revHash string) {
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
qw422016 := qt422016.AcquireWriter(qq422016)
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
StreamRevisionHTML(qw422016, rq, hyphaName, naviTitle, contents, relatives, revHash)
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
qt422016.ReleaseWriter(qw422016)
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
}
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, relatives, revHash string) string {
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
qb422016 := qt422016.AcquireByteBuffer()
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
WriteRevisionHTML(qb422016, rq, hyphaName, naviTitle, contents, relatives, revHash)
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
qs422016 := string(qb422016.B)
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
qt422016.ReleaseByteBuffer(qb422016)
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
return qs422016
-//line templates/readers.qtpl:29
+//line templates/readers.qtpl:30
}
// If `contents` == "", a helpful message is shown instead.
-//line templates/readers.qtpl:32
+//line templates/readers.qtpl:33
func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, relatives, prevHyphaName, nextHyphaName string, hasAmnt bool) {
-//line templates/readers.qtpl:32
+//line templates/readers.qtpl:33
qw422016.N().S(`
`)
-//line templates/readers.qtpl:33
+//line templates/readers.qtpl:34
streamnavHTML(qw422016, rq, hyphaName, "page")
-//line templates/readers.qtpl:33
+//line templates/readers.qtpl:34
qw422016.N().S(`
`)
-//line templates/readers.qtpl:37
+//line templates/readers.qtpl:38
qw422016.N().S(naviTitle)
-//line templates/readers.qtpl:37
+//line templates/readers.qtpl:38
qw422016.N().S(`
`)
-//line templates/readers.qtpl:38
+//line templates/readers.qtpl:39
if contents == "" {
-//line templates/readers.qtpl:38
+//line templates/readers.qtpl:39
qw422016.N().S(`
This hypha has no text. Why not create it?
`)
-//line templates/readers.qtpl:40
+//line templates/readers.qtpl:41
} else {
-//line templates/readers.qtpl:40
+//line templates/readers.qtpl:41
qw422016.N().S(`
`)
-//line templates/readers.qtpl:41
+//line templates/readers.qtpl:42
qw422016.N().S(contents)
-//line templates/readers.qtpl:41
+//line templates/readers.qtpl:42
qw422016.N().S(`
`)
-//line templates/readers.qtpl:42
+//line templates/readers.qtpl:43
}
-//line templates/readers.qtpl:42
+//line templates/readers.qtpl:43
qw422016.N().S(`
`)
-//line templates/readers.qtpl:52
+//line templates/readers.qtpl:53
if u := user.FromRequest(rq); !user.AuthUsed || u.Group != "anon" {
-//line templates/readers.qtpl:52
+//line templates/readers.qtpl:53
qw422016.N().S(`
`)
-//line templates/readers.qtpl:64
+//line templates/readers.qtpl:65
}
-//line templates/readers.qtpl:64
+//line templates/readers.qtpl:65
qw422016.N().S(`
`)
-//line templates/readers.qtpl:66
+//line templates/readers.qtpl:67
streamrelativeHyphae(qw422016, relatives)
-//line templates/readers.qtpl:66
+//line templates/readers.qtpl:67
qw422016.N().S(`
`)
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
}
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, relatives, prevHyphaName, nextHyphaName string, hasAmnt bool) {
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
qw422016 := qt422016.AcquireWriter(qq422016)
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, relatives, prevHyphaName, nextHyphaName, hasAmnt)
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
qt422016.ReleaseWriter(qw422016)
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
}
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, relatives, prevHyphaName, nextHyphaName string, hasAmnt bool) string {
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
qb422016 := qt422016.AcquireByteBuffer()
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, relatives, prevHyphaName, nextHyphaName, hasAmnt)
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
qs422016 := string(qb422016.B)
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
qt422016.ReleaseByteBuffer(qb422016)
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
return qs422016
-//line templates/readers.qtpl:68
+//line templates/readers.qtpl:69
}
diff --git a/tree/tree.go b/tree/tree.go
index 476b540..45046db 100644
--- a/tree/tree.go
+++ b/tree/tree.go
@@ -9,100 +9,122 @@ import (
"github.com/bouncepaw/mycorrhiza/util"
)
-// If Name == "", the tree is empty.
-type tree struct {
- name string
- exists bool
- prevSibling string
- nextSibling string
- siblings []string
- descendants []*tree
- root bool
- hyphaIterator func(func(string))
+type sibling struct {
+ name string
+ hasChildren bool
+}
+
+func (s *sibling) checkThisChild(hyphaName string) {
+ if !s.hasChildren && path.Dir(hyphaName) == s.name {
+ s.hasChildren = true
+ }
+}
+
+func (s *sibling) asHTML() string {
+ class := "navitree__entry navitree__sibling"
+ if s.hasChildren {
+ class += " navitree__sibling_fertile navitree__entry_fertile"
+ } else {
+ class += " navitree__sibling_infertile navitree__entry_infertile"
+ }
+ return fmt.Sprintf(
+ `%s`,
+ class,
+ s.name,
+ util.BeautifulName(path.Base(s.name)),
+ )
+}
+
+type mainFamilyMember struct {
+ name string
+ children []*mainFamilyMember
+}
+
+func (m *mainFamilyMember) checkThisChild(hyphaName string) (adopted bool) {
+ if path.Dir(hyphaName) == m.name {
+ m.children = append(m.children, &mainFamilyMember{
+ name: hyphaName,
+ children: make([]*mainFamilyMember, 0),
+ })
+ return true
+ }
+ return false
+}
+
+func (m *mainFamilyMember) asHTML() string {
+ if len(m.children) == 0 {
+ return fmt.Sprintf(`%s`, m.name, util.BeautifulName(path.Base(m.name)))
+ }
+ sort.Slice(m.children, func(i, j int) bool {
+ return m.children[i].name < m.children[j].name
+ })
+ html := fmt.Sprintf(`%s`, m.name, util.BeautifulName(path.Base(m.name)))
+ for _, child := range m.children {
+ html += child.asHTML()
+ }
+ return html + `
`
+}
+
+func mainFamilyFromPool(hyphaName string, subhyphaePool map[string]bool) *mainFamilyMember {
+ var (
+ nestLevel = strings.Count(hyphaName, "/")
+ adopted = make([]*mainFamilyMember, 0)
+ )
+ for subhyphaName, _ := range subhyphaePool {
+ subnestLevel := strings.Count(subhyphaName, "/")
+ if subnestLevel-1 == nestLevel && path.Dir(subhyphaName) == hyphaName {
+ delete(subhyphaePool, subhyphaName)
+ adopted = append(adopted, mainFamilyFromPool(subhyphaName, subhyphaePool))
+ }
+ }
+ return &mainFamilyMember{name: hyphaName, children: adopted}
}
// Tree generates a tree for `hyphaName` as html and returns next and previous hyphae if any.
func Tree(hyphaName string, hyphaIterator func(func(string))) (html, prev, next string) {
- t := &tree{name: hyphaName, root: true, hyphaIterator: hyphaIterator}
- t.fill()
- return t.asHtml(), util.BeautifulName(t.prevSibling), util.BeautifulName(t.nextSibling)
-}
-
-// subtree adds a descendant tree to `t` and returns that tree.
-func (t *tree) fork(descendantName string) *tree {
- subt := &tree{
- name: descendantName,
- root: false,
- hyphaIterator: t.hyphaIterator,
- }
- t.descendants = append(t.descendants, subt)
- return subt
-}
-
-// Compare current prev next hyphae and decide if any of them should be set to `name2`.
-func (t *tree) prevNextDetermine(name2 string) {
- if name2 < t.name && (name2 > t.prevSibling || t.prevSibling == "") {
- t.prevSibling = name2
- } else if name2 > t.name && (name2 < t.nextSibling || t.nextSibling == "") {
- t.nextSibling = name2
- }
-}
-
-// Compares names and does something with them, may generate a subtree.
-func (t *tree) compareNamesAndAppend(name2 string) {
- switch {
- case t.name == name2:
- t.exists = true
- case t.root && path.Dir(t.name) == path.Dir(name2):
- t.prevNextDetermine(name2)
- t.siblings = append(t.siblings, name2)
- case t.name == path.Dir(name2):
- t.fork(name2).fill()
- }
-}
-
-// Fills t.siblings and t.descendants, sorts them and does the same to the descendants.
-func (t *tree) fill() {
- t.hyphaIterator(func(hyphaName string) {
- t.compareNamesAndAppend(hyphaName)
- })
- sort.Strings(t.siblings)
- sort.Slice(t.descendants, func(i, j int) bool {
- return t.descendants[i].name < t.descendants[j].name
- })
-}
-
-// asHtml returns HTML representation of a tree.
-// It applies itself recursively on the tree's children.
-func (t *tree) asHtml() (html string) {
- if t.root {
- html += navitreeEntry(t.name, "navitree__pagename")
- } else {
- html += navitreeEntry(t.name, "navitree__name")
- }
-
- for _, subtree := range t.descendants {
- html += subtree.asHtml()
- }
-
- if t.root {
- for _, siblingName := range t.siblings {
- html += navitreeEntry(siblingName, "navitree__sibling")
+ var (
+ // One of the siblings is the hypha with name `hyphaName`
+ siblings = findSiblings(hyphaName, hyphaIterator)
+ subhyphaePool = make(map[string]bool)
+ I int
+ )
+ hyphaIterator(func(otherHyphaName string) {
+ for _, s := range siblings {
+ s.checkThisChild(otherHyphaName)
}
+ if strings.HasPrefix(otherHyphaName, hyphaName+"/") {
+ subhyphaePool[otherHyphaName] = true
+ }
+ })
+ for i, s := range siblings {
+ if s.name == hyphaName {
+ I = i
+ break
+ }
+ html += s.asHTML()
}
-
- return ``
+ html += mainFamilyFromPool(hyphaName, subhyphaePool).asHTML()
+ for _, s := range siblings[I+1:] {
+ html += s.asHTML()
+ }
+ if I != 0 {
+ prev = siblings[I-1].name
+ }
+ if I != len(siblings)-1 {
+ next = siblings[I+1].name
+ }
+ return fmt.Sprintf(``, html), prev, next
}
-// Strip hypha name from all ancestor names, replace _ with spaces, title case
-func beautifulName(uglyName string) string {
- return strings.Title(strings.ReplaceAll(path.Base(uglyName), "_", " "))
-}
-
-// navitreeEntry is a small utility function that makes generating html easier.
-func navitreeEntry(name, class string) string {
- return fmt.Sprintf(`
- %s
-
-`, class, name, beautifulName(name))
+func findSiblings(hyphaName string, hyphaIterator func(func(string))) []*sibling {
+ siblings := []*sibling{&sibling{name: hyphaName, hasChildren: true}}
+ hyphaIterator(func(otherHyphaName string) {
+ if path.Dir(hyphaName) == path.Dir(otherHyphaName) && hyphaName != otherHyphaName {
+ siblings = append(siblings, &sibling{name: otherHyphaName, hasChildren: false})
+ }
+ })
+ sort.Slice(siblings, func(i, j int) bool {
+ return siblings[i].name < siblings[j].name
+ })
+ return siblings
}