From 05c12e8452eae1be101ac23b688ebf73173fece7 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Sun, 29 Nov 2020 16:32:52 +0500 Subject: [PATCH 01/50] Show prev/next links --- README.md | 2 +- http_readers.go | 6 +- templates/asset.qtpl | 3 + templates/{css.qtpl.go => asset.qtpl.go} | 53 +++--- templates/{css.qtpl => default.css} | 10 +- templates/http_readers.qtpl | 13 +- templates/http_readers.qtpl.go | 217 ++++++++++++++--------- tree/tree.go | 20 ++- 8 files changed, 205 insertions(+), 119 deletions(-) create mode 100644 templates/asset.qtpl rename templates/{css.qtpl.go => asset.qtpl.go} (75%) rename templates/{css.qtpl => default.css} (88%) diff --git a/README.md b/README.md index 82876c6..784c616 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 🍄 MycorrhizaWiki 0.11 +# 🍄 MycorrhizaWiki 0.12 A wiki engine. ## Building diff --git a/http_readers.go b/http_readers.go index b8887e9..759f05d 100644 --- a/http_readers.go +++ b/http_readers.go @@ -39,12 +39,13 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { if err == nil { contents = markup.ToHtml(hyphaName, textContents) } + treeHTML, _, _ := tree.Tree(hyphaName, IterateHyphaNamesWith) page := templates.RevisionHTML( rq, hyphaName, naviTitle(hyphaName), contents, - tree.TreeAsHtml(hyphaName, IterateHyphaNamesWith), + treeHTML, revHash, ) w.Header().Set("Content-Type", "text/html;charset=utf-8") @@ -111,8 +112,9 @@ func handlerPage(w http.ResponseWriter, rq *http.Request) { contents = binaryHtmlBlock(hyphaName, data) + contents } } + treeHTML, prevHypha, nextHypha := tree.Tree(hyphaName, IterateHyphaNamesWith) util.HTTP200Page(w, base(hyphaName, templates.PageHTML(rq, hyphaName, naviTitle(hyphaName), contents, - tree.TreeAsHtml(hyphaName, IterateHyphaNamesWith)))) + treeHTML, prevHypha, nextHypha))) } diff --git a/templates/asset.qtpl b/templates/asset.qtpl new file mode 100644 index 0000000..91e873f --- /dev/null +++ b/templates/asset.qtpl @@ -0,0 +1,3 @@ +{% func DefaultCSS() %} +{% cat "default.css" %} +{% endfunc %} diff --git a/templates/css.qtpl.go b/templates/asset.qtpl.go similarity index 75% rename from templates/css.qtpl.go rename to templates/asset.qtpl.go index 72a25fc..2004603 100644 --- a/templates/css.qtpl.go +++ b/templates/asset.qtpl.go @@ -1,27 +1,29 @@ -// Code generated by qtc from "css.qtpl". DO NOT EDIT. +// Code generated by qtc from "asset.qtpl". DO NOT EDIT. // See https://github.com/valyala/quicktemplate for details. -//line templates/css.qtpl:1 +//line templates/asset.qtpl:1 package templates -//line templates/css.qtpl:1 +//line templates/asset.qtpl:1 import ( qtio422016 "io" qt422016 "github.com/valyala/quicktemplate" ) -//line templates/css.qtpl:1 +//line templates/asset.qtpl:1 var ( _ = qtio422016.Copy _ = qt422016.AcquireByteBuffer ) -//line templates/css.qtpl:1 +//line templates/asset.qtpl:1 func StreamDefaultCSS(qw422016 *qt422016.Writer) { -//line templates/css.qtpl:1 +//line templates/asset.qtpl:1 qw422016.N().S(` -@media screen and (min-width: 700px) { +`) +//line templates/asset.qtpl:2 + qw422016.N().S(`@media screen and (min-width: 700px) { main {margin: 0 auto; width: 700px;} } @media screen and (max-width: 700px) { @@ -56,7 +58,7 @@ article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wr .binary-container_with-video video, .binary-container_with-audio audio {width: 100%} .navi-title a {text-decoration:none;} -.img-gallery { text-align: center; margin-top: .25rem; } +.img-gallery { text-align: center; margin-top: .25rem; margin-bottom: .25rem; } .img-gallery_many-images { background-color: #eee; border-radius: .25rem; padding: .5rem; } .img-gallery img { max-width: 100%; max-height: 50vh; } figure { margin: 0; } @@ -73,32 +75,41 @@ nav ul li {list-style-type:none;margin-right:1rem;} .rc-entry__hash { font-style: italic; text-align: right; } .rc-entry__links { grid-column: 1 / span 2; } .rc-entry__author { font-style: italic; } + +.prevnext__el { display: block-inline; min-width: 40%; padding: .5rem; margin-bottom: .25rem; text-decoration: none; background-color: #eee; border-radius: .25rem; } +.prevnext__prev { float: left; } +.prevnext__next { float: right; text-align: right; } + +.page-separator { clear: both; } `) -//line templates/css.qtpl:54 +//line templates/asset.qtpl:2 + qw422016.N().S(` +`) +//line templates/asset.qtpl:3 } -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 func WriteDefaultCSS(qq422016 qtio422016.Writer) { -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 StreamDefaultCSS(qw422016) -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 qt422016.ReleaseWriter(qw422016) -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 } -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 func DefaultCSS() string { -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 qb422016 := qt422016.AcquireByteBuffer() -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 WriteDefaultCSS(qb422016) -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 qs422016 := string(qb422016.B) -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 qt422016.ReleaseByteBuffer(qb422016) -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 return qs422016 -//line templates/css.qtpl:54 +//line templates/asset.qtpl:3 } diff --git a/templates/css.qtpl b/templates/default.css similarity index 88% rename from templates/css.qtpl rename to templates/default.css index 7a6f6e3..060a814 100644 --- a/templates/css.qtpl +++ b/templates/default.css @@ -1,4 +1,3 @@ -{% func DefaultCSS() %} @media screen and (min-width: 700px) { main {margin: 0 auto; width: 700px;} } @@ -34,7 +33,7 @@ article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wr .binary-container_with-video video, .binary-container_with-audio audio {width: 100%} .navi-title a {text-decoration:none;} -.img-gallery { text-align: center; margin-top: .25rem; } +.img-gallery { text-align: center; margin-top: .25rem; margin-bottom: .25rem; } .img-gallery_many-images { background-color: #eee; border-radius: .25rem; padding: .5rem; } .img-gallery img { max-width: 100%; max-height: 50vh; } figure { margin: 0; } @@ -51,4 +50,9 @@ nav ul li {list-style-type:none;margin-right:1rem;} .rc-entry__hash { font-style: italic; text-align: right; } .rc-entry__links { grid-column: 1 / span 2; } .rc-entry__author { font-style: italic; } -{% endfunc %} + +.prevnext__el { display: block-inline; min-width: 40%; padding: .5rem; margin-bottom: .25rem; text-decoration: none; background-color: #eee; border-radius: .25rem; } +.prevnext__prev { float: left; } +.prevnext__next { float: right; text-align: right; } + +.page-separator { clear: both; } diff --git a/templates/http_readers.qtpl b/templates/http_readers.qtpl index a1cb2f3..da82cf9 100644 --- a/templates/http_readers.qtpl +++ b/templates/http_readers.qtpl @@ -1,4 +1,5 @@ {% import "net/http" %} +{% import "path" %} {% import "github.com/bouncepaw/mycorrhiza/user" %} {% func HistoryHTML(rq *http.Request, hyphaName, tbody string) %} @@ -35,7 +36,7 @@ {% endfunc %} If `contents` == "", a helpful message is shown instead. -{% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree string) %} +{% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) %}
{%= navHTML(rq, hyphaName, "page") %}
@@ -46,7 +47,15 @@ If `contents` == "", a helpful message is shown instead. {%s= contents %} {% endif %}
-
+
+ {% if prevHyphaName != "" %} + ← {%s path.Base(prevHyphaName) %} + {% endif %} + {% if nextHyphaName != "" %} + {%s path.Base(nextHyphaName) %} → + {% endif %} +
+
{% if u := user.FromRequest(rq).OrAnon(); !user.AuthUsed || u.Group > user.UserAnon %}
diff --git a/templates/http_readers.qtpl.go b/templates/http_readers.qtpl.go index 3ffac47..335b3d2 100644 --- a/templates/http_readers.qtpl.go +++ b/templates/http_readers.qtpl.go @@ -8,30 +8,33 @@ package templates import "net/http" //line templates/http_readers.qtpl:2 +import "path" + +//line templates/http_readers.qtpl:3 import "github.com/bouncepaw/mycorrhiza/user" -//line templates/http_readers.qtpl:4 +//line templates/http_readers.qtpl:5 import ( qtio422016 "io" qt422016 "github.com/valyala/quicktemplate" ) -//line templates/http_readers.qtpl:4 +//line templates/http_readers.qtpl:5 var ( _ = qtio422016.Copy _ = qt422016.AcquireByteBuffer ) -//line templates/http_readers.qtpl:4 +//line templates/http_readers.qtpl:5 func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, tbody string) { -//line templates/http_readers.qtpl:4 +//line templates/http_readers.qtpl:5 qw422016.N().S(`
`) -//line templates/http_readers.qtpl:6 +//line templates/http_readers.qtpl:7 streamnavHTML(qw422016, rq, hyphaName, "history") -//line templates/http_readers.qtpl:6 +//line templates/http_readers.qtpl:7 qw422016.N().S(` @@ -43,159 +46,199 @@ func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, t `) -//line templates/http_readers.qtpl:16 +//line templates/http_readers.qtpl:17 qw422016.N().S(tbody) -//line templates/http_readers.qtpl:16 +//line templates/http_readers.qtpl:17 qw422016.N().S(`
`) -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 } -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, tbody string) { -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 StreamHistoryHTML(qw422016, rq, hyphaName, tbody) -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 qt422016.ReleaseWriter(qw422016) -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 } -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 func HistoryHTML(rq *http.Request, hyphaName, tbody string) string { -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 WriteHistoryHTML(qb422016, rq, hyphaName, tbody) -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 qs422016 := string(qb422016.B) -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 return qs422016 -//line templates/http_readers.qtpl:20 +//line templates/http_readers.qtpl:21 } -//line templates/http_readers.qtpl:22 +//line templates/http_readers.qtpl:23 func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) { -//line templates/http_readers.qtpl:22 +//line templates/http_readers.qtpl:23 qw422016.N().S(`
`) -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 streamnavHTML(qw422016, rq, hyphaName, "revision", revHash) -//line templates/http_readers.qtpl:24 +//line templates/http_readers.qtpl:25 qw422016.N().S(`

Please note that viewing binary parts of hyphae is not supported in history for now.

`) -//line templates/http_readers.qtpl:27 +//line templates/http_readers.qtpl:28 qw422016.N().S(naviTitle) -//line templates/http_readers.qtpl:27 +//line templates/http_readers.qtpl:28 qw422016.N().S(` `) -//line templates/http_readers.qtpl:28 +//line templates/http_readers.qtpl:29 qw422016.N().S(contents) -//line templates/http_readers.qtpl:28 +//line templates/http_readers.qtpl:29 qw422016.N().S(`

`) -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 } -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) { -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 StreamRevisionHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, revHash) -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 qt422016.ReleaseWriter(qw422016) -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 } -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) string { -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 WriteRevisionHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, revHash) -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 qs422016 := string(qb422016.B) -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 return qs422016 -//line templates/http_readers.qtpl:35 +//line templates/http_readers.qtpl:36 } // If `contents` == "", a helpful message is shown instead. -//line templates/http_readers.qtpl:38 -func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree string) { -//line templates/http_readers.qtpl:38 +//line templates/http_readers.qtpl:39 +func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) { +//line templates/http_readers.qtpl:39 qw422016.N().S(`
`) -//line templates/http_readers.qtpl:40 +//line templates/http_readers.qtpl:41 streamnavHTML(qw422016, rq, hyphaName, "page") -//line templates/http_readers.qtpl:40 +//line templates/http_readers.qtpl:41 qw422016.N().S(`
`) -//line templates/http_readers.qtpl:42 +//line templates/http_readers.qtpl:43 qw422016.N().S(naviTitle) -//line templates/http_readers.qtpl:42 +//line templates/http_readers.qtpl:43 qw422016.N().S(` `) -//line templates/http_readers.qtpl:43 +//line templates/http_readers.qtpl:44 if contents == "" { -//line templates/http_readers.qtpl:43 +//line templates/http_readers.qtpl:44 qw422016.N().S(`

This hypha has no text. Why not create it?

`) -//line templates/http_readers.qtpl:45 +//line templates/http_readers.qtpl:46 } else { -//line templates/http_readers.qtpl:45 +//line templates/http_readers.qtpl:46 qw422016.N().S(` `) -//line templates/http_readers.qtpl:46 +//line templates/http_readers.qtpl:47 qw422016.N().S(contents) -//line templates/http_readers.qtpl:46 +//line templates/http_readers.qtpl:47 qw422016.N().S(` `) -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:48 } -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:48 qw422016.N().S(`
-
+
+ `) +//line templates/http_readers.qtpl:51 + if prevHyphaName != "" { +//line templates/http_readers.qtpl:51 + qw422016.N().S(` + ← `) +//line templates/http_readers.qtpl:52 + qw422016.E().S(path.Base(prevHyphaName)) +//line templates/http_readers.qtpl:52 + qw422016.N().S(` + `) +//line templates/http_readers.qtpl:53 + } +//line templates/http_readers.qtpl:53 + qw422016.N().S(` + `) +//line templates/http_readers.qtpl:54 + if nextHyphaName != "" { +//line templates/http_readers.qtpl:54 + qw422016.N().S(` + `) +//line templates/http_readers.qtpl:55 + qw422016.E().S(path.Base(nextHyphaName)) +//line templates/http_readers.qtpl:55 + qw422016.N().S(` → + `) +//line templates/http_readers.qtpl:56 + } +//line templates/http_readers.qtpl:56 + qw422016.N().S(` +
+
`) -//line templates/http_readers.qtpl:50 +//line templates/http_readers.qtpl:59 if u := user.FromRequest(rq).OrAnon(); !user.AuthUsed || u.Group > user.UserAnon { -//line templates/http_readers.qtpl:50 +//line templates/http_readers.qtpl:59 qw422016.N().S(` @@ -205,44 +248,44 @@ func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navi
`) -//line templates/http_readers.qtpl:59 +//line templates/http_readers.qtpl:68 } -//line templates/http_readers.qtpl:59 +//line templates/http_readers.qtpl:68 qw422016.N().S(`
`) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:73 } -//line templates/http_readers.qtpl:64 -func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree string) { -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:73 +func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) { +//line templates/http_readers.qtpl:73 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_readers.qtpl:64 - StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:73 + StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName) +//line templates/http_readers.qtpl:73 qt422016.ReleaseWriter(qw422016) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:73 } -//line templates/http_readers.qtpl:64 -func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree string) string { -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:73 +func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) string { +//line templates/http_readers.qtpl:73 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_readers.qtpl:64 - WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:73 + WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName) +//line templates/http_readers.qtpl:73 qs422016 := string(qb422016.B) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:73 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:73 return qs422016 -//line templates/http_readers.qtpl:64 +//line templates/http_readers.qtpl:73 } diff --git a/tree/tree.go b/tree/tree.go index 1655664..982fed7 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -10,17 +10,20 @@ import ( // 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)) } -// TreeAsHtml generates a tree for `hyphaName`. `hyphaStorage` has this type because package `tree` has no access to `HyphaData` data type. One day it shall have it, I guess. -func TreeAsHtml(hyphaName string, hyphaIterator func(func(string))) string { +// 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() + return t.asHtml(), t.prevSibling, t.nextSibling } // subtree adds a descendant tree to `t` and returns that tree. @@ -34,11 +37,22 @@ func (t *tree) fork(descendantName string) *tree { 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() From 3d20b3e77a26b78a1a208b6d3fd8ce1ba64d19f7 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Sun, 29 Nov 2020 22:06:45 +0500 Subject: [PATCH 02/50] Make history page easier to use --- history/information.go | 68 +++++++++-- http_readers.go | 8 +- templates/asset.qtpl.go | 9 +- templates/default.css | 9 +- templates/http_readers.qtpl | 18 +-- templates/http_readers.qtpl.go | 212 ++++++++++++++++----------------- 6 files changed, 184 insertions(+), 140 deletions(-) diff --git a/history/information.go b/history/information.go index bc65cac..80d5b5d 100644 --- a/history/information.go +++ b/history/information.go @@ -7,8 +7,10 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/bouncepaw/mycorrhiza/templates" + "github.com/bouncepaw/mycorrhiza/util" ) func RecentChanges(n int) string { @@ -37,8 +39,8 @@ func Revisions(hyphaName string) ([]Revision, error) { var ( out, err = gitsh( "log", "--oneline", "--no-merges", - // Hash, Commiter email, Commiter time, Commit msg separated by tab - "--pretty=format:\"%h\t%ce\t%ct\t%s\"", + // Hash, author email, author time, commit msg separated by tab + "--pretty=format:\"%h\t%ae\t%at\t%s\"", "--", hyphaName+".*", ) revs []Revision @@ -53,6 +55,58 @@ func Revisions(hyphaName string) ([]Revision, error) { return revs, err } +// HistoryWithRevisions returns an html representation of `revs` that is meant to be inserted in a history page. +func HistoryWithRevisions(hyphaName string, revs []Revision) (html string) { + var ( + currentYear int + currentMonth time.Month + ) + for i, rev := range revs { + if rev.Time.Month() != currentMonth || rev.Time.Year() != currentYear { + currentYear = rev.Time.Year() + currentMonth = rev.Time.Month() + if i != 0 { + html += ` + +` + } + html += fmt.Sprintf(` +
+ +

%[3]s

+
+
    `, + currentYear, currentMonth, + strconv.Itoa(currentYear)+" "+rev.Time.Month().String()) + } + html += rev.asHistoryEntry(hyphaName) + } + return html +} + +func (rev *Revision) asHistoryEntry(hyphaName string) (html string) { + author := "" + if rev.Username != "anon" { + author = fmt.Sprintf(` +
  • + + %[3]s + %[4]s + %[5]s +
  • +`, hyphaName, rev.timeHourMinute(), rev.Hash, rev.Message, author) +} + +// Return time like 13:42 +func (rev *Revision) timeHourMinute() string { + h, m, _ := rev.Time.Clock() + return strconv.Itoa(h) + ":" + strconv.Itoa(m) +} + // This regex is wrapped in "". For some reason, these quotes appear at some time and we have to get rid of them. var revisionLinePattern = regexp.MustCompile("\"(.*)\t(.*)@.*\t(.*)\t(.*)\"") @@ -66,16 +120,6 @@ func parseRevisionLine(line string) Revision { } } -// Represent revision as a table row. -func (rev *Revision) AsHtmlTableRow(hyphaName string) string { - return fmt.Sprintf(` - - - %s - %s -`, rev.TimeString(), rev.Hash, hyphaName, rev.Hash, rev.Message) -} - // See how the file with `filepath` looked at commit with `hash`. func FileAtRevision(filepath, hash string) (string, error) { out, err := gitsh("show", hash+":"+filepath) diff --git a/http_readers.go b/http_readers.go index 759f05d..e9bf8dc 100644 --- a/http_readers.go +++ b/http_readers.go @@ -57,19 +57,17 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { func handlerHistory(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) hyphaName := HyphaNameFromRq(rq, "history") - var tbody string + var list string // History can be found for files that do not exist anymore. revs, err := history.Revisions(hyphaName) if err == nil { - for _, rev := range revs { - tbody += rev.AsHtmlTableRow(hyphaName) - } + list = history.HistoryWithRevisions(hyphaName, revs) } log.Println("Found", len(revs), "revisions for", hyphaName) util.HTTP200Page(w, - base(hyphaName, templates.HistoryHTML(rq, hyphaName, tbody))) + base(hyphaName, templates.HistoryHTML(rq, hyphaName, list))) } // handlerText serves raw source text of the hypha. diff --git a/templates/asset.qtpl.go b/templates/asset.qtpl.go index 2004603..655f800 100644 --- a/templates/asset.qtpl.go +++ b/templates/asset.qtpl.go @@ -33,7 +33,7 @@ func StreamDefaultCSS(qw422016 *qt422016.Writer) { html {height:100%; padding:0; background-color:#ddd; background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox='0 0 42 44' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='brick-wall' fill='%23bbbbbb' fill-opacity='0.4'%3E%3Cpath d='M0 0h42v44H0V0zm1 1h40v20H1V1zM0 23h20v20H0V23zm22 0h20v20H22V23z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");} /* heropatterns.com */ body {height:100%; margin:0; font-size:16px; font-family:sans-serif;} -main {padding:1rem; background-color: white; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); } +main {padding:1rem; background-color: white; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); border-radius: 0 0 .25rem .25rem; } main > form {margin-bottom:1rem;} textarea {font-size:15px;} .edit {height:100%;} @@ -81,6 +81,13 @@ nav ul li {list-style-type:none;margin-right:1rem;} .prevnext__next { float: right; text-align: right; } .page-separator { clear: both; } +.history__entries { background-color: #eee; margin: 0; padding: 0; border-radius: .25rem; } +.history__month-anchor { text-decoration: none; color: black; } +.history__entry { list-style-type: none; padding: .25rem; } +.history-entry { padding: .25rem; } +.history-entry__time { font-weight: bold; } +.history-entry__author { font-style: italic; } + `) //line templates/asset.qtpl:2 qw422016.N().S(` diff --git a/templates/default.css b/templates/default.css index 060a814..f79a0a8 100644 --- a/templates/default.css +++ b/templates/default.css @@ -8,7 +8,7 @@ html {height:100%; padding:0; background-color:#ddd; background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox='0 0 42 44' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='brick-wall' fill='%23bbbbbb' fill-opacity='0.4'%3E%3Cpath d='M0 0h42v44H0V0zm1 1h40v20H1V1zM0 23h20v20H0V23zm22 0h20v20H22V23z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");} /* heropatterns.com */ body {height:100%; margin:0; font-size:16px; font-family:sans-serif;} -main {padding:1rem; background-color: white; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); } +main {padding:1rem; background-color: white; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); border-radius: 0 0 .25rem .25rem; } main > form {margin-bottom:1rem;} textarea {font-size:15px;} .edit {height:100%;} @@ -56,3 +56,10 @@ nav ul li {list-style-type:none;margin-right:1rem;} .prevnext__next { float: right; text-align: right; } .page-separator { clear: both; } +.history__entries { background-color: #eee; margin: 0; padding: 0; border-radius: .25rem; } +.history__month-anchor { text-decoration: none; color: black; } +.history__entry { list-style-type: none; padding: .25rem; } +.history-entry { padding: .25rem; } +.history-entry__time { font-weight: bold; } +.history-entry__author { font-style: italic; } + diff --git a/templates/http_readers.qtpl b/templates/http_readers.qtpl index da82cf9..ca58c7c 100644 --- a/templates/http_readers.qtpl +++ b/templates/http_readers.qtpl @@ -2,21 +2,13 @@ {% import "path" %} {% import "github.com/bouncepaw/mycorrhiza/user" %} -{% func HistoryHTML(rq *http.Request, hyphaName, tbody string) %} +{% func HistoryHTML(rq *http.Request, hyphaName, list string) %}
    {%= navHTML(rq, hyphaName, "history") %} - - - - - - - - - - {%s= tbody %} - -
    TimeHashMessage
    +
    +

    History of {%s hyphaName %}

    + {%s= list %} +
    {% endfunc %} diff --git a/templates/http_readers.qtpl.go b/templates/http_readers.qtpl.go index 335b3d2..95e85f5 100644 --- a/templates/http_readers.qtpl.go +++ b/templates/http_readers.qtpl.go @@ -27,7 +27,7 @@ var ( ) //line templates/http_readers.qtpl:5 -func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, tbody string) { +func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, list string) { //line templates/http_readers.qtpl:5 qw422016.N().S(`
    @@ -36,209 +36,205 @@ func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, t streamnavHTML(qw422016, rq, hyphaName, "history") //line templates/http_readers.qtpl:7 qw422016.N().S(` - - - - - - - - - +
    +

    History of `) +//line templates/http_readers.qtpl:9 + qw422016.E().S(hyphaName) +//line templates/http_readers.qtpl:9 + qw422016.N().S(`

    `) -//line templates/http_readers.qtpl:17 - qw422016.N().S(tbody) -//line templates/http_readers.qtpl:17 +//line templates/http_readers.qtpl:10 + qw422016.N().S(list) +//line templates/http_readers.qtpl:10 qw422016.N().S(` -
    -
    TimeHashMessage
    +
    `) -//line templates/http_readers.qtpl:21 +//line templates/http_readers.qtpl:13 } -//line templates/http_readers.qtpl:21 -func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, tbody string) { -//line templates/http_readers.qtpl:21 +//line templates/http_readers.qtpl:13 +func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, list string) { +//line templates/http_readers.qtpl:13 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_readers.qtpl:21 - StreamHistoryHTML(qw422016, rq, hyphaName, tbody) -//line templates/http_readers.qtpl:21 +//line templates/http_readers.qtpl:13 + StreamHistoryHTML(qw422016, rq, hyphaName, list) +//line templates/http_readers.qtpl:13 qt422016.ReleaseWriter(qw422016) -//line templates/http_readers.qtpl:21 +//line templates/http_readers.qtpl:13 } -//line templates/http_readers.qtpl:21 -func HistoryHTML(rq *http.Request, hyphaName, tbody string) string { -//line templates/http_readers.qtpl:21 +//line templates/http_readers.qtpl:13 +func HistoryHTML(rq *http.Request, hyphaName, list string) string { +//line templates/http_readers.qtpl:13 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_readers.qtpl:21 - WriteHistoryHTML(qb422016, rq, hyphaName, tbody) -//line templates/http_readers.qtpl:21 +//line templates/http_readers.qtpl:13 + WriteHistoryHTML(qb422016, rq, hyphaName, list) +//line templates/http_readers.qtpl:13 qs422016 := string(qb422016.B) -//line templates/http_readers.qtpl:21 +//line templates/http_readers.qtpl:13 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_readers.qtpl:21 +//line templates/http_readers.qtpl:13 return qs422016 -//line templates/http_readers.qtpl:21 +//line templates/http_readers.qtpl:13 } -//line templates/http_readers.qtpl:23 +//line templates/http_readers.qtpl:15 func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) { -//line templates/http_readers.qtpl:23 +//line templates/http_readers.qtpl:15 qw422016.N().S(`
    `) -//line templates/http_readers.qtpl:25 +//line templates/http_readers.qtpl:17 streamnavHTML(qw422016, rq, hyphaName, "revision", revHash) -//line templates/http_readers.qtpl:25 +//line templates/http_readers.qtpl:17 qw422016.N().S(`

    Please note that viewing binary parts of hyphae is not supported in history for now.

    `) -//line templates/http_readers.qtpl:28 +//line templates/http_readers.qtpl:20 qw422016.N().S(naviTitle) -//line templates/http_readers.qtpl:28 +//line templates/http_readers.qtpl:20 qw422016.N().S(` `) -//line templates/http_readers.qtpl:29 +//line templates/http_readers.qtpl:21 qw422016.N().S(contents) -//line templates/http_readers.qtpl:29 +//line templates/http_readers.qtpl:21 qw422016.N().S(`

    `) -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 } -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) { -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 StreamRevisionHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, revHash) -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 qt422016.ReleaseWriter(qw422016) -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 } -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) string { -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 WriteRevisionHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, revHash) -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 qs422016 := string(qb422016.B) -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 return qs422016 -//line templates/http_readers.qtpl:36 +//line templates/http_readers.qtpl:28 } // If `contents` == "", a helpful message is shown instead. -//line templates/http_readers.qtpl:39 +//line templates/http_readers.qtpl:31 func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) { -//line templates/http_readers.qtpl:39 +//line templates/http_readers.qtpl:31 qw422016.N().S(`
    `) -//line templates/http_readers.qtpl:41 +//line templates/http_readers.qtpl:33 streamnavHTML(qw422016, rq, hyphaName, "page") -//line templates/http_readers.qtpl:41 +//line templates/http_readers.qtpl:33 qw422016.N().S(`
    `) -//line templates/http_readers.qtpl:43 +//line templates/http_readers.qtpl:35 qw422016.N().S(naviTitle) -//line templates/http_readers.qtpl:43 +//line templates/http_readers.qtpl:35 qw422016.N().S(` `) -//line templates/http_readers.qtpl:44 +//line templates/http_readers.qtpl:36 if contents == "" { -//line templates/http_readers.qtpl:44 +//line templates/http_readers.qtpl:36 qw422016.N().S(`

    This hypha has no text. Why not create it?

    `) -//line templates/http_readers.qtpl:46 +//line templates/http_readers.qtpl:38 } else { -//line templates/http_readers.qtpl:46 +//line templates/http_readers.qtpl:38 qw422016.N().S(` `) -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:39 qw422016.N().S(contents) -//line templates/http_readers.qtpl:47 +//line templates/http_readers.qtpl:39 qw422016.N().S(` `) -//line templates/http_readers.qtpl:48 +//line templates/http_readers.qtpl:40 } -//line templates/http_readers.qtpl:48 +//line templates/http_readers.qtpl:40 qw422016.N().S(`
    `) -//line templates/http_readers.qtpl:51 +//line templates/http_readers.qtpl:43 if prevHyphaName != "" { -//line templates/http_readers.qtpl:51 +//line templates/http_readers.qtpl:43 qw422016.N().S(` ← `) -//line templates/http_readers.qtpl:52 +//line templates/http_readers.qtpl:44 qw422016.E().S(path.Base(prevHyphaName)) -//line templates/http_readers.qtpl:52 +//line templates/http_readers.qtpl:44 qw422016.N().S(` `) -//line templates/http_readers.qtpl:53 +//line templates/http_readers.qtpl:45 } -//line templates/http_readers.qtpl:53 +//line templates/http_readers.qtpl:45 qw422016.N().S(` `) -//line templates/http_readers.qtpl:54 +//line templates/http_readers.qtpl:46 if nextHyphaName != "" { -//line templates/http_readers.qtpl:54 +//line templates/http_readers.qtpl:46 qw422016.N().S(` `) -//line templates/http_readers.qtpl:55 +//line templates/http_readers.qtpl:47 qw422016.E().S(path.Base(nextHyphaName)) -//line templates/http_readers.qtpl:55 +//line templates/http_readers.qtpl:47 qw422016.N().S(` → `) -//line templates/http_readers.qtpl:56 +//line templates/http_readers.qtpl:48 } -//line templates/http_readers.qtpl:56 +//line templates/http_readers.qtpl:48 qw422016.N().S(`

    `) -//line templates/http_readers.qtpl:59 +//line templates/http_readers.qtpl:51 if u := user.FromRequest(rq).OrAnon(); !user.AuthUsed || u.Group > user.UserAnon { -//line templates/http_readers.qtpl:59 +//line templates/http_readers.qtpl:51 qw422016.N().S(`
    @@ -248,44 +244,44 @@ func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navi

    `) -//line templates/http_readers.qtpl:68 +//line templates/http_readers.qtpl:60 } -//line templates/http_readers.qtpl:68 +//line templates/http_readers.qtpl:60 qw422016.N().S(`
    `) -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 } -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) { -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName) -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 qt422016.ReleaseWriter(qw422016) -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 } -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string) string { -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName) -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 qs422016 := string(qb422016.B) -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 return qs422016 -//line templates/http_readers.qtpl:73 +//line templates/http_readers.qtpl:65 } From ce8da047eaa9c5d5d7882261fd074d98302d79d1 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Sun, 29 Nov 2020 22:09:02 +0500 Subject: [PATCH 03/50] Do not show login link for wikis with no auth --- templates/common.qtpl | 2 + templates/common.qtpl.go | 84 ++++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/templates/common.qtpl b/templates/common.qtpl index 321da84..29673cc 100644 --- a/templates/common.qtpl +++ b/templates/common.qtpl @@ -40,6 +40,7 @@ var navEntries = []navEntry{ {% endfunc %} {% func userMenuHTML(u *user.User) %} + {% if user.AuthUsed %} + {% endif %} {% endfunc %} diff --git a/templates/common.qtpl.go b/templates/common.qtpl.go index de53098..b2791b8 100644 --- a/templates/common.qtpl.go +++ b/templates/common.qtpl.go @@ -140,63 +140,73 @@ func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string func streamuserMenuHTML(qw422016 *qt422016.Writer, u *user.User) { //line templates/common.qtpl:42 qw422016.N().S(` + `) +//line templates/common.qtpl:43 + if user.AuthUsed { +//line templates/common.qtpl:43 + qw422016.N().S(` + `) +//line templates/common.qtpl:51 + } +//line templates/common.qtpl:51 + qw422016.N().S(` `) -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 } -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 func writeuserMenuHTML(qq422016 qtio422016.Writer, u *user.User) { -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 streamuserMenuHTML(qw422016, u) -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 qt422016.ReleaseWriter(qw422016) -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 } -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 func userMenuHTML(u *user.User) string { -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 qb422016 := qt422016.AcquireByteBuffer() -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 writeuserMenuHTML(qb422016, u) -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 qs422016 := string(qb422016.B) -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 qt422016.ReleaseByteBuffer(qb422016) -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 return qs422016 -//line templates/common.qtpl:50 +//line templates/common.qtpl:52 } From a885253d128683e6bbe654a301af00df96bde45d Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Tue, 1 Dec 2020 01:33:15 +0500 Subject: [PATCH 04/50] Fix uploading attachments on wikis with no auth --- http_mutators.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http_mutators.go b/http_mutators.go index 232fed3..1283f59 100644 --- a/http_mutators.go +++ b/http_mutators.go @@ -174,7 +174,7 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) var ( hyphaName = HyphaNameFromRq(rq, "upload-binary") - u = user.FromRequest(rq) + u = user.FromRequest(rq).OrAnon() ) if !u.CanProceed("upload-binary") { HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.") From c89b0702ab49beb5bdb68d70b298fec6ca6cfa48 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Tue, 1 Dec 2020 15:05:57 +0500 Subject: [PATCH 05/50] Handle relative paths in img --- markup/img.go | 2 +- templates/default.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/markup/img.go b/markup/img.go index 6445ed1..9539498 100644 --- a/markup/img.go +++ b/markup/img.go @@ -218,7 +218,7 @@ func (img *Img) checkLinks() map[string]bool { } HyphaIterate(func(hyphaName string) { for _, entry := range img.entries { - if hyphaName == entry.trimmedPath { + if hyphaName == xclCanonicalName(img.hyphaName, entry.trimmedPath) { m[entry.trimmedPath] = true } } diff --git a/templates/default.css b/templates/default.css index f79a0a8..5d5c6e2 100644 --- a/templates/default.css +++ b/templates/default.css @@ -26,7 +26,7 @@ article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wr .transclusion code, .transclusion .codeblock {background-color:#ddd;} .transclusion {background-color:#eee; border-radius: .25rem; } .transclusion__content > *:not(.binary-container) {margin: 0.5rem; } -.transclusion__link {display: block; text-align: right; font-style: italic; margin-top: 0.5rem; color: black; text-decoration: none;} +.transclusion__link {display: block; text-align: right; font-style: italic; margin-top: .5rem; margin-right: .25rem; color: black; text-decoration: none;} .transclusion__link::before {content: "⇐ ";} .binary-container_with-img img, From 5269411ea2e88d37bbb9c758cec387e0151be85e Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Tue, 8 Dec 2020 14:04:24 +0500 Subject: [PATCH 06/50] Move /history/ and /recent-changes handlers to their own file --- http_history.go | 48 +++++++++++++++++++++++++++++++++++++++++ http_readers.go | 18 ---------------- main.go | 24 ++++----------------- templates/asset.qtpl.go | 2 +- 4 files changed, 53 insertions(+), 39 deletions(-) create mode 100644 http_history.go diff --git a/http_history.go b/http_history.go new file mode 100644 index 0000000..8bec1ae --- /dev/null +++ b/http_history.go @@ -0,0 +1,48 @@ +package main + +import ( + "log" + "net/http" + "strconv" + "strings" + + "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/templates" + "github.com/bouncepaw/mycorrhiza/util" +) + +func init() { + http.HandleFunc("/history/", handlerHistory) + http.HandleFunc("/recent-changes/", handlerRecentChanges) +} + +// handlerHistory lists all revisions of a hypha +func handlerHistory(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + hyphaName := HyphaNameFromRq(rq, "history") + var list string + + // History can be found for files that do not exist anymore. + revs, err := history.Revisions(hyphaName) + if err == nil { + list = history.HistoryWithRevisions(hyphaName, revs) + } + log.Println("Found", len(revs), "revisions for", hyphaName) + + util.HTTP200Page(w, + base(hyphaName, templates.HistoryHTML(rq, hyphaName, list))) +} + +// Recent changes +func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { + log.Println(rq.URL) + var ( + noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/") + n, err = strconv.Atoi(noPrefix) + ) + if err == nil && n < 101 { + util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", history.RecentChanges(n))) + } else { + http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther) + } +} diff --git a/http_readers.go b/http_readers.go index e9bf8dc..6f7c58e 100644 --- a/http_readers.go +++ b/http_readers.go @@ -20,7 +20,6 @@ func init() { http.HandleFunc("/page/", handlerPage) http.HandleFunc("/text/", handlerText) http.HandleFunc("/binary/", handlerBinary) - http.HandleFunc("/history/", handlerHistory) http.HandleFunc("/rev/", handlerRevision) } @@ -53,23 +52,6 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { w.Write([]byte(base(hyphaName, page))) } -// handlerHistory lists all revisions of a hypha -func handlerHistory(w http.ResponseWriter, rq *http.Request) { - log.Println(rq.URL) - hyphaName := HyphaNameFromRq(rq, "history") - var list string - - // History can be found for files that do not exist anymore. - revs, err := history.Revisions(hyphaName) - if err == nil { - list = history.HistoryWithRevisions(hyphaName, revs) - } - log.Println("Found", len(revs), "revisions for", hyphaName) - - util.HTTP200Page(w, - base(hyphaName, templates.HistoryHTML(rq, hyphaName, list))) -} - // handlerText serves raw source text of the hypha. func handlerText(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) diff --git a/main.go b/main.go index 51d7610..1215861 100644 --- a/main.go +++ b/main.go @@ -10,8 +10,6 @@ import ( "os" "path/filepath" "regexp" - "strconv" - "strings" "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/templates" @@ -91,20 +89,6 @@ func handlerRandom(w http.ResponseWriter, rq *http.Request) { http.Redirect(w, rq, "/page/"+randomHyphaName, http.StatusSeeOther) } -// Recent changes -func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { - log.Println(rq.URL) - var ( - noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/") - n, err = strconv.Atoi(noPrefix) - ) - if err == nil && n < 101 { - util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", history.RecentChanges(n))) - } else { - http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther) - } -} - func handlerStyle(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) if _, err := os.Stat(WikiDir + "/static/common.css"); err == nil { @@ -128,14 +112,14 @@ func main() { history.Start(WikiDir) - http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static")))) - // See http_readers.go for /page/, /text/, /binary/, /history/. - // See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/. + // See http_readers.go for /page/, /text/, /binary/ + // See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/ // See http_auth.go for /login, /login-data, /logout, /logout-confirm + // See http_history.go for /history/, /recent-changes http.HandleFunc("/list", handlerList) http.HandleFunc("/reindex", handlerReindex) http.HandleFunc("/random", handlerRandom) - http.HandleFunc("/recent-changes/", handlerRecentChanges) + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static")))) http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) { http.ServeFile(w, rq, WikiDir+"/static/favicon.ico") }) diff --git a/templates/asset.qtpl.go b/templates/asset.qtpl.go index 655f800..16def29 100644 --- a/templates/asset.qtpl.go +++ b/templates/asset.qtpl.go @@ -51,7 +51,7 @@ article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wr .transclusion code, .transclusion .codeblock {background-color:#ddd;} .transclusion {background-color:#eee; border-radius: .25rem; } .transclusion__content > *:not(.binary-container) {margin: 0.5rem; } -.transclusion__link {display: block; text-align: right; font-style: italic; margin-top: 0.5rem; color: black; text-decoration: none;} +.transclusion__link {display: block; text-align: right; font-style: italic; margin-top: .5rem; margin-right: .25rem; color: black; text-decoration: none;} .transclusion__link::before {content: "⇐ ";} .binary-container_with-img img, From 8810e9891d0eacb43de97a71693c58aa442fc467 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Tue, 8 Dec 2020 20:15:32 +0500 Subject: [PATCH 07/50] Implement RSS, Atom and JSON feeds and add a new flag --- flag.go | 9 ++- go.mod | 1 + go.sum | 2 + history/history.go | 105 ++++++++++++++++++++----------- history/information.go | 48 ++++++++++++++ http_history.go | 29 +++++++++ templates/asset.qtpl.go | 1 + templates/default.css | 1 + templates/recent_changes.qtpl | 2 + templates/recent_changes.qtpl.go | 62 +++++++++--------- util/util.go | 1 + 11 files changed, 194 insertions(+), 67 deletions(-) diff --git a/flag.go b/flag.go index 8a9857a..2bd83ff 100644 --- a/flag.go +++ b/flag.go @@ -10,8 +10,9 @@ import ( ) func init() { - flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at") - flag.StringVar(&util.HomePage, "home", "home", "The home page") + flag.StringVar(&util.URL, "url", "http://0.0.0.0:$port", "URL at which your wiki can be found. Used to generate feeds") + flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at using HTTP") + flag.StringVar(&util.HomePage, "home", "home", "The home page name") flag.StringVar(&util.SiteTitle, "title", "🍄", "How to call your wiki in the navititle") flag.StringVar(&util.UserTree, "user-tree", "u", "Hypha which is a superhypha of all user pages") flag.StringVar(&util.AuthMethod, "auth-method", "none", "What auth method to use. Variants: \"none\", \"fixed\"") @@ -34,6 +35,10 @@ func parseCliArgs() { log.Fatal(err) } + if util.URL == "http://0.0.0.0:$port" { + util.URL = "http://0.0.0.0:" + util.ServerPort + } + if !isCanonicalName(util.HomePage) { log.Fatal("Error: you must use a proper name for the homepage") } diff --git a/go.mod b/go.mod index 995478b..b5d6fc3 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,6 @@ go 1.14 require ( github.com/adrg/xdg v0.2.2 + github.com/gorilla/feeds v1.1.1 github.com/valyala/quicktemplate v1.6.3 ) diff --git a/go.sum b/go.sum index dbbc51b..284389c 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo= github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY= +github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/history/history.go b/history/history.go index 247b4c5..dcd6268 100644 --- a/history/history.go +++ b/history/history.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os/exec" + "regexp" "strconv" "strings" "time" @@ -13,6 +14,8 @@ import ( "github.com/bouncepaw/mycorrhiza/util" ) +var renameMsgPattern = regexp.MustCompile(`^Rename ‘(.*)’ to ‘.*’`) + // Start initializes git credentials. func Start(wikiDir string) { _, err := gitsh("config", "user.name", "wikimind") @@ -27,10 +30,46 @@ func Start(wikiDir string) { // Revision represents a revision, duh. Hash is usually short. Username is extracted from email. type Revision struct { - Hash string - Username string - Time time.Time - Message string + Hash string + Username string + Time time.Time + Message string + hyphaeAffectedBuf []string +} + +// determine what hyphae were affected by this revision +func (rev *Revision) hyphaeAffected() (hyphae []string) { + if nil != rev.hyphaeAffectedBuf { + return rev.hyphaeAffectedBuf + } + hyphae = make([]string, 0) + var ( + // List of files affected by this revision, one per line. + out, err = gitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash) + // set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most currently). + set = make(map[string]bool) + isNewName = func(hyphaName string) bool { + if _, present := set[hyphaName]; present { + return false + } + set[hyphaName] = true + return true + } + ) + if err != nil { + return hyphae + } + for _, filename := range strings.Split(out.String(), "\n") { + if strings.IndexRune(filename, '.') >= 0 { + dotPos := strings.LastIndexByte(filename, '.') + hyphaName := string([]byte(filename)[0:dotPos]) // is it safe? + if isNewName(hyphaName) { + hyphae = append(hyphae, hyphaName) + } + } + } + rev.hyphaeAffectedBuf = hyphae + return hyphae } // TimeString returns a human readable time representation. @@ -40,42 +79,38 @@ func (rev Revision) TimeString() string { // HyphaeLinks returns a comma-separated list of hyphae that were affected by this revision as HTML string. func (rev Revision) HyphaeLinks() (html string) { - // diff-tree --no-commit-id --name-only -r - var ( - // List of files affected by this revision, one per line. - out, err = gitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash) - // set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most). - set = make(map[string]bool) - isNewName = func(hyphaName string) bool { - if _, present := set[hyphaName]; present { - return false - } else { - set[hyphaName] = true - return true - } - } - ) - if err != nil { - return "" - } - for _, filename := range strings.Split(out.String(), "\n") { - // If filename has an ampersand: - if strings.IndexRune(filename, '.') >= 0 { - // Remove ampersanded suffix from filename: - ampersandPos := strings.LastIndexByte(filename, '.') - hyphaName := string([]byte(filename)[0:ampersandPos]) // is it safe? - if isNewName(hyphaName) { - // Entries are separated by commas - if len(set) > 1 { - html += `` - } - html += fmt.Sprintf(`%[1]s`, hyphaName) - } + hyphae := rev.hyphaeAffected() + for i, hyphaName := range hyphae { + if i > 0 { + html += `` } + html += fmt.Sprintf(`%[1]s`, hyphaName) } return html } +func (rev *Revision) descriptionForFeed() (html string) { + return fmt.Sprintf( + `

    %s

    +

    Hyphae affected: %s

    `, rev.Message, rev.HyphaeLinks()) +} + +// Try and guess what link is the most important by looking at the message. +func (rev *Revision) bestLink() string { + var ( + revs = rev.hyphaeAffected() + renameRes = renameMsgPattern.FindStringSubmatch(rev.Message) + ) + switch { + case renameRes != nil: + return "/page/" + renameRes[1] + case len(revs) == 0: + return "" + default: + return "/page/" + revs[0] + } +} + func (rev Revision) RecentChangesEntry() (html string) { if user.AuthUsed && rev.Username != "anon" { return fmt.Sprintf(` diff --git a/history/information.go b/history/information.go index 80d5b5d..82c0eb3 100644 --- a/history/information.go +++ b/history/information.go @@ -11,8 +11,56 @@ import ( "github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/util" + "github.com/gorilla/feeds" ) +func recentChangesFeed() *feeds.Feed { + feed := &feeds.Feed{ + Title: "Recent changes", + Link: &feeds.Link{Href: util.URL}, + Description: "List of 30 recent changes on the wiki", + Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"}, + Updated: time.Now(), + } + var ( + out, err = gitsh( + "log", "--oneline", "--no-merges", + "--pretty=format:\"%h\t%ae\t%at\t%s\"", + "--max-count=30", + ) + revs []Revision + ) + if err == nil { + for _, line := range strings.Split(out.String(), "\n") { + revs = append(revs, parseRevisionLine(line)) + } + } + for _, rev := range revs { + feed.Add(&feeds.Item{ + Title: rev.Message, + Author: &feeds.Author{Name: rev.Username}, + Id: rev.Hash, + Description: rev.descriptionForFeed(), + Created: rev.Time, + Updated: rev.Time, + Link: &feeds.Link{Href: util.URL + rev.bestLink()}, + }) + } + return feed +} + +func RecentChangesRSS() (string, error) { + return recentChangesFeed().ToRss() +} + +func RecentChangesAtom() (string, error) { + return recentChangesFeed().ToAtom() +} + +func RecentChangesJSON() (string, error) { + return recentChangesFeed().ToJSON() +} + func RecentChanges(n int) string { var ( out, err = gitsh( diff --git a/http_history.go b/http_history.go index 8bec1ae..de010d5 100644 --- a/http_history.go +++ b/http_history.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" "net/http" "strconv" @@ -14,6 +15,9 @@ import ( func init() { http.HandleFunc("/history/", handlerHistory) http.HandleFunc("/recent-changes/", handlerRecentChanges) + http.HandleFunc("/recent-changes-rss", handlerRecentChangesRSS) + http.HandleFunc("/recent-changes-atom", handlerRecentChangesAtom) + http.HandleFunc("/recent-changes-json", handlerRecentChangesJSON) } // handlerHistory lists all revisions of a hypha @@ -46,3 +50,28 @@ func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther) } } + +func genericHandlerOfFeeds(w http.ResponseWriter, rq *http.Request, f func() (string, error), name string) { + log.Println(rq.URL) + if content, err := f(); err != nil { + w.Header().Set("Content-Type", "text/plain;charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, "An error while generating "+name+": "+err.Error()) + } else { + w.Header().Set("Content-Type", "application/rss+xml") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, content) + } +} + +func handlerRecentChangesRSS(w http.ResponseWriter, rq *http.Request) { + genericHandlerOfFeeds(w, rq, history.RecentChangesRSS, "RSS") +} + +func handlerRecentChangesAtom(w http.ResponseWriter, rq *http.Request) { + genericHandlerOfFeeds(w, rq, history.RecentChangesAtom, "Atom") +} + +func handlerRecentChangesJSON(w http.ResponseWriter, rq *http.Request) { + genericHandlerOfFeeds(w, rq, history.RecentChangesJSON, "JSON feed") +} diff --git a/templates/asset.qtpl.go b/templates/asset.qtpl.go index 16def29..a90d0ab 100644 --- a/templates/asset.qtpl.go +++ b/templates/asset.qtpl.go @@ -39,6 +39,7 @@ textarea {font-size:15px;} .edit {height:100%;} .edit-form {height:90%;} .edit-form textarea {width:100%;height:90%;} +.icon {margin-right: .25rem; vertical-align: bottom; } main h1:not(.navi-title) {font-size:1.7rem;} blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;} diff --git a/templates/default.css b/templates/default.css index 5d5c6e2..a7c0f07 100644 --- a/templates/default.css +++ b/templates/default.css @@ -14,6 +14,7 @@ textarea {font-size:15px;} .edit {height:100%;} .edit-form {height:90%;} .edit-form textarea {width:100%;height:90%;} +.icon {margin-right: .25rem; vertical-align: bottom; } main h1:not(.navi-title) {font-size:1.7rem;} blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;} diff --git a/templates/recent_changes.qtpl b/templates/recent_changes.qtpl index 738f39a..2980fd4 100644 --- a/templates/recent_changes.qtpl +++ b/templates/recent_changes.qtpl @@ -18,6 +18,8 @@ recent changes +

    Subscribe via RSS, Atom or JSON feed.

    + {% comment %} Here I am, willing to add some accesibility using ARIA. Turns out, role="feed" is not supported in any screen reader as of September diff --git a/templates/recent_changes.qtpl.go b/templates/recent_changes.qtpl.go index 8268ef1..329ccc5 100644 --- a/templates/recent_changes.qtpl.go +++ b/templates/recent_changes.qtpl.go @@ -77,81 +77,83 @@ func StreamRecentChangesHTML(qw422016 *qt422016.Writer, changes []string, n int) recent changes +

    Subscribe via RSS, Atom or JSON feed.

    + `) -//line templates/recent_changes.qtpl:26 +//line templates/recent_changes.qtpl:28 qw422016.N().S(`
    `) -//line templates/recent_changes.qtpl:29 +//line templates/recent_changes.qtpl:31 if len(changes) == 0 { -//line templates/recent_changes.qtpl:29 +//line templates/recent_changes.qtpl:31 qw422016.N().S(`

    Could not find any recent changes.

    `) -//line templates/recent_changes.qtpl:31 +//line templates/recent_changes.qtpl:33 } else { -//line templates/recent_changes.qtpl:31 +//line templates/recent_changes.qtpl:33 qw422016.N().S(` `) -//line templates/recent_changes.qtpl:32 +//line templates/recent_changes.qtpl:34 for i, entry := range changes { -//line templates/recent_changes.qtpl:32 +//line templates/recent_changes.qtpl:34 qw422016.N().S(`
      `) -//line templates/recent_changes.qtpl:35 +//line templates/recent_changes.qtpl:37 qw422016.N().S(entry) -//line templates/recent_changes.qtpl:35 +//line templates/recent_changes.qtpl:37 qw422016.N().S(`
    `) -//line templates/recent_changes.qtpl:37 +//line templates/recent_changes.qtpl:39 } -//line templates/recent_changes.qtpl:37 +//line templates/recent_changes.qtpl:39 qw422016.N().S(` `) -//line templates/recent_changes.qtpl:38 +//line templates/recent_changes.qtpl:40 } -//line templates/recent_changes.qtpl:38 +//line templates/recent_changes.qtpl:40 qw422016.N().S(`
`) -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 } -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 func WriteRecentChangesHTML(qq422016 qtio422016.Writer, changes []string, n int) { -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 StreamRecentChangesHTML(qw422016, changes, n) -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 qt422016.ReleaseWriter(qw422016) -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 } -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 func RecentChangesHTML(changes []string, n int) string { -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 qb422016 := qt422016.AcquireByteBuffer() -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 WriteRecentChangesHTML(qb422016, changes, n) -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 qs422016 := string(qb422016.B) -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 qt422016.ReleaseByteBuffer(qb422016) -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 return qs422016 -//line templates/recent_changes.qtpl:41 +//line templates/recent_changes.qtpl:43 } diff --git a/util/util.go b/util/util.go index 745bf96..032e9f7 100644 --- a/util/util.go +++ b/util/util.go @@ -8,6 +8,7 @@ import ( ) var ( + URL string ServerPort string HomePage string SiteTitle string From 37eb3d1c772dd60cbf7ec9fb1bd8996e0dbb5223 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Tue, 8 Dec 2020 20:26:18 +0500 Subject: [PATCH 08/50] Update README.md --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 784c616..69e179b 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Options: Port to serve the wiki at (default "1737") -title string How to call your wiki in the navititle (default "🍄") + -url string + URL at which your wiki can be found. Used to generate feeds (default "http://0.0.0.0:$port") -user-tree string Hypha which is a superhypha of all user pages (default "u") ``` @@ -37,16 +39,16 @@ Options: * Responsive design * Works in text browsers * Wiki pages (called hyphae) are written in mycomarkup -* Everything is stored as simple files, no database required. You can run a wiki on almost any directory and get something to work with. -* Page trees +* Everything is stored as simple files, no database required. You can run a wiki on almost any directory and get something to work with +* Page trees; links to previous and next pages * Changes are saved to git * List of hyphae page * History page * Random page -* Recent changes page +* Recent changes page; RSS, Atom and JSON feeds available * Hyphae can be deleted (while still preserving history) * Hyphae can be renamed (recursive renaming of subhyphae is also supported) -* Light on resources: I run a home wiki on this engine 24/7 at an [Orange π Lite](http://www.orangepi.org/orangepilite/). +* Light on resources: I run a home wiki on this engine 24/7 at an [Orange π Lite](http://www.orangepi.org/orangepilite/) * Authorization with pre-set credentials ## Contributing From 8ac27fc38572f8a7ba35c6a95ca49ec20c07145a Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Tue, 15 Dec 2020 23:59:36 +0500 Subject: [PATCH 09/50] Improve links visually --- main.go | 30 ++++ markup/lexer.go | 28 +++- markup/link.go | 9 +- markup/mycomarkup.go | 14 +- templates/asset.qtpl | 18 +++ templates/asset.qtpl.go | 180 +++++++++++++++++++++++- templates/default.css | 6 +- templates/icon/gemini-protocol-icon.svg | 1 + templates/icon/gopher-protocol-icon.svg | 11 ++ templates/icon/http-protocol-icon.svg | 1 + templates/icon/mailto-protocol-icon.svg | 1 + 11 files changed, 287 insertions(+), 12 deletions(-) create mode 100644 templates/icon/gemini-protocol-icon.svg create mode 100644 templates/icon/gopher-protocol-icon.svg create mode 100644 templates/icon/http-protocol-icon.svg create mode 100644 templates/icon/mailto-protocol-icon.svg diff --git a/main.go b/main.go index 1215861..d368bb0 100644 --- a/main.go +++ b/main.go @@ -4,12 +4,14 @@ package main import ( "fmt" + "io/ioutil" "log" "math/rand" "net/http" "os" "path/filepath" "regexp" + "strings" "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/templates" @@ -99,6 +101,33 @@ func handlerStyle(w http.ResponseWriter, rq *http.Request) { } } +func handlerIcon(w http.ResponseWriter, rq *http.Request) { + iconName := strings.TrimPrefix(rq.URL.Path, "/static/icon/") + if iconName == "https" { + iconName = "http" + } + files, err := ioutil.ReadDir(WikiDir + "/static/icon") + if err == nil { + for _, f := range files { + if strings.HasPrefix(f.Name(), iconName+"-protocol-icon") { + http.ServeFile(w, rq, WikiDir+"/static/icon/"+f.Name()) + return + } + } + } + w.Header().Set("Content-Type", "image/svg+xml") + switch iconName { + case "gemini": + w.Write([]byte(templates.IconGemini())) + case "mailto": + w.Write([]byte(templates.IconMailto())) + case "gopher": + w.Write([]byte(templates.IconGopher())) + default: + w.Write([]byte(templates.IconHTTP())) + } +} + func main() { log.Println("Running MycorrhizaWiki β") parseCliArgs() @@ -124,6 +153,7 @@ func main() { http.ServeFile(w, rq, WikiDir+"/static/favicon.ico") }) http.HandleFunc("/static/common.css", handlerStyle) + http.HandleFunc("/static/icon/", handlerIcon) http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) { http.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther) }) diff --git a/markup/lexer.go b/markup/lexer.go index 08476d6..9ff58f5 100644 --- a/markup/lexer.go +++ b/markup/lexer.go @@ -59,6 +59,9 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) { addLine(state.buf + "") case "pre": state.buf += "\n" + case "launchpad": + state.where = "" + addLine(state.buf + "") } return } @@ -80,6 +83,8 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) { goto listState case "number": goto numberState + case "launchpad": + goto launchpadState default: goto normalState } @@ -135,6 +140,23 @@ numberState: } return +launchpadState: + switch { + case startsWith("=>"): + href, text, class := Rocketlink(line, state.name) + state.buf += fmt.Sprintf(`
  • %s
  • `, class, href, text) + case startsWith("```"): + state.where = "pre" + addLine(state.buf + "") + state.id++ + state.buf = fmt.Sprintf("
    ", state.id, strings.TrimPrefix(line, "```"))
    +	default:
    +		state.where = ""
    +		addLine(state.buf + "")
    +		goto normalState
    +	}
    +	return
    +
     normalState:
     	state.id++
     	switch {
    @@ -168,9 +190,9 @@ normalState:
     		addLine(fmt.Sprintf(
     			"
    %s
    ", state.id, remover(">")(line))) case startsWith("=>"): - href, text, class := Rocketlink(line, state.name) - addLine(fmt.Sprintf( - `

    %s

    `, state.id, class, href, text)) + state.where = "launchpad" + state.buf = fmt.Sprintf("
      \n", state.id) + goto launchpadState case startsWith("<="): addLine(parseTransclusion(line, state.name)) diff --git a/markup/link.go b/markup/link.go index eb52c63..ef91e68 100644 --- a/markup/link.go +++ b/markup/link.go @@ -1,6 +1,7 @@ package markup import ( + "fmt" "path" "strings" ) @@ -19,7 +20,13 @@ func LinkParts(addr, display, hyphaName string) (href, text, class string) { switch { case strings.ContainsRune(addr, ':'): - return addr, text, "wikilink_external" + pos := strings.IndexRune(addr, ':') + destination := addr[:pos] + text = addr[pos+1:] + if strings.HasPrefix(text, "//") && len(text) > 2 { + text = text[2:] + } + return addr, text + fmt.Sprintf(``, destination), "wikilink_external" case strings.HasPrefix(addr, "/"): return addr, text, class case strings.HasPrefix(addr, "./"): diff --git a/markup/mycomarkup.go b/markup/mycomarkup.go index fa47e2b..f3fb7a2 100644 --- a/markup/mycomarkup.go +++ b/markup/mycomarkup.go @@ -11,11 +11,6 @@ type MycoDoc struct { // data hyphaName string contents string - - // state - recursionDepth int - - // results } // Constructor @@ -27,10 +22,17 @@ func Doc(hyphaName, contents string) *MycoDoc { } // AsHtml returns an html representation of the document -func (md *MycoDoc) AsHtml() string { +func (md *MycoDoc) AsHTML() string { return "" } +// OpenGraphHTML returns an html representation of og: meta tags. +func (md *MycoDoc) OpenGraphHTML() string { + return "" +} + +/* The rest of this file is currently unused. TODO: use it I guess */ + type BlockType int const ( diff --git a/templates/asset.qtpl b/templates/asset.qtpl index 91e873f..2e6b4f2 100644 --- a/templates/asset.qtpl +++ b/templates/asset.qtpl @@ -1,3 +1,21 @@ {% func DefaultCSS() %} {% cat "default.css" %} {% endfunc %} + +Next three are from https://remixicon.com/ +{% func IconHTTP() %} +{% cat "icon/http-protocol-icon.svg" %} +{% endfunc %} + +{% func IconGemini() %} +{% cat "icon/gemini-protocol-icon.svg" %} +{% endfunc %} + +{% func IconMailto() %} +{% cat "icon/mailto-protocol-icon.svg" %} +{% endfunc %} + +This is a modified version of https://www.svgrepo.com/svg/232085/rat +{% func IconGopher() %} +{% cat "icon/gopher-protocol-icon.svg" %} +{% endfunc %} diff --git a/templates/asset.qtpl.go b/templates/asset.qtpl.go index a90d0ab..dc09918 100644 --- a/templates/asset.qtpl.go +++ b/templates/asset.qtpl.go @@ -45,7 +45,8 @@ main h1:not(.navi-title) {font-size:1.7rem;} blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;} .wikilink_new {color:#a55858;} .wikilink_new:visited {color:#a55858;} -.wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;} +.wikilink__destination-type {display: inline; margin-left: .5rem; vertical-align: sub; } + article code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; } article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wrap; border-radius: .25rem;} .codeblock code {padding:0; font-size:15px;} @@ -55,6 +56,9 @@ article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wr .transclusion__link {display: block; text-align: right; font-style: italic; margin-top: .5rem; margin-right: .25rem; color: black; text-decoration: none;} .transclusion__link::before {content: "⇐ ";} +.launchpad { position: relative; list-style: none; } +.launchpad li::before { content: '⇒'; position: absolute; left: 0; } + .binary-container_with-img img, .binary-container_with-video video, .binary-container_with-audio audio {width: 100%} @@ -121,3 +125,177 @@ func DefaultCSS() string { return qs422016 //line templates/asset.qtpl:3 } + +// Next three are from https://remixicon.com/ + +//line templates/asset.qtpl:6 +func StreamIconHTTP(qw422016 *qt422016.Writer) { +//line templates/asset.qtpl:6 + qw422016.N().S(` +`) +//line templates/asset.qtpl:7 + qw422016.N().S(` +`) +//line templates/asset.qtpl:7 + qw422016.N().S(` +`) +//line templates/asset.qtpl:8 +} + +//line templates/asset.qtpl:8 +func WriteIconHTTP(qq422016 qtio422016.Writer) { +//line templates/asset.qtpl:8 + qw422016 := qt422016.AcquireWriter(qq422016) +//line templates/asset.qtpl:8 + StreamIconHTTP(qw422016) +//line templates/asset.qtpl:8 + qt422016.ReleaseWriter(qw422016) +//line templates/asset.qtpl:8 +} + +//line templates/asset.qtpl:8 +func IconHTTP() string { +//line templates/asset.qtpl:8 + qb422016 := qt422016.AcquireByteBuffer() +//line templates/asset.qtpl:8 + WriteIconHTTP(qb422016) +//line templates/asset.qtpl:8 + qs422016 := string(qb422016.B) +//line templates/asset.qtpl:8 + qt422016.ReleaseByteBuffer(qb422016) +//line templates/asset.qtpl:8 + return qs422016 +//line templates/asset.qtpl:8 +} + +//line templates/asset.qtpl:10 +func StreamIconGemini(qw422016 *qt422016.Writer) { +//line templates/asset.qtpl:10 + qw422016.N().S(` +`) +//line templates/asset.qtpl:11 + qw422016.N().S(` +`) +//line templates/asset.qtpl:11 + qw422016.N().S(` +`) +//line templates/asset.qtpl:12 +} + +//line templates/asset.qtpl:12 +func WriteIconGemini(qq422016 qtio422016.Writer) { +//line templates/asset.qtpl:12 + qw422016 := qt422016.AcquireWriter(qq422016) +//line templates/asset.qtpl:12 + StreamIconGemini(qw422016) +//line templates/asset.qtpl:12 + qt422016.ReleaseWriter(qw422016) +//line templates/asset.qtpl:12 +} + +//line templates/asset.qtpl:12 +func IconGemini() string { +//line templates/asset.qtpl:12 + qb422016 := qt422016.AcquireByteBuffer() +//line templates/asset.qtpl:12 + WriteIconGemini(qb422016) +//line templates/asset.qtpl:12 + qs422016 := string(qb422016.B) +//line templates/asset.qtpl:12 + qt422016.ReleaseByteBuffer(qb422016) +//line templates/asset.qtpl:12 + return qs422016 +//line templates/asset.qtpl:12 +} + +//line templates/asset.qtpl:14 +func StreamIconMailto(qw422016 *qt422016.Writer) { +//line templates/asset.qtpl:14 + qw422016.N().S(` +`) +//line templates/asset.qtpl:15 + qw422016.N().S(` +`) +//line templates/asset.qtpl:15 + qw422016.N().S(` +`) +//line templates/asset.qtpl:16 +} + +//line templates/asset.qtpl:16 +func WriteIconMailto(qq422016 qtio422016.Writer) { +//line templates/asset.qtpl:16 + qw422016 := qt422016.AcquireWriter(qq422016) +//line templates/asset.qtpl:16 + StreamIconMailto(qw422016) +//line templates/asset.qtpl:16 + qt422016.ReleaseWriter(qw422016) +//line templates/asset.qtpl:16 +} + +//line templates/asset.qtpl:16 +func IconMailto() string { +//line templates/asset.qtpl:16 + qb422016 := qt422016.AcquireByteBuffer() +//line templates/asset.qtpl:16 + WriteIconMailto(qb422016) +//line templates/asset.qtpl:16 + qs422016 := string(qb422016.B) +//line templates/asset.qtpl:16 + qt422016.ReleaseByteBuffer(qb422016) +//line templates/asset.qtpl:16 + return qs422016 +//line templates/asset.qtpl:16 +} + +// This is a modified version of https://www.svgrepo.com/svg/232085/rat + +//line templates/asset.qtpl:19 +func StreamIconGopher(qw422016 *qt422016.Writer) { +//line templates/asset.qtpl:19 + qw422016.N().S(` +`) +//line templates/asset.qtpl:20 + qw422016.N().S(` + + + +`) +//line templates/asset.qtpl:20 + qw422016.N().S(` +`) +//line templates/asset.qtpl:21 +} + +//line templates/asset.qtpl:21 +func WriteIconGopher(qq422016 qtio422016.Writer) { +//line templates/asset.qtpl:21 + qw422016 := qt422016.AcquireWriter(qq422016) +//line templates/asset.qtpl:21 + StreamIconGopher(qw422016) +//line templates/asset.qtpl:21 + qt422016.ReleaseWriter(qw422016) +//line templates/asset.qtpl:21 +} + +//line templates/asset.qtpl:21 +func IconGopher() string { +//line templates/asset.qtpl:21 + qb422016 := qt422016.AcquireByteBuffer() +//line templates/asset.qtpl:21 + WriteIconGopher(qb422016) +//line templates/asset.qtpl:21 + qs422016 := string(qb422016.B) +//line templates/asset.qtpl:21 + qt422016.ReleaseByteBuffer(qb422016) +//line templates/asset.qtpl:21 + return qs422016 +//line templates/asset.qtpl:21 +} diff --git a/templates/default.css b/templates/default.css index a7c0f07..bc1c814 100644 --- a/templates/default.css +++ b/templates/default.css @@ -20,7 +20,8 @@ main h1:not(.navi-title) {font-size:1.7rem;} blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;} .wikilink_new {color:#a55858;} .wikilink_new:visited {color:#a55858;} -.wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;} +.wikilink__destination-type {display: inline; margin-left: .5rem; vertical-align: sub; } + article code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; } article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wrap; border-radius: .25rem;} .codeblock code {padding:0; font-size:15px;} @@ -30,6 +31,9 @@ article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wr .transclusion__link {display: block; text-align: right; font-style: italic; margin-top: .5rem; margin-right: .25rem; color: black; text-decoration: none;} .transclusion__link::before {content: "⇐ ";} +.launchpad { position: relative; list-style: none; } +.launchpad li::before { content: '⇒'; position: absolute; left: 0; } + .binary-container_with-img img, .binary-container_with-video video, .binary-container_with-audio audio {width: 100%} diff --git a/templates/icon/gemini-protocol-icon.svg b/templates/icon/gemini-protocol-icon.svg new file mode 100644 index 0000000..70beccb --- /dev/null +++ b/templates/icon/gemini-protocol-icon.svg @@ -0,0 +1 @@ + diff --git a/templates/icon/gopher-protocol-icon.svg b/templates/icon/gopher-protocol-icon.svg new file mode 100644 index 0000000..bce1a36 --- /dev/null +++ b/templates/icon/gopher-protocol-icon.svg @@ -0,0 +1,11 @@ + + + + diff --git a/templates/icon/http-protocol-icon.svg b/templates/icon/http-protocol-icon.svg new file mode 100644 index 0000000..d4af44a --- /dev/null +++ b/templates/icon/http-protocol-icon.svg @@ -0,0 +1 @@ + diff --git a/templates/icon/mailto-protocol-icon.svg b/templates/icon/mailto-protocol-icon.svg new file mode 100644 index 0000000..424021c --- /dev/null +++ b/templates/icon/mailto-protocol-icon.svg @@ -0,0 +1 @@ + From df26dfe511bc240fd337edeaa1ab9cc068f3d3a0 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Wed, 16 Dec 2020 00:05:51 +0500 Subject: [PATCH 10/50] Fix icons being escaped in paragraphs --- markup/lexer.go | 4 ++-- markup/link.go | 10 +++++----- markup/paragraph.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/markup/lexer.go b/markup/lexer.go index 9ff58f5..6ba3b94 100644 --- a/markup/lexer.go +++ b/markup/lexer.go @@ -143,8 +143,8 @@ numberState: launchpadState: switch { case startsWith("=>"): - href, text, class := Rocketlink(line, state.name) - state.buf += fmt.Sprintf(`
    • %s
    • `, class, href, text) + href, text, class, icon := Rocketlink(line, state.name) + state.buf += fmt.Sprintf(`
    • %s%s
    • `, class, href, text, icon) case startsWith("```"): state.where = "pre" addLine(state.buf + "
    ") diff --git a/markup/link.go b/markup/link.go index ef91e68..8aac40c 100644 --- a/markup/link.go +++ b/markup/link.go @@ -10,7 +10,7 @@ import ( // // => addr display // [[addr|display]] -func LinkParts(addr, display, hyphaName string) (href, text, class string) { +func LinkParts(addr, display, hyphaName string) (href, text, class, icon string) { if display == "" { text = addr } else { @@ -26,9 +26,9 @@ func LinkParts(addr, display, hyphaName string) (href, text, class string) { if strings.HasPrefix(text, "//") && len(text) > 2 { text = text[2:] } - return addr, text + fmt.Sprintf(``, destination), "wikilink_external" + return addr, text, "wikilink_external", fmt.Sprintf(``, destination) case strings.HasPrefix(addr, "/"): - return addr, text, class + return addr, text, class, "" case strings.HasPrefix(addr, "./"): hyphaName = canonicalName(path.Join(hyphaName, addr[2:])) case strings.HasPrefix(addr, "../"): @@ -39,12 +39,12 @@ func LinkParts(addr, display, hyphaName string) (href, text, class string) { if !HyphaExists(hyphaName) { class += " wikilink_new" } - return "/page/" + hyphaName, text, class + return "/page/" + hyphaName, text, class, "" } // Parse markup line starting with "=>" according to wikilink rules. // See http://localhost:1737/page/wikilink -func Rocketlink(src, hyphaName string) (href, text, class string) { +func Rocketlink(src, hyphaName string) (href, text, class, icon string) { src = strings.TrimSpace(src[2:]) // Drop => if src == "" { return diff --git a/markup/paragraph.go b/markup/paragraph.go index 8f90085..88d5e43 100644 --- a/markup/paragraph.go +++ b/markup/paragraph.go @@ -56,8 +56,8 @@ func getLinkNode(input *bytes.Buffer, hyphaName string) string { currBuf.WriteByte(b) } } - href, text, class := LinkParts(addrBuf.String(), displayBuf.String(), hyphaName) - return fmt.Sprintf(`%s`, href, class, html.EscapeString(text)) + href, text, class, icon := LinkParts(addrBuf.String(), displayBuf.String(), hyphaName) + return fmt.Sprintf(`%s%s`, href, class, html.EscapeString(text), icon) } // getTextNode splits the `input` into two parts `textNode` and `rest` by the first encountered rune that resembles a span tag. If there is none, `textNode = input`, `rest = ""`. It handles escaping with backslash. From 7b9375da3961924261a653193daa71660569d7de Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Thu, 17 Dec 2020 17:59:59 +0500 Subject: [PATCH 11/50] Implement OpenGraph meta tags --- flag.go | 2 +- http_readers.go | 19 +++-- hypha.go | 6 ++ markup/img.go | 10 +++ markup/lexer.go | 15 ++-- markup/mycomarkup.go | 61 ++++++++++++++- markup/parser.go | 19 +---- markup/xclusion.go | 9 ++- templates/http_stuff.qtpl | 3 +- templates/http_stuff.qtpl.go | 141 +++++++++++++++++++---------------- 10 files changed, 183 insertions(+), 102 deletions(-) diff --git a/flag.go b/flag.go index 2bd83ff..551e0cf 100644 --- a/flag.go +++ b/flag.go @@ -10,7 +10,7 @@ import ( ) func init() { - flag.StringVar(&util.URL, "url", "http://0.0.0.0:$port", "URL at which your wiki can be found. Used to generate feeds") + flag.StringVar(&util.URL, "url", "http://0.0.0.0:$port", "URL at which your wiki can be found. Used to generate feeds and social media previews") flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at using HTTP") flag.StringVar(&util.HomePage, "home", "home", "The home page name") flag.StringVar(&util.SiteTitle, "title", "🍄", "How to call your wiki in the navititle") diff --git a/http_readers.go b/http_readers.go index 6f7c58e..497a30f 100644 --- a/http_readers.go +++ b/http_readers.go @@ -36,7 +36,7 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { textContents, err = history.FileAtRevision(textPath, revHash) ) if err == nil { - contents = markup.ToHtml(hyphaName, textContents) + contents = markup.Doc(hyphaName, textContents).AsHTML() } treeHTML, _, _ := tree.Tree(hyphaName, IterateHyphaNamesWith) page := templates.RevisionHTML( @@ -81,20 +81,27 @@ func handlerPage(w http.ResponseWriter, rq *http.Request) { hyphaName = HyphaNameFromRq(rq, "page") data, hyphaExists = HyphaStorage[hyphaName] contents string + openGraph string ) if hyphaExists { fileContentsT, errT := ioutil.ReadFile(data.textPath) _, errB := os.Stat(data.binaryPath) if errT == nil { - contents = markup.ToHtml(hyphaName, string(fileContentsT)) + md := markup.Doc(hyphaName, string(fileContentsT)) + contents = md.AsHTML() + openGraph = md.OpenGraphHTML() } if !os.IsNotExist(errB) { contents = binaryHtmlBlock(hyphaName, data) + contents } } treeHTML, prevHypha, nextHypha := tree.Tree(hyphaName, IterateHyphaNamesWith) - util.HTTP200Page(w, base(hyphaName, templates.PageHTML(rq, hyphaName, - naviTitle(hyphaName), - contents, - treeHTML, prevHypha, nextHypha))) + util.HTTP200Page(w, + templates.BaseHTML( + hyphaName, + templates.PageHTML(rq, hyphaName, + naviTitle(hyphaName), + contents, + treeHTML, prevHypha, nextHypha), + openGraph)) } diff --git a/hypha.go b/hypha.go index e412c14..5280639 100644 --- a/hypha.go +++ b/hypha.go @@ -33,6 +33,12 @@ func init() { return } markup.HyphaIterate = IterateHyphaNamesWith + markup.HyphaImageForOG = func(hyphaName string) string { + if hd, isOld := GetHyphaData(hyphaName); isOld && hd.binaryPath != "" { + return util.URL + "/binary/" + hyphaName + } + return util.URL + "/favicon.ico" + } } // GetHyphaData finds a hypha addressed by `hyphaName` and returns its `hyphaData`. `hyphaData` is set to a zero value if this hypha does not exist. `isOld` is false if this hypha does not exist. diff --git a/markup/img.go b/markup/img.go index 9539498..99c919b 100644 --- a/markup/img.go +++ b/markup/img.go @@ -4,6 +4,8 @@ import ( "fmt" "regexp" "strings" + + "github.com/bouncepaw/mycorrhiza/util" ) var imgRe = regexp.MustCompile(`^img\s+{`) @@ -184,6 +186,14 @@ func (img *Img) binaryPathFor(path string) string { } } +func (img *Img) ogBinaryPathFor(path string) string { + path = img.binaryPathFor(path) + if strings.HasPrefix(path, "/binary/") { + return util.URL + path + } + return path +} + func (img *Img) pagePathFor(path string) string { path = strings.TrimSpace(path) if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 { diff --git a/markup/lexer.go b/markup/lexer.go index 6ba3b94..b1c3abc 100644 --- a/markup/lexer.go +++ b/markup/lexer.go @@ -9,6 +9,9 @@ import ( // HyphaExists holds function that checks that a hypha is present. var HyphaExists func(string) bool +// +var HyphaImageForOG func(string) string + // HyphaAccess holds function that accesses a hypha by its name. var HyphaAccess func(string) (rawText, binaryHtml string, err error) @@ -29,21 +32,21 @@ type GemLexerState struct { type Line struct { id int - // interface{} may be bad. What I need is a sum of string and Transclusion + // interface{} may be bad. TODO: a proper type contents interface{} } -func lex(name, content string) (ast []Line) { - var state = GemLexerState{name: name} +func (md *MycoDoc) lex() (ast []Line) { + var state = GemLexerState{name: md.hyphaName} - for _, line := range append(strings.Split(content, "\n"), "") { - geminiLineToAST(line, &state, &ast) + for _, line := range append(strings.Split(md.contents, "\n"), "") { + lineToAST(line, &state, &ast) } return ast } // Lex `line` in markup and save it to `ast` using `state`. -func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) { +func lineToAST(line string, state *GemLexerState, ast *[]Line) { addLine := func(text interface{}) { *ast = append(*ast, Line{id: state.id, contents: text}) } diff --git a/markup/mycomarkup.go b/markup/mycomarkup.go index f3fb7a2..656ca28 100644 --- a/markup/mycomarkup.go +++ b/markup/mycomarkup.go @@ -2,8 +2,12 @@ package markup import ( + "fmt" "html" + "regexp" "strings" + + "github.com/bouncepaw/mycorrhiza/util" ) // A Mycomarkup-formatted document @@ -11,24 +15,75 @@ type MycoDoc struct { // data hyphaName string contents string + // indicators + parsedAlready bool + // results + ast []Line + html string + firstImageURL string + description string } // Constructor func Doc(hyphaName, contents string) *MycoDoc { - return &MycoDoc{ + md := &MycoDoc{ hyphaName: hyphaName, contents: contents, } + return md +} + +func (md *MycoDoc) Lex(recursionLevel int) *MycoDoc { + if !md.parsedAlready { + md.ast = md.lex() + } + md.parsedAlready = true + return md } // AsHtml returns an html representation of the document func (md *MycoDoc) AsHTML() string { - return "" + md.html = Parse(md.Lex(0).ast, 0, 0, 0) + return md.html } +// Used to clear opengraph description from html tags. This method is usually bad because of dangers of malformed HTML, but I'm going to use it only for Mycorrhiza-generated HTML, so it's okay. The question mark is required; without it the whole string is eaten away. +var htmlTagRe = regexp.MustCompile(`<.*?>`) + // OpenGraphHTML returns an html representation of og: meta tags. func (md *MycoDoc) OpenGraphHTML() string { - return "" + md.ogFillVars() + return strings.Join([]string{ + ogTag("title", md.hyphaName), + ogTag("type", "article"), + ogTag("image", md.firstImageURL), + ogTag("url", util.URL+"/page/"+md.hyphaName), + ogTag("determiner", ""), + ogTag("description", htmlTagRe.ReplaceAllString(md.description, "")), + }, "\n") +} + +func (md *MycoDoc) ogFillVars() *MycoDoc { + foundDesc := false + md.firstImageURL = HyphaImageForOG(md.hyphaName) + for _, line := range md.ast { + switch v := line.contents.(type) { + case string: + if !foundDesc { + md.description = v + foundDesc = true + } + case Img: + if len(v.entries) > 0 { + md.firstImageURL = v.entries[0].path.String() + } + } + } + return md +} + +func ogTag(property, content string) string { + return fmt.Sprintf(``, property, content) } /* The rest of this file is currently unused. TODO: use it I guess */ diff --git a/markup/parser.go b/markup/parser.go index 23887d1..50ff158 100644 --- a/markup/parser.go +++ b/markup/parser.go @@ -1,35 +1,24 @@ package markup -import () - const maxRecursionLevel = 3 -type GemParserState struct { - recursionLevel int -} - -func Parse(ast []Line, from, to int, state GemParserState) (html string) { - if state.recursionLevel > maxRecursionLevel { +func Parse(ast []Line, from, to int, recursionLevel int) (html string) { + if recursionLevel > maxRecursionLevel { return "Transclusion depth limit" } for _, line := range ast { if line.id >= from && (line.id <= to || to == 0) || line.id == -1 { switch v := line.contents.(type) { case Transclusion: - html += Transclude(v, state) + html += Transclude(v, recursionLevel) case Img: html += v.ToHtml() case string: html += v default: - html += "Unknown" + html += "Unknown element." } } } return html } - -func ToHtml(name, text string) string { - state := GemParserState{} - return Parse(lex(name, text), 0, 0, state) -} diff --git a/markup/xclusion.go b/markup/xclusion.go index b4a4370..a5df6c8 100644 --- a/markup/xclusion.go +++ b/markup/xclusion.go @@ -17,14 +17,14 @@ type Transclusion struct { } // Transclude transcludes `xcl` and returns html representation. -func Transclude(xcl Transclusion, state GemParserState) (html string) { - state.recursionLevel++ +func Transclude(xcl Transclusion, recursionLevel int) (html string) { + recursionLevel++ tmptOk := `
    %s
    %s
    ` tmptFailed := `
    -

    Failed to transclude %s

    +

    Hypha %s does not exist

    ` if xcl.from == xclError || xcl.to == xclError || xcl.from > xcl.to { return fmt.Sprintf(tmptFailed, xcl.name, xcl.name) @@ -34,7 +34,8 @@ func Transclude(xcl Transclusion, state GemParserState) (html string) { if err != nil { return fmt.Sprintf(tmptFailed, xcl.name, xcl.name) } - xclText := Parse(lex(xcl.name, rawText), xcl.from, xcl.to, state) + md := Doc(xcl.name, rawText) + xclText := Parse(md.lex(), xcl.from, xcl.to, recursionLevel) return fmt.Sprintf(tmptOk, xcl.name, xcl.name, binaryHtml+xclText) } diff --git a/templates/http_stuff.qtpl b/templates/http_stuff.qtpl index 9bacf51..3a88e2f 100644 --- a/templates/http_stuff.qtpl +++ b/templates/http_stuff.qtpl @@ -1,10 +1,11 @@ -{% func BaseHTML(title, body string) %} +{% func BaseHTML(title, body string, headElements ...string) %} {%s title %} + {% for _, el := range headElements %}{%s= el %}{% endfor %} {%s= body %} diff --git a/templates/http_stuff.qtpl.go b/templates/http_stuff.qtpl.go index 8fa0096..1e2bd99 100644 --- a/templates/http_stuff.qtpl.go +++ b/templates/http_stuff.qtpl.go @@ -18,7 +18,7 @@ var ( ) //line templates/http_stuff.qtpl:1 -func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string) { +func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, headElements ...string) { //line templates/http_stuff.qtpl:1 qw422016.N().S(` @@ -31,55 +31,64 @@ func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string) { qw422016.E().S(title) //line templates/http_stuff.qtpl:7 qw422016.N().S(` + `) +//line templates/http_stuff.qtpl:8 + for _, el := range headElements { +//line templates/http_stuff.qtpl:8 + qw422016.N().S(el) +//line templates/http_stuff.qtpl:8 + } +//line templates/http_stuff.qtpl:8 + qw422016.N().S(` `) -//line templates/http_stuff.qtpl:10 +//line templates/http_stuff.qtpl:11 qw422016.N().S(body) -//line templates/http_stuff.qtpl:10 +//line templates/http_stuff.qtpl:11 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:13 +//line templates/http_stuff.qtpl:14 } -//line templates/http_stuff.qtpl:13 -func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string) { -//line templates/http_stuff.qtpl:13 +//line templates/http_stuff.qtpl:14 +func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string, headElements ...string) { +//line templates/http_stuff.qtpl:14 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_stuff.qtpl:13 - StreamBaseHTML(qw422016, title, body) -//line templates/http_stuff.qtpl:13 +//line templates/http_stuff.qtpl:14 + StreamBaseHTML(qw422016, title, body, headElements...) +//line templates/http_stuff.qtpl:14 qt422016.ReleaseWriter(qw422016) -//line templates/http_stuff.qtpl:13 +//line templates/http_stuff.qtpl:14 } -//line templates/http_stuff.qtpl:13 -func BaseHTML(title, body string) string { -//line templates/http_stuff.qtpl:13 +//line templates/http_stuff.qtpl:14 +func BaseHTML(title, body string, headElements ...string) string { +//line templates/http_stuff.qtpl:14 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_stuff.qtpl:13 - WriteBaseHTML(qb422016, title, body) -//line templates/http_stuff.qtpl:13 +//line templates/http_stuff.qtpl:14 + WriteBaseHTML(qb422016, title, body, headElements...) +//line templates/http_stuff.qtpl:14 qs422016 := string(qb422016.B) -//line templates/http_stuff.qtpl:13 +//line templates/http_stuff.qtpl:14 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_stuff.qtpl:13 +//line templates/http_stuff.qtpl:14 return qs422016 -//line templates/http_stuff.qtpl:13 +//line templates/http_stuff.qtpl:14 } -//line templates/http_stuff.qtpl:15 +//line templates/http_stuff.qtpl:16 func StreamHyphaListHTML(qw422016 *qt422016.Writer, tbody string, pageCount int) { -//line templates/http_stuff.qtpl:15 +//line templates/http_stuff.qtpl:16 qw422016.N().S(`

    List of hyphae

    This wiki has `) -//line templates/http_stuff.qtpl:18 +//line templates/http_stuff.qtpl:19 qw422016.N().D(pageCount) -//line templates/http_stuff.qtpl:18 +//line templates/http_stuff.qtpl:19 qw422016.N().S(` hyphae.

    @@ -90,105 +99,105 @@ func StreamHyphaListHTML(qw422016 *qt422016.Writer, tbody string, pageCount int) `) -//line templates/http_stuff.qtpl:27 +//line templates/http_stuff.qtpl:28 qw422016.N().S(tbody) -//line templates/http_stuff.qtpl:27 +//line templates/http_stuff.qtpl:28 qw422016.N().S(`
    `) -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 } -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 func WriteHyphaListHTML(qq422016 qtio422016.Writer, tbody string, pageCount int) { -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 StreamHyphaListHTML(qw422016, tbody, pageCount) -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 qt422016.ReleaseWriter(qw422016) -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 } -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 func HyphaListHTML(tbody string, pageCount int) string { -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 WriteHyphaListHTML(qb422016, tbody, pageCount) -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 qs422016 := string(qb422016.B) -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 return qs422016 -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 } -//line templates/http_stuff.qtpl:33 +//line templates/http_stuff.qtpl:34 func StreamHyphaListRowHTML(qw422016 *qt422016.Writer, hyphaName, binaryMime string, binaryPresent bool) { -//line templates/http_stuff.qtpl:33 +//line templates/http_stuff.qtpl:34 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:35 +//line templates/http_stuff.qtpl:36 qw422016.E().S(hyphaName) -//line templates/http_stuff.qtpl:35 +//line templates/http_stuff.qtpl:36 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:36 +//line templates/http_stuff.qtpl:37 if binaryPresent { -//line templates/http_stuff.qtpl:36 +//line templates/http_stuff.qtpl:37 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:37 +//line templates/http_stuff.qtpl:38 qw422016.E().S(binaryMime) -//line templates/http_stuff.qtpl:37 +//line templates/http_stuff.qtpl:38 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:38 +//line templates/http_stuff.qtpl:39 } else { -//line templates/http_stuff.qtpl:38 +//line templates/http_stuff.qtpl:39 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:40 +//line templates/http_stuff.qtpl:41 } -//line templates/http_stuff.qtpl:40 +//line templates/http_stuff.qtpl:41 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 } -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 func WriteHyphaListRowHTML(qq422016 qtio422016.Writer, hyphaName, binaryMime string, binaryPresent bool) { -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 StreamHyphaListRowHTML(qw422016, hyphaName, binaryMime, binaryPresent) -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 qt422016.ReleaseWriter(qw422016) -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 } -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 func HyphaListRowHTML(hyphaName, binaryMime string, binaryPresent bool) string { -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 WriteHyphaListRowHTML(qb422016, hyphaName, binaryMime, binaryPresent) -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 qs422016 := string(qb422016.B) -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 return qs422016 -//line templates/http_stuff.qtpl:42 +//line templates/http_stuff.qtpl:43 } From e8f9bf608ff85324d2e113b47b48f19ee146c4a1 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Thu, 17 Dec 2020 18:33:07 +0500 Subject: [PATCH 12/50] Draw rocketlink arrows with svg --- templates/asset.qtpl.go | 4 ++-- templates/default.css | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/asset.qtpl.go b/templates/asset.qtpl.go index dc09918..0be6bb6 100644 --- a/templates/asset.qtpl.go +++ b/templates/asset.qtpl.go @@ -56,8 +56,8 @@ article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wr .transclusion__link {display: block; text-align: right; font-style: italic; margin-top: .5rem; margin-right: .25rem; color: black; text-decoration: none;} .transclusion__link::before {content: "⇐ ";} -.launchpad { position: relative; list-style: none; } -.launchpad li::before { content: '⇒'; position: absolute; left: 0; } +/* Derived from https://commons.wikimedia.org/wiki/File:U%2B21D2.svg */ +.launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); } .binary-container_with-img img, .binary-container_with-video video, diff --git a/templates/default.css b/templates/default.css index bc1c814..edddd1a 100644 --- a/templates/default.css +++ b/templates/default.css @@ -31,8 +31,8 @@ article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wr .transclusion__link {display: block; text-align: right; font-style: italic; margin-top: .5rem; margin-right: .25rem; color: black; text-decoration: none;} .transclusion__link::before {content: "⇐ ";} -.launchpad { position: relative; list-style: none; } -.launchpad li::before { content: '⇒'; position: absolute; left: 0; } +/* Derived from https://commons.wikimedia.org/wiki/File:U%2B21D2.svg */ +.launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); } .binary-container_with-img img, .binary-container_with-video video, From 598a87f878f462ea7514828566bf58c6b6579d2e Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Thu, 17 Dec 2020 19:58:11 +0500 Subject: [PATCH 13/50] Fix incorrect display text in links --- markup/link.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/markup/link.go b/markup/link.go index 8aac40c..1c358be 100644 --- a/markup/link.go +++ b/markup/link.go @@ -22,9 +22,11 @@ func LinkParts(addr, display, hyphaName string) (href, text, class, icon string) case strings.ContainsRune(addr, ':'): pos := strings.IndexRune(addr, ':') destination := addr[:pos] - text = addr[pos+1:] - if strings.HasPrefix(text, "//") && len(text) > 2 { - text = text[2:] + if display == "" { + text = addr[pos+1:] + if strings.HasPrefix(text, "//") && len(text) > 2 { + text = text[2:] + } } return addr, text, "wikilink_external", fmt.Sprintf(``, destination) case strings.HasPrefix(addr, "/"): From 30c95dd0378e7fdf427428a2a49e7e4d6e92dcb8 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Tue, 29 Dec 2020 13:10:11 +0500 Subject: [PATCH 14/50] Add robots.txt --- main.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/main.go b/main.go index d368bb0..0caa1d5 100644 --- a/main.go +++ b/main.go @@ -128,6 +128,17 @@ func handlerIcon(w http.ResponseWriter, rq *http.Request) { } } +func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + w.Write([]byte( + `User-agent: * +Allow: /page/ +Allow: /recent-changes +Disallow: / +Crawl-delay: 5`)) +} + func main() { log.Println("Running MycorrhizaWiki β") parseCliArgs() @@ -157,5 +168,6 @@ func main() { http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) { http.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther) }) + http.HandleFunc("/robots.txt", handlerRobotsTxt) log.Fatal(http.ListenAndServe("0.0.0.0:"+util.ServerPort, nil)) } From 4d2be01b8594489a353486a0087affc4b0c169d4 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Fri, 1 Jan 2021 09:07:56 +0500 Subject: [PATCH 15/50] Add tables to mycomarkup --- markup/lexer.go | 15 ++- markup/parser.go | 2 + markup/table.go | 215 ++++++++++++++++++++++++++++++++++++++++ metarrhiza | 2 +- templates/asset.qtpl.go | 4 + templates/default.css | 4 + 6 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 markup/table.go diff --git a/markup/lexer.go b/markup/lexer.go index b1c3abc..b69a3cf 100644 --- a/markup/lexer.go +++ b/markup/lexer.go @@ -27,7 +27,8 @@ type GemLexerState struct { id int buf string // Temporaries - img *Img + img *Img + table *Table } type Line struct { @@ -80,6 +81,8 @@ func lineToAST(line string, state *GemLexerState, ast *[]Line) { switch state.where { case "img": goto imgState + case "table": + goto tableState case "pre": goto preformattedState case "list": @@ -99,6 +102,13 @@ imgState: } return +tableState: + if shouldGoBackToNormal := state.table.Process(line); shouldGoBackToNormal { + state.where = "" + addLine(*state.table) + } + return + preformattedState: switch { case startsWith("```"): @@ -209,6 +219,9 @@ normalState: state.where = "img" state.img = img } + case MatchesTable(line): + state.where = "table" + state.table = TableFromFirstLine(line, state.name) default: addLine(fmt.Sprintf("

    %s

    ", state.id, ParagraphToHtml(state.name, line))) } diff --git a/markup/parser.go b/markup/parser.go index 50ff158..665f8ba 100644 --- a/markup/parser.go +++ b/markup/parser.go @@ -13,6 +13,8 @@ func Parse(ast []Line, from, to int, recursionLevel int) (html string) { html += Transclude(v, recursionLevel) case Img: html += v.ToHtml() + case Table: + html += v.asHtml() case string: html += v default: diff --git a/markup/table.go b/markup/table.go new file mode 100644 index 0000000..f6c6886 --- /dev/null +++ b/markup/table.go @@ -0,0 +1,215 @@ +package markup + +import ( + "fmt" + "regexp" + "strings" + "unicode" + // "github.com/bouncepaw/mycorrhiza/util" +) + +var tableRe = regexp.MustCompile(`^table\s+{`) + +func MatchesTable(line string) bool { + return tableRe.MatchString(line) +} + +func TableFromFirstLine(line, hyphaName string) *Table { + return &Table{ + hyphaName: hyphaName, + caption: line[strings.IndexRune(line, '{')+1:], + rows: make([]*tableRow, 0), + } +} + +func (t *Table) Process(line string) (shouldGoBackToNormal bool) { + if strings.TrimSpace(line) == "}" && !t.inMultiline { + return true + } + if !t.inMultiline { + t.pushRow() + } + var ( + escaping bool + lookingForNonSpace = !t.inMultiline + countingColspan bool + ) + for i, r := range line { + switch { + case lookingForNonSpace && unicode.IsSpace(r): + case lookingForNonSpace && (r == '!' || r == '|'): + t.currCellMarker = r + t.currColspan = 1 + lookingForNonSpace = false + countingColspan = true + case lookingForNonSpace: + t.currCellMarker = '^' // ^ represents implicit |, not part of syntax + t.currColspan = 1 + lookingForNonSpace = false + t.currCellBuilder.WriteRune(r) + + case escaping: + t.currCellBuilder.WriteRune(r) + + case t.inMultiline && r == '}': + t.inMultiline = false + case t.inMultiline && i == len(line)-1: + t.currCellBuilder.WriteRune('\n') + case t.inMultiline: + t.currCellBuilder.WriteRune(r) + + // Not in multiline: + case (r == '|' || r == '!') && !countingColspan: + t.pushCell() + t.currCellMarker = r + t.currColspan = 1 + countingColspan = true + case r == t.currCellMarker && (r == '|' || r == '!') && countingColspan: + t.currColspan++ + case r == '{': + t.inMultiline = true + countingColspan = false + case i == len(line)-1: + t.pushCell() + default: + t.currCellBuilder.WriteRune(r) + countingColspan = false + } + } + return false +} + +type Table struct { + // data + hyphaName string + caption string + rows []*tableRow + // state + inMultiline bool + // tmp + currCellMarker rune + currColspan uint + currCellBuilder strings.Builder +} + +func (t *Table) pushRow() { + t.rows = append(t.rows, &tableRow{ + cells: make([]*tableCell, 0), + }) +} + +func (t *Table) pushCell() { + tc := &tableCell{ + content: t.currCellBuilder.String(), + colspan: t.currColspan, + } + switch t.currCellMarker { + case '|', '^': + tc.kind = tableCellDatum + case '!': + tc.kind = tableCellHeader + } + // We expect the table to have at least one row ready, so no nil-checking + tr := t.rows[len(t.rows)-1] + tr.cells = append(tr.cells, tc) + t.currCellBuilder = strings.Builder{} +} + +func (t *Table) asHtml() (html string) { + if t.caption != "" { + html += fmt.Sprintf("%s", t.caption) + } + if len(t.rows) > 0 && t.rows[0].looksLikeThead() { + html += fmt.Sprintf("%s", t.rows[0].asHtml(t.hyphaName)) + t.rows = t.rows[1:] + } + html += "\n\n" + for _, tr := range t.rows { + html += tr.asHtml(t.hyphaName) + } + return fmt.Sprintf(`%s
    `, html) +} + +type tableRow struct { + cells []*tableCell +} + +func (tr *tableRow) asHtml(hyphaName string) (html string) { + for _, tc := range tr.cells { + html += tc.asHtml(hyphaName) + } + return fmt.Sprintf("%s\n", html) +} + +// Most likely, rows with more than two header cells are theads. I allow one extra datum cell for tables like this: +// | ! a ! b +// ! c | d | e +// ! f | g | h +func (tr *tableRow) looksLikeThead() bool { + var ( + headerAmount = 0 + datumAmount = 0 + ) + for _, tc := range tr.cells { + switch tc.kind { + case tableCellHeader: + headerAmount++ + case tableCellDatum: + datumAmount++ + } + } + return headerAmount >= 2 && datumAmount <= 1 +} + +type tableCell struct { + kind tableCellKind + colspan uint + content string +} + +func (tc *tableCell) asHtml(hyphaName string) string { + return fmt.Sprintf( + "<%[1]s %[2]s>%[3]s\n", + tc.kind.tagName(), + tc.colspanAttribute(), + tc.contentAsHtml(hyphaName), + ) +} + +func (tc *tableCell) colspanAttribute() string { + if tc.colspan <= 1 { + return "" + } + return fmt.Sprintf(`colspan="%d"`, tc.colspan) +} + +func (tc *tableCell) contentAsHtml(hyphaName string) (html string) { + for _, line := range strings.Split(tc.content, "\n") { + if line = strings.TrimSpace(line); line != "" { + if html != "" { + html += `
    ` + } + html += ParagraphToHtml(hyphaName, line) + } + } + return html +} + +type tableCellKind int + +const ( + tableCellUnknown tableCellKind = iota + tableCellHeader + tableCellDatum +) + +func (tck tableCellKind) tagName() string { + switch tck { + case tableCellHeader: + return "th" + case tableCellDatum: + return "td" + default: + return "p" + } +} diff --git a/metarrhiza b/metarrhiza index 7828352..95f48bf 160000 --- a/metarrhiza +++ b/metarrhiza @@ -1 +1 @@ -Subproject commit 7828352598c19afe5f2e13df0219656ac7b44c9c +Subproject commit 95f48bfd7a7cfef17d56cef207a770767d727950 diff --git a/templates/asset.qtpl.go b/templates/asset.qtpl.go index 0be6bb6..b0416a1 100644 --- a/templates/asset.qtpl.go +++ b/templates/asset.qtpl.go @@ -93,6 +93,10 @@ nav ul li {list-style-type:none;margin-right:1rem;} .history-entry__time { font-weight: bold; } .history-entry__author { font-style: italic; } +table { background-color: #eee; border: #ddd 1px solid; border-radius: .25rem; +min-width: 4rem; } +td { padding: .25rem; border: #ddd 1px solid; } +caption { caption-side: top; font-size: small; } `) //line templates/asset.qtpl:2 qw422016.N().S(` diff --git a/templates/default.css b/templates/default.css index edddd1a..1c041f9 100644 --- a/templates/default.css +++ b/templates/default.css @@ -68,3 +68,7 @@ nav ul li {list-style-type:none;margin-right:1rem;} .history-entry__time { font-weight: bold; } .history-entry__author { font-style: italic; } +table { background-color: #eee; border: #ddd 1px solid; border-radius: .25rem; +min-width: 4rem; } +td { padding: .25rem; border: #ddd 1px solid; } +caption { caption-side: top; font-size: small; } From 301592ad7d73d08a1eba206624402e06bee43ef7 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Sun, 3 Jan 2021 01:52:23 +0500 Subject: [PATCH 16/50] Start implementing memdb-driven hyphae --- .gitignore | 1 - go.mod | 1 + go.sum | 8 ++++++ hypha/count.go | 33 ++++++++++++++++++++++ hypha/hypha.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 hypha/count.go create mode 100644 hypha/hypha.go diff --git a/.gitignore b/.gitignore index db5b71e..c450f48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -hypha mycorrhiza diff --git a/go.mod b/go.mod index b5d6fc3..c785b3a 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,6 @@ go 1.14 require ( github.com/adrg/xdg v0.2.2 github.com/gorilla/feeds v1.1.1 + github.com/hashicorp/go-memdb v1.3.0 github.com/valyala/quicktemplate v1.6.3 ) diff --git a/go.sum b/go.sum index 284389c..9b58270 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,14 @@ github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY= github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= +github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU= +github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/hypha/count.go b/hypha/count.go new file mode 100644 index 0000000..3c9aef4 --- /dev/null +++ b/hypha/count.go @@ -0,0 +1,33 @@ +package hypha + +import ( + "sync" +) + +type count struct { + value uint + sync.Mutex +} + +// Count is a global variable. Its value is number of all existing hyphae. Hypha mutators are expected to manipulate the value. It is concurrent-safe. +var Count = count{} + +// Increment the value of Count. +func (c *count) Increment() { + c.Lock() + c.value++ + c.Unlock() +} + +// Decrement the value of Count. +func (c *count) Decrement() { + c.Lock() + c.value-- + c.Unlock() +} + +// Get value of Count. +func (c *count) Value() uint { + // it is concurrent-safe to not lock here, right? + return c.value +} diff --git a/hypha/hypha.go b/hypha/hypha.go new file mode 100644 index 0000000..2ca5ffc --- /dev/null +++ b/hypha/hypha.go @@ -0,0 +1,76 @@ +package hypha + +import ( + "errors" + + "github.com/hashicorp/go-memdb" +) + +type Hypha struct { + Name string + Exists bool + TextPath string + BinaryPath string + OutLinks []string + BackLinks []string +} + +func AddHypha(h Hypha) error { + return errors.New("Not implemented") +} + +// Create the DB schema +var schema = &memdb.DBSchema{ + Tables: map[string]*memdb.TableSchema{ + "hyphae": &memdb.TableSchema{ + Name: "hyphae", + Indexes: map[string]*memdb.IndexSchema{ + "name": &memdb.IndexSchema{ + Name: "name", + Unique: true, + Indexer: &memdb.StringFieldIndex{Field: "Name"}, + }, + "exists": &memdb.IndexSchema{ + Name: "exists", + Unique: false, + AllowEmpty: true, + Indexer: &memdb.BoolFieldIndex{Field: "Exists"}, + }, + "text-path": &memdb.IndexSchema{ + Name: "text-path", + Unique: true, + AllowEmpty: true, + Indexer: &memdb.StringFieldIndex{Field: "TextPath"}, + }, + "binary-path": &memdb.IndexSchema{ + Name: "binary-path", + Unique: true, + AllowEmpty: true, + Indexer: &memdb.StringFieldIndex{Field: "BinaryPath"}, + }, + "out-links": &memdb.IndexSchema{ + Name: "out-links", + Unique: false, + AllowEmpty: true, + Indexer: &memdb.StringSliceFieldIndex{Field: "OutLinks"}, + }, + "back-links": &memdb.IndexSchema{ + Name: "back-links", + Unique: false, + AllowEmpty: true, + Indexer: &memdb.StringSliceFieldIndex{Field: "BackLinks"}, + }, + }, + }, + }, +} + +var db *memdb.MemDB + +func init() { + var err error + db, err = memdb.NewMemDB(schema) + if err != nil { + panic(err) + } +} From 1d5e2e515ca70a749b1985389c9960f43ffb02c9 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Sun, 3 Jan 2021 02:25:04 +0500 Subject: [PATCH 17/50] Fix anon hypha deletion bug and start using hypha.Count() --- history/operations.go | 1 + http_mutators.go | 2 +- hypha.go | 6 +++++- hypha/count.go | 33 ------------------------------ hyphae/count.go | 31 ++++++++++++++++++++++++++++ {hypha => hyphae}/hypha.go | 41 +++++++++++++++++--------------------- main.go | 9 +++++---- 7 files changed, 61 insertions(+), 62 deletions(-) delete mode 100644 hypha/count.go create mode 100644 hyphae/count.go rename {hypha => hyphae}/hypha.go (53%) diff --git a/history/operations.go b/history/operations.go index e16c367..fdc64af 100644 --- a/history/operations.go +++ b/history/operations.go @@ -121,6 +121,7 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp { // WithUser sets a user for the commit. func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp { + u = u.OrAnon() if u.Group != user.UserAnon { hop.name = u.Name hop.email = u.Name + "@mycorrhiza" diff --git a/http_mutators.go b/http_mutators.go index 1283f59..f1cd5da 100644 --- a/http_mutators.go +++ b/http_mutators.go @@ -96,7 +96,7 @@ func handlerDeleteConfirm(w http.ResponseWriter, rq *http.Request) { hyphaData, isOld = HyphaStorage[hyphaName] u = user.FromRequest(rq) ) - if !u.CanProceed("delete-confirm") { + if !user.CanProceed(rq, "delete-confirm") { HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.") log.Println("Rejected", rq.URL) return diff --git a/hypha.go b/hypha.go index 5280639..c9f2612 100644 --- a/hypha.go +++ b/hypha.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/markup" "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" @@ -84,6 +85,7 @@ func uploadHelp(hop *history.HistoryOp, hyphaName, ext string, data []byte, u *u // New hyphae must be added to the hypha storage if !isOld { HyphaStorage[hyphaName] = hyphaData + hyphae.IncrementCount() } *originalFullPath = fullPath return hop.WithFiles(fullPath). @@ -121,6 +123,7 @@ func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.Histor Apply() if len(hop.Errs) == 0 { delete(HyphaStorage, hyphaName) + hyphae.DecrementCount() } return hop } @@ -218,7 +221,7 @@ func binaryHtmlBlock(hyphaName string, hd *HyphaData) string { default: return fmt.Sprintf(`
    -

    This hypha's media cannot be rendered. Access it directly

    +

    This hypha's media cannot be rendered. Download it

    `, hyphaName) } @@ -250,6 +253,7 @@ func Index(path string) { } else { hyphaData = &HyphaData{} HyphaStorage[hyphaName] = hyphaData + hyphae.IncrementCount() } if isText { hyphaData.textPath = hyphaPartPath diff --git a/hypha/count.go b/hypha/count.go deleted file mode 100644 index 3c9aef4..0000000 --- a/hypha/count.go +++ /dev/null @@ -1,33 +0,0 @@ -package hypha - -import ( - "sync" -) - -type count struct { - value uint - sync.Mutex -} - -// Count is a global variable. Its value is number of all existing hyphae. Hypha mutators are expected to manipulate the value. It is concurrent-safe. -var Count = count{} - -// Increment the value of Count. -func (c *count) Increment() { - c.Lock() - c.value++ - c.Unlock() -} - -// Decrement the value of Count. -func (c *count) Decrement() { - c.Lock() - c.value-- - c.Unlock() -} - -// Get value of Count. -func (c *count) Value() uint { - // it is concurrent-safe to not lock here, right? - return c.value -} diff --git a/hyphae/count.go b/hyphae/count.go new file mode 100644 index 0000000..2c1d5f1 --- /dev/null +++ b/hyphae/count.go @@ -0,0 +1,31 @@ +package hyphae + +import ( + "sync" +) + +// Its value is number of all existing hyphae. Hypha mutators are expected to manipulate the value. It is concurrent-safe. +var count = struct { + value int + sync.Mutex +}{} + +// Increment the value of hyphae count. +func IncrementCount() { + count.Lock() + count.value++ + count.Unlock() +} + +// Decrement the value of hyphae count. +func DecrementCount() { + count.Lock() + count.value-- + count.Unlock() +} + +// Count how many hyphae there are. +func Count() int { + // it is concurrent-safe to not lock here, right? + return count.value +} diff --git a/hypha/hypha.go b/hyphae/hypha.go similarity index 53% rename from hypha/hypha.go rename to hyphae/hypha.go index 2ca5ffc..be82302 100644 --- a/hypha/hypha.go +++ b/hyphae/hypha.go @@ -1,4 +1,4 @@ -package hypha +package hyphae import ( "errors" @@ -25,40 +25,35 @@ var schema = &memdb.DBSchema{ "hyphae": &memdb.TableSchema{ Name: "hyphae", Indexes: map[string]*memdb.IndexSchema{ - "name": &memdb.IndexSchema{ - Name: "name", + "id": &memdb.IndexSchema{ + Name: "id", Unique: true, Indexer: &memdb.StringFieldIndex{Field: "Name"}, }, "exists": &memdb.IndexSchema{ - Name: "exists", - Unique: false, - AllowEmpty: true, - Indexer: &memdb.BoolFieldIndex{Field: "Exists"}, + Name: "exists", + Unique: false, + Indexer: &memdb.BoolFieldIndex{Field: "Exists"}, }, "text-path": &memdb.IndexSchema{ - Name: "text-path", - Unique: true, - AllowEmpty: true, - Indexer: &memdb.StringFieldIndex{Field: "TextPath"}, + Name: "text-path", + Unique: true, + Indexer: &memdb.StringFieldIndex{Field: "TextPath"}, }, "binary-path": &memdb.IndexSchema{ - Name: "binary-path", - Unique: true, - AllowEmpty: true, - Indexer: &memdb.StringFieldIndex{Field: "BinaryPath"}, + Name: "binary-path", + Unique: true, + Indexer: &memdb.StringFieldIndex{Field: "BinaryPath"}, }, "out-links": &memdb.IndexSchema{ - Name: "out-links", - Unique: false, - AllowEmpty: true, - Indexer: &memdb.StringSliceFieldIndex{Field: "OutLinks"}, + Name: "out-links", + Unique: false, + Indexer: &memdb.StringSliceFieldIndex{Field: "OutLinks"}, }, "back-links": &memdb.IndexSchema{ - Name: "back-links", - Unique: false, - AllowEmpty: true, - Indexer: &memdb.StringSliceFieldIndex{Field: "BackLinks"}, + Name: "back-links", + Unique: false, + Indexer: &memdb.StringSliceFieldIndex{Field: "BackLinks"}, }, }, }, diff --git a/main.go b/main.go index 0caa1d5..ae1aa0f 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,7 @@ import ( "strings" "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" @@ -50,7 +51,7 @@ func handlerList(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) var ( tbody string - pageCount = len(HyphaStorage) + pageCount = hyphae.Count() ) for hyphaName, data := range HyphaStorage { tbody += templates.HyphaListRowHTML(hyphaName, ExtensionToMime(filepath.Ext(data.binaryPath)), data.binaryPath != "") @@ -73,14 +74,14 @@ func handlerReindex(w http.ResponseWriter, rq *http.Request) { log.Println("Wiki storage directory is", WikiDir) log.Println("Start indexing hyphae...") Index(WikiDir) - log.Println("Indexed", len(HyphaStorage), "hyphae") + log.Println("Indexed", hyphae.Count(), "hyphae") } // Redirect to a random hypha. func handlerRandom(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) var randomHyphaName string - i := rand.Intn(len(HyphaStorage)) + i := rand.Intn(hyphae.Count()) for hyphaName := range HyphaStorage { if i == 0 { randomHyphaName = hyphaName @@ -148,7 +149,7 @@ func main() { log.Println("Wiki storage directory is", WikiDir) log.Println("Start indexing hyphae...") Index(WikiDir) - log.Println("Indexed", len(HyphaStorage), "hyphae") + log.Println("Indexed", hyphae.Count(), "hyphae") history.Start(WikiDir) From 987dc5b20ff984e254114ebb13fc0cbbe7b040ae Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Mon, 4 Jan 2021 01:39:54 +0500 Subject: [PATCH 18/50] Support consecutive lines in paragraphs --- markup/lexer.go | 32 ++++++++++++++++++++++++++++++-- metarrhiza | 2 +- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/markup/lexer.go b/markup/lexer.go index b69a3cf..4dae95b 100644 --- a/markup/lexer.go +++ b/markup/lexer.go @@ -51,6 +51,13 @@ func lineToAST(line string, state *GemLexerState, ast *[]Line) { addLine := func(text interface{}) { *ast = append(*ast, Line{id: state.id, contents: text}) } + addParagraphIfNeeded := func() { + if state.where == "p" { + state.where = "" + addLine(fmt.Sprintf("

    %s

    ", state.id, strings.ReplaceAll(ParagraphToHtml(state.name, state.buf), "\n", "
    "))) + state.buf = "" + } + } // Process empty lines depending on the current state if "" == strings.TrimSpace(line) { @@ -66,6 +73,8 @@ func lineToAST(line string, state *GemLexerState, ast *[]Line) { case "launchpad": state.where = "" addLine(state.buf + "") + case "p": + addParagraphIfNeeded() } return } @@ -91,7 +100,7 @@ func lineToAST(line string, state *GemLexerState, ast *[]Line) { goto numberState case "launchpad": goto launchpadState - default: + default: // "p" or "" goto normalState } @@ -175,43 +184,57 @@ normalState: switch { case startsWith("```"): + addParagraphIfNeeded() state.where = "pre" state.buf = fmt.Sprintf("
    ", state.id, strings.TrimPrefix(line, "```"))
     	case startsWith("* "):
    +		addParagraphIfNeeded()
     		state.where = "list"
     		state.buf = fmt.Sprintf("
      \n", state.id) goto listState case startsWith("*. "): + addParagraphIfNeeded() state.where = "number" state.buf = fmt.Sprintf("
        \n", state.id) goto numberState case startsWith("###### "): + addParagraphIfNeeded() addHeading(6) case startsWith("##### "): + addParagraphIfNeeded() addHeading(5) case startsWith("#### "): + addParagraphIfNeeded() addHeading(4) case startsWith("### "): + addParagraphIfNeeded() addHeading(3) case startsWith("## "): + addParagraphIfNeeded() addHeading(2) case startsWith("# "): + addParagraphIfNeeded() addHeading(1) case startsWith(">"): + addParagraphIfNeeded() addLine(fmt.Sprintf( "
        %s
        ", state.id, remover(">")(line))) case startsWith("=>"): + addParagraphIfNeeded() state.where = "launchpad" state.buf = fmt.Sprintf("
          \n", state.id) goto launchpadState case startsWith("<="): + addParagraphIfNeeded() addLine(parseTransclusion(line, state.name)) case line == "----": + addParagraphIfNeeded() *ast = append(*ast, Line{id: -1, contents: "
          "}) case MatchesImg(line): + addParagraphIfNeeded() img, shouldGoBackToNormal := ImgFromFirstLine(line, state.name) if shouldGoBackToNormal { addLine(*img) @@ -220,9 +243,14 @@ normalState: state.img = img } case MatchesTable(line): + addParagraphIfNeeded() state.where = "table" state.table = TableFromFirstLine(line, state.name) + + case state.where == "p": + state.buf += "\n" + line default: - addLine(fmt.Sprintf("

          %s

          ", state.id, ParagraphToHtml(state.name, line))) + state.where = "p" + state.buf = line } } diff --git a/metarrhiza b/metarrhiza index 95f48bf..b67144c 160000 --- a/metarrhiza +++ b/metarrhiza @@ -1 +1 @@ -Subproject commit 95f48bfd7a7cfef17d56cef207a770767d727950 +Subproject commit b67144cc770900104a652483c11cabfecc7325aa From bee862563d8f1f02ef121ea257768abae7917940 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Mon, 4 Jan 2021 02:10:33 +0500 Subject: [PATCH 19/50] Auto-detect links --- hyphae/hypha.go | 21 +++++++++++++++++---- markup/paragraph.go | 26 +++++++++++++++++++++----- templates/asset.qtpl.go | 2 +- templates/default.css | 2 +- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/hyphae/hypha.go b/hyphae/hypha.go index be82302..e129355 100644 --- a/hyphae/hypha.go +++ b/hyphae/hypha.go @@ -1,8 +1,6 @@ package hyphae import ( - "errors" - "github.com/hashicorp/go-memdb" ) @@ -15,8 +13,23 @@ type Hypha struct { BackLinks []string } -func AddHypha(h Hypha) error { - return errors.New("Not implemented") +// AddHypha adds a hypha named `name` with such `textPath` and `binaryPath`. Both paths can be empty. Does //not// check for hypha's existence beforehand. Count is handled. +func AddHypha(name, textPath, binaryPath string) { + txn := db.Txn(true) + txn.Insert("hyphae", + &Hypha{ + Name: name, + TextPath: textPath, + BinaryPath: binaryPath, + OutLinks: make([]string, 0), + BackLinks: make([]string, 0), + }) + txn.Commit() + IncrementCount() +} + +// DeleteHypha clears both paths and all out-links from the named hypha and marks it as non-existent. It does not actually delete it from the memdb. Count is handled. +func DeleteHypha(name string) { } // Create the DB schema diff --git a/markup/paragraph.go b/markup/paragraph.go index 88d5e43..9148501 100644 --- a/markup/paragraph.go +++ b/markup/paragraph.go @@ -5,6 +5,7 @@ import ( "fmt" "html" "strings" + "unicode" ) type spanTokenType int @@ -34,8 +35,10 @@ func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, o } } -func getLinkNode(input *bytes.Buffer, hyphaName string) string { - input.Next(2) +func getLinkNode(input *bytes.Buffer, hyphaName string, isBracketedLink bool) string { + if isBracketedLink { + input.Next(2) // drop those [[ + } var ( escaping = false addrBuf = bytes.Buffer{} @@ -47,11 +50,13 @@ func getLinkNode(input *bytes.Buffer, hyphaName string) string { if escaping { currBuf.WriteByte(b) escaping = false - } else if b == '|' && currBuf == &addrBuf { + } else if isBracketedLink && b == '|' && currBuf == &addrBuf { currBuf = &displayBuf - } else if b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) { + } else if isBracketedLink && b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) { input.Next(1) break + } else if !isBracketedLink && unicode.IsSpace(rune(b)) { + break } else { currBuf.WriteByte(b) } @@ -65,6 +70,12 @@ func getTextNode(input *bytes.Buffer) string { var ( textNodeBuffer = bytes.Buffer{} escaping = false + startsWith = func(t string) bool { + return bytes.HasPrefix(input.Bytes(), []byte(t)) + } + couldBeLinkStart = func() bool { + return startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://") + } ) // Always read the first byte in advance to avoid endless loops that kill computers (sad experience) if input.Len() != 0 { @@ -82,6 +93,9 @@ func getTextNode(input *bytes.Buffer) string { } else if strings.IndexByte("/*`^,![~", b) >= 0 { input.UnreadByte() break + } else if couldBeLinkStart() { + textNodeBuffer.WriteByte(b) + break } else { textNodeBuffer.WriteByte(b) } @@ -132,7 +146,9 @@ func ParagraphToHtml(hyphaName, input string) string { ret.WriteString(tagFromState(spanMark, tagState, "s", "~~")) p.Next(2) case startsWith("[["): - ret.WriteString(getLinkNode(p, hyphaName)) + ret.WriteString(getLinkNode(p, hyphaName, true)) + case startsWith("https://"), startsWith("http://"), startsWith("gemini://"), startsWith("gopher://"), startsWith("ftp://"): + ret.WriteString(getLinkNode(p, hyphaName, false)) default: ret.WriteString(html.EscapeString(getTextNode(p))) } diff --git a/templates/asset.qtpl.go b/templates/asset.qtpl.go index b0416a1..a6cf70f 100644 --- a/templates/asset.qtpl.go +++ b/templates/asset.qtpl.go @@ -45,7 +45,7 @@ main h1:not(.navi-title) {font-size:1.7rem;} blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;} .wikilink_new {color:#a55858;} .wikilink_new:visited {color:#a55858;} -.wikilink__destination-type {display: inline; margin-left: .5rem; vertical-align: sub; } +.wikilink__destination-type {display: inline; margin: 0 .25rem; vertical-align: sub; } article code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; } article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wrap; border-radius: .25rem;} diff --git a/templates/default.css b/templates/default.css index 1c041f9..0d3ef7b 100644 --- a/templates/default.css +++ b/templates/default.css @@ -20,7 +20,7 @@ main h1:not(.navi-title) {font-size:1.7rem;} blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;} .wikilink_new {color:#a55858;} .wikilink_new:visited {color:#a55858;} -.wikilink__destination-type {display: inline; margin-left: .5rem; vertical-align: sub; } +.wikilink__destination-type {display: inline; margin: 0 .25rem; vertical-align: sub; } article code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; } article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wrap; border-radius: .25rem;} From 184aa1ae322211aa8f1d7b7fb933bcbf5038570a Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Mon, 4 Jan 2021 02:18:23 +0500 Subject: [PATCH 20/50] Support mycomarkup in blockquotes --- markup/lexer.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/markup/lexer.go b/markup/lexer.go index 4dae95b..24aaee5 100644 --- a/markup/lexer.go +++ b/markup/lexer.go @@ -219,8 +219,13 @@ normalState: case startsWith(">"): addParagraphIfNeeded() - addLine(fmt.Sprintf( - "
          %s
          ", state.id, remover(">")(line))) + addLine( + fmt.Sprintf( + "
          %s
          ", + state.id, + ParagraphToHtml(state.name, remover(">")(line)), + ), + ) case startsWith("=>"): addParagraphIfNeeded() state.where = "launchpad" From b1f33c872c63e9cfa240a033df88f7c1e33b94f2 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Sun, 10 Jan 2021 01:49:48 +0500 Subject: [PATCH 21/50] Make account system concurrent-safe and refactor it a little --- flag.go | 2 +- go.mod | 1 + go.sum | 11 ++ history/operations.go | 3 +- http_mutators.go | 6 +- hyphae/hypha.go | 55 +--------- markup/paragraph.go | 5 +- storage/storage.go | 65 ++++++++++++ templates/auth.qtpl | 4 +- templates/auth.qtpl.go | 4 +- templates/common.qtpl | 9 +- templates/common.qtpl.go | 139 ++++++++++++------------- templates/http_readers.qtpl | 2 +- templates/http_readers.qtpl.go | 2 +- user/{fs.go => files.go} | 66 +++++++----- user/group.go | 70 ------------- user/net.go | 75 ++++++++++++++ user/user.go | 180 ++++++++++----------------------- user/users.go | 44 ++++++++ 19 files changed, 385 insertions(+), 358 deletions(-) create mode 100644 storage/storage.go rename user/{fs.go => files.go} (55%) delete mode 100644 user/group.go create mode 100644 user/net.go create mode 100644 user/users.go diff --git a/flag.go b/flag.go index 551e0cf..c2cb461 100644 --- a/flag.go +++ b/flag.go @@ -51,7 +51,7 @@ func parseCliArgs() { case "none": case "fixed": user.AuthUsed = true - user.PopulateFixedUserStorage() + user.ReadUsersFromFilesystem() default: log.Fatal("Error: unknown auth method:", util.AuthMethod) } diff --git a/go.mod b/go.mod index c785b3a..2d5ea15 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,6 @@ require ( github.com/adrg/xdg v0.2.2 github.com/gorilla/feeds v1.1.1 github.com/hashicorp/go-memdb v1.3.0 + github.com/kr/pretty v0.2.1 // indirect github.com/valyala/quicktemplate v1.6.3 ) diff --git a/go.sum b/go.sum index 9b58270..b20873b 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,7 @@ github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo= github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY= github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= @@ -8,14 +9,22 @@ github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrh github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU= github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= +github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -29,5 +38,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/history/operations.go b/history/operations.go index fdc64af..49195b6 100644 --- a/history/operations.go +++ b/history/operations.go @@ -121,8 +121,7 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp { // WithUser sets a user for the commit. func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp { - u = u.OrAnon() - if u.Group != user.UserAnon { + if u.Group != "anon" { hop.name = u.Name hop.email = u.Name + "@mycorrhiza" } diff --git a/http_mutators.go b/http_mutators.go index f1cd5da..6927e7f 100644 --- a/http_mutators.go +++ b/http_mutators.go @@ -44,7 +44,7 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) { newName = CanonicalName(rq.PostFormValue("new-name")) _, newNameIsUsed = HyphaStorage[newName] recursive = rq.PostFormValue("recursive") == "true" - u = user.FromRequest(rq).OrAnon() + u = user.FromRequest(rq) ) switch { case !u.CanProceed("rename-confirm"): @@ -151,7 +151,7 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { var ( hyphaName = HyphaNameFromRq(rq, "upload-text") textData = rq.PostFormValue("text") - u = user.FromRequest(rq).OrAnon() + u = user.FromRequest(rq) ) if ok := user.CanProceed(rq, "upload-text"); !ok { HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.") @@ -174,7 +174,7 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { log.Println(rq.URL) var ( hyphaName = HyphaNameFromRq(rq, "upload-binary") - u = user.FromRequest(rq).OrAnon() + u = user.FromRequest(rq) ) if !u.CanProceed("upload-binary") { HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.") diff --git a/hyphae/hypha.go b/hyphae/hypha.go index e129355..366fd5a 100644 --- a/hyphae/hypha.go +++ b/hyphae/hypha.go @@ -1,7 +1,7 @@ package hyphae import ( - "github.com/hashicorp/go-memdb" + "github.com/bouncepaw/mycorrhiza/storage" ) type Hypha struct { @@ -15,7 +15,7 @@ type Hypha struct { // AddHypha adds a hypha named `name` with such `textPath` and `binaryPath`. Both paths can be empty. Does //not// check for hypha's existence beforehand. Count is handled. func AddHypha(name, textPath, binaryPath string) { - txn := db.Txn(true) + txn := storage.DB.Txn(true) txn.Insert("hyphae", &Hypha{ Name: name, @@ -31,54 +31,3 @@ func AddHypha(name, textPath, binaryPath string) { // DeleteHypha clears both paths and all out-links from the named hypha and marks it as non-existent. It does not actually delete it from the memdb. Count is handled. func DeleteHypha(name string) { } - -// Create the DB schema -var schema = &memdb.DBSchema{ - Tables: map[string]*memdb.TableSchema{ - "hyphae": &memdb.TableSchema{ - Name: "hyphae", - Indexes: map[string]*memdb.IndexSchema{ - "id": &memdb.IndexSchema{ - Name: "id", - Unique: true, - Indexer: &memdb.StringFieldIndex{Field: "Name"}, - }, - "exists": &memdb.IndexSchema{ - Name: "exists", - Unique: false, - Indexer: &memdb.BoolFieldIndex{Field: "Exists"}, - }, - "text-path": &memdb.IndexSchema{ - Name: "text-path", - Unique: true, - Indexer: &memdb.StringFieldIndex{Field: "TextPath"}, - }, - "binary-path": &memdb.IndexSchema{ - Name: "binary-path", - Unique: true, - Indexer: &memdb.StringFieldIndex{Field: "BinaryPath"}, - }, - "out-links": &memdb.IndexSchema{ - Name: "out-links", - Unique: false, - Indexer: &memdb.StringSliceFieldIndex{Field: "OutLinks"}, - }, - "back-links": &memdb.IndexSchema{ - Name: "back-links", - Unique: false, - Indexer: &memdb.StringSliceFieldIndex{Field: "BackLinks"}, - }, - }, - }, - }, -} - -var db *memdb.MemDB - -func init() { - var err error - db, err = memdb.NewMemDB(schema) - if err != nil { - panic(err) - } -} diff --git a/markup/paragraph.go b/markup/paragraph.go index 9148501..bcb1c84 100644 --- a/markup/paragraph.go +++ b/markup/paragraph.go @@ -120,6 +120,9 @@ func ParagraphToHtml(hyphaName, input string) string { startsWith = func(t string) bool { return bytes.HasPrefix(p.Bytes(), []byte(t)) } + noTagsActive = func() bool { + return !(tagState[spanItalic] || tagState[spanBold] || tagState[spanMono] || tagState[spanSuper] || tagState[spanSub] || tagState[spanMark] || tagState[spanLink]) + } ) for p.Len() != 0 { @@ -147,7 +150,7 @@ func ParagraphToHtml(hyphaName, input string) string { p.Next(2) case startsWith("[["): ret.WriteString(getLinkNode(p, hyphaName, true)) - case startsWith("https://"), startsWith("http://"), startsWith("gemini://"), startsWith("gopher://"), startsWith("ftp://"): + case (startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://")) && noTagsActive(): ret.WriteString(getLinkNode(p, hyphaName, false)) default: ret.WriteString(html.EscapeString(getTextNode(p))) diff --git a/storage/storage.go b/storage/storage.go new file mode 100644 index 0000000..528c109 --- /dev/null +++ b/storage/storage.go @@ -0,0 +1,65 @@ +package storage + +import ( + "github.com/hashicorp/go-memdb" +) + +// Create the DB schema +var schema = &memdb.DBSchema{ + Tables: map[string]*memdb.TableSchema{ + "hyphae": &memdb.TableSchema{ + Name: "hyphae", + Indexes: map[string]*memdb.IndexSchema{ + "id": &memdb.IndexSchema{ + Name: "id", + Unique: true, + Indexer: &memdb.StringFieldIndex{Field: "Name"}, + }, + "exists": &memdb.IndexSchema{ + Name: "exists", + Unique: false, + Indexer: &memdb.BoolFieldIndex{Field: "Exists"}, + }, + "out-links": &memdb.IndexSchema{ + Name: "out-links", + Unique: false, + Indexer: &memdb.StringSliceFieldIndex{Field: "OutLinks"}, + }, + "back-links": &memdb.IndexSchema{ + Name: "back-links", + Unique: false, + Indexer: &memdb.StringSliceFieldIndex{Field: "BackLinks"}, + }, + }, + }, + }, +} + +var DB *memdb.MemDB + +func init() { + var err error + DB, err = memdb.NewMemDB(schema) + if err != nil { + panic(err) + } +} + +func ForEveryRecord(table string, λ func(obj interface{})) error { + txn := DB.Txn(false) + defer txn.Abort() + + it, err := txn.Get(table, "id") + if err != nil { + return err + } + + for obj := it.Next(); obj != nil; obj = it.Next() { + λ(obj) + } + + return nil +} + +func TxnW() *memdb.Txn { return DB.Txn(true) } +func TxnR() *memdb.Txn { return DB.Txn(false) } diff --git a/templates/auth.qtpl b/templates/auth.qtpl index bbb7d25..32ab3d8 100644 --- a/templates/auth.qtpl +++ b/templates/auth.qtpl @@ -6,7 +6,7 @@ {% if user.AuthUsed %}

          Login

          -

          Use the data you were given by the administrator.

          +

          Use the data you were given by an administrator.

          Username @@ -15,7 +15,7 @@ Password
          -

          By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you.

          +

          By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.

          Cancel
          diff --git a/templates/auth.qtpl.go b/templates/auth.qtpl.go index 27d13fe..ed88d8d 100644 --- a/templates/auth.qtpl.go +++ b/templates/auth.qtpl.go @@ -33,7 +33,7 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) { qw422016.N().S(`

          Login

          -

          Use the data you were given by the administrator.

          +

          Use the data you were given by an administrator.

          Username @@ -42,7 +42,7 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) { Password
          -

          By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you.

          +

          By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.

          Cancel
          diff --git a/templates/common.qtpl b/templates/common.qtpl index 29673cc..a288422 100644 --- a/templates/common.qtpl +++ b/templates/common.qtpl @@ -20,9 +20,10 @@ var navEntries = []navEntry{ %} {% func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %} -{% code - u := user.FromRequest(rq).OrAnon() +{% code + u := user.FromRequest(rq) %} + diff --git a/templates/http_stuff.qtpl.go b/templates/http_stuff.qtpl.go index 667627a..2de5906 100644 --- a/templates/http_stuff.qtpl.go +++ b/templates/http_stuff.qtpl.go @@ -24,7 +24,7 @@ var ( ) //line templates/http_stuff.qtpl:4 -func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, headElements ...string) { +func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User, headElements ...string) { //line templates/http_stuff.qtpl:4 qw422016.N().S(` @@ -68,56 +68,61 @@ func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, headElements //line templates/http_stuff.qtpl:19 } //line templates/http_stuff.qtpl:19 - qw422016.N().S(`
        + qw422016.N().S(` `) +//line templates/http_stuff.qtpl:20 + qw422016.N().S(userMenuHTML(u)) +//line templates/http_stuff.qtpl:20 + qw422016.N().S(` +
    `) -//line templates/http_stuff.qtpl:23 +//line templates/http_stuff.qtpl:24 qw422016.N().S(body) -//line templates/http_stuff.qtpl:23 +//line templates/http_stuff.qtpl:24 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:26 +//line templates/http_stuff.qtpl:27 } -//line templates/http_stuff.qtpl:26 -func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string, headElements ...string) { -//line templates/http_stuff.qtpl:26 +//line templates/http_stuff.qtpl:27 +func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string, u *user.User, headElements ...string) { +//line templates/http_stuff.qtpl:27 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_stuff.qtpl:26 - StreamBaseHTML(qw422016, title, body, headElements...) -//line templates/http_stuff.qtpl:26 +//line templates/http_stuff.qtpl:27 + StreamBaseHTML(qw422016, title, body, u, headElements...) +//line templates/http_stuff.qtpl:27 qt422016.ReleaseWriter(qw422016) -//line templates/http_stuff.qtpl:26 +//line templates/http_stuff.qtpl:27 } -//line templates/http_stuff.qtpl:26 -func BaseHTML(title, body string, headElements ...string) string { -//line templates/http_stuff.qtpl:26 +//line templates/http_stuff.qtpl:27 +func BaseHTML(title, body string, u *user.User, headElements ...string) string { +//line templates/http_stuff.qtpl:27 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_stuff.qtpl:26 - WriteBaseHTML(qb422016, title, body, headElements...) -//line templates/http_stuff.qtpl:26 +//line templates/http_stuff.qtpl:27 + WriteBaseHTML(qb422016, title, body, u, headElements...) +//line templates/http_stuff.qtpl:27 qs422016 := string(qb422016.B) -//line templates/http_stuff.qtpl:26 +//line templates/http_stuff.qtpl:27 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_stuff.qtpl:26 +//line templates/http_stuff.qtpl:27 return qs422016 -//line templates/http_stuff.qtpl:26 +//line templates/http_stuff.qtpl:27 } -//line templates/http_stuff.qtpl:28 +//line templates/http_stuff.qtpl:29 func StreamHyphaListHTML(qw422016 *qt422016.Writer, tbody string, pageCount int) { -//line templates/http_stuff.qtpl:28 +//line templates/http_stuff.qtpl:29 qw422016.N().S(`

    List of hyphae

    This wiki has `) -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 qw422016.N().D(pageCount) -//line templates/http_stuff.qtpl:31 +//line templates/http_stuff.qtpl:32 qw422016.N().S(` hyphae.

    @@ -128,203 +133,203 @@ func StreamHyphaListHTML(qw422016 *qt422016.Writer, tbody string, pageCount int) `) -//line templates/http_stuff.qtpl:40 +//line templates/http_stuff.qtpl:41 qw422016.N().S(tbody) -//line templates/http_stuff.qtpl:40 +//line templates/http_stuff.qtpl:41 qw422016.N().S(`
    `) -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 } -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 func WriteHyphaListHTML(qq422016 qtio422016.Writer, tbody string, pageCount int) { -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 StreamHyphaListHTML(qw422016, tbody, pageCount) -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 qt422016.ReleaseWriter(qw422016) -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 } -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 func HyphaListHTML(tbody string, pageCount int) string { -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 WriteHyphaListHTML(qb422016, tbody, pageCount) -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 qs422016 := string(qb422016.B) -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 return qs422016 -//line templates/http_stuff.qtpl:44 +//line templates/http_stuff.qtpl:45 } -//line templates/http_stuff.qtpl:46 +//line templates/http_stuff.qtpl:47 func StreamHyphaListRowHTML(qw422016 *qt422016.Writer, hyphaName, binaryMime string, binaryPresent bool) { -//line templates/http_stuff.qtpl:46 +//line templates/http_stuff.qtpl:47 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:48 +//line templates/http_stuff.qtpl:49 qw422016.E().S(hyphaName) -//line templates/http_stuff.qtpl:48 +//line templates/http_stuff.qtpl:49 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:49 +//line templates/http_stuff.qtpl:50 if binaryPresent { -//line templates/http_stuff.qtpl:49 +//line templates/http_stuff.qtpl:50 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:50 +//line templates/http_stuff.qtpl:51 qw422016.E().S(binaryMime) -//line templates/http_stuff.qtpl:50 +//line templates/http_stuff.qtpl:51 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:51 +//line templates/http_stuff.qtpl:52 } else { -//line templates/http_stuff.qtpl:51 +//line templates/http_stuff.qtpl:52 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:53 +//line templates/http_stuff.qtpl:54 } -//line templates/http_stuff.qtpl:53 +//line templates/http_stuff.qtpl:54 qw422016.N().S(` `) -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 } -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 func WriteHyphaListRowHTML(qq422016 qtio422016.Writer, hyphaName, binaryMime string, binaryPresent bool) { -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 StreamHyphaListRowHTML(qw422016, hyphaName, binaryMime, binaryPresent) -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 qt422016.ReleaseWriter(qw422016) -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 } -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 func HyphaListRowHTML(hyphaName, binaryMime string, binaryPresent bool) string { -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 WriteHyphaListRowHTML(qb422016, hyphaName, binaryMime, binaryPresent) -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 qs422016 := string(qb422016.B) -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 return qs422016 -//line templates/http_stuff.qtpl:55 +//line templates/http_stuff.qtpl:56 } -//line templates/http_stuff.qtpl:57 +//line templates/http_stuff.qtpl:58 func StreamAboutHTML(qw422016 *qt422016.Writer) { -//line templates/http_stuff.qtpl:57 +//line templates/http_stuff.qtpl:58 qw422016.N().S(`

    About `) -//line templates/http_stuff.qtpl:60 +//line templates/http_stuff.qtpl:61 qw422016.E().S(util.SiteName) -//line templates/http_stuff.qtpl:60 +//line templates/http_stuff.qtpl:61 qw422016.N().S(`

    See /list for information about hyphae on this wiki.

    `) -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 } -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 func WriteAboutHTML(qq422016 qtio422016.Writer) { -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 qw422016 := qt422016.AcquireWriter(qq422016) -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 StreamAboutHTML(qw422016) -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 qt422016.ReleaseWriter(qw422016) -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 } -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 func AboutHTML() string { -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 qb422016 := qt422016.AcquireByteBuffer() -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 WriteAboutHTML(qb422016) -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 qs422016 := string(qb422016.B) -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 qt422016.ReleaseByteBuffer(qb422016) -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 return qs422016 -//line templates/http_stuff.qtpl:77 +//line templates/http_stuff.qtpl:78 } diff --git a/user/net.go b/user/net.go index 55b08e1..caec383 100644 --- a/user/net.go +++ b/user/net.go @@ -17,7 +17,7 @@ func CanProceed(rq *http.Request, route string) bool { func FromRequest(rq *http.Request) *User { cookie, err := rq.Cookie("mycorrhiza_token") if err != nil { - return emptyUser() + return EmptyUser() } return userByToken(cookie.Value) } diff --git a/user/user.go b/user/user.go index b45e2c5..efc0028 100644 --- a/user/user.go +++ b/user/user.go @@ -37,7 +37,7 @@ var groupRight = map[string]int{ "admin": 4, } -func emptyUser() *User { +func EmptyUser() *User { return &User{ Name: "anon", Group: "anon", diff --git a/user/users.go b/user/users.go index 397ecf0..aa5bb4b 100644 --- a/user/users.go +++ b/user/users.go @@ -44,7 +44,7 @@ func userByToken(token string) *User { username := usernameUntyped.(string) return userByName(username) } - return emptyUser() + return EmptyUser() } func userByName(username string) *User { @@ -52,7 +52,7 @@ func userByName(username string) *User { user := userUntyped.(*User) return user } - return emptyUser() + return EmptyUser() } func commenceSession(username, token string) { From 9316e671b60de50f74ec26270607d77510c9f709 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Sun, 24 Jan 2021 13:18:59 +0500 Subject: [PATCH 47/50] Move the protocol icons to the left, change svg icon colors --- markup/lexer.go | 4 ++-- markup/link.go | 12 ++++++------ markup/paragraph.go | 4 ++-- metarrhiza | 2 +- templates/asset.qtpl.go | 24 +++++++++++++++--------- templates/default.css | 12 +++++++++--- templates/icon/gemini-protocol-icon.svg | 2 +- templates/icon/gopher-protocol-icon.svg | 6 +++--- templates/icon/http-protocol-icon.svg | 2 +- templates/icon/mailto-protocol-icon.svg | 2 +- util/header_links.go | 4 ++-- 11 files changed, 43 insertions(+), 31 deletions(-) diff --git a/markup/lexer.go b/markup/lexer.go index 24aaee5..4516eb0 100644 --- a/markup/lexer.go +++ b/markup/lexer.go @@ -165,8 +165,8 @@ numberState: launchpadState: switch { case startsWith("=>"): - href, text, class, icon := Rocketlink(line, state.name) - state.buf += fmt.Sprintf(`
  • %s%s
  • `, class, href, text, icon) + href, text, class := Rocketlink(line, state.name) + state.buf += fmt.Sprintf(`
  • %s
  • `, class, href, text) case startsWith("```"): state.where = "pre" addLine(state.buf + "") diff --git a/markup/link.go b/markup/link.go index 1c358be..af3e51c 100644 --- a/markup/link.go +++ b/markup/link.go @@ -10,13 +10,13 @@ import ( // // => addr display // [[addr|display]] -func LinkParts(addr, display, hyphaName string) (href, text, class, icon string) { +func LinkParts(addr, display, hyphaName string) (href, text, class string) { if display == "" { text = addr } else { text = strings.TrimSpace(display) } - class = "wikilink_internal" + class = "wikilink wikilink_internal" switch { case strings.ContainsRune(addr, ':'): @@ -28,9 +28,9 @@ func LinkParts(addr, display, hyphaName string) (href, text, class, icon string) text = text[2:] } } - return addr, text, "wikilink_external", fmt.Sprintf(``, destination) + return addr, text, fmt.Sprintf("wikilink wikilink_external wikilink_%s", destination) case strings.HasPrefix(addr, "/"): - return addr, text, class, "" + return addr, text, class case strings.HasPrefix(addr, "./"): hyphaName = canonicalName(path.Join(hyphaName, addr[2:])) case strings.HasPrefix(addr, "../"): @@ -41,12 +41,12 @@ func LinkParts(addr, display, hyphaName string) (href, text, class, icon string) if !HyphaExists(hyphaName) { class += " wikilink_new" } - return "/page/" + hyphaName, text, class, "" + return "/page/" + hyphaName, text, class } // Parse markup line starting with "=>" according to wikilink rules. // See http://localhost:1737/page/wikilink -func Rocketlink(src, hyphaName string) (href, text, class, icon string) { +func Rocketlink(src, hyphaName string) (href, text, class string) { src = strings.TrimSpace(src[2:]) // Drop => if src == "" { return diff --git a/markup/paragraph.go b/markup/paragraph.go index bcb1c84..f506bb6 100644 --- a/markup/paragraph.go +++ b/markup/paragraph.go @@ -61,8 +61,8 @@ func getLinkNode(input *bytes.Buffer, hyphaName string, isBracketedLink bool) st currBuf.WriteByte(b) } } - href, text, class, icon := LinkParts(addrBuf.String(), displayBuf.String(), hyphaName) - return fmt.Sprintf(`%s%s`, href, class, html.EscapeString(text), icon) + href, text, class := LinkParts(addrBuf.String(), displayBuf.String(), hyphaName) + return fmt.Sprintf(`%s`, href, class, html.EscapeString(text)) } // getTextNode splits the `input` into two parts `textNode` and `rest` by the first encountered rune that resembles a span tag. If there is none, `textNode = input`, `rest = ""`. It handles escaping with backslash. diff --git a/metarrhiza b/metarrhiza index b67144c..be5b922 160000 --- a/metarrhiza +++ b/metarrhiza @@ -1 +1 @@ -Subproject commit b67144cc770900104a652483c11cabfecc7325aa +Subproject commit be5b922e9b564551601d21ed45bf7d9ced65c6bb diff --git a/templates/asset.qtpl.go b/templates/asset.qtpl.go index c13cf38..95a984b 100644 --- a/templates/asset.qtpl.go +++ b/templates/asset.qtpl.go @@ -57,7 +57,14 @@ textarea {font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif; main h1:not(.navi-title) {font-size:1.7rem;} blockquote { margin-left: 0; padding-left: 1rem; } -.wikilink__destination-type {display: inline; margin: 0 .25rem; vertical-align: sub; } +.wikilink_external::before { display: inline-block; width: 18px; height: 16px; vertical-align: sub; } +/* .wikilink_external { padding-left: 16px; } */ +.wikilink_gopher::before { content: url("/static/icon/gopher"); } +.wikilink_http::before { content: url("/static/icon/http"); } +.wikilink_https::before { content: url("/static/icon/http"); } +/* .wikilink_https { background: transparent url("/static/icon/http") center left no-repeat; } */ +.wikilink_gemini::before { content: url("/static/icon/gemini"); } +.wikilink_mailto::before { content: url("/static/icon/mailto"); } article { overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; line-height: 150%; } article h1, article h2, article h3, article h4, article h5, article h6 { margin: 1.5rem 0 0 0; } @@ -72,7 +79,7 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25 .transclusion__link::before {content: "⇐ ";} /* Derived from https://commons.wikimedia.org/wiki/File:U%2B21D2.svg */ -.launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); } +.launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath fill='%23999' d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); } .binary-container_with-img img, .binary-container_with-video video, @@ -126,7 +133,6 @@ td { padding: .25rem; } caption { caption-side: top; font-size: small; } /* Color stuff */ -svg path { fill: currentColor !important; } /* Lighter stuff #eee */ article code, article .codeblock, @@ -241,7 +247,7 @@ func StreamIconHTTP(qw422016 *qt422016.Writer) { qw422016.N().S(` `) //line templates/asset.qtpl:7 - qw422016.N().S(` + qw422016.N().S(` `) //line templates/asset.qtpl:7 qw422016.N().S(` @@ -281,7 +287,7 @@ func StreamIconGemini(qw422016 *qt422016.Writer) { qw422016.N().S(` `) //line templates/asset.qtpl:11 - qw422016.N().S(` + qw422016.N().S(` `) //line templates/asset.qtpl:11 qw422016.N().S(` @@ -321,7 +327,7 @@ func StreamIconMailto(qw422016 *qt422016.Writer) { qw422016.N().S(` `) //line templates/asset.qtpl:15 - qw422016.N().S(` + qw422016.N().S(` `) //line templates/asset.qtpl:15 qw422016.N().S(` @@ -363,8 +369,8 @@ func StreamIconGopher(qw422016 *qt422016.Writer) { qw422016.N().S(` `) //line templates/asset.qtpl:20 - qw422016.N().S(` - + - + `) //line templates/asset.qtpl:20 diff --git a/templates/default.css b/templates/default.css index a669a87..0d1ccda 100644 --- a/templates/default.css +++ b/templates/default.css @@ -32,7 +32,14 @@ textarea {font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif; main h1:not(.navi-title) {font-size:1.7rem;} blockquote { margin-left: 0; padding-left: 1rem; } -.wikilink__destination-type {display: inline; margin: 0 .25rem; vertical-align: sub; } +.wikilink_external::before { display: inline-block; width: 18px; height: 16px; vertical-align: sub; } +/* .wikilink_external { padding-left: 16px; } */ +.wikilink_gopher::before { content: url("/static/icon/gopher"); } +.wikilink_http::before { content: url("/static/icon/http"); } +.wikilink_https::before { content: url("/static/icon/http"); } +/* .wikilink_https { background: transparent url("/static/icon/http") center left no-repeat; } */ +.wikilink_gemini::before { content: url("/static/icon/gemini"); } +.wikilink_mailto::before { content: url("/static/icon/mailto"); } article { overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; line-height: 150%; } article h1, article h2, article h3, article h4, article h5, article h6 { margin: 1.5rem 0 0 0; } @@ -47,7 +54,7 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25 .transclusion__link::before {content: "⇐ ";} /* Derived from https://commons.wikimedia.org/wiki/File:U%2B21D2.svg */ -.launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); } +.launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath fill='%23999' d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); } .binary-container_with-img img, .binary-container_with-video video, @@ -101,7 +108,6 @@ td { padding: .25rem; } caption { caption-side: top; font-size: small; } /* Color stuff */ -svg path { fill: currentColor !important; } /* Lighter stuff #eee */ article code, article .codeblock, diff --git a/templates/icon/gemini-protocol-icon.svg b/templates/icon/gemini-protocol-icon.svg index 70beccb..f2a6414 100644 --- a/templates/icon/gemini-protocol-icon.svg +++ b/templates/icon/gemini-protocol-icon.svg @@ -1 +1 @@ - + diff --git a/templates/icon/gopher-protocol-icon.svg b/templates/icon/gopher-protocol-icon.svg index bce1a36..3ee0c59 100644 --- a/templates/icon/gopher-protocol-icon.svg +++ b/templates/icon/gopher-protocol-icon.svg @@ -1,5 +1,5 @@ - - + - + diff --git a/templates/icon/http-protocol-icon.svg b/templates/icon/http-protocol-icon.svg index c375ec4..5bb72f9 100644 --- a/templates/icon/http-protocol-icon.svg +++ b/templates/icon/http-protocol-icon.svg @@ -1 +1 @@ - + diff --git a/templates/icon/mailto-protocol-icon.svg b/templates/icon/mailto-protocol-icon.svg index 424021c..863ccc1 100644 --- a/templates/icon/mailto-protocol-icon.svg +++ b/templates/icon/mailto-protocol-icon.svg @@ -1 +1 @@ - + diff --git a/util/header_links.go b/util/header_links.go index 9c78565..c574768 100644 --- a/util/header_links.go +++ b/util/header_links.go @@ -14,11 +14,11 @@ func SetDefaultHeaderLinks() { } // rocketlinkλ is markup.Rocketlink. You have to pass it like that to avoid cyclical dependency. -func ParseHeaderLinks(text string, rocketlinkλ func(string, string) (string, string, string, string)) { +func ParseHeaderLinks(text string, rocketlinkλ func(string, string) (string, string, string)) { HeaderLinks = []HeaderLink{} for _, line := range strings.Split(text, "\n") { if strings.HasPrefix(line, "=>") { - href, text, _, _ := rocketlinkλ(line, HeaderLinksHypha) + href, text, _ := rocketlinkλ(line, HeaderLinksHypha) HeaderLinks = append(HeaderLinks, HeaderLink{ Href: href, Display: text, From 225c83d8516d0f5267cdf21aa280e609e5441a10 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Sun, 24 Jan 2021 13:33:51 +0500 Subject: [PATCH 48/50] Update README.md for 0.12 --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d672a18..dd14e51 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # 🍄 MycorrhizaWiki 0.12 A wiki engine. +[Main wiki](https://mycorrhiza.lesarbr.es) + ## Building +Also see [detailed instructions](https://mycorrhiza.lesarbr.es/page/deploy) on wiki. ```sh git clone --recurse-submodules https://github.com/bouncepaw/mycorrhiza cd mycorrhiza @@ -37,8 +40,8 @@ Options: ``` ## Features -* Edit pages through html forms -* Responsive design +* Edit pages through html forms, graphical preview +* Responsive design, dark theme (synced with system theme) * Works in text browsers * Wiki pages (called hyphae) are written in mycomarkup * Everything is stored as simple files, no database required. You can run a wiki on almost any directory and get something to work with @@ -50,12 +53,10 @@ Options: * Recent changes page; RSS, Atom and JSON feeds available * Hyphae can be deleted (while still preserving history) * Hyphae can be renamed (recursive renaming of subhyphae is also supported) -* Light on resources: I run a home wiki on this engine 24/7 at an [Orange π Lite](http://www.orangepi.org/orangepilite/) +* Light on resources * Authorization with pre-set credentials ## Contributing -Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where some development is coordinated. Feel free to open an issue or contact me. +Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where some development is coordinated. You can also sponsor on [boosty](https://boosty.to/bouncepaw). Feel free to open an issue or contact directly. -## Future plans -* Tagging system -* Better history viewing +You can view list of all planned features on [our kanban board](https://github.com/bouncepaw/mycorrhiza/projects/1). From f34757afbd7a3c73b7496963fcfe2a0094e17d09 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Sun, 24 Jan 2021 13:36:03 +0500 Subject: [PATCH 49/50] Drop memdb dependency because it is not used yet --- go.mod | 1 - go.sum | 9 ------- hyphae/hypha.go | 16 ++---------- storage/storage.go | 65 ---------------------------------------------- 4 files changed, 2 insertions(+), 89 deletions(-) delete mode 100644 storage/storage.go diff --git a/go.mod b/go.mod index 2d5ea15..137cbae 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.14 require ( github.com/adrg/xdg v0.2.2 github.com/gorilla/feeds v1.1.1 - github.com/hashicorp/go-memdb v1.3.0 github.com/kr/pretty v0.2.1 // indirect github.com/valyala/quicktemplate v1.6.3 ) diff --git a/go.sum b/go.sum index b20873b..f950cb8 100644 --- a/go.sum +++ b/go.sum @@ -5,15 +5,6 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY= github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= -github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE= -github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU= -github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= -github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= diff --git a/hyphae/hypha.go b/hyphae/hypha.go index 366fd5a..d70cd1e 100644 --- a/hyphae/hypha.go +++ b/hyphae/hypha.go @@ -1,8 +1,7 @@ package hyphae -import ( - "github.com/bouncepaw/mycorrhiza/storage" -) +// TODO: do +import () type Hypha struct { Name string @@ -15,17 +14,6 @@ type Hypha struct { // AddHypha adds a hypha named `name` with such `textPath` and `binaryPath`. Both paths can be empty. Does //not// check for hypha's existence beforehand. Count is handled. func AddHypha(name, textPath, binaryPath string) { - txn := storage.DB.Txn(true) - txn.Insert("hyphae", - &Hypha{ - Name: name, - TextPath: textPath, - BinaryPath: binaryPath, - OutLinks: make([]string, 0), - BackLinks: make([]string, 0), - }) - txn.Commit() - IncrementCount() } // DeleteHypha clears both paths and all out-links from the named hypha and marks it as non-existent. It does not actually delete it from the memdb. Count is handled. diff --git a/storage/storage.go b/storage/storage.go deleted file mode 100644 index 528c109..0000000 --- a/storage/storage.go +++ /dev/null @@ -1,65 +0,0 @@ -package storage - -import ( - "github.com/hashicorp/go-memdb" -) - -// Create the DB schema -var schema = &memdb.DBSchema{ - Tables: map[string]*memdb.TableSchema{ - "hyphae": &memdb.TableSchema{ - Name: "hyphae", - Indexes: map[string]*memdb.IndexSchema{ - "id": &memdb.IndexSchema{ - Name: "id", - Unique: true, - Indexer: &memdb.StringFieldIndex{Field: "Name"}, - }, - "exists": &memdb.IndexSchema{ - Name: "exists", - Unique: false, - Indexer: &memdb.BoolFieldIndex{Field: "Exists"}, - }, - "out-links": &memdb.IndexSchema{ - Name: "out-links", - Unique: false, - Indexer: &memdb.StringSliceFieldIndex{Field: "OutLinks"}, - }, - "back-links": &memdb.IndexSchema{ - Name: "back-links", - Unique: false, - Indexer: &memdb.StringSliceFieldIndex{Field: "BackLinks"}, - }, - }, - }, - }, -} - -var DB *memdb.MemDB - -func init() { - var err error - DB, err = memdb.NewMemDB(schema) - if err != nil { - panic(err) - } -} - -func ForEveryRecord(table string, λ func(obj interface{})) error { - txn := DB.Txn(false) - defer txn.Abort() - - it, err := txn.Get(table, "id") - if err != nil { - return err - } - - for obj := it.Next(); obj != nil; obj = it.Next() { - λ(obj) - } - - return nil -} - -func TxnW() *memdb.Txn { return DB.Txn(true) } -func TxnR() *memdb.Txn { return DB.Txn(false) } From 86c760d676c75bec9a677f56f1324e0e39419a71 Mon Sep 17 00:00:00 2001 From: bouncepaw Date: Sun, 24 Jan 2021 13:40:17 +0500 Subject: [PATCH 50/50] Add a missing option to the Usage section --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index dd14e51..db4db81 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ Options: What auth method to use. Variants: "none", "fixed" (default "none") -fixed-credentials-path string Used when -auth-method=fixed. Path to file with user credentials. (default "mycocredentials.json") + -header-links-hypha string + Optional hypha that overrides the header links -home string The home page (default "home") -icon string