1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2024-12-13 05:50:27 +00:00

Merge pull request #35 from bouncepaw/0.13

0.13
This commit is contained in:
Timur Ismagilov 2021-03-05 14:40:31 +05:00 committed by GitHub
commit 15aab93dd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
85 changed files with 5242 additions and 3238 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
mycorrhiza
hyphae/*.gog

View File

@ -1,9 +1,12 @@
run: build
./mycorrhiza metarrhiza
run_with_fixed_auth: build
auth_run: build
./mycorrhiza -auth-method fixed metarrhiza
gemini_run: build
./mycorrhiza -gemini-cert-path "." metarrhiza
build:
go generate
go build .

View File

@ -1,4 +1,4 @@
# 🍄 MycorrhizaWiki 0.12
# 🍄 MycorrhizaWiki 0.13
A wiki engine.
[Main wiki](https://mycorrhiza.lesarbr.es)
@ -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")
-gemini-cert-path string
Directory where you store Gemini certificates. Leave empty if you don't want to use Gemini.
-header-links-hypha string
Optional hypha that overrides the header links
-home string
@ -57,6 +59,7 @@ Options:
* Hyphae can be renamed (recursive renaming of subhyphae is also supported)
* Light on resources
* Authorization with pre-set credentials
* Basic Gemini protocol support
## Contributing
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.

View File

@ -1,58 +1,124 @@
// Code generated by qtc from "asset.qtpl". DO NOT EDIT.
// Code generated by qtc from "assets.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line templates/asset.qtpl:1
package templates
//line assets/assets.qtpl:1
package assets
//line templates/asset.qtpl:1
//line assets/assets.qtpl:1
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line templates/asset.qtpl:1
//line assets/assets.qtpl:1
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line templates/asset.qtpl:1
//line assets/assets.qtpl:1
func StreamDefaultCSS(qw422016 *qt422016.Writer) {
//line templates/asset.qtpl:1
//line assets/assets.qtpl:1
qw422016.N().S(`
`)
//line templates/asset.qtpl:2
qw422016.N().S(`/* Layout stuff */
@media screen and (min-width: 800px) {
main { padding:1rem 2rem; margin: 0 auto; width: 800px; }
.hypha-tabs { padding: 1rem 2rem; margin: 0 auto; width: 800px; }
header { margin: 0 auto; width: 800px; }
//line assets/assets.qtpl:2
qw422016.N().S(`.amnt-grid { display: grid; grid-template-columns: 1fr 1fr; }
.upload-binary__input { display: block; margin: .25rem 0; }
.modal__title { font-size: 2rem; }
.modal__title_small { font-size: 1.5rem; }
.modal__confirmation-msg { margin: 0 0 .5rem 0; }
.modal__action { display: inline-block; font-size: 1rem; padding: .25rem; border-radius: .25rem; }
.modal__submit { border: 1px #999 solid; }
.modal__cancel { border: 1px #999 dashed; text-decoration: none; }
.hypha-list { padding-left: 0; }
.hypha-list__entry { list-style-type: none; }
.hypha-list__link { text-decoration: none; display: inline-block; padding: .25rem; }
.hypha-list__link:hover { text-decoration: underline; }
.hypha-list__amnt-type { font-size: smaller; color: #999; }
/* General element positions, from small to big */
/* Phones and whatnot */
.layout { display: grid; row-gap: 1rem; }
header { width: 100%; margin-bottom: 1rem; }
.header-links__list, .hypha-tabs__flex { margin: 0; padding: 0; display: flex; flex-wrap: wrap; }
.header-links__entry, .hypha-tabs__tab { list-style-type: none; }
.header-links__entry { margin-right: .5rem; }
.header-links__entry_user { font-style:italic; }
.header-links__link { display: inline-block; padding: .25rem; text-decoration: none; }
.hypha-tabs { padding: 0; margin: 0; }
.hypha-tabs__tab { margin-right: .5rem; padding: 0; }
.hypha-tabs__link { display: inline-block; padding: .25rem; text-decoration: none; }
.hypha-tabs__selection { display: inline-block; padding: .25rem; font-weight: bold; }
.layout-card li { list-style-type: none; }
.backlinks__list { padding: 0; margin: 0; }
.backlinks__link { text-decoration: none; display: block; padding: .25rem; padding-left: 1.25rem; }
@media screen and (max-width: 800px) {
.amnt-grid { grid-template-columns: 1fr; }
.layout { grid-template-column: auto; grid-template-row: auto auto auto; }
.main-width { width: 100%; }
main { padding: 1rem; margin: 0; }
}
/* No longer a phone but still small screen: draw normal tabs, center main */
@media screen and (min-width: 801px) {
.main-width { padding: 1rem 2rem; width: 800px; margin: 0 auto; }
main { border-radius: .25rem; }
.layout-card { width: 800px; margin: 0 auto; }
.header-links { padding: 0; }
.header-links__entry { margin-right: 1.5rem; }
.header-links__entry_user { margin: 0 2rem 0 auto; }
.header-links__entry:nth-of-type(1),
.hypha-tabs__tab:nth-of-type(1) { margin-left: 2rem; }
.hypha-tabs__tab { margin-right: 1.5rem; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); border-bottom: 2px #ddd solid; padding: 0 .5rem; }
.hypha-tabs { padding: 0; }
.hypha-tabs__tab { border-radius: .25rem .25rem 0 0; margin-right: 0; }
.hypha-tabs__selection, .hypha-tabs__link { padding: .25rem .5rem; }
.header-links__entry:nth-of-type(1), .hypha-tabs__tab:nth-of-type(1) { margin-left: 2rem; }
}
@media screen and (max-width: 800px) {
main { padding: 1rem; margin: 0; width: 100%; }
.hypha-tabs{ padding: 1rem; margin: 0; width: 100%; }
.hypha-tabs__tab { box-shadow: none; margin-right: .5rem; padding: .25rem .5rem; }
header { width: 100%; }
.header-links__entry { margin-right: .5rem; }
/* Wide enough to fit two columns ok */
@media screen and (min-width: 1100px) {
.layout { display: grid; grid-template-columns: auto 1fr; column-gap: 1rem; margin: 0 1rem; row-gap: 1rem; }
.main-width { margin: 0; }
main { grid-column: 1 / span 1; grid-row: 1 / span 2; }
.relative-hyphae { grid-column: 2 / span 1; grid-row: 1 / span 1; }
.layout-card { width: 100%; }
}
@media screen and (min-width: 1250px) {
.layout { grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); }
.layout-card { max-width: 16rem; }
.main-width { margin: 0 auto; }
.backlinks { grid-column: 1 / span 1; margin-right: 0; }
main { grid-column: 2 / span 1; }
.relative-hyphae { grid-column: 3 / span 1; margin-left: 0; }
.backlinks__title { text-align: right; }
.backlinks__link { text-align: right; padding-right: 1.25rem; padding-left: .25rem; }
}
*, *::before, *::after {box-sizing: border-box;}
html { height:100%; padding:0; }
body {height:100%; margin:0; font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;}
main {border-radius: 0 0 .25rem .25rem; }
body {height:100%; margin:0; }
body, input { font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;}
main > form {margin-bottom:1rem;}
textarea {font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;}
.edit_no-preview {height:100%;}
.edit_with-preview .edit-form textarea { min-height: 500px; }
.edit { min-height: 80vh; }
.edit__title { margin-top: 0; }
.edit__preview { border: 2px dashed #ddd; }
.edit-form {height:90%;}
.edit-form textarea {width:100%;height:90%;}
.edit-form {height:70vh;}
.edit-form textarea {width:100%;height:95%;}
.edit-form__save { font-weight: bold; }
.icon {margin-right: .25rem; vertical-align: bottom; }
main h1:not(.navi-title) {font-size:1.7rem;}
@ -67,7 +133,10 @@ blockquote { margin-left: 0; padding-left: 1rem; }
.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; }
main h1, main h2, main h3, main h4, main h5, main h6 { margin: 1.5rem 0 0 0; }
.heading__link { text-decoration: none; display: inline-block; }
.heading__link::after { width: 1rem; content: "§"; color: transparent; }
.heading__link:hover::after, .heading__link:active::after { color: #999; }
article p { margin: .5rem 0; }
article ul, ol { padding-left: 1.5rem; margin: .5rem 0; }
article code { padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; }
@ -85,6 +154,7 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25
.binary-container_with-video video,
.binary-container_with-audio audio {width: 100%}
.subhyphae__title { padding-bottom: .5rem; clear: both; }
.navi-title { padding-bottom: .5rem; margin: .25rem 0; }
.navi-title a {text-decoration:none; }
.navi-title__separator { margin: 0 .25rem; }
@ -101,19 +171,11 @@ figcaption { padding-bottom: .5rem; }
#new-name {width:100%;}
header { margin-bottom: .5rem; }
.header-links__entry_user { font-style:italic; }
.header-links__link { text-decoration: none; display: block; width: 100%; height: 100%; padding: .25rem; }
.hypha-tabs { padding: 0; }
.header-links__list, .hypha-tabs__flex { margin: 0; padding: 0; display: flex; flex-wrap: wrap; }
.header-links__entry, .hypha-tabs__tab { list-style-type: none; }
.hypha-tabs__tab a { text-decoration: none; }
.hypha-tabs__tab_active { font-weight: bold; }
.rc-entry { display: grid; list-style-type: none; padding: .25rem; grid-template-columns: 1fr 1fr; }
.rc-entry { display: grid; list-style-type: none; padding: .25rem; grid-template-columns: 1fr 1fr; border-radius: .25rem; }
.rc-entry__time { font-style: italic; }
.rc-entry__hash { font-style: italic; text-align: right; }
.rc-entry__links { grid-column: 1 / span 2; }
.rc-entry__links, .rc-entry__msg { 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; border-radius: .25rem; }
@ -132,6 +194,22 @@ table { border: #ddd 1px solid; border-radius: .25rem; min-width: 4rem; }
td { padding: .25rem; }
caption { caption-side: top; font-size: small; }
.subhyphae__list, .subhyphae__list ul { display: flex; padding: 0; margin: 0; flex-wrap: wrap; }
.subhyphae__entry { list-style-type: none; border: 1px solid #999; padding: 0; margin: .125rem; border-radius: .25rem; }
.subhyphae__link { display: block; padding: .25rem; text-decoration: none; }
.subhyphae__link:hover { background: #eee; }
.navitree { padding: 0; margin: 0; }
.navitree__entry { }
.navitree > .navitree__entry > a::before { display: inline-block; width: .5rem; color: #999; margin: 0 .25rem; }
.navitree > .navitree__entry_infertile > a::before { content: " "} /* nbsp, careful */
.navitree > .navitree__sibling_fertile > a::before { content: "▸"}
.navitree__trunk { border-left: 1px #999 solid; }
.navitree__link { text-decoration: none; display: block; padding: .25rem; }
.navitree__entry_this > span { display: block; padding: .25rem; font-weight: bold; }
.navitree__entry_this > span::before { content: " "; display: inline-block; width: 1rem; }
/* Color stuff */
/* Lighter stuff #eee */
article code,
@ -142,11 +220,24 @@ article .codeblock,
.prevnext__el,
table { background-color: #eee; }
.hypha-tabs__tab { background-color: #eee; }
.hypha-tabs__tab a { color: black; }
.hypha-tabs__tab_active { border-bottom: 2px white solid; background: white; }
@media screen and (max-width: 800px) {
.hypha-tabs { background-color: white; }
.hypha-tabs__tab { box-shadow: none; }
.hypha-tabs,
.hypha-tabs__tab { background-color: white; }
}
@media screen and (min-width: 801px) {
.hypha-tabs__tab { border: 1px #ddd solid; }
.hypha-tabs__tab_active { border-bottom: 1px white solid; }
}
.layout-card { border-radius: .25rem; background-color: white; }
.layout-card__title { font-size: 1rem; margin: 0; padding: .25rem .5rem; border-radius: .25rem .25rem 0 0; }
.layout-card__title { background-color: #eee; }
/* Other stuff */
html { 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");
@ -154,11 +245,7 @@ background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox=
header { background-color: #bbb; }
.header-links__link { color: black; }
.header-links__link:hover { background-color: #eee; }
main, .hypha-tabs__tab { background-color: white; }
.hypha-tabs__tab { clip-path: inset(-20px -20px 0 -20px); }
.hypha-tabs__tab a { color: black; }
.hypha-tabs__tab_active { border-bottom: 2px white solid; }
main { background-color: white; }
blockquote { border-left: 4px black solid; }
.wikilink_new {color:#a55858;}
@ -169,21 +256,24 @@ blockquote { border-left: 4px black solid; }
.upload-amnt { border: #eee 1px solid; }
td { border: #ddd 1px solid; }
.navitree__link:hover, .backlinks__link:hover { background-color: #eee; }
/* Dark theme! */
@media (prefers-color-scheme: dark) {
html { background: #222; color: #ddd; }
main, article, .hypha-tabs__tab, header { background-color: #343434; color: #ddd; }
main, article, .hypha-tabs__tab, header, .layout-card { background-color: #343434; color: #ddd; }
a, .wikilink_external { color: #f1fa8c; }
a:visited, .wikilink_external:visited { color: #ffb86c; }
.wikilink_new, .wikilink_new:visited { color: #dd4444; }
.navitree__link:hover, .backlinks__link:hover { background-color: #444; }
.header-links__link, .header-links__link:visited,
.prevnext__el, .prevnext__el:visited { color: #ddd; }
.header-links__link:hover { background-color: #444; }
.hypha-tabs__tab a, .hypha-tabs__tab { color: #ddd; background-color: #232323; border: 0; }
.hypha-tabs__tab_active { background-color: #343434; }
.layout-card__title, .hypha-tabs__tab_active { background-color: #343434; }
blockquote { border-left: 4px #ddd solid; }
@ -206,169 +296,170 @@ mark { background: rgba(130, 80, 30, 5); color: inherit; }
}
}
.backlinks { display: none; }
`)
//line templates/asset.qtpl:2
//line assets/assets.qtpl:2
qw422016.N().S(`
`)
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
}
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
func WriteDefaultCSS(qq422016 qtio422016.Writer) {
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
StreamDefaultCSS(qw422016)
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
qt422016.ReleaseWriter(qw422016)
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
}
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
func DefaultCSS() string {
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
qb422016 := qt422016.AcquireByteBuffer()
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
WriteDefaultCSS(qb422016)
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
qs422016 := string(qb422016.B)
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
qt422016.ReleaseByteBuffer(qb422016)
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
return qs422016
//line templates/asset.qtpl:3
//line assets/assets.qtpl:3
}
// Next three are from https://remixicon.com/
//line templates/asset.qtpl:6
//line assets/assets.qtpl:6
func StreamIconHTTP(qw422016 *qt422016.Writer) {
//line templates/asset.qtpl:6
//line assets/assets.qtpl:6
qw422016.N().S(`
`)
//line templates/asset.qtpl:7
//line assets/assets.qtpl:7
qw422016.N().S(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="#999" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.008 8.008 0 0 0 5.648 6.667zM10.03 13c.151 2.439.848 4.73 1.97 6.752A15.905 15.905 0 0 0 13.97 13h-3.94zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.008 8.008 0 0 0 19.938 13zM4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333 8.008 8.008 0 0 0 4.062 11zm5.969 0h3.938A15.905 15.905 0 0 0 12 4.248 15.905 15.905 0 0 0 10.03 11zm4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.008 8.008 0 0 0-5.648-6.667z"/></svg>
`)
//line templates/asset.qtpl:7
//line assets/assets.qtpl:7
qw422016.N().S(`
`)
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
}
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
func WriteIconHTTP(qq422016 qtio422016.Writer) {
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
StreamIconHTTP(qw422016)
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
qt422016.ReleaseWriter(qw422016)
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
}
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
func IconHTTP() string {
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
qb422016 := qt422016.AcquireByteBuffer()
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
WriteIconHTTP(qb422016)
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
qs422016 := string(qb422016.B)
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
qt422016.ReleaseByteBuffer(qb422016)
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
return qs422016
//line templates/asset.qtpl:8
//line assets/assets.qtpl:8
}
//line templates/asset.qtpl:10
//line assets/assets.qtpl:10
func StreamIconGemini(qw422016 *qt422016.Writer) {
//line templates/asset.qtpl:10
//line assets/assets.qtpl:10
qw422016.N().S(`
`)
//line templates/asset.qtpl:11
//line assets/assets.qtpl:11
qw422016.N().S(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="#999" d="M15.502 20A6.523 6.523 0 0 1 12 23.502 6.523 6.523 0 0 1 8.498 20h2.26c.326.489.747.912 1.242 1.243.495-.33.916-.754 1.243-1.243h2.259zM18 14.805l2 2.268V19H4v-1.927l2-2.268V9c0-3.483 2.504-6.447 6-7.545C15.496 2.553 18 5.517 18 9v5.805zM17.27 17L16 15.56V9c0-2.318-1.57-4.43-4-5.42C9.57 4.57 8 6.681 8 9v6.56L6.73 17h10.54zM12 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></svg>
`)
//line templates/asset.qtpl:11
//line assets/assets.qtpl:11
qw422016.N().S(`
`)
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
}
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
func WriteIconGemini(qq422016 qtio422016.Writer) {
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
StreamIconGemini(qw422016)
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
qt422016.ReleaseWriter(qw422016)
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
}
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
func IconGemini() string {
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
qb422016 := qt422016.AcquireByteBuffer()
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
WriteIconGemini(qb422016)
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
qs422016 := string(qb422016.B)
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
qt422016.ReleaseByteBuffer(qb422016)
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
return qs422016
//line templates/asset.qtpl:12
//line assets/assets.qtpl:12
}
//line templates/asset.qtpl:14
//line assets/assets.qtpl:14
func StreamIconMailto(qw422016 *qt422016.Writer) {
//line templates/asset.qtpl:14
//line assets/assets.qtpl:14
qw422016.N().S(`
`)
//line templates/asset.qtpl:15
//line assets/assets.qtpl:15
qw422016.N().S(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="#999" d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm17 4.238l-7.928 7.1L4 7.216V19h16V7.238zM4.511 5l7.55 6.662L19.502 5H4.511z"/></svg>
`)
//line templates/asset.qtpl:15
//line assets/assets.qtpl:15
qw422016.N().S(`
`)
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
}
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
func WriteIconMailto(qq422016 qtio422016.Writer) {
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
StreamIconMailto(qw422016)
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
qt422016.ReleaseWriter(qw422016)
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
}
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
func IconMailto() string {
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
qb422016 := qt422016.AcquireByteBuffer()
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
WriteIconMailto(qb422016)
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
qs422016 := string(qb422016.B)
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
qt422016.ReleaseByteBuffer(qb422016)
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
return qs422016
//line templates/asset.qtpl:16
//line assets/assets.qtpl:16
}
// This is a modified version of https://www.svgrepo.com/svg/232085/rat
//line templates/asset.qtpl:19
//line assets/assets.qtpl:19
func StreamIconGopher(qw422016 *qt422016.Writer) {
//line templates/asset.qtpl:19
//line assets/assets.qtpl:19
qw422016.N().S(`
`)
//line templates/asset.qtpl:20
//line assets/assets.qtpl:20
qw422016.N().S(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16">
<path fill="#999" d="M447.238,204.944v-70.459c0-8.836-7.164-16-16-16c-34.051,0-64.414,21.118-75.079,55.286
C226.094,41.594,0,133.882,0,319.435c0,0.071,0.01,0.14,0.011,0.21c0.116,44.591,36.423,80.833,81.04,80.833h171.203
@ -381,34 +472,34 @@ c55.425-8.382,107.014,29.269,115.759,84.394H295.484z"/>
<circle fill="#999" cx="415.238" cy="260.05" r="21.166"/>
</svg>
`)
//line templates/asset.qtpl:20
//line assets/assets.qtpl:20
qw422016.N().S(`
`)
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
}
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
func WriteIconGopher(qq422016 qtio422016.Writer) {
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
StreamIconGopher(qw422016)
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
qt422016.ReleaseWriter(qw422016)
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
}
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
func IconGopher() string {
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
qb422016 := qt422016.AcquireByteBuffer()
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
WriteIconGopher(qb422016)
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
qs422016 := string(qb422016.B)
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
qt422016.ReleaseByteBuffer(qb422016)
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
return qs422016
//line templates/asset.qtpl:21
//line assets/assets.qtpl:21
}

View File

@ -1,33 +1,99 @@
/* Layout stuff */
@media screen and (min-width: 800px) {
main { padding:1rem 2rem; margin: 0 auto; width: 800px; }
.hypha-tabs { padding: 1rem 2rem; margin: 0 auto; width: 800px; }
header { margin: 0 auto; width: 800px; }
.amnt-grid { display: grid; grid-template-columns: 1fr 1fr; }
.upload-binary__input { display: block; margin: .25rem 0; }
.modal__title { font-size: 2rem; }
.modal__title_small { font-size: 1.5rem; }
.modal__confirmation-msg { margin: 0 0 .5rem 0; }
.modal__action { display: inline-block; font-size: 1rem; padding: .25rem; border-radius: .25rem; }
.modal__submit { border: 1px #999 solid; }
.modal__cancel { border: 1px #999 dashed; text-decoration: none; }
.hypha-list { padding-left: 0; }
.hypha-list__entry { list-style-type: none; }
.hypha-list__link { text-decoration: none; display: inline-block; padding: .25rem; }
.hypha-list__link:hover { text-decoration: underline; }
.hypha-list__amnt-type { font-size: smaller; color: #999; }
/* General element positions, from small to big */
/* Phones and whatnot */
.layout { display: grid; row-gap: 1rem; }
header { width: 100%; margin-bottom: 1rem; }
.header-links__list, .hypha-tabs__flex { margin: 0; padding: 0; display: flex; flex-wrap: wrap; }
.header-links__entry, .hypha-tabs__tab { list-style-type: none; }
.header-links__entry { margin-right: .5rem; }
.header-links__entry_user { font-style:italic; }
.header-links__link { display: inline-block; padding: .25rem; text-decoration: none; }
.hypha-tabs { padding: 0; margin: 0; }
.hypha-tabs__tab { margin-right: .5rem; padding: 0; }
.hypha-tabs__link { display: inline-block; padding: .25rem; text-decoration: none; }
.hypha-tabs__selection { display: inline-block; padding: .25rem; font-weight: bold; }
.layout-card li { list-style-type: none; }
.backlinks__list { padding: 0; margin: 0; }
.backlinks__link { text-decoration: none; display: block; padding: .25rem; padding-left: 1.25rem; }
@media screen and (max-width: 800px) {
.amnt-grid { grid-template-columns: 1fr; }
.layout { grid-template-column: auto; grid-template-row: auto auto auto; }
.main-width { width: 100%; }
main { padding: 1rem; margin: 0; }
}
/* No longer a phone but still small screen: draw normal tabs, center main */
@media screen and (min-width: 801px) {
.main-width { padding: 1rem 2rem; width: 800px; margin: 0 auto; }
main { border-radius: .25rem; }
.layout-card { width: 800px; margin: 0 auto; }
.header-links { padding: 0; }
.header-links__entry { margin-right: 1.5rem; }
.header-links__entry_user { margin: 0 2rem 0 auto; }
.header-links__entry:nth-of-type(1),
.hypha-tabs__tab:nth-of-type(1) { margin-left: 2rem; }
.hypha-tabs__tab { margin-right: 1.5rem; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); border-bottom: 2px #ddd solid; padding: 0 .5rem; }
.hypha-tabs { padding: 0; }
.hypha-tabs__tab { border-radius: .25rem .25rem 0 0; margin-right: 0; }
.hypha-tabs__selection, .hypha-tabs__link { padding: .25rem .5rem; }
.header-links__entry:nth-of-type(1), .hypha-tabs__tab:nth-of-type(1) { margin-left: 2rem; }
}
@media screen and (max-width: 800px) {
main { padding: 1rem; margin: 0; width: 100%; }
.hypha-tabs{ padding: 1rem; margin: 0; width: 100%; }
.hypha-tabs__tab { box-shadow: none; margin-right: .5rem; padding: .25rem .5rem; }
header { width: 100%; }
.header-links__entry { margin-right: .5rem; }
/* Wide enough to fit two columns ok */
@media screen and (min-width: 1100px) {
.layout { display: grid; grid-template-columns: auto 1fr; column-gap: 1rem; margin: 0 1rem; row-gap: 1rem; }
.main-width { margin: 0; }
main { grid-column: 1 / span 1; grid-row: 1 / span 2; }
.relative-hyphae { grid-column: 2 / span 1; grid-row: 1 / span 1; }
.layout-card { width: 100%; }
}
@media screen and (min-width: 1250px) {
.layout { grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); }
.layout-card { max-width: 16rem; }
.main-width { margin: 0 auto; }
.backlinks { grid-column: 1 / span 1; margin-right: 0; }
main { grid-column: 2 / span 1; }
.relative-hyphae { grid-column: 3 / span 1; margin-left: 0; }
.backlinks__title { text-align: right; }
.backlinks__link { text-align: right; padding-right: 1.25rem; padding-left: .25rem; }
}
*, *::before, *::after {box-sizing: border-box;}
html { height:100%; padding:0; }
body {height:100%; margin:0; font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;}
main {border-radius: 0 0 .25rem .25rem; }
body {height:100%; margin:0; }
body, input { font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;}
main > form {margin-bottom:1rem;}
textarea {font-size:16px; font-family: 'PT Sans', 'Liberation Sans', sans-serif;}
.edit_no-preview {height:100%;}
.edit_with-preview .edit-form textarea { min-height: 500px; }
.edit { min-height: 80vh; }
.edit__title { margin-top: 0; }
.edit__preview { border: 2px dashed #ddd; }
.edit-form {height:90%;}
.edit-form textarea {width:100%;height:90%;}
.edit-form {height:70vh;}
.edit-form textarea {width:100%;height:95%;}
.edit-form__save { font-weight: bold; }
.icon {margin-right: .25rem; vertical-align: bottom; }
main h1:not(.navi-title) {font-size:1.7rem;}
@ -42,7 +108,10 @@ blockquote { margin-left: 0; padding-left: 1rem; }
.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; }
main h1, main h2, main h3, main h4, main h5, main h6 { margin: 1.5rem 0 0 0; }
.heading__link { text-decoration: none; display: inline-block; }
.heading__link::after { width: 1rem; content: "§"; color: transparent; }
.heading__link:hover::after, .heading__link:active::after { color: #999; }
article p { margin: .5rem 0; }
article ul, ol { padding-left: 1.5rem; margin: .5rem 0; }
article code { padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; }
@ -60,6 +129,7 @@ article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25
.binary-container_with-video video,
.binary-container_with-audio audio {width: 100%}
.subhyphae__title { padding-bottom: .5rem; clear: both; }
.navi-title { padding-bottom: .5rem; margin: .25rem 0; }
.navi-title a {text-decoration:none; }
.navi-title__separator { margin: 0 .25rem; }
@ -76,19 +146,11 @@ figcaption { padding-bottom: .5rem; }
#new-name {width:100%;}
header { margin-bottom: .5rem; }
.header-links__entry_user { font-style:italic; }
.header-links__link { text-decoration: none; display: block; width: 100%; height: 100%; padding: .25rem; }
.hypha-tabs { padding: 0; }
.header-links__list, .hypha-tabs__flex { margin: 0; padding: 0; display: flex; flex-wrap: wrap; }
.header-links__entry, .hypha-tabs__tab { list-style-type: none; }
.hypha-tabs__tab a { text-decoration: none; }
.hypha-tabs__tab_active { font-weight: bold; }
.rc-entry { display: grid; list-style-type: none; padding: .25rem; grid-template-columns: 1fr 1fr; }
.rc-entry { display: grid; list-style-type: none; padding: .25rem; grid-template-columns: 1fr 1fr; border-radius: .25rem; }
.rc-entry__time { font-style: italic; }
.rc-entry__hash { font-style: italic; text-align: right; }
.rc-entry__links { grid-column: 1 / span 2; }
.rc-entry__links, .rc-entry__msg { 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; border-radius: .25rem; }
@ -107,6 +169,22 @@ table { border: #ddd 1px solid; border-radius: .25rem; min-width: 4rem; }
td { padding: .25rem; }
caption { caption-side: top; font-size: small; }
.subhyphae__list, .subhyphae__list ul { display: flex; padding: 0; margin: 0; flex-wrap: wrap; }
.subhyphae__entry { list-style-type: none; border: 1px solid #999; padding: 0; margin: .125rem; border-radius: .25rem; }
.subhyphae__link { display: block; padding: .25rem; text-decoration: none; }
.subhyphae__link:hover { background: #eee; }
.navitree { padding: 0; margin: 0; }
.navitree__entry { }
.navitree > .navitree__entry > a::before { display: inline-block; width: .5rem; color: #999; margin: 0 .25rem; }
.navitree > .navitree__entry_infertile > a::before { content: " "} /* nbsp, careful */
.navitree > .navitree__sibling_fertile > a::before { content: "▸"}
.navitree__trunk { border-left: 1px #999 solid; }
.navitree__link { text-decoration: none; display: block; padding: .25rem; }
.navitree__entry_this > span { display: block; padding: .25rem; font-weight: bold; }
.navitree__entry_this > span::before { content: " "; display: inline-block; width: 1rem; }
/* Color stuff */
/* Lighter stuff #eee */
article code,
@ -117,11 +195,24 @@ article .codeblock,
.prevnext__el,
table { background-color: #eee; }
.hypha-tabs__tab { background-color: #eee; }
.hypha-tabs__tab a { color: black; }
.hypha-tabs__tab_active { border-bottom: 2px white solid; background: white; }
@media screen and (max-width: 800px) {
.hypha-tabs { background-color: white; }
.hypha-tabs__tab { box-shadow: none; }
.hypha-tabs,
.hypha-tabs__tab { background-color: white; }
}
@media screen and (min-width: 801px) {
.hypha-tabs__tab { border: 1px #ddd solid; }
.hypha-tabs__tab_active { border-bottom: 1px white solid; }
}
.layout-card { border-radius: .25rem; background-color: white; }
.layout-card__title { font-size: 1rem; margin: 0; padding: .25rem .5rem; border-radius: .25rem .25rem 0 0; }
.layout-card__title { background-color: #eee; }
/* Other stuff */
html { 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");
@ -129,11 +220,7 @@ background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox=
header { background-color: #bbb; }
.header-links__link { color: black; }
.header-links__link:hover { background-color: #eee; }
main, .hypha-tabs__tab { background-color: white; }
.hypha-tabs__tab { clip-path: inset(-20px -20px 0 -20px); }
.hypha-tabs__tab a { color: black; }
.hypha-tabs__tab_active { border-bottom: 2px white solid; }
main { background-color: white; }
blockquote { border-left: 4px black solid; }
.wikilink_new {color:#a55858;}
@ -144,21 +231,24 @@ blockquote { border-left: 4px black solid; }
.upload-amnt { border: #eee 1px solid; }
td { border: #ddd 1px solid; }
.navitree__link:hover, .backlinks__link:hover { background-color: #eee; }
/* Dark theme! */
@media (prefers-color-scheme: dark) {
html { background: #222; color: #ddd; }
main, article, .hypha-tabs__tab, header { background-color: #343434; color: #ddd; }
main, article, .hypha-tabs__tab, header, .layout-card { background-color: #343434; color: #ddd; }
a, .wikilink_external { color: #f1fa8c; }
a:visited, .wikilink_external:visited { color: #ffb86c; }
.wikilink_new, .wikilink_new:visited { color: #dd4444; }
.navitree__link:hover, .backlinks__link:hover { background-color: #444; }
.header-links__link, .header-links__link:visited,
.prevnext__el, .prevnext__el:visited { color: #ddd; }
.header-links__link:hover { background-color: #444; }
.hypha-tabs__tab a, .hypha-tabs__tab { color: #ddd; background-color: #232323; border: 0; }
.hypha-tabs__tab_active { background-color: #343434; }
.layout-card__title, .hypha-tabs__tab_active { background-color: #343434; }
blockquote { border-left: 4px #ddd solid; }
@ -181,3 +271,4 @@ mark { background: rgba(130, 80, 30, 5); color: inherit; }
}
}
.backlinks { display: none; }

View File

Before

Width:  |  Height:  |  Size: 473 B

After

Width:  |  Height:  |  Size: 473 B

View File

Before

Width:  |  Height:  |  Size: 951 B

After

Width:  |  Height:  |  Size: 951 B

View File

Before

Width:  |  Height:  |  Size: 627 B

After

Width:  |  Height:  |  Size: 627 B

View File

Before

Width:  |  Height:  |  Size: 261 B

After

Width:  |  Height:  |  Size: 261 B

View File

@ -19,6 +19,7 @@ func init() {
flag.StringVar(&util.AuthMethod, "auth-method", "none", "What auth method to use. Variants: \"none\", \"fixed\"")
flag.StringVar(&util.FixedCredentialsPath, "fixed-credentials-path", "mycocredentials.json", "Used when -auth-method=fixed. Path to file with user credentials.")
flag.StringVar(&util.HeaderLinksHypha, "header-links-hypha", "", "Optional hypha that overrides the header links")
flag.StringVar(&util.GeminiCertPath, "gemini-cert-path", "", "Directory where you store Gemini certificates. Leave empty if you don't want to use Gemini.")
}
// Do the things related to cli args and die maybe
@ -41,9 +42,9 @@ func parseCliArgs() {
util.URL = "http://0.0.0.0:" + util.ServerPort
}
util.HomePage = CanonicalName(util.HomePage)
util.UserHypha = CanonicalName(util.UserHypha)
util.HeaderLinksHypha = CanonicalName(util.HeaderLinksHypha)
util.HomePage = util.CanonicalName(util.HomePage)
util.UserHypha = util.CanonicalName(util.UserHypha)
util.HeaderLinksHypha = util.CanonicalName(util.HeaderLinksHypha)
switch util.AuthMethod {
case "none":

84
gemini.go Normal file
View File

@ -0,0 +1,84 @@
package main
import (
"crypto/tls"
"crypto/x509/pkix"
"io/ioutil"
"log"
"path/filepath"
"time"
"git.sr.ht/~adnano/go-gemini"
"git.sr.ht/~adnano/go-gemini/certificate"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/util"
)
func geminiHomeHypha(w *gemini.ResponseWriter, rq *gemini.Request) {
log.Println(rq.URL)
w.Write([]byte(`# MycorrhizaWiki
You have successfully served the wiki through Gemini. Currently, support is really work-in-progress; you should resort to using Mycorrhiza through the web protocols.
Visit home hypha:
=> /hypha/` + util.HomePage))
}
func geminiHypha(w *gemini.ResponseWriter, rq *gemini.Request) {
log.Println(rq.URL)
var (
hyphaName = geminiHyphaNameFromRq(rq, "page", "hypha")
h = hyphae.ByName(hyphaName)
hasAmnt = h.Exists && h.BinaryPath != ""
contents string
)
if h.Exists {
fileContentsT, errT := ioutil.ReadFile(h.TextPath)
if errT == nil {
md := markup.Doc(hyphaName, string(fileContentsT))
contents = md.AsGemtext()
}
}
if hasAmnt {
w.Write([]byte("This hypha has an attachment\n"))
}
w.Write([]byte(contents))
}
func handleGemini() {
if util.GeminiCertPath == "" {
return
}
certPath, err := filepath.Abs(util.GeminiCertPath)
if err != nil {
log.Fatal(err)
}
var server gemini.Server
server.ReadTimeout = 30 * time.Second
server.WriteTimeout = 1 * time.Minute
if err := server.Certificates.Load(certPath); err != nil {
log.Fatal(err)
}
server.CreateCertificate = func(hostname string) (tls.Certificate, error) {
return certificate.Create(certificate.CreateOptions{
Subject: pkix.Name{
CommonName: hostname,
},
DNSNames: []string{hostname},
Duration: 365 * 24 * time.Hour,
})
}
var mux gemini.ServeMux
mux.HandleFunc("/", geminiHomeHypha)
mux.HandleFunc("/hypha/", geminiHypha)
mux.HandleFunc("/page/", geminiHypha)
server.Handle("localhost", &mux)
if err := server.ListenAndServe(); err != nil {
log.Fatal(err)
}
}

2
go.mod
View File

@ -3,8 +3,10 @@ module github.com/bouncepaw/mycorrhiza
go 1.14
require (
git.sr.ht/~adnano/go-gemini v0.1.13
github.com/adrg/xdg v0.2.2
github.com/gorilla/feeds v1.1.1
github.com/kr/pretty v0.2.1 // indirect
github.com/valyala/quicktemplate v1.6.3
tildegit.org/solderpunk/gemcert v0.0.0-20200801165357-fc14deb27512 // indirect
)

4
go.sum
View File

@ -1,3 +1,5 @@
git.sr.ht/~adnano/go-gemini v0.1.13 h1:vzKkkVrOzMpfJ1AAeE/PChg0Rw5Zf+9HrnwsgVxXUT4=
git.sr.ht/~adnano/go-gemini v0.1.13/go.mod h1:If1VxEWcZDrRt5FeAFnGTcM2Ud1E3BXs3VJ5rnZWKq0=
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=
@ -33,3 +35,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
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=
tildegit.org/solderpunk/gemcert v0.0.0-20200801165357-fc14deb27512 h1:reGEt1vmGompn/6FitHdBatILTsK9CYnQOCw3weoW/s=
tildegit.org/solderpunk/gemcert v0.0.0-20200801165357-fc14deb27512/go.mod h1:gqBK7AJ5wPR1bpFOuPmlQObYxwXrFdZmNb2vdzquqoA=

View File

@ -10,7 +10,6 @@ import (
"strings"
"time"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
)
@ -77,8 +76,8 @@ func (rev Revision) TimeString() string {
return rev.Time.Format(time.RFC822)
}
// HyphaeLinks returns a comma-separated list of hyphae that were affected by this revision as HTML string.
func (rev Revision) HyphaeLinks() (html string) {
// HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
func (rev Revision) HyphaeLinksHTML() (html string) {
hyphae := rev.hyphaeAffected()
for i, hyphaName := range hyphae {
if i > 0 {
@ -92,7 +91,7 @@ func (rev Revision) HyphaeLinks() (html string) {
func (rev *Revision) descriptionForFeed() (html string) {
return fmt.Sprintf(
`<p>%s</p>
<p><b>Hyphae affected:</b> %s</p>`, rev.Message, rev.HyphaeLinks())
<p><b>Hyphae affected:</b> %s</p>`, rev.Message, rev.HyphaeLinksHTML())
}
// Try and guess what link is the most important by looking at the message.
@ -111,23 +110,6 @@ func (rev *Revision) bestLink() string {
}
}
func (rev Revision) RecentChangesEntry() (html string) {
if user.AuthUsed && rev.Username != "anon" {
return fmt.Sprintf(`
<li class="rc-entry__time"><time>%[1]s</time></li>
<li class="rc-entry__hash">%[2]s</li>
<li class="rc-entry__links">%[5]s</li>
<li class="rc-entry__msg">%[6]s <span class="rc-entry__author">by <a href="/page/%[3]s/%[4]s" rel="author">%[4]s</a></span></li>
`, rev.TimeString(), rev.Hash, util.UserHypha, rev.Username, rev.HyphaeLinks(), rev.Message)
}
return fmt.Sprintf(`
<li class="rc-entry__time"><time>%[1]s</time></li>
<li class="rc-entry__hash">%[2]s</li>
<li class="rc-entry__links">%[3]s</li>
<li class="rc-entry__msg">%[4]s</li>
`, rev.TimeString(), rev.Hash, rev.HyphaeLinks(), rev.Message)
}
// Path to git executable. Set at init()
var gitpath string

View File

@ -9,7 +9,6 @@ import (
"strings"
"time"
"github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/gorilla/feeds"
)
@ -61,7 +60,7 @@ func RecentChangesJSON() (string, error) {
return recentChangesFeed().ToJSON()
}
func RecentChanges(n int) string {
func RecentChanges(n int) []Revision {
var (
out, err = gitsh(
"log", "--oneline", "--no-merges",
@ -75,11 +74,7 @@ func RecentChanges(n int) string {
revs = append(revs, parseRevisionLine(line))
}
}
entries := make([]string, len(revs))
for i, rev := range revs {
entries[i] = rev.RecentChangesEntry()
}
return templates.RecentChangesHTML(entries, n)
return revs
}
// FileChanged tells you if the file has been changed.
@ -177,6 +172,6 @@ func parseRevisionLine(line string) Revision {
// See how the file with `filepath` looked at commit with `hash`.
func FileAtRevision(filepath, hash string) (string, error) {
out, err := gitsh("show", hash+":"+filepath)
out, err := gitsh("show", hash+":"+strings.TrimPrefix(filepath, util.WikiDir+"/"))
return out.String(), err
}

View File

@ -59,12 +59,16 @@ func (hop *HistoryOp) gitop(args ...string) *HistoryOp {
return hop
}
// WithError appends the `err` to the list of errors.
func (hop *HistoryOp) WithError(err error) *HistoryOp {
// WithErr appends the `err` to the list of errors.
func (hop *HistoryOp) WithErr(err error) *HistoryOp {
hop.Errs = append(hop.Errs, err)
return hop
}
func (hop *HistoryOp) WithErrAbort(err error) *HistoryOp {
return hop.WithErr(err).Abort()
}
// WithFilesRemoved git-rm-s all passed `paths`. Paths can be rooted or not. Paths that are empty strings are ignored.
func (hop *HistoryOp) WithFilesRemoved(paths ...string) *HistoryOp {
args := []string{"rm", "--quiet", "--"}
@ -134,3 +138,11 @@ func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
}
return hop
}
func (hop *HistoryOp) HasErrors() bool {
return len(hop.Errs) > 0
}
func (hop *HistoryOp) FirstErrorText() string {
return hop.Errs[0].Error()
}

33
http_admin.go Normal file
View File

@ -0,0 +1,33 @@
package main
import (
"log"
"net/http"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/views"
)
// This is not init(), because user.AuthUsed is not set at init-stage.
func initAdmin() {
if user.AuthUsed {
http.HandleFunc("/admin", handlerAdmin)
http.HandleFunc("/admin/shutdown", handlerAdminShutdown)
}
}
func handlerAdmin(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
if user.CanProceed(rq, "admin") {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(base("Admin panel", views.AdminPanelHTML(), user.FromRequest(rq))))
}
}
func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
if user.CanProceed(rq, "admin/shutdown") && rq.Method == "POST" {
log.Fatal("An admin commanded the wiki to shutdown")
}
}

View File

@ -4,8 +4,9 @@ import (
"log"
"net/http"
"github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
)
func init() {
@ -28,7 +29,7 @@ func handlerLogout(w http.ResponseWriter, rq *http.Request) {
log.Println("Unknown user tries to log out")
w.WriteHeader(http.StatusForbidden)
}
w.Write([]byte(base("Logout?", templates.LogoutHTML(can), u)))
w.Write([]byte(base("Logout?", views.LogoutHTML(can), u)))
}
func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) {
@ -39,12 +40,12 @@ func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) {
func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
username = CanonicalName(rq.PostFormValue("username"))
username = util.CanonicalName(rq.PostFormValue("username"))
password = rq.PostFormValue("password")
err = user.LoginDataHTTP(w, rq, username, password)
)
if err != "" {
w.Write([]byte(base(err, templates.LoginErrorHTML(err), user.EmptyUser())))
w.Write([]byte(base(err, views.LoginErrorHTML(err), user.EmptyUser())))
} else {
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
@ -58,5 +59,5 @@ func handlerLogin(w http.ResponseWriter, rq *http.Request) {
} else {
w.WriteHeader(http.StatusForbidden)
}
w.Write([]byte(base("Login", templates.LoginHTML(), user.EmptyUser())))
w.Write([]byte(base("Login", views.LoginHTML(), user.EmptyUser())))
}

View File

@ -8,9 +8,9 @@ import (
"strings"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
)
func init() {
@ -35,7 +35,7 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) {
log.Println("Found", len(revs), "revisions for", hyphaName)
util.HTTP200Page(w,
base(hyphaName, templates.HistoryHTML(rq, hyphaName, list), user.FromRequest(rq)))
base(hyphaName, views.HistoryHTML(rq, hyphaName, list), user.FromRequest(rq)))
}
// Recent changes
@ -46,7 +46,7 @@ func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
n, err = strconv.Atoi(noPrefix)
)
if err == nil && n < 101 {
util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", history.RecentChanges(n), user.FromRequest(rq)))
util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", views.RecentChangesHTML(n), user.FromRequest(rq)))
} else {
http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther)
}

View File

@ -5,10 +5,13 @@ import (
"log"
"net/http"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/shroom"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
)
func init() {
@ -25,182 +28,140 @@ func init() {
http.HandleFunc("/unattach-confirm/", handlerUnattachConfirm)
}
func handlerUnattachAsk(w http.ResponseWriter, rq *http.Request) {
func factoryHandlerAsker(
actionPath string,
asker func(*user.User, *hyphae.Hypha) (error, string),
succTitleTemplate string,
succPageTemplate func(*http.Request, string, bool) string,
) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
hyphaName = HyphaNameFromRq(rq, "unattach-ask")
hd, isOld = HyphaStorage[hyphaName]
hasAmnt = hd != nil && hd.binaryPath != ""
)
if !hasAmnt {
HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach")
log.Println("Rejected (no amnt):", rq.URL)
return
} else if ok := user.CanProceed(rq, "unattach-confirm"); !ok {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments")
log.Println("Rejected (no rights):", rq.URL)
return
}
util.HTTP200Page(w, base("Unattach "+hyphaName+"?", templates.UnattachAskHTML(rq, hyphaName, isOld), user.FromRequest(rq)))
}
func handlerUnattachConfirm(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
hyphaName = HyphaNameFromRq(rq, "unattach-confirm")
hyphaData, isOld = HyphaStorage[hyphaName]
hasAmnt = hyphaData != nil && hyphaData.binaryPath != ""
hyphaName = HyphaNameFromRq(rq, actionPath)
h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq)
)
if !u.CanProceed("unattach-confirm") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments")
log.Println("Rejected (no rights):", rq.URL)
if err, errtitle := asker(u, h); err != nil {
HttpErr(
w,
http.StatusInternalServerError,
hyphaName,
errtitle,
err.Error())
return
}
if !hasAmnt {
HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach")
log.Println("Rejected (no amnt):", rq.URL)
return
} else if !isOld {
// The precondition is to have the hypha in the first place.
HttpErr(w, http.StatusPreconditionFailed, hyphaName,
"Error: no such hypha",
"Could not unattach this hypha because it does not exist")
return
util.HTTP200Page(
w,
base(
fmt.Sprintf(succTitleTemplate, hyphaName),
succPageTemplate(rq, hyphaName, h.Exists),
u))
}
if hop := hyphaData.UnattachHypha(hyphaName, u); len(hop.Errs) != 0 {
}
var handlerUnattachAsk = factoryHandlerAsker(
"unattach-ask",
shroom.CanUnattach,
"Unattach %s?",
views.UnattachAskHTML,
)
var handlerDeleteAsk = factoryHandlerAsker(
"delete-ask",
shroom.CanDelete,
"Delete %s?",
views.DeleteAskHTML,
)
var handlerRenameAsk = factoryHandlerAsker(
"rename-ask",
shroom.CanRename,
"Rename %s?",
views.RenameAskHTML,
)
func factoryHandlerConfirmer(
actionPath string,
confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.HistoryOp, string),
) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
hyphaName = HyphaNameFromRq(rq, actionPath)
h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq)
)
if hop, errtitle := confirmer(h, u, rq); hop.HasErrors() {
HttpErr(w, http.StatusInternalServerError, hyphaName,
"Error: could not unattach hypha",
fmt.Sprintf("Could not unattach this hypha due to internal errors. Server errors: <code>%v</code>", hop.Errs))
errtitle,
hop.FirstErrorText())
return
}
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
}
func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
hyphaName = HyphaNameFromRq(rq, "rename-ask")
_, isOld = HyphaStorage[hyphaName]
u = user.FromRequest(rq)
)
if !u.CanProceed("rename-confirm") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.")
log.Println("Rejected", rq.URL)
return
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
}
util.HTTP200Page(w, base("Rename "+hyphaName+"?", templates.RenameAskHTML(rq, hyphaName, isOld), u))
}
func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var handlerUnattachConfirm = factoryHandlerConfirmer(
"unattach-confirm",
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) {
return shroom.UnattachHypha(u, h)
},
)
var handlerDeleteConfirm = factoryHandlerConfirmer(
"delete-confirm",
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) {
return shroom.DeleteHypha(u, h)
},
)
var handlerRenameConfirm = factoryHandlerConfirmer(
"rename-confirm",
func(oldHypha *hyphae.Hypha, u *user.User, rq *http.Request) (*history.HistoryOp, string) {
var (
hyphaName = HyphaNameFromRq(rq, "rename-confirm")
_, isOld = HyphaStorage[hyphaName]
newName = CanonicalName(rq.PostFormValue("new-name"))
_, newNameIsUsed = HyphaStorage[newName]
newName = util.CanonicalName(rq.PostFormValue("new-name"))
recursive = rq.PostFormValue("recursive") == "true"
u = user.FromRequest(rq)
newHypha = hyphae.ByName(newName)
)
switch {
case !u.CanProceed("rename-confirm"):
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.")
log.Println("Rejected", rq.URL)
case newNameIsUsed:
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: hypha exists",
fmt.Sprintf("Hypha named <a href='/page/%s'>%s</a> already exists.", hyphaName, hyphaName))
case newName == "":
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: no name",
"No new name is given.")
case !isOld:
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: no such hypha",
"Cannot rename a hypha that does not exist yet.")
case !HyphaPattern.MatchString(newName):
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: invalid name",
"Invalid new name. Names cannot contain characters <code>^?!:#@&gt;&lt;*|\"\\'&amp;%</code>")
default:
if hop := RenameHypha(hyphaName, newName, recursive, u); len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName,
"Error: could not rename hypha",
fmt.Sprintf("Could not rename this hypha due to an internal error. Server errors: <code>%v</code>", hop.Errs))
} else {
http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther)
}
}
}
// handlerDeleteAsk shows a delete dialog.
func handlerDeleteAsk(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
hyphaName = HyphaNameFromRq(rq, "delete-ask")
_, isOld = HyphaStorage[hyphaName]
u = user.FromRequest(rq)
)
if !u.CanProceed("delete-ask") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.")
log.Println("Rejected", rq.URL)
return
}
util.HTTP200Page(w, base("Delete "+hyphaName+"?", templates.DeleteAskHTML(rq, hyphaName, isOld), u))
}
// handlerDeleteConfirm deletes a hypha for sure
func handlerDeleteConfirm(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
hyphaName = HyphaNameFromRq(rq, "delete-confirm")
hyphaData, isOld = HyphaStorage[hyphaName]
u = user.FromRequest(rq)
)
if !u.CanProceed("delete-confirm") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.")
log.Println("Rejected", rq.URL)
return
}
if !isOld {
// The precondition is to have the hypha in the first place.
HttpErr(w, http.StatusPreconditionFailed, hyphaName,
"Error: no such hypha",
"Could not delete this hypha because it does not exist.")
return
}
if hop := hyphaData.DeleteHypha(hyphaName, u); len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName,
"Error: could not delete hypha",
fmt.Sprintf("Could not delete this hypha due to internal errors. Server errors: <code>%v</code>", hop.Errs))
return
}
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
}
return shroom.RenameHypha(oldHypha, newHypha, recursive, u)
},
)
// handlerEdit shows the edit form. It doesn't edit anything actually.
func handlerEdit(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
hyphaName = HyphaNameFromRq(rq, "edit")
hyphaData, isOld = HyphaStorage[hyphaName]
h = hyphae.ByName(hyphaName)
warning string
textAreaFill string
err error
u = user.FromRequest(rq)
)
if !u.CanProceed("edit") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
log.Println("Rejected", rq.URL)
if err, errtitle := shroom.CanEdit(u, h); err != nil {
HttpErr(w, http.StatusInternalServerError, hyphaName,
errtitle,
err.Error())
return
}
if isOld {
textAreaFill, err = FetchTextPart(hyphaData)
if h.Exists {
textAreaFill, err = shroom.FetchTextPart(h)
if err != nil {
log.Println(err)
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", "Could not fetch text data")
HttpErr(w, http.StatusInternalServerError, hyphaName,
"Error",
"Could not fetch text data")
return
}
} else {
warning = `<p>You are creating a new hypha.</p>`
warning = `<p class="warning warning_new-hypha">You are creating a new hypha.</p>`
}
util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(rq, hyphaName, textAreaFill, warning), u))
util.HTTP200Page(
w,
base(
"Edit "+hyphaName,
views.EditHTML(rq, hyphaName, textAreaFill, warning),
u))
}
// handlerUploadText uploads a new text part for the hypha.
@ -208,62 +169,79 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
hyphaName = HyphaNameFromRq(rq, "upload-text")
h = hyphae.ByName(hyphaName)
textData = rq.PostFormValue("text")
action = rq.PostFormValue("action")
u = user.FromRequest(rq)
hop *history.HistoryOp
errtitle string
)
if !u.CanProceed("upload-text") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
log.Println("Rejected", rq.URL)
if action != "Preview" {
hop, errtitle = shroom.UploadText(h, []byte(textData), u)
if hop.HasErrors() {
HttpErr(w, http.StatusForbidden, hyphaName,
errtitle,
hop.FirstErrorText())
return
}
if textData == "" {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
return
}
if action == "Preview" {
util.HTTP200Page(w, base("Preview "+hyphaName, templates.PreviewHTML(rq, hyphaName, textData, "", markup.Doc(hyphaName, textData).AsHTML()), u))
} else if hop := UploadText(hyphaName, textData, u); len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
util.HTTP200Page(
w,
base(
"Preview "+hyphaName,
views.PreviewHTML(
rq,
hyphaName,
textData,
"",
markup.Doc(hyphaName, textData).AsHTML()),
u))
} else {
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
}
}
// handlerUploadBinary uploads a new binary part for the hypha.
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
rq.ParseMultipartForm(10 << 20) // Set upload limit
var (
hyphaName = HyphaNameFromRq(rq, "upload-binary")
h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq)
file, handler, err = rq.FormFile("binary")
)
if !u.CanProceed("upload-binary") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.")
log.Println("Rejected", rq.URL)
return
if err != nil {
HttpErr(w, http.StatusInternalServerError, hyphaName,
"Error",
err.Error())
}
rq.ParseMultipartForm(10 << 20) // Set upload limit
file, handler, err := rq.FormFile("binary")
if file != nil {
defer file.Close()
if err, errtitle := shroom.CanAttach(u, h); err != nil {
HttpErr(w, http.StatusInternalServerError, hyphaName,
errtitle,
err.Error())
}
// If file is not passed:
if err != nil {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed")
return
}
// If file is passed:
if file != nil {
defer file.Close()
}
var (
mime = handler.Header.Get("Content-Type")
hop = UploadBinary(hyphaName, mime, file, u)
hop, errtitle = shroom.UploadBinary(h, mime, file, u)
)
if len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
if hop.HasErrors() {
HttpErr(w, http.StatusInternalServerError, hyphaName, errtitle, hop.FirstErrorText())
return
}
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
}

View File

@ -10,18 +10,35 @@ import (
"strings"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/tree"
"github.com/bouncepaw/mycorrhiza/mimetype"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
)
func init() {
http.HandleFunc("/page/", handlerPage)
http.HandleFunc("/page/", handlerHypha)
http.HandleFunc("/hypha/", handlerHypha)
http.HandleFunc("/text/", handlerText)
http.HandleFunc("/binary/", handlerBinary)
http.HandleFunc("/rev/", handlerRevision)
http.HandleFunc("/attachment/", handlerAttachment)
}
func handlerAttachment(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
hyphaName = HyphaNameFromRq(rq, "attachment")
h = hyphae.ByName(hyphaName)
u = user.FromRequest(rq)
)
util.HTTP200Page(w,
views.BaseHTML(
fmt.Sprintf("Attachment of %s", util.BeautifulName(hyphaName)),
views.AttachmentMenuHTML(rq, h, u),
u))
}
// handlerRevision displays a specific revision of text part a page
@ -31,37 +48,34 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/")
firstSlashIndex = strings.IndexRune(shorterUrl, '/')
revHash = shorterUrl[:firstSlashIndex]
hyphaName = CanonicalName(shorterUrl[firstSlashIndex+1:])
hyphaName = util.CanonicalName(shorterUrl[firstSlashIndex+1:])
h = hyphae.ByName(hyphaName)
contents = fmt.Sprintf(`<p>This hypha had no text at this revision.</p>`)
textPath = hyphaName + ".myco"
textContents, err = history.FileAtRevision(textPath, revHash)
textContents, err = history.FileAtRevision(h.TextPath, revHash)
u = user.FromRequest(rq)
)
if err == nil {
contents = markup.Doc(hyphaName, textContents).AsHTML()
}
treeHTML, _, _ := tree.Tree(hyphaName, IterateHyphaNamesWith)
page := templates.RevisionHTML(
page := views.RevisionHTML(
rq,
hyphaName,
naviTitle(hyphaName),
h,
contents,
treeHTML,
revHash,
)
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(base(hyphaName, page, u)))
w.Write([]byte(base(util.BeautifulName(hyphaName), page, u)))
}
// handlerText serves raw source text of the hypha.
func handlerText(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
hyphaName := HyphaNameFromRq(rq, "text")
if data, ok := HyphaStorage[hyphaName]; ok {
log.Println("Serving", data.textPath)
if h := hyphae.ByName(hyphaName); h.Exists {
log.Println("Serving", h.TextPath)
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
http.ServeFile(w, rq, data.textPath)
http.ServeFile(w, rq, h.TextPath)
}
}
@ -69,45 +83,39 @@ func handlerText(w http.ResponseWriter, rq *http.Request) {
func handlerBinary(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
hyphaName := HyphaNameFromRq(rq, "binary")
if data, ok := HyphaStorage[hyphaName]; ok {
log.Println("Serving", data.binaryPath)
w.Header().Set("Content-Type", ExtensionToMime(filepath.Ext(data.binaryPath)))
http.ServeFile(w, rq, data.binaryPath)
if h := hyphae.ByName(hyphaName); h.Exists {
log.Println("Serving", h.BinaryPath)
w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.BinaryPath)))
http.ServeFile(w, rq, h.BinaryPath)
}
}
// handlerPage is the main hypha action that displays the hypha and the binary upload form along with some navigation.
func handlerPage(w http.ResponseWriter, rq *http.Request) {
// handlerHypha is the main hypha action that displays the hypha and the binary upload form along with some navigation.
func handlerHypha(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
hyphaName = HyphaNameFromRq(rq, "page")
data, hyphaExists = HyphaStorage[hyphaName]
hasAmnt = hyphaExists && data.binaryPath != ""
hyphaName = HyphaNameFromRq(rq, "page", "hypha")
h = hyphae.ByName(hyphaName)
contents string
openGraph string
u = user.FromRequest(rq)
)
if hyphaExists {
fileContentsT, errT := ioutil.ReadFile(data.textPath)
_, errB := os.Stat(data.binaryPath)
if h.Exists {
fileContentsT, errT := ioutil.ReadFile(h.TextPath)
_, errB := os.Stat(h.BinaryPath)
if errT == nil {
md := markup.Doc(hyphaName, string(fileContentsT))
contents = md.AsHTML()
openGraph = md.OpenGraphHTML()
}
if !os.IsNotExist(errB) {
contents = binaryHtmlBlock(hyphaName, data) + contents
contents = views.AttachmentHTML(h) + contents
}
}
treeHTML, prevHypha, nextHypha := tree.Tree(hyphaName, IterateHyphaNamesWith)
util.HTTP200Page(w,
templates.BaseHTML(
hyphaName,
templates.PageHTML(rq, hyphaName,
naviTitle(hyphaName),
contents,
treeHTML, prevHypha, nextHypha,
hasAmnt),
views.BaseHTML(
util.BeautifulName(hyphaName),
views.HyphaHTML(rq, h, contents),
u,
openGraph))
}

329
hypha.go
View File

@ -1,329 +0,0 @@
package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"mime/multipart"
"os"
"path/filepath"
"regexp"
"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"
)
func init() {
markup.HyphaExists = func(hyphaName string) bool {
_, hyphaExists := HyphaStorage[hyphaName]
return hyphaExists
}
markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) {
if hyphaData, ok := HyphaStorage[hyphaName]; ok {
rawText, err = FetchTextPart(hyphaData)
if hyphaData.binaryPath != "" {
binaryBlock = binaryHtmlBlock(hyphaName, hyphaData)
}
} else {
err = errors.New("Hypha " + hyphaName + " does not exist")
}
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.
func GetHyphaData(hyphaName string) (hyphaData *HyphaData, isOld bool) {
hyphaData, isOld = HyphaStorage[hyphaName]
if hyphaData == nil {
hyphaData = &HyphaData{}
}
return
}
// HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types.
type HyphaData struct {
textPath string
binaryPath string
}
// uploadHelp is a helper function for UploadText and UploadBinary
func uploadHelp(hop *history.HistoryOp, hyphaName, ext string, data []byte, u *user.User) *history.HistoryOp {
var (
hyphaData, isOld = GetHyphaData(hyphaName)
fullPath = filepath.Join(WikiDir, hyphaName+ext)
originalFullPath = &hyphaData.textPath
)
if hop.Type == history.TypeEditBinary {
originalFullPath = &hyphaData.binaryPath
}
if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil {
return hop.WithError(err)
}
if err := ioutil.WriteFile(fullPath, data, 0644); err != nil {
return hop.WithError(err)
}
if isOld && *originalFullPath != fullPath && *originalFullPath != "" {
if err := history.Rename(*originalFullPath, fullPath); err != nil {
return hop.WithError(err)
}
log.Println("Move", *originalFullPath, "to", fullPath)
}
// New hyphae must be added to the hypha storage
if !isOld {
HyphaStorage[hyphaName] = hyphaData
hyphae.IncrementCount()
}
*originalFullPath = fullPath
if isOld && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) {
return hop.Abort()
}
return hop.WithFiles(fullPath).
WithUser(u).
Apply()
}
// UploadText loads a new text part from `textData` for hypha `hyphaName`.
func UploadText(hyphaName, textData string, u *user.User) *history.HistoryOp {
return uploadHelp(
history.
Operation(history.TypeEditText).
WithMsg(fmt.Sprintf("Edit %s", hyphaName)),
hyphaName, ".myco", []byte(textData), u)
}
// UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`.
func UploadBinary(hyphaName, mime string, file multipart.File, u *user.User) *history.HistoryOp {
var (
hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for %s with type %s", hyphaName, mime))
data, err = ioutil.ReadAll(file)
)
if err != nil {
return hop.WithError(err).Apply()
}
return uploadHelp(hop, hyphaName, MimeToExtension(mime), data, u)
}
// DeleteHypha deletes hypha and makes a history record about that.
func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.HistoryOp {
hop := history.Operation(history.TypeDeleteHypha).
WithFilesRemoved(hd.textPath, hd.binaryPath).
WithMsg(fmt.Sprintf("Delete %s", hyphaName)).
WithUser(u).
Apply()
if len(hop.Errs) == 0 {
delete(HyphaStorage, hyphaName)
hyphae.DecrementCount()
}
return hop
}
// UnattachHypha unattaches hypha and makes a history record about that.
func (hd *HyphaData) UnattachHypha(hyphaName string, u *user.User) *history.HistoryOp {
hop := history.Operation(history.TypeUnattachHypha).
WithFilesRemoved(hd.binaryPath).
WithMsg(fmt.Sprintf("Unattach %s", hyphaName)).
WithUser(u).
Apply()
if len(hop.Errs) == 0 {
hd, ok := HyphaStorage[hyphaName]
if ok {
if hd.binaryPath != "" {
hd.binaryPath = ""
}
// If nothing is left of the hypha
if hd.textPath == "" {
delete(HyphaStorage, hyphaName)
hyphae.DecrementCount()
}
}
}
return hop
}
func findHyphaeToRename(hyphaName string, recursive bool) []string {
hyphae := []string{hyphaName}
if recursive {
hyphae = append(hyphae, util.FindSubhyphae(hyphaName, IterateHyphaNamesWith)...)
}
return hyphae
}
func renamingPairs(hyphaNames []string, replaceName func(string) string) (map[string]string, error) {
renameMap := make(map[string]string)
for _, hn := range hyphaNames {
if hd, ok := HyphaStorage[hn]; ok {
if _, nameUsed := HyphaStorage[replaceName(hn)]; nameUsed {
return nil, errors.New("Hypha " + replaceName(hn) + " already exists")
}
if hd.textPath != "" {
renameMap[hd.textPath] = replaceName(hd.textPath)
}
if hd.binaryPath != "" {
renameMap[hd.binaryPath] = replaceName(hd.binaryPath)
}
}
}
return renameMap, nil
}
// word Data is plural here
func relocateHyphaData(hyphaNames []string, replaceName func(string) string) {
for _, hyphaName := range hyphaNames {
if hd, ok := HyphaStorage[hyphaName]; ok {
hd.textPath = replaceName(hd.textPath)
hd.binaryPath = replaceName(hd.binaryPath)
HyphaStorage[replaceName(hyphaName)] = hd
delete(HyphaStorage, hyphaName)
}
}
}
// RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way.
func RenameHypha(hyphaName, newName string, recursive bool, u *user.User) *history.HistoryOp {
var (
re = regexp.MustCompile(`(?i)` + hyphaName)
replaceName = func(str string) string {
return re.ReplaceAllString(CanonicalName(str), newName)
}
hyphaNames = findHyphaeToRename(hyphaName, recursive)
renameMap, err = renamingPairs(hyphaNames, replaceName)
renameMsg = "Rename %s to %s"
hop = history.Operation(history.TypeRenameHypha)
)
if err != nil {
hop.Errs = append(hop.Errs, err)
return hop
}
if recursive {
renameMsg += " recursively"
}
hop.WithFilesRenamed(renameMap).
WithMsg(fmt.Sprintf(renameMsg, hyphaName, newName)).
WithUser(u).
Apply()
if len(hop.Errs) == 0 {
relocateHyphaData(hyphaNames, replaceName)
}
return hop
}
// binaryHtmlBlock creates an html block for binary part of the hypha.
func binaryHtmlBlock(hyphaName string, hd *HyphaData) string {
switch filepath.Ext(hd.binaryPath) {
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
return fmt.Sprintf(`
<div class="binary-container binary-container_with-img">
<a href="/binary/%[1]s"><img src="/binary/%[1]s"/></a>
</div>`, hyphaName)
case ".ogg", ".webm", ".mp4":
return fmt.Sprintf(`
<div class="binary-container binary-container_with-video">
<video>
<source src="/binary/%[1]s"/>
<p>Your browser does not support video. See video's <a href="/binary/%[1]s">direct url</a></p>
</video>
`, hyphaName)
case ".mp3":
return fmt.Sprintf(`
<div class="binary-container binary-container_with-audio">
<audio>
<source src="/binary/%[1]s"/>
<p>Your browser does not support audio. See audio's <a href="/binary/%[1]s">direct url</a></p>
</audio>
`, hyphaName)
default:
return fmt.Sprintf(`
<div class="binary-container binary-container_with-nothing">
<p>This hypha's media cannot be rendered. <a href="/binary/%s">Download it</a></p>
</div>
`, hyphaName)
}
}
// Index finds all hypha files in the full `path` and saves them to HyphaStorage. This function is recursive.
func Index(path string) {
nodes, err := ioutil.ReadDir(path)
if err != nil {
log.Fatal(err)
}
for _, node := range nodes {
// If this hypha looks like it can be a hypha path, go deeper. Do not touch the .git and static folders for they have an admnistrative importance!
if node.IsDir() && isCanonicalName(node.Name()) && node.Name() != ".git" && node.Name() != "static" {
Index(filepath.Join(path, node.Name()))
continue
}
var (
hyphaPartPath = filepath.Join(path, node.Name())
hyphaName, isText, skip = DataFromFilename(hyphaPartPath)
hyphaData *HyphaData
)
if !skip {
// Reuse the entry for existing hyphae, create a new one for those that do not exist yet.
if hd, ok := HyphaStorage[hyphaName]; ok {
hyphaData = hd
} else {
hyphaData = &HyphaData{}
HyphaStorage[hyphaName] = hyphaData
hyphae.IncrementCount()
}
if isText {
hyphaData.textPath = hyphaPartPath
} else {
// Notify the user about binary part collisions. It's a design decision to just use any of them, it's the user's fault that they have screwed up the folder structure, but the engine should at least let them know, right?
if hyphaData.binaryPath != "" {
log.Println("There is a file collision for binary part of a hypha:", hyphaData.binaryPath, "and", hyphaPartPath, "-- going on with the latter")
}
hyphaData.binaryPath = hyphaPartPath
}
}
}
}
// FetchTextPart tries to read text file in the `d`. If there is no file, empty string is returned.
func FetchTextPart(d *HyphaData) (string, error) {
if d.textPath == "" {
return "", nil
}
_, err := os.Stat(d.textPath)
if os.IsNotExist(err) {
return "", nil
} else if err != nil {
return "", err
}
text, err := ioutil.ReadFile(d.textPath)
if err != nil {
return "", err
}
return string(text), nil
}
func setHeaderLinks() {
if userLinksHypha, ok := GetHyphaData(util.HeaderLinksHypha); !ok {
util.SetDefaultHeaderLinks()
} else {
contents, err := ioutil.ReadFile(userLinksHypha.textPath)
if err != nil || len(contents) == 0 {
util.SetDefaultHeaderLinks()
} else {
text := string(contents)
util.ParseHeaderLinks(text, markup.Rocketlink)
}
}
}

View File

@ -10,21 +10,21 @@ var count = struct {
sync.Mutex
}{}
// Set the value of hyphae count to zero.
// Set the value of hyphae count to zero. Use when reloading hyphae.
func ResetCount() {
count.Lock()
count.value = 0
count.Unlock()
}
// Increment the value of hyphae count.
// Increment the value of the hyphae counter. Use when creating new hyphae or loading hyphae from disk.
func IncrementCount() {
count.Lock()
count.value++
count.Unlock()
}
// Decrement the value of hyphae count.
// Decrement the value of the hyphae counter. Use when deleting existing hyphae.
func DecrementCount() {
count.Lock()
count.value--
@ -33,6 +33,5 @@ func DecrementCount() {
// Count how many hyphae there are.
func Count() int {
// it is concurrent-safe to not lock here, right?
return count.value
}

66
hyphae/files.go Normal file
View File

@ -0,0 +1,66 @@
package hyphae
import (
"io/ioutil"
"log"
"path/filepath"
"github.com/bouncepaw/mycorrhiza/mimetype"
"github.com/bouncepaw/mycorrhiza/util"
)
// Index finds all hypha files in the full `path` and saves them to the hypha storage.
func Index(path string) {
byNamesMutex.Lock()
defer byNamesMutex.Unlock()
byNames = make(map[string]*Hypha)
ch := make(chan *Hypha, 5)
go func(ch chan *Hypha) {
indexHelper(path, 0, ch)
close(ch)
}(ch)
for h := range ch {
// At this time it is safe to ignore the mutex, because there is only one worker.
if oldHypha, ok := byNames[h.Name]; ok {
oldHypha.MergeIn(h)
} else {
byNames[h.Name] = h
IncrementCount()
}
}
}
// indexHelper finds all hypha files in the full `path` and sends them to the channel. Handling of duplicate entries and attachment and counting them is up to the caller.
func indexHelper(path string, nestLevel uint, ch chan *Hypha) {
nodes, err := ioutil.ReadDir(path)
if err != nil {
log.Fatal(err)
}
for _, node := range nodes {
// If this hypha looks like it can be a hypha path, go deeper. Do not touch the .git and static folders for they have an admnistrative importance!
if node.IsDir() &&
util.IsCanonicalName(node.Name()) &&
node.Name() != ".git" &&
!(nestLevel == 0 && node.Name() == "static") {
indexHelper(filepath.Join(path, node.Name()), nestLevel+1, ch)
continue
}
var (
hyphaPartPath = filepath.Join(path, node.Name())
hyphaName, isText, skip = mimetype.DataFromFilename(hyphaPartPath)
hypha = &Hypha{Name: hyphaName, Exists: true}
)
if !skip {
if isText {
hypha.TextPath = hyphaPartPath
} else {
hypha.BinaryPath = hyphaPartPath
}
ch <- hypha
}
}
}

View File

@ -1,21 +0,0 @@
package hyphae
// TODO: do
import ()
type Hypha struct {
Name string
Exists bool
TextPath string
BinaryPath string
OutLinks []string
BackLinks []string
}
// 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) {
}
// 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) {
}

175
hyphae/hyphae.go Normal file
View File

@ -0,0 +1,175 @@
// The `hyphae` package is for the Hypha type, hypha storage and stuff like that. It shall not depend on mycorrhiza modules other than util.
package hyphae
import (
"log"
"regexp"
"sync"
)
// HyphaPattern is a pattern which all hyphae must match.
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
type Hypha struct {
sync.RWMutex
Name string
Exists bool
TextPath string
BinaryPath string
OutLinks []*Hypha
BackLinks []*Hypha
}
var byNames = make(map[string]*Hypha)
var byNamesMutex = sync.Mutex{}
// EmptyHypha returns an empty hypha struct with given name.
func EmptyHypha(hyphaName string) *Hypha {
return &Hypha{
Name: hyphaName,
Exists: false,
TextPath: "",
BinaryPath: "",
OutLinks: make([]*Hypha, 0),
BackLinks: make([]*Hypha, 0),
}
}
// ByName returns a hypha by name. If h.Exists, the returned hypha pointer is known to be part of the hypha index (byNames map).
func ByName(hyphaName string) (h *Hypha) {
h, exists := byNames[hyphaName]
if exists {
return h
}
return EmptyHypha(hyphaName)
}
// Insert inserts the hypha into the storage. It overwrites the previous record, if there was any, and returns false. If the was no previous record, return true.
func (h *Hypha) Insert() (justCreated bool) {
hp := ByName(h.Name)
byNamesMutex.Lock()
defer byNamesMutex.Unlock()
if hp.Exists {
hp = h
} else {
h.Exists = true
byNames[h.Name] = h
IncrementCount()
}
return !hp.Exists
}
func (h *Hypha) InsertIfNew() (justCreated bool) {
if !h.Exists {
return h.Insert()
}
return false
}
func (h *Hypha) InsertIfNewKeepExistence() {
hp := ByName(h.Name)
byNamesMutex.Lock()
defer byNamesMutex.Unlock()
if hp.Exists {
hp = h
} else {
byNames[h.Name] = h
}
}
func (h *Hypha) Delete() {
byNamesMutex.Lock()
h.Lock()
delete(byNames, h.Name)
DecrementCount()
byNamesMutex.Unlock()
h.Unlock()
for _, outlinkHypha := range h.OutLinks {
outlinkHypha.DropBackLink(h)
}
}
func (h *Hypha) RenameTo(newName string) {
byNamesMutex.Lock()
h.Lock()
delete(byNames, h.Name)
h.Name = newName
byNames[h.Name] = h
byNamesMutex.Unlock()
h.Unlock()
}
// MergeIn merges in content file paths from a different hypha object. Prints warnings sometimes.
func (h *Hypha) MergeIn(oh *Hypha) {
if h.TextPath == "" && oh.TextPath != "" {
h.TextPath = oh.TextPath
}
if oh.BinaryPath != "" {
if h.BinaryPath != "" {
log.Println("There is a file collision for binary part of a hypha:", h.BinaryPath, "and", oh.BinaryPath, "-- going on with the latter")
}
h.BinaryPath = oh.BinaryPath
}
}
// ## Link related stuff
// Notes in pseudocode and whatnot:
// * (Reader h) does not mutate h => safe
// * (Rename h) reuses the same hypha object => safe
// * (Unattach h) and (Attach h) do not change (Backlinks h) => safe
// * (Delete h) does not change (Backlinks h), but changes (Outlinks h), removing h from them => make it safe
// * (Unattach h) and (Attach h) => h may start or stop existing => may change (Outlinks h) => make it safe
// * (Edit h) => h may start existing => may change (Backlinks h) => make it safe
// * (Edit h) may add or remove h to or from (Outlinks h) => make it safe
func (h *Hypha) AddOutLink(oh *Hypha) (added bool) {
h.Lock()
defer h.Unlock()
for _, outlink := range h.OutLinks {
if outlink == oh {
return false
}
}
h.OutLinks = append(h.OutLinks, oh)
return true
}
func (h *Hypha) AddBackLink(bh *Hypha) (added bool) {
h.Lock()
defer h.Unlock()
for _, backlink := range h.BackLinks {
if backlink == h {
return false
}
}
h.BackLinks = append(h.BackLinks, bh)
return true
}
func (h *Hypha) DropBackLink(bh *Hypha) {
h.Lock()
defer h.Unlock()
if len(h.BackLinks) <= 1 {
h.BackLinks = make([]*Hypha, 0)
return
}
lastBackLinkIndex := len(h.BackLinks)
for i, backlink := range h.BackLinks {
if backlink == bh {
if i != lastBackLinkIndex {
h.BackLinks[i] = h.BackLinks[lastBackLinkIndex]
}
h.BackLinks = h.BackLinks[:lastBackLinkIndex]
return
}
}
}

57
hyphae/iterators.go Normal file
View File

@ -0,0 +1,57 @@
// File `iterators.go` contains stuff that iterates over hyphae.
package hyphae
import (
"strings"
)
// YieldExistingHyphae iterates over all hyphae and yields all existing ones.
func YieldExistingHyphae() chan *Hypha {
ch := make(chan *Hypha)
go func() {
for _, h := range byNames {
if h.Exists {
ch <- h
}
}
close(ch)
}()
return ch
}
// FilterTextHyphae filters the source channel and yields only those hyphae than have text parts.
func FilterTextHyphae(src chan *Hypha) chan *Hypha {
sink := make(chan *Hypha)
go func() {
for h := range src {
if h.TextPath != "" {
sink <- h
}
}
close(sink)
}()
return sink
}
// Subhyphae returns slice of subhyphae.
func (h *Hypha) Subhyphae() []*Hypha {
hyphae := []*Hypha{}
for subh := range YieldExistingHyphae() {
if strings.HasPrefix(subh.Name, h.Name+"/") {
hyphae = append(hyphae, subh)
}
}
return hyphae
}
// AreFreeNames checks if all given `hyphaNames` are not taken. If they are not taken, `ok` is true. If not, `firstFailure` is the name of the first met hypha that is not free.
func AreFreeNames(hyphaNames ...string) (firstFailure string, ok bool) {
for h := range YieldExistingHyphae() {
for _, hn := range hyphaNames {
if hn == h.Name {
return hn, false
}
}
}
return "", true
}

132
link/link.go Normal file
View File

@ -0,0 +1,132 @@
package link
import (
"fmt"
"path"
"strings"
"github.com/bouncepaw/mycorrhiza/util"
)
// LinkType tells what type the given link is.
type LinkType int
const (
LinkInavild LinkType = iota
// LinkLocalRoot is a link like "/list", "/user-list", etc.
LinkLocalRoot
// LinkLocalHypha is a link like "test", "../test", etc.
LinkLocalHypha
// LinkExternal is an external link with specified protocol.
LinkExternal
// LinkInterwiki is currently unused.
LinkInterwiki
)
// Link is an abstraction for universal representation of links, be they links in mycomarkup links or whatever.
type Link struct {
// Address is what the link points to.
Address string
// Display is what gets nested into the <a> tag.
Display string
Kind LinkType
DestinationUnknown bool
// #...
Anchor string
Protocol string
// How the link address looked originally in source text.
SrcAddress string
// How the link display text looked originally in source text. May be empty.
SrcDisplay string
// RelativeTo is hypha name to which the link is relative to.
RelativeTo string
}
// DoubtExistence sets DestinationUnknown to true if the link is local hypha link.
func (l *Link) DoubtExistence() {
if l.Kind == LinkLocalHypha {
l.DestinationUnknown = true
}
}
// Classes returns CSS class string for given link.
func (l *Link) Classes() string {
if l.Kind == LinkExternal {
return fmt.Sprintf("wikilink wikilink_external wikilink_%s", l.Protocol)
}
classes := "wikilink wikilink_internal"
if l.DestinationUnknown {
classes += " wikilink_new"
}
return classes
}
// Href returns content for the href attrubite for hyperlink. You should always use it.
func (l *Link) Href() string {
switch l.Kind {
case LinkExternal, LinkLocalRoot:
return l.Address
default:
return "/hypha/" + l.Address + l.Anchor
}
}
// ImgSrc returns content for src attribute of img tag. Used with `img{}`.
func (l *Link) ImgSrc() string {
switch l.Kind {
case LinkExternal, LinkLocalRoot:
return l.Address
default:
return "/binary/" + l.Address
}
}
// From returns a Link object given these `address` and `display` on relative to given `hyphaName`.
func From(address, display, hyphaName string) *Link {
address = strings.TrimSpace(address)
link := Link{
SrcAddress: address,
SrcDisplay: display,
RelativeTo: hyphaName,
}
if display == "" {
link.Display = address
} else {
link.Display = strings.TrimSpace(display)
}
switch {
case strings.ContainsRune(address, ':'):
pos := strings.IndexRune(address, ':')
link.Protocol = address[:pos]
link.Kind = LinkExternal
if display == "" {
link.Display = address[pos+1:]
if strings.HasPrefix(link.Display, "//") && len(link.Display) > 2 {
link.Display = link.Display[2:]
}
}
link.Address = address
case strings.HasPrefix(address, "/"):
link.Address = address
link.Kind = LinkLocalRoot
case strings.HasPrefix(address, "./"):
link.Kind = LinkLocalHypha
link.Address = util.CanonicalName(path.Join(hyphaName, address[2:]))
case strings.HasPrefix(address, "../"):
link.Kind = LinkLocalHypha
link.Address = util.CanonicalName(path.Join(path.Dir(hyphaName), address[3:]))
case strings.HasPrefix(address, "#"):
link.Kind = LinkLocalHypha
link.Address = util.CanonicalName(hyphaName)
link.Anchor = address
default:
link.Kind = LinkLocalHypha
link.Address = util.CanonicalName(address)
}
return &link
}

94
main.go
View File

@ -1,5 +1,6 @@
//go:generate go get -u github.com/valyala/quicktemplate/qtc
//go:generate qtc -dir=templates
//go:generate qtc -dir=assets
//go:generate qtc -dir=views
package main
import (
@ -9,33 +10,20 @@ import (
"math/rand"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/bouncepaw/mycorrhiza/assets"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/shroom"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
)
// WikiDir is a rooted path to the wiki storage directory.
var WikiDir string
// HyphaPattern is a pattern which all hyphae must match.
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
// HyphaStorage is a mapping between canonical hypha names and their meta information.
var HyphaStorage = make(map[string]*HyphaData)
// IterateHyphaNamesWith is a closure to be passed to subpackages to let them iterate all hypha names read-only.
func IterateHyphaNamesWith(f func(string)) {
for hyphaName := range HyphaStorage {
f(hyphaName)
}
}
// HttpErr is used by many handlers to signal errors in a compact way.
func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) {
log.Println(errMsg, "for", name)
@ -46,7 +34,7 @@ func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) {
base(
title,
fmt.Sprintf(
`<main><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`,
`<main class="main-width"><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`,
errMsg,
name,
),
@ -58,19 +46,11 @@ func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) {
// Show all hyphae
func handlerList(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
tbody string
pageCount = hyphae.Count()
u = user.FromRequest(rq)
)
for hyphaName, data := range HyphaStorage {
tbody += templates.HyphaListRowHTML(hyphaName, ExtensionToMime(filepath.Ext(data.binaryPath)), data.binaryPath != "")
}
util.HTTP200Page(w, base("List of pages", templates.HyphaListHTML(tbody, pageCount), u))
util.HTTP200Page(w, base("List of pages", views.HyphaListHTML(), user.FromRequest(rq)))
}
// This part is present in all html documents.
var base = templates.BaseHTML
var base = views.BaseHTML
// Reindex all hyphae by checking the wiki storage directory anew.
func handlerReindex(w http.ResponseWriter, rq *http.Request) {
@ -81,14 +61,15 @@ func handlerReindex(w http.ResponseWriter, rq *http.Request) {
return
}
hyphae.ResetCount()
HyphaStorage = make(map[string]*HyphaData)
log.Println("Wiki storage directory is", WikiDir)
log.Println("Start indexing hyphae...")
Index(WikiDir)
hyphae.Index(WikiDir)
log.Println("Indexed", hyphae.Count(), "hyphae")
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
// Stop the wiki
// Update header links by reading the configured hypha, if there is any, or resorting to default values.
func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
@ -97,7 +78,7 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
log.Println("Rejected", rq.URL)
return
}
setHeaderLinks()
shroom.SetHeaderLinks()
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
@ -106,23 +87,25 @@ func handlerRandom(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var randomHyphaName string
i := rand.Intn(hyphae.Count())
for hyphaName := range HyphaStorage {
for h := range hyphae.YieldExistingHyphae() {
if i == 0 {
randomHyphaName = hyphaName
break
randomHyphaName = h.Name
}
i--
}
http.Redirect(w, rq, "/page/"+randomHyphaName, http.StatusSeeOther)
http.Redirect(w, rq, "/hypha/"+randomHyphaName, http.StatusSeeOther)
}
func handlerStyle(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
if _, err := os.Stat(WikiDir + "/static/common.css"); err == nil {
http.ServeFile(w, rq, WikiDir+"/static/common.css")
if _, err := os.Stat(util.WikiDir + "/static/common.css"); err == nil {
http.ServeFile(w, rq, util.WikiDir+"/static/common.css")
} else {
w.Header().Set("Content-Type", "text/css;charset=utf-8")
w.Write([]byte(templates.DefaultCSS()))
w.Write([]byte(assets.DefaultCSS()))
}
if bytes, err := ioutil.ReadFile(util.WikiDir + "/static/custom.css"); err == nil {
w.Write(bytes)
}
}
@ -143,20 +126,26 @@ func handlerIcon(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
switch iconName {
case "gemini":
w.Write([]byte(templates.IconGemini()))
w.Write([]byte(assets.IconGemini()))
case "mailto":
w.Write([]byte(templates.IconMailto()))
w.Write([]byte(assets.IconMailto()))
case "gopher":
w.Write([]byte(templates.IconGopher()))
w.Write([]byte(assets.IconGopher()))
default:
w.Write([]byte(templates.IconHTTP()))
w.Write([]byte(assets.IconHTTP()))
}
}
func handlerAbout(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(base("About "+util.SiteName, templates.AboutHTML(), user.FromRequest(rq))))
w.Write([]byte(base("About "+util.SiteName, views.AboutHTML(), user.FromRequest(rq))))
}
func handlerUserList(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(base("User list", views.UserListHTML(), user.FromRequest(rq))))
}
func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) {
@ -177,13 +166,19 @@ func main() {
log.Fatal(err)
}
log.Println("Wiki storage directory is", WikiDir)
Index(WikiDir)
hyphae.Index(WikiDir)
log.Println("Indexed", hyphae.Count(), "hyphae")
shroom.FindAllBacklinks()
log.Println("Found all backlinks")
history.Start(WikiDir)
setHeaderLinks()
shroom.SetHeaderLinks()
// See http_readers.go for /page/, /text/, /binary/
go handleGemini()
// See http_admin.go for /admin, /admin/*
initAdmin()
// See http_readers.go for /page/, /hypha/, /text/, /binary/, /attachment/
// See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/, /unattach-ask/, /unattach-confirm/
// See http_auth.go for /login, /login-data, /logout, /logout-confirm
// See http_history.go for /history/, /recent-changes
@ -192,15 +187,16 @@ func main() {
http.HandleFunc("/update-header-links", handlerUpdateHeaderLinks)
http.HandleFunc("/random", handlerRandom)
http.HandleFunc("/about", handlerAbout)
http.HandleFunc("/user-list", handlerUserList)
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")
})
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)
})
http.HandleFunc("/robots.txt", handlerRobotsTxt)
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
http.Redirect(w, rq, "/hypha/"+util.HomePage, http.StatusSeeOther)
})
log.Fatal(http.ListenAndServe("0.0.0.0:"+util.ServerPort, nil))
}

34
markup/hr.go Normal file
View File

@ -0,0 +1,34 @@
package markup
import (
"unicode"
)
// MatchesHorizontalLine checks if the string can be interpreted as suitable for rendering as <hr/>.
//
// The rule is: if there are more than 4 characters "-" in the string, then make it a horizontal line.
// Otherwise it is a paragraph (<p>).
func MatchesHorizontalLine(line string) bool {
counter := 0
// Check initially that the symbol is "-". If it is not a "-", it is most likely a space or another character.
// With unicode.IsLetter() we can separate spaces and characters.
for _, ch := range line {
if ch == '-' {
counter++
continue
}
// If we bump into any other character (letter) in the line, it is immediately an incorrect horizontal line.
// There is no point in counting further, we end the loop.
if unicode.IsLetter(ch) {
counter = 0
break
}
}
if counter >= 4 {
return true
}
return false
}

View File

@ -5,7 +5,7 @@ import (
"regexp"
"strings"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/link"
)
var imgRe = regexp.MustCompile(`^img\s+{`)
@ -14,44 +14,6 @@ func MatchesImg(line string) bool {
return imgRe.MatchString(line)
}
type imgEntry struct {
trimmedPath string
path strings.Builder
sizeW strings.Builder
sizeH strings.Builder
desc strings.Builder
}
func (entry *imgEntry) descriptionAsHtml(hyphaName string) (html string) {
if entry.desc.Len() == 0 {
return ""
}
lines := strings.Split(entry.desc.String(), "\n")
for _, line := range lines {
if line = strings.TrimSpace(line); line != "" {
if html != "" {
html += `<br>`
}
html += ParagraphToHtml(hyphaName, line)
}
}
return `<figcaption>` + html + `</figcaption>`
}
func (entry *imgEntry) sizeWAsAttr() string {
if entry.sizeW.Len() == 0 {
return ""
}
return ` width="` + entry.sizeW.String() + `"`
}
func (entry *imgEntry) sizeHAsAttr() string {
if entry.sizeH.Len() == 0 {
return ""
}
return ` height="` + entry.sizeH.String() + `"`
}
type imgState int
const (
@ -71,6 +33,8 @@ type Img struct {
func (img *Img) pushEntry() {
if strings.TrimSpace(img.currEntry.path.String()) != "" {
img.currEntry.srclink = link.From(img.currEntry.path.String(), "", img.hyphaName)
img.currEntry.srclink.DoubtExistence()
img.entries = append(img.entries, img.currEntry)
img.currEntry = imgEntry{}
img.currEntry.path.Reset()
@ -177,23 +141,6 @@ func ImgFromFirstLine(line, hyphaName string) (img *Img, shouldGoBackToNormal bo
return img, img.Process(line)
}
func (img *Img) binaryPathFor(path string) string {
path = strings.TrimSpace(path)
if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
return path
} else {
return "/binary/" + xclCanonicalName(img.hyphaName, path)
}
}
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 {
@ -214,30 +161,18 @@ func parseDimensions(dimensions string) (sizeW, sizeH string) {
return
}
func (img *Img) checkLinks() map[string]bool {
m := make(map[string]bool)
for i, entry := range img.entries {
// Also trim them for later use
entry.trimmedPath = strings.TrimSpace(entry.path.String())
isAbsoluteUrl := strings.ContainsRune(entry.trimmedPath, ':')
if !isAbsoluteUrl {
entry.trimmedPath = canonicalName(entry.trimmedPath)
}
img.entries[i] = entry
m[entry.trimmedPath] = isAbsoluteUrl
}
HyphaIterate(func(hyphaName string) {
func (img *Img) markExistenceOfSrcLinks() {
HyphaIterate(func(hn string) {
for _, entry := range img.entries {
if hyphaName == xclCanonicalName(img.hyphaName, entry.trimmedPath) {
m[entry.trimmedPath] = true
if hn == entry.srclink.Address {
entry.srclink.DestinationUnknown = false
}
}
})
return m
}
func (img *Img) ToHtml() (html string) {
linkAvailabilityMap := img.checkLinks()
img.markExistenceOfSrcLinks()
isOneImageOnly := len(img.entries) == 1 && img.entries[0].desc.Len() == 0
if isOneImageOnly {
html += `<section class="img-gallery img-gallery_one-image">`
@ -247,15 +182,19 @@ func (img *Img) ToHtml() (html string) {
for _, entry := range img.entries {
html += `<figure>`
// If is existing hypha or an external path
if linkAvailabilityMap[entry.trimmedPath] {
if entry.srclink.DestinationUnknown {
html += fmt.Sprintf(
`<a class="%s" href="%s">Hypha <i>%s</i> does not exist</a>`,
entry.srclink.Classes(),
entry.srclink.Href(),
entry.srclink.Address)
} else {
html += fmt.Sprintf(
`<a href="%s"><img src="%s" %s %s></a>`,
img.pagePathFor(entry.trimmedPath),
img.binaryPathFor(entry.trimmedPath),
entry.sizeWAsAttr(), entry.sizeHAsAttr())
} else { // If is a non-existent hypha
html += fmt.Sprintf(`<a class="wikilink_new" href="%s">Hypha <em>%s</em> does not exist</a>`, img.pagePathFor(entry.trimmedPath), entry.trimmedPath)
entry.srclink.Href(),
entry.srclink.ImgSrc(),
entry.sizeWAsAttr(),
entry.sizeHAsAttr())
}
html += entry.descriptionAsHtml(img.hyphaName)
html += `</figure>`

45
markup/img_entry.go Normal file
View File

@ -0,0 +1,45 @@
package markup
import (
"strings"
"github.com/bouncepaw/mycorrhiza/link"
)
type imgEntry struct {
srclink *link.Link
path strings.Builder
sizeW strings.Builder
sizeH strings.Builder
desc strings.Builder
}
func (entry *imgEntry) descriptionAsHtml(hyphaName string) (html string) {
if entry.desc.Len() == 0 {
return ""
}
lines := strings.Split(entry.desc.String(), "\n")
for _, line := range lines {
if line = strings.TrimSpace(line); line != "" {
if html != "" {
html += `<br>`
}
html += ParagraphToHtml(hyphaName, line)
}
}
return `<figcaption>` + html + `</figcaption>`
}
func (entry *imgEntry) sizeWAsAttr() string {
if entry.sizeW.Len() == 0 {
return ""
}
return ` width="` + entry.sizeW.String() + `"`
}
func (entry *imgEntry) sizeHAsAttr() string {
if entry.sizeH.Len() == 0 {
return ""
}
return ` height="` + entry.sizeH.String() + `"`
}

View File

@ -4,6 +4,8 @@ import (
"fmt"
"html"
"strings"
"github.com/bouncepaw/mycorrhiza/util"
)
// HyphaExists holds function that checks that a hypha is present.
@ -83,7 +85,8 @@ func lineToAST(line string, state *GemLexerState, ast *[]Line) {
return strings.HasPrefix(line, token)
}
addHeading := func(i int) {
addLine(fmt.Sprintf("<h%d id='%d'>%s</h%d>", i, state.id, ParagraphToHtml(state.name, line[i+1:]), i))
id := util.LettersNumbersOnly(line[i+1:])
addLine(fmt.Sprintf(`<h%d id='%d'>%s<a href="#%s" id="%s" class="heading__link"></a></h%d>`, i, state.id, ParagraphToHtml(state.name, line[i+1:]), id, id, i))
}
// Beware! Usage of goto. Some may say it is considered evil but in this case it helped to make a better-structured code.
@ -166,7 +169,7 @@ launchpadState:
switch {
case startsWith("=>"):
href, text, class := Rocketlink(line, state.name)
state.buf += fmt.Sprintf(` <li class="launchpad__entry"><a class="rocketlink %s" href="%s">%s</a></li>`, class, href, text)
state.buf += fmt.Sprintf(` <li class="launchpad__entry"><a href="%s" class="rocketlink %s">%s</a></li>`, href, class, text)
case startsWith("```"):
state.where = "pre"
addLine(state.buf + "</ul>")
@ -235,7 +238,7 @@ normalState:
case startsWith("<="):
addParagraphIfNeeded()
addLine(parseTransclusion(line, state.name))
case line == "----":
case MatchesHorizontalLine(line):
addParagraphIfNeeded()
*ast = append(*ast, Line{id: -1, contents: "<hr/>"})
case MatchesImg(line):

View File

@ -1,47 +1,22 @@
package markup
import (
"fmt"
"path"
"strings"
"github.com/bouncepaw/mycorrhiza/link"
)
// LinkParts determines what href, text and class should resulting <a> have based on mycomarkup's addr, display and hypha name.
//
// => addr display
// [[addr|display]]
// TODO: deprecate
func LinkParts(addr, display, hyphaName string) (href, text, class string) {
if display == "" {
text = addr
} else {
text = strings.TrimSpace(display)
l := link.From(addr, display, hyphaName)
if l.Kind == link.LinkLocalHypha && !HyphaExists(l.Address) {
l.DestinationUnknown = true
}
class = "wikilink wikilink_internal"
switch {
case strings.ContainsRune(addr, ':'):
pos := strings.IndexRune(addr, ':')
destination := addr[:pos]
if display == "" {
text = addr[pos+1:]
if strings.HasPrefix(text, "//") && len(text) > 2 {
text = text[2:]
}
}
return addr, text, fmt.Sprintf("wikilink wikilink_external wikilink_%s", destination)
case strings.HasPrefix(addr, "/"):
return addr, text, class
case strings.HasPrefix(addr, "./"):
hyphaName = canonicalName(path.Join(hyphaName, addr[2:]))
case strings.HasPrefix(addr, "../"):
hyphaName = canonicalName(path.Join(path.Dir(hyphaName), addr[3:]))
default:
hyphaName = canonicalName(addr)
}
if !HyphaExists(hyphaName) {
class += " wikilink_new"
}
return "/page/" + hyphaName, text, class
return l.Href(), l.Display, l.Classes()
}
// Parse markup line starting with "=>" according to wikilink rules.

View File

@ -7,6 +7,7 @@ import (
"regexp"
"strings"
"github.com/bouncepaw/mycorrhiza/link"
"github.com/bouncepaw/mycorrhiza/util"
)
@ -47,6 +48,11 @@ func (md *MycoDoc) AsHTML() string {
return md.html
}
// AsGemtext returns a gemtext representation of the document. Currently really limited, just returns source text
func (md *MycoDoc) AsGemtext() string {
return md.contents
}
// 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(`<.*?>`)
@ -57,15 +63,16 @@ func (md *MycoDoc) OpenGraphHTML() string {
ogTag("title", md.hyphaName),
ogTag("type", "article"),
ogTag("image", md.firstImageURL),
ogTag("url", util.URL+"/page/"+md.hyphaName),
ogTag("url", util.URL+"/hypha/"+md.hyphaName),
ogTag("determiner", ""),
ogTag("description", htmlTagRe.ReplaceAllString(md.description, "")),
}, "\n")
}
func (md *MycoDoc) ogFillVars() *MycoDoc {
md.firstImageURL = util.URL + "/favicon.ico"
foundDesc := false
md.firstImageURL = HyphaImageForOG(md.hyphaName)
foundImg := false
for _, line := range md.ast {
switch v := line.contents.(type) {
case string:
@ -74,8 +81,12 @@ func (md *MycoDoc) ogFillVars() *MycoDoc {
foundDesc = true
}
case Img:
if len(v.entries) > 0 {
md.firstImageURL = v.entries[0].path.String()
if !foundImg && len(v.entries) > 0 {
md.firstImageURL = v.entries[0].srclink.ImgSrc()
if v.entries[0].srclink.Kind != link.LinkExternal {
md.firstImageURL = util.URL + md.firstImageURL
}
foundImg = true
}
}
}
@ -153,6 +164,7 @@ func crawl(name, content string) []string {
preAcc += html.EscapeString(line)
}
}
break
}
return []string{}

56
markup/outlink.go Normal file
View File

@ -0,0 +1,56 @@
package markup
import (
"regexp"
"strings"
"github.com/bouncepaw/mycorrhiza/link"
)
// OutLinks returns a channel of names of hyphae this mycodocument links.
// Links include:
// * Regular links
// * Rocketlinks
// * Transclusion
// * Image galleries
func (md *MycoDoc) OutLinks() chan string {
ch := make(chan string)
if !md.parsedAlready {
md.Lex(0)
}
go func() {
for _, line := range md.ast {
switch v := line.contents.(type) {
case string:
if strings.HasPrefix(v, "<p") || strings.HasPrefix(v, "<ul class='launchpad'") {
extractLinks(v, ch)
}
case Transclusion:
ch <- v.name
case Img:
extractImageLinks(v, ch)
}
}
close(ch)
}()
return ch
}
var reLinks = regexp.MustCompile(`<a href="/hypha/([^"]*)".*?</a>`)
func extractLinks(html string, ch chan string) {
if results := reLinks.FindAllStringSubmatch(html, -1); results != nil {
for _, result := range results {
// result[0] is always present at this point and is not needed, because it is the whole matched substring (which we don't need)
ch <- result[1]
}
}
}
func extractImageLinks(img Img, ch chan string) {
for _, entry := range img.entries {
if entry.srclink.Kind == link.LinkLocalHypha {
ch <- entry.srclink.Address
}
}
}

View File

@ -55,7 +55,8 @@ func getLinkNode(input *bytes.Buffer, hyphaName string, isBracketedLink bool) st
} else if isBracketedLink && b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) {
input.Next(1)
break
} else if !isBracketedLink && unicode.IsSpace(rune(b)) {
} else if !isBracketedLink && (unicode.IsSpace(rune(b)) || strings.ContainsRune("<>{}|\\^[]`,()", rune(b))) {
input.UnreadByte()
break
} else {
currBuf.WriteByte(b)

@ -1 +1 @@
Subproject commit be5b922e9b564551601d21ed45bf7d9ced65c6bb
Subproject commit e7040f3e0dc41809063b77fcbc12fe33b234ea87

62
mime.go
View File

@ -1,62 +0,0 @@
package main
import (
"path/filepath"
"strings"
)
func MimeToExtension(mime string) string {
mm := map[string]string{
"application/octet-stream": "bin",
"image/jpeg": "jpg",
"image/gif": "gif",
"image/png": "png",
"image/webp": "webp",
"image/svg+xml": "svg",
"image/x-icon": "ico",
"application/ogg": "ogg",
"video/webm": "webm",
"audio/mp3": "mp3",
"video/mp4": "mp4",
}
if ext, ok := mm[mime]; ok {
return "." + ext
}
return ".bin"
}
func ExtensionToMime(ext string) string {
mm := map[string]string{
".bin": "application/octet-stream",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".png": "image/png",
".webp": "image/webp",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".ogg": "application/ogg",
".webm": "video/webm",
".mp3": "audio/mp3",
".mp4": "video/mp4",
}
if mime, ok := mm[ext]; ok {
return mime
}
return "application/octet-stream"
}
// DataFromFilename fetches all meta information from hypha content file with path `fullPath`. If it is not a content file, `skip` is true, and you are expected to ignore this file when indexing hyphae. `name` is name of the hypha to which this file relates. `isText` is true when the content file is text, false when is binary. `mimeId` is an integer representation of content type. Cast it to TextType if `isText == true`, cast it to BinaryType if `isText == false`.
func DataFromFilename(fullPath string) (name string, isText bool, skip bool) {
shortPath := strings.TrimPrefix(fullPath, WikiDir)[1:]
ext := filepath.Ext(shortPath)
name = CanonicalName(strings.TrimSuffix(shortPath, ext))
switch ext {
case ".myco":
isText = true
case "", shortPath:
skip = true
}
return
}

68
mimetype/mime.go Normal file
View File

@ -0,0 +1,68 @@
package mimetype
import (
"path/filepath"
"strings"
"github.com/bouncepaw/mycorrhiza/util"
)
// ToExtension returns dotted extension for given mime-type.
func ToExtension(mime string) string {
if ext, ok := mapMime2Ext[mime]; ok {
return "." + ext
}
return ".bin"
}
// FromExtension returns mime-type for given extension. The extension must start with a dot.
func FromExtension(ext string) string {
if mime, ok := mapExt2Mime[ext]; ok {
return mime
}
return "application/octet-stream"
}
// DataFromFilename fetches all meta information from hypha content file with path `fullPath`. If it is not a content file, `skip` is true, and you are expected to ignore this file when indexing hyphae. `name` is name of the hypha to which this file relates. `isText` is true when the content file is text, false when is binary.
func DataFromFilename(fullPath string) (name string, isText bool, skip bool) {
shortPath := util.ShorterPath(fullPath)
ext := filepath.Ext(shortPath)
name = util.CanonicalName(strings.TrimSuffix(shortPath, ext))
switch ext {
case ".myco":
isText = true
case "", shortPath:
skip = true
}
return
}
var mapMime2Ext = map[string]string{
"application/octet-stream": "bin",
"image/jpeg": "jpg",
"image/gif": "gif",
"image/png": "png",
"image/webp": "webp",
"image/svg+xml": "svg",
"image/x-icon": "ico",
"application/ogg": "ogg",
"video/webm": "webm",
"audio/mp3": "mp3",
"video/mp4": "mp4",
}
var mapExt2Mime = map[string]string{
".bin": "application/octet-stream",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".png": "image/png",
".webp": "image/webp",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".ogg": "application/ogg",
".webm": "video/webm",
".mp3": "audio/mp3",
".mp4": "video/mp4",
}

58
name.go
View File

@ -1,52 +1,34 @@
package main
import (
"fmt"
"log"
"net/http"
"strings"
"git.sr.ht/~adnano/go-gemini"
"github.com/bouncepaw/mycorrhiza/util"
)
// isCanonicalName checks if the `name` is canonical.
func isCanonicalName(name string) bool {
return HyphaPattern.MatchString(name)
// HyphaNameFromRq extracts hypha name from http request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha".
func HyphaNameFromRq(rq *http.Request, actions ...string) string {
p := rq.URL.Path
for _, action := range actions {
if strings.HasPrefix(p, "/"+action+"/") {
return util.CanonicalName(strings.TrimPrefix(p, "/"+action+"/"))
}
}
panic("HyphaNameFromRq: no matching action passed")
}
// CanonicalName makes sure the `name` is canonical. A name is canonical if it is lowercase and all spaces are replaced with underscores.
func CanonicalName(name string) string {
return strings.ToLower(strings.ReplaceAll(name, " ", "_"))
}
// naviTitle turns `canonicalName` into html string with each hypha path parts higlighted as links.
// TODO: rework as a template
func naviTitle(canonicalName string) string {
var (
html = fmt.Sprintf(`<h1 class="navi-title" id="navi-title">
<a href="/page/%s">%s</a><span aria-hidden="true" class="navi-title__colon">:</span>`, util.HomePage, util.SiteNavIcon)
prevAcc = `/page/`
parts = strings.Split(canonicalName, "/")
rel = "up"
)
for i, part := range parts {
if i > 0 {
html += `<span aria-hidden="true" class="navi-title__separator">/</span>`
// geminiHyphaNameFromRq extracts hypha name from gemini request. You have to also pass the action which is embedded in the url or several actions. For url /hypha/hypha, the action would be "hypha".
func geminiHyphaNameFromRq(rq *gemini.Request, actions ...string) string {
p := rq.URL.Path
for _, action := range actions {
if strings.HasPrefix(p, "/"+action+"/") {
return util.CanonicalName(strings.TrimPrefix(p, "/"+action+"/"))
}
if i == len(parts)-1 {
rel = "bookmark"
}
html += fmt.Sprintf(
`<a href="%s" rel="%s">%s</a>`,
prevAcc+part,
rel,
util.BeautifulName(part),
)
prevAcc += part + "/"
}
return html + "</h1>"
}
// HyphaNameFromRq extracts hypha name from http request. You have to also pass the action which is embedded in the url. For url /page/hypha, the action would be "page".
func HyphaNameFromRq(rq *http.Request, action string) string {
return CanonicalName(strings.TrimPrefix(rq.URL.Path, "/"+action+"/"))
log.Fatal("HyphaNameFromRq: no matching action passed")
return ""
}

36
shroom/backlink.go Normal file
View File

@ -0,0 +1,36 @@
package shroom
import (
"io/ioutil"
"log"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
)
// FindAllBacklinks iterates over all hyphae that have text parts, sets their outlinks and then sets backlinks.
func FindAllBacklinks() {
for h := range hyphae.FilterTextHyphae(hyphae.YieldExistingHyphae()) {
findBacklinkWorker(h)
}
}
func findBacklinkWorker(h *hyphae.Hypha) {
var (
textContents, err = ioutil.ReadFile(h.TextPath)
)
if err == nil {
for outlink := range markup.Doc(h.Name, string(textContents)).OutLinks() {
outlinkHypha := hyphae.ByName(outlink)
if outlinkHypha == h {
break
}
outlinkHypha.AddBackLink(h)
outlinkHypha.InsertIfNewKeepExistence()
h.AddOutLink(outlinkHypha)
}
} else {
log.Println("Error when reading text contents of %s: %s", h.Name, err.Error())
}
}

92
shroom/can.go Normal file
View File

@ -0,0 +1,92 @@
package shroom
import (
"errors"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/user"
)
func canFactory(
rejectLogger func(*hyphae.Hypha, *user.User, string),
action string,
dispatcher func(*hyphae.Hypha, *user.User) (string, string),
noRightsMsg string,
notExistsMsg string,
careAboutExistince bool,
) func(*user.User, *hyphae.Hypha) (error, string) {
return func(u *user.User, h *hyphae.Hypha) (error, string) {
if !u.CanProceed(action) {
rejectLogger(h, u, "no rights")
return errors.New(noRightsMsg), "Not enough rights"
}
if careAboutExistince && !h.Exists {
rejectLogger(h, u, "does not exist")
return errors.New(notExistsMsg), "Does not exist"
}
if dispatcher == nil {
return nil, ""
}
errmsg, errtitle := dispatcher(h, u)
if errtitle == "" {
return nil, ""
}
return errors.New(errmsg), errtitle
}
}
var (
CanDelete = canFactory(
rejectDeleteLog,
"delete-confirm",
nil,
"Not enough rights to delete, you must be a moderator",
"Cannot delete this hypha because it does not exist",
true,
)
CanRename = canFactory(
rejectRenameLog,
"rename-confirm",
nil,
"Not enough rights to rename, you must be a trusted editor",
"Cannot rename this hypha because it does not exist",
true,
)
CanUnattach = canFactory(
rejectUnattachLog,
"unattach-confirm",
func(h *hyphae.Hypha, u *user.User) (errmsg, errtitle string) {
if h.BinaryPath == "" {
rejectUnattachLog(h, u, "no amnt")
return "Cannot unattach this hypha because it has no attachment", "No attachment"
}
return "", ""
},
"Not enough rights to unattach, you must be a trusted editor",
"Cannot unattach this hypha because it does not exist",
true,
)
CanEdit = canFactory(
rejectEditLog,
"upload-text",
nil,
"You must be an editor to edit a hypha",
"You cannot edit a hypha that does not exist",
false,
)
CanAttach = canFactory(
rejectAttachLog,
"upload-binary",
nil,
"You must be an editor to attach a hypha",
"You cannot attach a hypha that does not exist",
false,
)
)

29
shroom/delete.go Normal file
View File

@ -0,0 +1,29 @@
package shroom
import (
"fmt"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/user"
)
// DeleteHypha deletes hypha and makes a history record about that.
func DeleteHypha(u *user.User, h *hyphae.Hypha) (hop *history.HistoryOp, errtitle string) {
hop = history.Operation(history.TypeDeleteHypha)
if err, errtitle := CanDelete(u, h); errtitle != "" {
hop.WithErrAbort(err)
return hop, errtitle
}
hop.
WithFilesRemoved(h.TextPath, h.BinaryPath).
WithMsg(fmt.Sprintf("Delete %s", h.Name)).
WithUser(u).
Apply()
if !hop.HasErrors() {
h.Delete()
}
return hop, ""
}

38
shroom/init.go Normal file
View File

@ -0,0 +1,38 @@
package shroom
import (
"errors"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/views"
)
func init() {
markup.HyphaExists = func(hyphaName string) bool {
return hyphae.ByName(hyphaName).Exists
}
markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) {
if h := hyphae.ByName(hyphaName); h.Exists {
rawText, err = FetchTextPart(h)
if h.BinaryPath != "" {
binaryBlock = views.AttachmentHTML(h)
}
} else {
err = errors.New("Hypha " + hyphaName + " does not exist")
}
return
}
markup.HyphaIterate = func(λ func(string)) {
for h := range hyphae.YieldExistingHyphae() {
λ(h.Name)
}
}
markup.HyphaImageForOG = func(hyphaName string) string {
if h := hyphae.ByName(hyphaName); h.Exists && h.BinaryPath != "" {
return util.URL + "/binary/" + hyphaName
}
return util.URL + "/favicon.ico"
}
}

24
shroom/log.go Normal file
View File

@ -0,0 +1,24 @@
package shroom
import (
"log"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/user"
)
func rejectDeleteLog(h *hyphae.Hypha, u *user.User, errmsg string) {
log.Printf("Reject delete %s by @%s: %s\n", h.Name, u.Name, errmsg)
}
func rejectRenameLog(h *hyphae.Hypha, u *user.User, errmsg string) {
log.Printf("Reject rename %s by @%s: %s\n", h.Name, u.Name, errmsg)
}
func rejectUnattachLog(h *hyphae.Hypha, u *user.User, errmsg string) {
log.Printf("Reject unattach %s by @%s: %s\n", h.Name, u.Name, errmsg)
}
func rejectEditLog(h *hyphae.Hypha, u *user.User, errmsg string) {
log.Printf("Reject edit %s by @%s: %s\n", h.Name, u.Name, errmsg)
}
func rejectAttachLog(h *hyphae.Hypha, u *user.User, errmsg string) {
log.Printf("Reject attach %s by @%s: %s\n", h.Name, u.Name, errmsg)
}

106
shroom/rename.go Normal file
View File

@ -0,0 +1,106 @@
package shroom
import (
"errors"
"fmt"
"regexp"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
)
func canRenameThisToThat(oh *hyphae.Hypha, nh *hyphae.Hypha, u *user.User) (err error, errtitle string) {
if nh.Exists {
rejectRenameLog(oh, u, fmt.Sprintf("name %s taken already", nh.Name))
return errors.New(fmt.Sprintf("Hypha named <a href='/hypha/%[1]s'>%[1]s</a> already exists, cannot rename", nh.Name)), "Name taken"
}
if nh.Name == "" {
rejectRenameLog(oh, u, "no new name given")
return errors.New("No new name is given"), "No name given"
}
if !hyphae.HyphaPattern.MatchString(nh.Name) {
rejectRenameLog(oh, u, fmt.Sprintf("new name %s invalid", nh.Name))
return errors.New("Invalid new name. Names cannot contain characters <code>^?!:#@&gt;&lt;*|\"\\'&amp;%</code>"), "Invalid name"
}
return nil, ""
}
// RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way.
func RenameHypha(h *hyphae.Hypha, newHypha *hyphae.Hypha, recursive bool, u *user.User) (hop *history.HistoryOp, errtitle string) {
newHypha.Lock()
defer newHypha.Unlock()
hop = history.Operation(history.TypeRenameHypha)
if err, errtitle := CanRename(u, h); errtitle != "" {
hop.WithErrAbort(err)
return hop, errtitle
}
if err, errtitle := canRenameThisToThat(h, newHypha, u); errtitle != "" {
hop.WithErrAbort(err)
return hop, errtitle
}
var (
re = regexp.MustCompile(`(?i)` + h.Name)
replaceName = func(str string) string {
return re.ReplaceAllString(util.CanonicalName(str), newHypha.Name)
}
hyphaeToRename = findHyphaeToRename(h, recursive)
renameMap, err = renamingPairs(hyphaeToRename, replaceName)
renameMsg = "Rename %s to %s"
)
if err != nil {
hop.Errs = append(hop.Errs, err)
return hop, hop.FirstErrorText()
}
if recursive && len(hyphaeToRename) > 0 {
renameMsg += " recursively"
}
hop.WithFilesRenamed(renameMap).
WithMsg(fmt.Sprintf(renameMsg, h.Name, newHypha.Name)).
WithUser(u).
Apply()
if len(hop.Errs) == 0 {
for _, h := range hyphaeToRename {
h.RenameTo(replaceName(h.Name))
h.Lock()
h.TextPath = replaceName(h.TextPath)
h.BinaryPath = replaceName(h.BinaryPath)
h.Unlock()
}
}
return hop, ""
}
func findHyphaeToRename(superhypha *hyphae.Hypha, recursive bool) []*hyphae.Hypha {
hyphae := []*hyphae.Hypha{superhypha}
if recursive {
hyphae = append(hyphae, superhypha.Subhyphae()...)
}
return hyphae
}
func renamingPairs(hyphaeToRename []*hyphae.Hypha, replaceName func(string) string) (map[string]string, error) {
renameMap := make(map[string]string)
newNames := make([]string, len(hyphaeToRename))
for _, h := range hyphaeToRename {
h.Lock()
newNames = append(newNames, replaceName(h.Name))
if h.TextPath != "" {
renameMap[h.TextPath] = replaceName(h.TextPath)
}
if h.BinaryPath != "" {
renameMap[h.BinaryPath] = replaceName(h.BinaryPath)
}
h.Unlock()
}
if firstFailure, ok := hyphae.AreFreeNames(newNames...); !ok {
return nil, errors.New("Hypha " + firstFailure + " already exists")
}
return renameMap, nil
}

41
shroom/unattach.go Normal file
View File

@ -0,0 +1,41 @@
package shroom
import (
"errors"
"fmt"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/user"
)
// UnattachHypha unattaches hypha and makes a history record about that.
func UnattachHypha(u *user.User, h *hyphae.Hypha) (hop *history.HistoryOp, errtitle string) {
hop = history.Operation(history.TypeUnattachHypha)
if err, errtitle := CanUnattach(u, h); errtitle != "" {
hop.WithErrAbort(err)
return hop, errtitle
}
hop.
WithFilesRemoved(h.BinaryPath).
WithMsg(fmt.Sprintf("Unattach %s", h.Name)).
WithUser(u).
Apply()
if len(hop.Errs) > 0 {
rejectUnattachLog(h, u, "fail")
// FIXME: something may be wrong here
return hop.WithErrAbort(errors.New(fmt.Sprintf("Could not unattach this hypha due to internal server errors: <code>%v</code>", hop.Errs))), "Error"
}
if h.BinaryPath != "" {
h.BinaryPath = ""
}
// If nothing is left of the hypha
if h.TextPath == "" {
h.Delete()
}
return hop, ""
}

87
shroom/upload.go Normal file
View File

@ -0,0 +1,87 @@
package shroom
import (
"errors"
"fmt"
"io/ioutil"
"log"
"mime/multipart"
"os"
"path/filepath"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/mimetype"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util"
)
func UploadText(h *hyphae.Hypha, data []byte, u *user.User) (hop *history.HistoryOp, errtitle string) {
hop = history.Operation(history.TypeEditText)
if h.Exists {
hop.WithMsg(fmt.Sprintf("Edit %s", h.Name))
} else {
hop.WithMsg(fmt.Sprintf("Create %s", h.Name))
}
if err, errtitle := CanEdit(u, h); err != nil {
return hop.WithErrAbort(err), errtitle
}
if len(data) == 0 {
return hop.WithErrAbort(errors.New("No data passed")), "Empty"
}
return uploadHelp(h, hop, ".myco", data, u)
}
func UploadBinary(h *hyphae.Hypha, mime string, file multipart.File, u *user.User) (*history.HistoryOp, string) {
var (
hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for %s with type %s", h.Name, mime))
data, err = ioutil.ReadAll(file)
)
if err != nil {
return hop.WithErrAbort(err), err.Error()
}
if err, errtitle := CanAttach(u, h); err != nil {
return hop.WithErrAbort(err), errtitle
}
if len(data) == 0 {
return hop.WithErrAbort(errors.New("No data passed")), "Empty"
}
return uploadHelp(h, hop, mimetype.ToExtension(mime), data, u)
}
// uploadHelp is a helper function for UploadText and UploadBinary
func uploadHelp(h *hyphae.Hypha, hop *history.HistoryOp, ext string, data []byte, u *user.User) (*history.HistoryOp, string) {
var (
fullPath = filepath.Join(util.WikiDir, h.Name+ext)
originalFullPath = &h.TextPath
)
if hop.Type == history.TypeEditBinary {
originalFullPath = &h.BinaryPath
}
if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil {
return hop.WithErrAbort(err), err.Error()
}
if err := ioutil.WriteFile(fullPath, data, 0644); err != nil {
return hop.WithErrAbort(err), err.Error()
}
if h.Exists && *originalFullPath != fullPath && *originalFullPath != "" {
if err := history.Rename(*originalFullPath, fullPath); err != nil {
return hop.WithErrAbort(err), err.Error()
}
log.Println("Move", *originalFullPath, "to", fullPath)
}
h.InsertIfNew()
if h.Exists && h.TextPath != "" && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) {
return hop.Abort(), "No changes"
}
*originalFullPath = fullPath
return hop.WithFiles(fullPath).WithUser(u).Apply(), ""
}

38
shroom/view.go Normal file
View File

@ -0,0 +1,38 @@
package shroom
import (
"io/ioutil"
"os"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/util"
)
// FetchTextPart tries to read text file of the given hypha. If there is no file, empty string is returned.
func FetchTextPart(h *hyphae.Hypha) (string, error) {
if h.TextPath == "" {
return "", nil
}
text, err := ioutil.ReadFile(h.TextPath)
if os.IsNotExist(err) {
return "", nil
} else if err != nil {
return "", err
}
return string(text), nil
}
func SetHeaderLinks() {
if userLinksHypha := hyphae.ByName(util.HeaderLinksHypha); !userLinksHypha.Exists {
util.SetDefaultHeaderLinks()
} else {
contents, err := ioutil.ReadFile(userLinksHypha.TextPath)
if err != nil || len(contents) == 0 {
util.SetDefaultHeaderLinks()
} else {
text := string(contents)
util.ParseHeaderLinks(text, markup.Rocketlink)
}
}
}

View File

@ -1,60 +0,0 @@
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% func LoginHTML() %}
<main>
<section>
{% if user.AuthUsed %}
<h1>Login</h1>
<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data">
<p>Use the data you were given by an administrator.</p>
<fieldset>
<legend>Username</legend>
<input type="text" required autofocus name="username" autocomplete="on">
</fieldset>
<fieldset>
<legend>Password</legend>
<input type="password" required name="password" autocomplete="on">
</fieldset>
<p>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.</p>
<input type="submit">
<a href="/">Cancel</a>
</form>
{% else %}
<p>Administrator of this wiki have not configured any authorization method. You can make edits anonymously.</p>
<p><a href="/">← Go home</a></p>
{% endif %}
</section>
</main>
{% endfunc %}
{% func LoginErrorHTML(err string) %}
<main>
<section>
{% switch err %}
{% case "unknown username" %}
<p class="error">Unknown username.</p>
{% case "wrong password" %}
<p class="error">Wrong password.</p>
{% default %}
<p class="error">{%s err %}</p>
{% endswitch %}
<p><a href="/login">← Try again</a></p>
</section>
</main>
{% endfunc %}
{% func LogoutHTML(can bool) %}
<main>
<section>
{% if can %}
<h1>Log out?</h1>
<p><a href="/logout-confirm"><strong>Confirm</strong></a></p>
<p><a href="/">Cancel</a></p>
{% else %}
<p>You cannot log out because you are not logged in.</p>
<p><a href="/login">Login</a></p>
<p><a href="/login">← Home</a></p>
{% endif %}
</section>
</main>
{% endfunc %}

View File

@ -1,218 +0,0 @@
// Code generated by qtc from "auth.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line templates/auth.qtpl:1
package templates
//line templates/auth.qtpl:1
import "github.com/bouncepaw/mycorrhiza/user"
//line templates/auth.qtpl:3
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line templates/auth.qtpl:3
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line templates/auth.qtpl:3
func StreamLoginHTML(qw422016 *qt422016.Writer) {
//line templates/auth.qtpl:3
qw422016.N().S(`
<main>
<section>
`)
//line templates/auth.qtpl:6
if user.AuthUsed {
//line templates/auth.qtpl:6
qw422016.N().S(`
<h1>Login</h1>
<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data">
<p>Use the data you were given by an administrator.</p>
<fieldset>
<legend>Username</legend>
<input type="text" required autofocus name="username" autocomplete="on">
</fieldset>
<fieldset>
<legend>Password</legend>
<input type="password" required name="password" autocomplete="on">
</fieldset>
<p>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.</p>
<input type="submit">
<a href="/">Cancel</a>
</form>
`)
//line templates/auth.qtpl:22
} else {
//line templates/auth.qtpl:22
qw422016.N().S(`
<p>Administrator of this wiki have not configured any authorization method. You can make edits anonymously.</p>
<p><a href="/"> Go home</a></p>
`)
//line templates/auth.qtpl:25
}
//line templates/auth.qtpl:25
qw422016.N().S(`
</section>
</main>
`)
//line templates/auth.qtpl:28
}
//line templates/auth.qtpl:28
func WriteLoginHTML(qq422016 qtio422016.Writer) {
//line templates/auth.qtpl:28
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/auth.qtpl:28
StreamLoginHTML(qw422016)
//line templates/auth.qtpl:28
qt422016.ReleaseWriter(qw422016)
//line templates/auth.qtpl:28
}
//line templates/auth.qtpl:28
func LoginHTML() string {
//line templates/auth.qtpl:28
qb422016 := qt422016.AcquireByteBuffer()
//line templates/auth.qtpl:28
WriteLoginHTML(qb422016)
//line templates/auth.qtpl:28
qs422016 := string(qb422016.B)
//line templates/auth.qtpl:28
qt422016.ReleaseByteBuffer(qb422016)
//line templates/auth.qtpl:28
return qs422016
//line templates/auth.qtpl:28
}
//line templates/auth.qtpl:30
func StreamLoginErrorHTML(qw422016 *qt422016.Writer, err string) {
//line templates/auth.qtpl:30
qw422016.N().S(`
<main>
<section>
`)
//line templates/auth.qtpl:33
switch err {
//line templates/auth.qtpl:34
case "unknown username":
//line templates/auth.qtpl:34
qw422016.N().S(`
<p class="error">Unknown username.</p>
`)
//line templates/auth.qtpl:36
case "wrong password":
//line templates/auth.qtpl:36
qw422016.N().S(`
<p class="error">Wrong password.</p>
`)
//line templates/auth.qtpl:38
default:
//line templates/auth.qtpl:38
qw422016.N().S(`
<p class="error">`)
//line templates/auth.qtpl:39
qw422016.E().S(err)
//line templates/auth.qtpl:39
qw422016.N().S(`</p>
`)
//line templates/auth.qtpl:40
}
//line templates/auth.qtpl:40
qw422016.N().S(`
<p><a href="/login"> Try again</a></p>
</section>
</main>
`)
//line templates/auth.qtpl:44
}
//line templates/auth.qtpl:44
func WriteLoginErrorHTML(qq422016 qtio422016.Writer, err string) {
//line templates/auth.qtpl:44
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/auth.qtpl:44
StreamLoginErrorHTML(qw422016, err)
//line templates/auth.qtpl:44
qt422016.ReleaseWriter(qw422016)
//line templates/auth.qtpl:44
}
//line templates/auth.qtpl:44
func LoginErrorHTML(err string) string {
//line templates/auth.qtpl:44
qb422016 := qt422016.AcquireByteBuffer()
//line templates/auth.qtpl:44
WriteLoginErrorHTML(qb422016, err)
//line templates/auth.qtpl:44
qs422016 := string(qb422016.B)
//line templates/auth.qtpl:44
qt422016.ReleaseByteBuffer(qb422016)
//line templates/auth.qtpl:44
return qs422016
//line templates/auth.qtpl:44
}
//line templates/auth.qtpl:46
func StreamLogoutHTML(qw422016 *qt422016.Writer, can bool) {
//line templates/auth.qtpl:46
qw422016.N().S(`
<main>
<section>
`)
//line templates/auth.qtpl:49
if can {
//line templates/auth.qtpl:49
qw422016.N().S(`
<h1>Log out?</h1>
<p><a href="/logout-confirm"><strong>Confirm</strong></a></p>
<p><a href="/">Cancel</a></p>
`)
//line templates/auth.qtpl:53
} else {
//line templates/auth.qtpl:53
qw422016.N().S(`
<p>You cannot log out because you are not logged in.</p>
<p><a href="/login">Login</a></p>
<p><a href="/login"> Home</a></p>
`)
//line templates/auth.qtpl:57
}
//line templates/auth.qtpl:57
qw422016.N().S(`
</section>
</main>
`)
//line templates/auth.qtpl:60
}
//line templates/auth.qtpl:60
func WriteLogoutHTML(qq422016 qtio422016.Writer, can bool) {
//line templates/auth.qtpl:60
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/auth.qtpl:60
StreamLogoutHTML(qw422016, can)
//line templates/auth.qtpl:60
qt422016.ReleaseWriter(qw422016)
//line templates/auth.qtpl:60
}
//line templates/auth.qtpl:60
func LogoutHTML(can bool) string {
//line templates/auth.qtpl:60
qb422016 := qt422016.AcquireByteBuffer()
//line templates/auth.qtpl:60
WriteLogoutHTML(qb422016, can)
//line templates/auth.qtpl:60
qs422016 := string(qb422016.B)
//line templates/auth.qtpl:60
qt422016.ReleaseByteBuffer(qb422016)
//line templates/auth.qtpl:60
return qs422016
//line templates/auth.qtpl:60
}

View File

@ -1,214 +0,0 @@
// Code generated by qtc from "common.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line templates/common.qtpl:1
package templates
//line templates/common.qtpl:1
import "net/http"
//line templates/common.qtpl:2
import "github.com/bouncepaw/mycorrhiza/user"
//line templates/common.qtpl:3
import "github.com/bouncepaw/mycorrhiza/util"
// This is the <nav> seen on top of many pages.
//line templates/common.qtpl:6
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line templates/common.qtpl:6
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line templates/common.qtpl:7
type navEntry struct {
path string
title string
}
var navEntries = []navEntry{
{"page", "Hypha"},
{"edit", "Edit"},
{"text", "Raw text"},
{"history", "History"},
{"revision", "NOT REACHED"},
{"rename-ask", "Rename"},
{"delete-ask", "Delete"},
}
//line templates/common.qtpl:22
func streamnavHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
//line templates/common.qtpl:22
qw422016.N().S(`
`)
//line templates/common.qtpl:24
u := user.FromRequest(rq)
//line templates/common.qtpl:25
qw422016.N().S(`
<nav class="hypha-tabs">
<ul class="hypha-tabs__flex">
`)
//line templates/common.qtpl:29
for _, entry := range navEntries {
//line templates/common.qtpl:30
if navType == "revision" && entry.path == "revision" {
//line templates/common.qtpl:30
qw422016.N().S(` <li class="hypha-tabs__tab hypha-tabs__tab_active">
`)
//line templates/common.qtpl:32
qw422016.E().S(revisionHash[0])
//line templates/common.qtpl:32
qw422016.N().S(`
</li>
`)
//line templates/common.qtpl:34
} else if navType == entry.path {
//line templates/common.qtpl:34
qw422016.N().S(` <li class="hypha-tabs__tab hypha-tabs__tab_active">
`)
//line templates/common.qtpl:36
qw422016.E().S(entry.title)
//line templates/common.qtpl:36
qw422016.N().S(`
</li>
`)
//line templates/common.qtpl:38
} else if entry.path != "revision" && u.CanProceed(entry.path) {
//line templates/common.qtpl:38
qw422016.N().S(` <li class="hypha-tabs__tab">
<a href="/`)
//line templates/common.qtpl:40
qw422016.E().S(entry.path)
//line templates/common.qtpl:40
qw422016.N().S(`/`)
//line templates/common.qtpl:40
qw422016.E().S(hyphaName)
//line templates/common.qtpl:40
qw422016.N().S(`">`)
//line templates/common.qtpl:40
qw422016.E().S(entry.title)
//line templates/common.qtpl:40
qw422016.N().S(`</a>
</li>
`)
//line templates/common.qtpl:42
}
//line templates/common.qtpl:43
}
//line templates/common.qtpl:43
qw422016.N().S(` </ul>
</nav>
`)
//line templates/common.qtpl:46
}
//line templates/common.qtpl:46
func writenavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
//line templates/common.qtpl:46
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/common.qtpl:46
streamnavHTML(qw422016, rq, hyphaName, navType, revisionHash...)
//line templates/common.qtpl:46
qt422016.ReleaseWriter(qw422016)
//line templates/common.qtpl:46
}
//line templates/common.qtpl:46
func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string {
//line templates/common.qtpl:46
qb422016 := qt422016.AcquireByteBuffer()
//line templates/common.qtpl:46
writenavHTML(qb422016, rq, hyphaName, navType, revisionHash...)
//line templates/common.qtpl:46
qs422016 := string(qb422016.B)
//line templates/common.qtpl:46
qt422016.ReleaseByteBuffer(qb422016)
//line templates/common.qtpl:46
return qs422016
//line templates/common.qtpl:46
}
//line templates/common.qtpl:48
func streamuserMenuHTML(qw422016 *qt422016.Writer, u *user.User) {
//line templates/common.qtpl:48
qw422016.N().S(`
`)
//line templates/common.qtpl:49
if user.AuthUsed {
//line templates/common.qtpl:49
qw422016.N().S(`
<li class="header-links__entry header-links__entry_user">
`)
//line templates/common.qtpl:51
if u.Group == "anon" {
//line templates/common.qtpl:51
qw422016.N().S(`
<a href="/login" class="header-links__link">Login</a>
`)
//line templates/common.qtpl:53
} else {
//line templates/common.qtpl:53
qw422016.N().S(`
<a href="/page/`)
//line templates/common.qtpl:54
qw422016.E().S(util.UserHypha)
//line templates/common.qtpl:54
qw422016.N().S(`/`)
//line templates/common.qtpl:54
qw422016.E().S(u.Name)
//line templates/common.qtpl:54
qw422016.N().S(`" class="header-links__link">`)
//line templates/common.qtpl:54
qw422016.E().S(u.Name)
//line templates/common.qtpl:54
qw422016.N().S(`</a>
`)
//line templates/common.qtpl:55
}
//line templates/common.qtpl:55
qw422016.N().S(`
</li>
`)
//line templates/common.qtpl:57
}
//line templates/common.qtpl:57
qw422016.N().S(`
`)
//line templates/common.qtpl:58
}
//line templates/common.qtpl:58
func writeuserMenuHTML(qq422016 qtio422016.Writer, u *user.User) {
//line templates/common.qtpl:58
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/common.qtpl:58
streamuserMenuHTML(qw422016, u)
//line templates/common.qtpl:58
qt422016.ReleaseWriter(qw422016)
//line templates/common.qtpl:58
}
//line templates/common.qtpl:58
func userMenuHTML(u *user.User) string {
//line templates/common.qtpl:58
qb422016 := qt422016.AcquireByteBuffer()
//line templates/common.qtpl:58
writeuserMenuHTML(qb422016, u)
//line templates/common.qtpl:58
qs422016 := string(qb422016.B)
//line templates/common.qtpl:58
qt422016.ReleaseByteBuffer(qb422016)
//line templates/common.qtpl:58
return qs422016
//line templates/common.qtpl:58
}

View File

@ -1,27 +0,0 @@
{% import "net/http" %}
This dialog is to be shown to a user when they try to delete a hypha.
{% func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
{%= navHTML(rq, hyphaName, "delete-ask") %}
<main>
{% if isOld %}
<section>
<h1>Delete {%s hyphaName %}?</h1>
<p>Do you really want to delete hypha <em>{%s hyphaName %}</em>?</p>
<p>In this version of MycorrhizaWiki you cannot undelete a deleted hypha but the history can still be accessed.</p>
<p><a href="/delete-confirm/{%s hyphaName %}"><strong>Confirm</strong></a></p>
<p><a href="/page/{%s hyphaName %}">Cancel</a></p>
</section>
{% else %}
{%= cannotDeleteDueToNonExistence(hyphaName) %}
{% endif %}
</main>
{% endfunc %}
{% func cannotDeleteDueToNonExistence(hyphaName string) %}
<section>
<h1>Cannot delete {%s hyphaName %}</h1>
<p>This hypha does not exist.</p>
<p><a href="/page/{%s hyphaName %}">Go back</a></p>
</section>
{% endfunc %}

View File

@ -1,154 +0,0 @@
// Code generated by qtc from "delete.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line templates/delete.qtpl:1
package templates
//line templates/delete.qtpl:1
import "net/http"
// This dialog is to be shown to a user when they try to delete a hypha.
//line templates/delete.qtpl:4
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line templates/delete.qtpl:4
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line templates/delete.qtpl:4
func StreamDeleteAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line templates/delete.qtpl:4
qw422016.N().S(`
`)
//line templates/delete.qtpl:5
streamnavHTML(qw422016, rq, hyphaName, "delete-ask")
//line templates/delete.qtpl:5
qw422016.N().S(`
<main>
`)
//line templates/delete.qtpl:7
if isOld {
//line templates/delete.qtpl:7
qw422016.N().S(`
<section>
<h1>Delete `)
//line templates/delete.qtpl:9
qw422016.E().S(hyphaName)
//line templates/delete.qtpl:9
qw422016.N().S(`?</h1>
<p>Do you really want to delete hypha <em>`)
//line templates/delete.qtpl:10
qw422016.E().S(hyphaName)
//line templates/delete.qtpl:10
qw422016.N().S(`</em>?</p>
<p>In this version of MycorrhizaWiki you cannot undelete a deleted hypha but the history can still be accessed.</p>
<p><a href="/delete-confirm/`)
//line templates/delete.qtpl:12
qw422016.E().S(hyphaName)
//line templates/delete.qtpl:12
qw422016.N().S(`"><strong>Confirm</strong></a></p>
<p><a href="/page/`)
//line templates/delete.qtpl:13
qw422016.E().S(hyphaName)
//line templates/delete.qtpl:13
qw422016.N().S(`">Cancel</a></p>
</section>
`)
//line templates/delete.qtpl:15
} else {
//line templates/delete.qtpl:15
qw422016.N().S(`
`)
//line templates/delete.qtpl:16
streamcannotDeleteDueToNonExistence(qw422016, hyphaName)
//line templates/delete.qtpl:16
qw422016.N().S(`
`)
//line templates/delete.qtpl:17
}
//line templates/delete.qtpl:17
qw422016.N().S(`
</main>
`)
//line templates/delete.qtpl:19
}
//line templates/delete.qtpl:19
func WriteDeleteAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line templates/delete.qtpl:19
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/delete.qtpl:19
StreamDeleteAskHTML(qw422016, rq, hyphaName, isOld)
//line templates/delete.qtpl:19
qt422016.ReleaseWriter(qw422016)
//line templates/delete.qtpl:19
}
//line templates/delete.qtpl:19
func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) string {
//line templates/delete.qtpl:19
qb422016 := qt422016.AcquireByteBuffer()
//line templates/delete.qtpl:19
WriteDeleteAskHTML(qb422016, rq, hyphaName, isOld)
//line templates/delete.qtpl:19
qs422016 := string(qb422016.B)
//line templates/delete.qtpl:19
qt422016.ReleaseByteBuffer(qb422016)
//line templates/delete.qtpl:19
return qs422016
//line templates/delete.qtpl:19
}
//line templates/delete.qtpl:21
func streamcannotDeleteDueToNonExistence(qw422016 *qt422016.Writer, hyphaName string) {
//line templates/delete.qtpl:21
qw422016.N().S(`
<section>
<h1>Cannot delete `)
//line templates/delete.qtpl:23
qw422016.E().S(hyphaName)
//line templates/delete.qtpl:23
qw422016.N().S(`</h1>
<p>This hypha does not exist.</p>
<p><a href="/page/`)
//line templates/delete.qtpl:25
qw422016.E().S(hyphaName)
//line templates/delete.qtpl:25
qw422016.N().S(`">Go back</a></p>
</section>
`)
//line templates/delete.qtpl:27
}
//line templates/delete.qtpl:27
func writecannotDeleteDueToNonExistence(qq422016 qtio422016.Writer, hyphaName string) {
//line templates/delete.qtpl:27
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/delete.qtpl:27
streamcannotDeleteDueToNonExistence(qw422016, hyphaName)
//line templates/delete.qtpl:27
qt422016.ReleaseWriter(qw422016)
//line templates/delete.qtpl:27
}
//line templates/delete.qtpl:27
func cannotDeleteDueToNonExistence(hyphaName string) string {
//line templates/delete.qtpl:27
qb422016 := qt422016.AcquireByteBuffer()
//line templates/delete.qtpl:27
writecannotDeleteDueToNonExistence(qb422016, hyphaName)
//line templates/delete.qtpl:27
qs422016 := string(qb422016.B)
//line templates/delete.qtpl:27
qt422016.ReleaseByteBuffer(qb422016)
//line templates/delete.qtpl:27
return qs422016
//line templates/delete.qtpl:27
}

View File

@ -1,67 +0,0 @@
{% import "net/http" %}
{% import "path" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% func HistoryHTML(rq *http.Request, hyphaName, list string) %}
{%= navHTML(rq, hyphaName, "history") %}
<main>
<article class="history">
<h1>History of {%s hyphaName %}</h1>
{%s= list %}
</article>
</main>
{% endfunc %}
{% func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) %}
{%= navHTML(rq, hyphaName, "revision", revHash) %}
<main>
<article>
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
{%s= naviTitle %}
{%s= contents %}
</article>
<hr/>
<aside>
{%s= tree %}
</aside>
</main>
{% endfunc %}
If `contents` == "", a helpful message is shown instead.
{% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) %}
{%= navHTML(rq, hyphaName, "page") %}
<main>
<article>
{%s= naviTitle %}
{% if contents == "" %}
<p>This hypha has no text. Why not <a href="/edit/{%s hyphaName %}">create it</a>?</p>
{% else %}
{%s= contents %}
{% endif %}
</article>
<section class="prevnext">
{% if prevHyphaName != "" %}
<a class="prevnext__el prevnext__prev" href="/page/{%s prevHyphaName %}" rel="prev">← {%s path.Base(prevHyphaName) %}</a>
{% endif %}
{% if nextHyphaName != "" %}
<a class="prevnext__el prevnext__next" href="/page/{%s nextHyphaName %}" rel="next">{%s path.Base(nextHyphaName) %} →</a>
{% endif %}
</section>
{% if u := user.FromRequest(rq); !user.AuthUsed || u.Group != "anon" %}
<form action="/upload-binary/{%s hyphaName %}"
method="post" enctype="multipart/form-data"
class="upload-amnt">
{% if hasAmnt %}
<a class="upload-amnt__unattach" href="/unattach-ask/{%s hyphaName %}">Unattach current attachment?</a>
{% endif %}
<label for="upload-binary__input">Upload a new attachment</label>
<br>
<input type="file" id="upload-binary__input" name="binary"/>
<input type="submit"/>
</form>
{% endif %}
<aside>
{%s= tree %}
</aside>
</main>
{% endfunc %}

View File

@ -1,301 +0,0 @@
// Code generated by qtc from "http_readers.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line templates/http_readers.qtpl:1
package templates
//line templates/http_readers.qtpl:1
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:5
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line templates/http_readers.qtpl:5
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line templates/http_readers.qtpl:5
func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, list string) {
//line templates/http_readers.qtpl:5
qw422016.N().S(`
`)
//line templates/http_readers.qtpl:6
streamnavHTML(qw422016, rq, hyphaName, "history")
//line templates/http_readers.qtpl:6
qw422016.N().S(`
<main>
<article class="history">
<h1>History of `)
//line templates/http_readers.qtpl:9
qw422016.E().S(hyphaName)
//line templates/http_readers.qtpl:9
qw422016.N().S(`</h1>
`)
//line templates/http_readers.qtpl:10
qw422016.N().S(list)
//line templates/http_readers.qtpl:10
qw422016.N().S(`
</article>
</main>
`)
//line templates/http_readers.qtpl:13
}
//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:13
StreamHistoryHTML(qw422016, rq, hyphaName, list)
//line templates/http_readers.qtpl:13
qt422016.ReleaseWriter(qw422016)
//line templates/http_readers.qtpl:13
}
//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:13
WriteHistoryHTML(qb422016, rq, hyphaName, list)
//line templates/http_readers.qtpl:13
qs422016 := string(qb422016.B)
//line templates/http_readers.qtpl:13
qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_readers.qtpl:13
return qs422016
//line templates/http_readers.qtpl:13
}
//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:15
qw422016.N().S(`
`)
//line templates/http_readers.qtpl:16
streamnavHTML(qw422016, rq, hyphaName, "revision", revHash)
//line templates/http_readers.qtpl:16
qw422016.N().S(`
<main>
<article>
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
`)
//line templates/http_readers.qtpl:20
qw422016.N().S(naviTitle)
//line templates/http_readers.qtpl:20
qw422016.N().S(`
`)
//line templates/http_readers.qtpl:21
qw422016.N().S(contents)
//line templates/http_readers.qtpl:21
qw422016.N().S(`
</article>
<hr/>
<aside>
`)
//line templates/http_readers.qtpl:25
qw422016.N().S(tree)
//line templates/http_readers.qtpl:25
qw422016.N().S(`
</aside>
</main>
`)
//line templates/http_readers.qtpl:28
}
//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:28
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/http_readers.qtpl:28
StreamRevisionHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, revHash)
//line templates/http_readers.qtpl:28
qt422016.ReleaseWriter(qw422016)
//line templates/http_readers.qtpl:28
}
//line templates/http_readers.qtpl:28
func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) string {
//line templates/http_readers.qtpl:28
qb422016 := qt422016.AcquireByteBuffer()
//line templates/http_readers.qtpl:28
WriteRevisionHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, revHash)
//line templates/http_readers.qtpl:28
qs422016 := string(qb422016.B)
//line templates/http_readers.qtpl:28
qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_readers.qtpl:28
return qs422016
//line templates/http_readers.qtpl:28
}
// If `contents` == "", a helpful message is shown instead.
//line templates/http_readers.qtpl:31
func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) {
//line templates/http_readers.qtpl:31
qw422016.N().S(`
`)
//line templates/http_readers.qtpl:32
streamnavHTML(qw422016, rq, hyphaName, "page")
//line templates/http_readers.qtpl:32
qw422016.N().S(`
<main>
<article>
`)
//line templates/http_readers.qtpl:35
qw422016.N().S(naviTitle)
//line templates/http_readers.qtpl:35
qw422016.N().S(`
`)
//line templates/http_readers.qtpl:36
if contents == "" {
//line templates/http_readers.qtpl:36
qw422016.N().S(`
<p>This hypha has no text. Why not <a href="/edit/`)
//line templates/http_readers.qtpl:37
qw422016.E().S(hyphaName)
//line templates/http_readers.qtpl:37
qw422016.N().S(`">create it</a>?</p>
`)
//line templates/http_readers.qtpl:38
} else {
//line templates/http_readers.qtpl:38
qw422016.N().S(`
`)
//line templates/http_readers.qtpl:39
qw422016.N().S(contents)
//line templates/http_readers.qtpl:39
qw422016.N().S(`
`)
//line templates/http_readers.qtpl:40
}
//line templates/http_readers.qtpl:40
qw422016.N().S(`
</article>
<section class="prevnext">
`)
//line templates/http_readers.qtpl:43
if prevHyphaName != "" {
//line templates/http_readers.qtpl:43
qw422016.N().S(`
<a class="prevnext__el prevnext__prev" href="/page/`)
//line templates/http_readers.qtpl:44
qw422016.E().S(prevHyphaName)
//line templates/http_readers.qtpl:44
qw422016.N().S(`" rel="prev">← `)
//line templates/http_readers.qtpl:44
qw422016.E().S(path.Base(prevHyphaName))
//line templates/http_readers.qtpl:44
qw422016.N().S(`</a>
`)
//line templates/http_readers.qtpl:45
}
//line templates/http_readers.qtpl:45
qw422016.N().S(`
`)
//line templates/http_readers.qtpl:46
if nextHyphaName != "" {
//line templates/http_readers.qtpl:46
qw422016.N().S(`
<a class="prevnext__el prevnext__next" href="/page/`)
//line templates/http_readers.qtpl:47
qw422016.E().S(nextHyphaName)
//line templates/http_readers.qtpl:47
qw422016.N().S(`" rel="next">`)
//line templates/http_readers.qtpl:47
qw422016.E().S(path.Base(nextHyphaName))
//line templates/http_readers.qtpl:47
qw422016.N().S(` </a>
`)
//line templates/http_readers.qtpl:48
}
//line templates/http_readers.qtpl:48
qw422016.N().S(`
</section>
`)
//line templates/http_readers.qtpl:50
if u := user.FromRequest(rq); !user.AuthUsed || u.Group != "anon" {
//line templates/http_readers.qtpl:50
qw422016.N().S(`
<form action="/upload-binary/`)
//line templates/http_readers.qtpl:51
qw422016.E().S(hyphaName)
//line templates/http_readers.qtpl:51
qw422016.N().S(`"
method="post" enctype="multipart/form-data"
class="upload-amnt">
`)
//line templates/http_readers.qtpl:54
if hasAmnt {
//line templates/http_readers.qtpl:54
qw422016.N().S(`
<a class="upload-amnt__unattach" href="/unattach-ask/`)
//line templates/http_readers.qtpl:55
qw422016.E().S(hyphaName)
//line templates/http_readers.qtpl:55
qw422016.N().S(`">Unattach current attachment?</a>
`)
//line templates/http_readers.qtpl:56
}
//line templates/http_readers.qtpl:56
qw422016.N().S(`
<label for="upload-binary__input">Upload a new attachment</label>
<br>
<input type="file" id="upload-binary__input" name="binary"/>
<input type="submit"/>
</form>
`)
//line templates/http_readers.qtpl:62
}
//line templates/http_readers.qtpl:62
qw422016.N().S(`
<aside>
`)
//line templates/http_readers.qtpl:64
qw422016.N().S(tree)
//line templates/http_readers.qtpl:64
qw422016.N().S(`
</aside>
</main>
`)
//line templates/http_readers.qtpl:67
}
//line templates/http_readers.qtpl:67
func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) {
//line templates/http_readers.qtpl:67
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/http_readers.qtpl:67
StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt)
//line templates/http_readers.qtpl:67
qt422016.ReleaseWriter(qw422016)
//line templates/http_readers.qtpl:67
}
//line templates/http_readers.qtpl:67
func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) string {
//line templates/http_readers.qtpl:67
qb422016 := qt422016.AcquireByteBuffer()
//line templates/http_readers.qtpl:67
WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt)
//line templates/http_readers.qtpl:67
qs422016 := string(qb422016.B)
//line templates/http_readers.qtpl:67
qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_readers.qtpl:67
return qs422016
//line templates/http_readers.qtpl:67
}

View File

@ -1,78 +0,0 @@
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% func BaseHTML(title, body string, u *user.User, headElements ...string) %}
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/static/common.css">
<title>{%s title %}</title>
{% for _, el := range headElements %}{%s= el %}{% endfor %}
</head>
<body>
<header>
<nav class="header-links">
<ul class="header-links__list">
{%- for _, link := range util.HeaderLinks -%}
<li class="header-links__entry"><a class="header-links__link" href="{%s link.Href %}">{%s link.Display %}</a></li>
{%- endfor -%}
{%s= userMenuHTML(u) %}
</ul>
</nav>
</header>
{%s= body %}
</body>
</html>
{% endfunc %}
{% func HyphaListHTML(tbody string, pageCount int) %}
<main>
<h1>List of hyphae</h1>
<p>This wiki has {%d pageCount %} hyphae.</p>
<table>
<thead>
<tr>
<th>Full name</th>
<th>Binary part type</th>
</tr>
</thead>
<tbody>
{%s= tbody %}
</tbody>
</table>
</main>
{% endfunc %}
{% func HyphaListRowHTML(hyphaName, binaryMime string, binaryPresent bool) %}
<tr>
<td><a href="/page/{%s hyphaName %}">{%s hyphaName %}</a></td>
{% if binaryPresent %}
<td>{%s binaryMime %}</td>
{% else %}
<td></td>
{% endif %}
</tr>
{% endfunc %}
{% func AboutHTML() %}
<main>
<section>
<h1>About {%s util.SiteName %}</h1>
<ul>
<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> β 0.12 indev</li>
{%- if user.AuthUsed -%}
<li><b>User count:</b> {%d user.Count() %}</li>
<li><b>Home page:</b> <a href="/">{%s util.HomePage %}</a></li>
<li><b>Administrators:</b> {%- for i, username := range user.ListUsersWithGroup("admin") -%}
{%- if i > 0 -%}<span aria-hidden="true">, </span>
{%- endif -%}
<a href="/page/{%s util.UserHypha %}/{%s username %}">{%s username %}</a>{%- endfor -%}</li>
{%- else -%}
<li>This wiki does not use authorization</li>
{%- endif -%}
</ul>
<p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p>
</section>
</main>
{% endfunc %}

View File

@ -1,335 +0,0 @@
// Code generated by qtc from "http_stuff.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line templates/http_stuff.qtpl:1
package templates
//line templates/http_stuff.qtpl:1
import "github.com/bouncepaw/mycorrhiza/util"
//line templates/http_stuff.qtpl:2
import "github.com/bouncepaw/mycorrhiza/user"
//line templates/http_stuff.qtpl:4
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line templates/http_stuff.qtpl:4
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line templates/http_stuff.qtpl:4
func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User, headElements ...string) {
//line templates/http_stuff.qtpl:4
qw422016.N().S(`
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/static/common.css">
<title>`)
//line templates/http_stuff.qtpl:10
qw422016.E().S(title)
//line templates/http_stuff.qtpl:10
qw422016.N().S(`</title>
`)
//line templates/http_stuff.qtpl:11
for _, el := range headElements {
//line templates/http_stuff.qtpl:11
qw422016.N().S(el)
//line templates/http_stuff.qtpl:11
}
//line templates/http_stuff.qtpl:11
qw422016.N().S(`
</head>
<body>
<header>
<nav class="header-links">
<ul class="header-links__list">
`)
//line templates/http_stuff.qtpl:17
for _, link := range util.HeaderLinks {
//line templates/http_stuff.qtpl:17
qw422016.N().S(` <li class="header-links__entry"><a class="header-links__link" href="`)
//line templates/http_stuff.qtpl:18
qw422016.E().S(link.Href)
//line templates/http_stuff.qtpl:18
qw422016.N().S(`">`)
//line templates/http_stuff.qtpl:18
qw422016.E().S(link.Display)
//line templates/http_stuff.qtpl:18
qw422016.N().S(`</a></li>
`)
//line templates/http_stuff.qtpl:19
}
//line templates/http_stuff.qtpl:19
qw422016.N().S(` `)
//line templates/http_stuff.qtpl:20
qw422016.N().S(userMenuHTML(u))
//line templates/http_stuff.qtpl:20
qw422016.N().S(`
</ul>
</nav>
</header>
`)
//line templates/http_stuff.qtpl:24
qw422016.N().S(body)
//line templates/http_stuff.qtpl:24
qw422016.N().S(`
</body>
</html>
`)
//line templates/http_stuff.qtpl:27
}
//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:27
StreamBaseHTML(qw422016, title, body, u, headElements...)
//line templates/http_stuff.qtpl:27
qt422016.ReleaseWriter(qw422016)
//line templates/http_stuff.qtpl:27
}
//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:27
WriteBaseHTML(qb422016, title, body, u, headElements...)
//line templates/http_stuff.qtpl:27
qs422016 := string(qb422016.B)
//line templates/http_stuff.qtpl:27
qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_stuff.qtpl:27
return qs422016
//line templates/http_stuff.qtpl:27
}
//line templates/http_stuff.qtpl:29
func StreamHyphaListHTML(qw422016 *qt422016.Writer, tbody string, pageCount int) {
//line templates/http_stuff.qtpl:29
qw422016.N().S(`
<main>
<h1>List of hyphae</h1>
<p>This wiki has `)
//line templates/http_stuff.qtpl:32
qw422016.N().D(pageCount)
//line templates/http_stuff.qtpl:32
qw422016.N().S(` hyphae.</p>
<table>
<thead>
<tr>
<th>Full name</th>
<th>Binary part type</th>
</tr>
</thead>
<tbody>
`)
//line templates/http_stuff.qtpl:41
qw422016.N().S(tbody)
//line templates/http_stuff.qtpl:41
qw422016.N().S(`
</tbody>
</table>
</main>
`)
//line templates/http_stuff.qtpl:45
}
//line templates/http_stuff.qtpl:45
func WriteHyphaListHTML(qq422016 qtio422016.Writer, tbody string, pageCount int) {
//line templates/http_stuff.qtpl:45
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/http_stuff.qtpl:45
StreamHyphaListHTML(qw422016, tbody, pageCount)
//line templates/http_stuff.qtpl:45
qt422016.ReleaseWriter(qw422016)
//line templates/http_stuff.qtpl:45
}
//line templates/http_stuff.qtpl:45
func HyphaListHTML(tbody string, pageCount int) string {
//line templates/http_stuff.qtpl:45
qb422016 := qt422016.AcquireByteBuffer()
//line templates/http_stuff.qtpl:45
WriteHyphaListHTML(qb422016, tbody, pageCount)
//line templates/http_stuff.qtpl:45
qs422016 := string(qb422016.B)
//line templates/http_stuff.qtpl:45
qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_stuff.qtpl:45
return qs422016
//line templates/http_stuff.qtpl:45
}
//line templates/http_stuff.qtpl:47
func StreamHyphaListRowHTML(qw422016 *qt422016.Writer, hyphaName, binaryMime string, binaryPresent bool) {
//line templates/http_stuff.qtpl:47
qw422016.N().S(`
<tr>
<td><a href="/page/`)
//line templates/http_stuff.qtpl:49
qw422016.E().S(hyphaName)
//line templates/http_stuff.qtpl:49
qw422016.N().S(`">`)
//line templates/http_stuff.qtpl:49
qw422016.E().S(hyphaName)
//line templates/http_stuff.qtpl:49
qw422016.N().S(`</a></td>
`)
//line templates/http_stuff.qtpl:50
if binaryPresent {
//line templates/http_stuff.qtpl:50
qw422016.N().S(`
<td>`)
//line templates/http_stuff.qtpl:51
qw422016.E().S(binaryMime)
//line templates/http_stuff.qtpl:51
qw422016.N().S(`</td>
`)
//line templates/http_stuff.qtpl:52
} else {
//line templates/http_stuff.qtpl:52
qw422016.N().S(`
<td></td>
`)
//line templates/http_stuff.qtpl:54
}
//line templates/http_stuff.qtpl:54
qw422016.N().S(`
</tr>
`)
//line templates/http_stuff.qtpl:56
}
//line templates/http_stuff.qtpl:56
func WriteHyphaListRowHTML(qq422016 qtio422016.Writer, hyphaName, binaryMime string, binaryPresent bool) {
//line templates/http_stuff.qtpl:56
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/http_stuff.qtpl:56
StreamHyphaListRowHTML(qw422016, hyphaName, binaryMime, binaryPresent)
//line templates/http_stuff.qtpl:56
qt422016.ReleaseWriter(qw422016)
//line templates/http_stuff.qtpl:56
}
//line templates/http_stuff.qtpl:56
func HyphaListRowHTML(hyphaName, binaryMime string, binaryPresent bool) string {
//line templates/http_stuff.qtpl:56
qb422016 := qt422016.AcquireByteBuffer()
//line templates/http_stuff.qtpl:56
WriteHyphaListRowHTML(qb422016, hyphaName, binaryMime, binaryPresent)
//line templates/http_stuff.qtpl:56
qs422016 := string(qb422016.B)
//line templates/http_stuff.qtpl:56
qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_stuff.qtpl:56
return qs422016
//line templates/http_stuff.qtpl:56
}
//line templates/http_stuff.qtpl:58
func StreamAboutHTML(qw422016 *qt422016.Writer) {
//line templates/http_stuff.qtpl:58
qw422016.N().S(`
<main>
<section>
<h1>About `)
//line templates/http_stuff.qtpl:61
qw422016.E().S(util.SiteName)
//line templates/http_stuff.qtpl:61
qw422016.N().S(`</h1>
<ul>
<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> β 0.12 indev</li>
`)
//line templates/http_stuff.qtpl:64
if user.AuthUsed {
//line templates/http_stuff.qtpl:64
qw422016.N().S(` <li><b>User count:</b> `)
//line templates/http_stuff.qtpl:65
qw422016.N().D(user.Count())
//line templates/http_stuff.qtpl:65
qw422016.N().S(`</li>
<li><b>Home page:</b> <a href="/">`)
//line templates/http_stuff.qtpl:66
qw422016.E().S(util.HomePage)
//line templates/http_stuff.qtpl:66
qw422016.N().S(`</a></li>
<li><b>Administrators:</b>`)
//line templates/http_stuff.qtpl:67
for i, username := range user.ListUsersWithGroup("admin") {
//line templates/http_stuff.qtpl:68
if i > 0 {
//line templates/http_stuff.qtpl:68
qw422016.N().S(`<span aria-hidden="true">, </span>
`)
//line templates/http_stuff.qtpl:69
}
//line templates/http_stuff.qtpl:69
qw422016.N().S(` <a href="/page/`)
//line templates/http_stuff.qtpl:70
qw422016.E().S(util.UserHypha)
//line templates/http_stuff.qtpl:70
qw422016.N().S(`/`)
//line templates/http_stuff.qtpl:70
qw422016.E().S(username)
//line templates/http_stuff.qtpl:70
qw422016.N().S(`">`)
//line templates/http_stuff.qtpl:70
qw422016.E().S(username)
//line templates/http_stuff.qtpl:70
qw422016.N().S(`</a>`)
//line templates/http_stuff.qtpl:70
}
//line templates/http_stuff.qtpl:70
qw422016.N().S(`</li>
`)
//line templates/http_stuff.qtpl:71
} else {
//line templates/http_stuff.qtpl:71
qw422016.N().S(` <li>This wiki does not use authorization</li>
`)
//line templates/http_stuff.qtpl:73
}
//line templates/http_stuff.qtpl:73
qw422016.N().S(` </ul>
<p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p>
</section>
</main>
`)
//line templates/http_stuff.qtpl:78
}
//line templates/http_stuff.qtpl:78
func WriteAboutHTML(qq422016 qtio422016.Writer) {
//line templates/http_stuff.qtpl:78
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/http_stuff.qtpl:78
StreamAboutHTML(qw422016)
//line templates/http_stuff.qtpl:78
qt422016.ReleaseWriter(qw422016)
//line templates/http_stuff.qtpl:78
}
//line templates/http_stuff.qtpl:78
func AboutHTML() string {
//line templates/http_stuff.qtpl:78
qb422016 := qt422016.AcquireByteBuffer()
//line templates/http_stuff.qtpl:78
WriteAboutHTML(qb422016)
//line templates/http_stuff.qtpl:78
qs422016 := string(qb422016.B)
//line templates/http_stuff.qtpl:78
qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_stuff.qtpl:78
return qs422016
//line templates/http_stuff.qtpl:78
}

View File

@ -1,159 +0,0 @@
// Code generated by qtc from "recent_changes.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line templates/recent_changes.qtpl:1
package templates
//line templates/recent_changes.qtpl:1
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line templates/recent_changes.qtpl:1
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line templates/recent_changes.qtpl:1
func StreamRecentChangesHTML(qw422016 *qt422016.Writer, changes []string, n int) {
//line templates/recent_changes.qtpl:1
qw422016.N().S(`
<main class="recent-changes">
<h1>Recent Changes</h1>
<p><a href="/"> Back</a></p>
<nav class="recent-changes__count">
See
`)
//line templates/recent_changes.qtpl:8
for _, m := range []int{20, 0, 50, 0, 100} {
//line templates/recent_changes.qtpl:8
qw422016.N().S(`
`)
//line templates/recent_changes.qtpl:9
switch m {
//line templates/recent_changes.qtpl:10
case 0:
//line templates/recent_changes.qtpl:10
qw422016.N().S(`
<span aria-hidden="true">|</span>
`)
//line templates/recent_changes.qtpl:12
case n:
//line templates/recent_changes.qtpl:12
qw422016.N().S(`
<b>`)
//line templates/recent_changes.qtpl:13
qw422016.N().D(n)
//line templates/recent_changes.qtpl:13
qw422016.N().S(`</b>
`)
//line templates/recent_changes.qtpl:14
default:
//line templates/recent_changes.qtpl:14
qw422016.N().S(`
<a href="/recent-changes/`)
//line templates/recent_changes.qtpl:15
qw422016.N().D(m)
//line templates/recent_changes.qtpl:15
qw422016.N().S(`">`)
//line templates/recent_changes.qtpl:15
qw422016.N().D(m)
//line templates/recent_changes.qtpl:15
qw422016.N().S(`</a>
`)
//line templates/recent_changes.qtpl:16
}
//line templates/recent_changes.qtpl:16
qw422016.N().S(`
`)
//line templates/recent_changes.qtpl:17
}
//line templates/recent_changes.qtpl:17
qw422016.N().S(`
recent changes
</nav>
<p><img class="icon" width="20" height="20" src="https://upload.wikimedia.org/wikipedia/commons/4/46/Generic_Feed-icon.svg">Subscribe via <a href="/recent-changes-rss">RSS</a>, <a href="/recent-changes-atom">Atom</a> or <a href="/recent-changes-json">JSON feed</a>.</p>
`)
//line templates/recent_changes.qtpl:28
qw422016.N().S(`
<section class="recent-changes__list" role="feed">
`)
//line templates/recent_changes.qtpl:31
if len(changes) == 0 {
//line templates/recent_changes.qtpl:31
qw422016.N().S(`
<p>Could not find any recent changes.</p>
`)
//line templates/recent_changes.qtpl:33
} else {
//line templates/recent_changes.qtpl:33
qw422016.N().S(`
`)
//line templates/recent_changes.qtpl:34
for i, entry := range changes {
//line templates/recent_changes.qtpl:34
qw422016.N().S(`
<ul class="recent-changes__entry rc-entry" role="article"
aria-setsize="`)
//line templates/recent_changes.qtpl:36
qw422016.N().D(n)
//line templates/recent_changes.qtpl:36
qw422016.N().S(`" aria-posinset="`)
//line templates/recent_changes.qtpl:36
qw422016.N().D(i)
//line templates/recent_changes.qtpl:36
qw422016.N().S(`">
`)
//line templates/recent_changes.qtpl:37
qw422016.N().S(entry)
//line templates/recent_changes.qtpl:37
qw422016.N().S(`
</ul>
`)
//line templates/recent_changes.qtpl:39
}
//line templates/recent_changes.qtpl:39
qw422016.N().S(`
`)
//line templates/recent_changes.qtpl:40
}
//line templates/recent_changes.qtpl:40
qw422016.N().S(`
</section>
</main>
`)
//line templates/recent_changes.qtpl:43
}
//line templates/recent_changes.qtpl:43
func WriteRecentChangesHTML(qq422016 qtio422016.Writer, changes []string, n int) {
//line templates/recent_changes.qtpl:43
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/recent_changes.qtpl:43
StreamRecentChangesHTML(qw422016, changes, n)
//line templates/recent_changes.qtpl:43
qt422016.ReleaseWriter(qw422016)
//line templates/recent_changes.qtpl:43
}
//line templates/recent_changes.qtpl:43
func RecentChangesHTML(changes []string, n int) string {
//line templates/recent_changes.qtpl:43
qb422016 := qt422016.AcquireByteBuffer()
//line templates/recent_changes.qtpl:43
WriteRecentChangesHTML(qb422016, changes, n)
//line templates/recent_changes.qtpl:43
qs422016 := string(qb422016.B)
//line templates/recent_changes.qtpl:43
qt422016.ReleaseByteBuffer(qb422016)
//line templates/recent_changes.qtpl:43
return qs422016
//line templates/recent_changes.qtpl:43
}

View File

@ -1,37 +0,0 @@
{% import "net/http" %}
This dialog is to be shown to a user when they try to rename a hypha.
{% func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
{%= navHTML(rq, hyphaName, "rename-ask") %}
<main>
{%- if isOld -%}
<section>
<h1>Rename {%s hyphaName %}</h1>
<form action="/rename-confirm/{%s hyphaName %}" method="post" enctype="multipart/form-data">
<fieldset>
<legend>New name</legend>
<input type="text" value="{%s hyphaName %}" required autofocus id="new-name" name="new-name"/>
</fieldset>
<fieldset>
<legend>Settings</legend>
<input type="checkbox" id="recursive" name="recursive" value="true" checked/>
<label for="recursive">Keep subhyphae</label>
</fieldset>
<p>If you rename this hypha, all incoming links and all relative outcoming links will break. You will also lose all history for the new name. Rename carefully.</p>
<input type="submit"/>
</form>
</section>
{%- else -%}
{%= cannotRenameDueToNonExistence(hyphaName) %}
{%- endif -%}
</main>
{% endfunc %}
{% func cannotRenameDueToNonExistence(hyphaName string) %}
<section>
<h1>Cannot rename {%s hyphaName %}</h1>
<p>This hypha does not exist.</p>
<p><a href="/page/{%s hyphaName %}">Go back</a></p>
</section>
{% endfunc %}

View File

@ -1,158 +0,0 @@
// Code generated by qtc from "rename.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line templates/rename.qtpl:1
package templates
//line templates/rename.qtpl:1
import "net/http"
// This dialog is to be shown to a user when they try to rename a hypha.
//line templates/rename.qtpl:3
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line templates/rename.qtpl:3
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line templates/rename.qtpl:3
func StreamRenameAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line templates/rename.qtpl:3
qw422016.N().S(`
`)
//line templates/rename.qtpl:4
streamnavHTML(qw422016, rq, hyphaName, "rename-ask")
//line templates/rename.qtpl:4
qw422016.N().S(`
<main>
`)
//line templates/rename.qtpl:6
if isOld {
//line templates/rename.qtpl:6
qw422016.N().S(` <section>
<h1>Rename `)
//line templates/rename.qtpl:8
qw422016.E().S(hyphaName)
//line templates/rename.qtpl:8
qw422016.N().S(`</h1>
<form action="/rename-confirm/`)
//line templates/rename.qtpl:9
qw422016.E().S(hyphaName)
//line templates/rename.qtpl:9
qw422016.N().S(`" method="post" enctype="multipart/form-data">
<fieldset>
<legend>New name</legend>
<input type="text" value="`)
//line templates/rename.qtpl:12
qw422016.E().S(hyphaName)
//line templates/rename.qtpl:12
qw422016.N().S(`" required autofocus id="new-name" name="new-name"/>
</fieldset>
<fieldset>
<legend>Settings</legend>
<input type="checkbox" id="recursive" name="recursive" value="true" checked/>
<label for="recursive">Keep subhyphae</label>
</fieldset>
<p>If you rename this hypha, all incoming links and all relative outcoming links will break. You will also lose all history for the new name. Rename carefully.</p>
<input type="submit"/>
</form>
</section>
`)
//line templates/rename.qtpl:25
} else {
//line templates/rename.qtpl:25
qw422016.N().S(` `)
//line templates/rename.qtpl:26
streamcannotRenameDueToNonExistence(qw422016, hyphaName)
//line templates/rename.qtpl:26
qw422016.N().S(`
`)
//line templates/rename.qtpl:27
}
//line templates/rename.qtpl:27
qw422016.N().S(`</main>
`)
//line templates/rename.qtpl:29
}
//line templates/rename.qtpl:29
func WriteRenameAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line templates/rename.qtpl:29
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/rename.qtpl:29
StreamRenameAskHTML(qw422016, rq, hyphaName, isOld)
//line templates/rename.qtpl:29
qt422016.ReleaseWriter(qw422016)
//line templates/rename.qtpl:29
}
//line templates/rename.qtpl:29
func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) string {
//line templates/rename.qtpl:29
qb422016 := qt422016.AcquireByteBuffer()
//line templates/rename.qtpl:29
WriteRenameAskHTML(qb422016, rq, hyphaName, isOld)
//line templates/rename.qtpl:29
qs422016 := string(qb422016.B)
//line templates/rename.qtpl:29
qt422016.ReleaseByteBuffer(qb422016)
//line templates/rename.qtpl:29
return qs422016
//line templates/rename.qtpl:29
}
//line templates/rename.qtpl:31
func streamcannotRenameDueToNonExistence(qw422016 *qt422016.Writer, hyphaName string) {
//line templates/rename.qtpl:31
qw422016.N().S(`
<section>
<h1>Cannot rename `)
//line templates/rename.qtpl:33
qw422016.E().S(hyphaName)
//line templates/rename.qtpl:33
qw422016.N().S(`</h1>
<p>This hypha does not exist.</p>
<p><a href="/page/`)
//line templates/rename.qtpl:35
qw422016.E().S(hyphaName)
//line templates/rename.qtpl:35
qw422016.N().S(`">Go back</a></p>
</section>
`)
//line templates/rename.qtpl:37
}
//line templates/rename.qtpl:37
func writecannotRenameDueToNonExistence(qq422016 qtio422016.Writer, hyphaName string) {
//line templates/rename.qtpl:37
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/rename.qtpl:37
streamcannotRenameDueToNonExistence(qw422016, hyphaName)
//line templates/rename.qtpl:37
qt422016.ReleaseWriter(qw422016)
//line templates/rename.qtpl:37
}
//line templates/rename.qtpl:37
func cannotRenameDueToNonExistence(hyphaName string) string {
//line templates/rename.qtpl:37
qb422016 := qt422016.AcquireByteBuffer()
//line templates/rename.qtpl:37
writecannotRenameDueToNonExistence(qb422016, hyphaName)
//line templates/rename.qtpl:37
qs422016 := string(qb422016.B)
//line templates/rename.qtpl:37
qt422016.ReleaseByteBuffer(qb422016)
//line templates/rename.qtpl:37
return qs422016
//line templates/rename.qtpl:37
}

View File

@ -1,24 +0,0 @@
{% import "net/http" %}
{% func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
<main>
{%= navHTML(rq, hyphaName, "unattach-ask") %}
{%- if isOld -%}
<section>
<h1>Unattach {%s hyphaName %}?</h1>
<p>Do you really want to unattach hypha <em>{%s hyphaName %}</em>?</p>
<p><a href="/unattach-confirm/{%s hyphaName %}"><strong>Confirm</strong></a></p>
<p><a href="/page/{%s hyphaName %}">Cancel</a></p>
</section>
{%- else -%}
{%= cannotUnattachDueToNonExistence(hyphaName) %}
{%- endif -%}
</main>
{% endfunc %}
{% func cannotUnattachDueToNonExistence(hyphaName string) %}
<section>
<h1>Cannot unattach {%s hyphaName %}</h1>
<p>This hypha does not exist.</p>
<p><a href="/page/{%s hyphaName %}">Go back</a></p>
</section>
{% endfunc %}

View File

@ -1,148 +0,0 @@
// Code generated by qtc from "unattach.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line templates/unattach.qtpl:1
package templates
//line templates/unattach.qtpl:1
import "net/http"
//line templates/unattach.qtpl:2
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line templates/unattach.qtpl:2
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line templates/unattach.qtpl:2
func StreamUnattachAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line templates/unattach.qtpl:2
qw422016.N().S(`
<main>
`)
//line templates/unattach.qtpl:4
streamnavHTML(qw422016, rq, hyphaName, "unattach-ask")
//line templates/unattach.qtpl:4
qw422016.N().S(`
`)
//line templates/unattach.qtpl:5
if isOld {
//line templates/unattach.qtpl:5
qw422016.N().S(` <section>
<h1>Unattach `)
//line templates/unattach.qtpl:7
qw422016.E().S(hyphaName)
//line templates/unattach.qtpl:7
qw422016.N().S(`?</h1>
<p>Do you really want to unattach hypha <em>`)
//line templates/unattach.qtpl:8
qw422016.E().S(hyphaName)
//line templates/unattach.qtpl:8
qw422016.N().S(`</em>?</p>
<p><a href="/unattach-confirm/`)
//line templates/unattach.qtpl:9
qw422016.E().S(hyphaName)
//line templates/unattach.qtpl:9
qw422016.N().S(`"><strong>Confirm</strong></a></p>
<p><a href="/page/`)
//line templates/unattach.qtpl:10
qw422016.E().S(hyphaName)
//line templates/unattach.qtpl:10
qw422016.N().S(`">Cancel</a></p>
</section>
`)
//line templates/unattach.qtpl:12
} else {
//line templates/unattach.qtpl:12
qw422016.N().S(` `)
//line templates/unattach.qtpl:13
streamcannotUnattachDueToNonExistence(qw422016, hyphaName)
//line templates/unattach.qtpl:13
qw422016.N().S(`
`)
//line templates/unattach.qtpl:14
}
//line templates/unattach.qtpl:14
qw422016.N().S(`</main>
`)
//line templates/unattach.qtpl:16
}
//line templates/unattach.qtpl:16
func WriteUnattachAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line templates/unattach.qtpl:16
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/unattach.qtpl:16
StreamUnattachAskHTML(qw422016, rq, hyphaName, isOld)
//line templates/unattach.qtpl:16
qt422016.ReleaseWriter(qw422016)
//line templates/unattach.qtpl:16
}
//line templates/unattach.qtpl:16
func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) string {
//line templates/unattach.qtpl:16
qb422016 := qt422016.AcquireByteBuffer()
//line templates/unattach.qtpl:16
WriteUnattachAskHTML(qb422016, rq, hyphaName, isOld)
//line templates/unattach.qtpl:16
qs422016 := string(qb422016.B)
//line templates/unattach.qtpl:16
qt422016.ReleaseByteBuffer(qb422016)
//line templates/unattach.qtpl:16
return qs422016
//line templates/unattach.qtpl:16
}
//line templates/unattach.qtpl:18
func streamcannotUnattachDueToNonExistence(qw422016 *qt422016.Writer, hyphaName string) {
//line templates/unattach.qtpl:18
qw422016.N().S(`
<section>
<h1>Cannot unattach `)
//line templates/unattach.qtpl:20
qw422016.E().S(hyphaName)
//line templates/unattach.qtpl:20
qw422016.N().S(`</h1>
<p>This hypha does not exist.</p>
<p><a href="/page/`)
//line templates/unattach.qtpl:22
qw422016.E().S(hyphaName)
//line templates/unattach.qtpl:22
qw422016.N().S(`">Go back</a></p>
</section>
`)
//line templates/unattach.qtpl:24
}
//line templates/unattach.qtpl:24
func writecannotUnattachDueToNonExistence(qq422016 qtio422016.Writer, hyphaName string) {
//line templates/unattach.qtpl:24
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/unattach.qtpl:24
streamcannotUnattachDueToNonExistence(qw422016, hyphaName)
//line templates/unattach.qtpl:24
qt422016.ReleaseWriter(qw422016)
//line templates/unattach.qtpl:24
}
//line templates/unattach.qtpl:24
func cannotUnattachDueToNonExistence(hyphaName string) string {
//line templates/unattach.qtpl:24
qb422016 := qt422016.AcquireByteBuffer()
//line templates/unattach.qtpl:24
writecannotUnattachDueToNonExistence(qb422016, hyphaName)
//line templates/unattach.qtpl:24
qs422016 := string(qb422016.B)
//line templates/unattach.qtpl:24
qt422016.ReleaseByteBuffer(qb422016)
//line templates/unattach.qtpl:24
return qs422016
//line templates/unattach.qtpl:24
}

View File

@ -6,103 +6,135 @@ import (
"sort"
"strings"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/util"
)
// If Name == "", the tree is empty.
type tree struct {
type sibling struct {
name string
exists bool
prevSibling string
nextSibling string
siblings []string
descendants []*tree
root bool
hyphaIterator func(func(string))
hasChildren bool
}
func (s *sibling) checkThisChild(hyphaName string) {
if !s.hasChildren && path.Dir(hyphaName) == s.name {
s.hasChildren = true
}
}
func (s *sibling) asHTML() string {
class := "navitree__entry navitree__sibling"
if s.hasChildren {
class += " navitree__sibling_fertile navitree__entry_fertile"
} else {
class += " navitree__sibling_infertile navitree__entry_infertile"
}
return fmt.Sprintf(
`<li class="%s"><a class="navitree__link" href="/hypha/%s">%s</a></li>`,
class,
s.name,
util.BeautifulName(path.Base(s.name)),
)
}
type mainFamilyMember struct {
name string
children []*mainFamilyMember
}
func (m *mainFamilyMember) checkThisChild(hyphaName string) (adopted bool) {
if path.Dir(hyphaName) == m.name {
m.children = append(m.children, &mainFamilyMember{
name: hyphaName,
children: make([]*mainFamilyMember, 0),
})
return true
}
return false
}
func (m *mainFamilyMember) asHTML() string {
if len(m.children) == 0 {
return fmt.Sprintf(`<li class="subhyphae__entry"><a class="subhyphae__link" href="/hypha/%s">%s</a></li>`, m.name, util.BeautifulName(path.Base(m.name)))
}
sort.Slice(m.children, func(i, j int) bool {
return m.children[i].name < m.children[j].name
})
html := fmt.Sprintf(`<li class="subhyphae__entry"><a class="subhyphae__link" href="/hypha/%s">%s</a><ul>`, m.name, util.BeautifulName(path.Base(m.name)))
for _, child := range m.children {
html += child.asHTML()
}
return html + `</li></ul></li>`
}
func mainFamilyFromPool(hyphaName string, subhyphaePool map[string]bool) *mainFamilyMember {
var (
nestLevel = strings.Count(hyphaName, "/")
adopted = make([]*mainFamilyMember, 0)
)
for subhyphaName, _ := range subhyphaePool {
subnestLevel := strings.Count(subhyphaName, "/")
if subnestLevel-1 == nestLevel && path.Dir(subhyphaName) == hyphaName {
delete(subhyphaePool, subhyphaName)
adopted = append(adopted, mainFamilyFromPool(subhyphaName, subhyphaePool))
}
}
return &mainFamilyMember{name: hyphaName, children: adopted}
}
func subhyphaeMatrix(hyphaName string, subhyphaePool map[string]bool) string {
var html string
children := mainFamilyFromPool(hyphaName, subhyphaePool).children
sort.Slice(children, func(i, j int) bool {
return children[i].name < children[j].name
})
for _, child := range children {
html += child.asHTML()
}
return html
}
// Tree generates a tree for `hyphaName` as html and returns next and previous hyphae if any.
func Tree(hyphaName string, hyphaIterator func(func(string))) (html, prev, next string) {
t := &tree{name: hyphaName, root: true, hyphaIterator: hyphaIterator}
t.fill()
return t.asHtml(), util.BeautifulName(t.prevSibling), util.BeautifulName(t.nextSibling)
}
// subtree adds a descendant tree to `t` and returns that tree.
func (t *tree) fork(descendantName string) *tree {
subt := &tree{
name: descendantName,
root: false,
hyphaIterator: t.hyphaIterator,
func Tree(hyphaName string) (relatives, subhyphae, prev, next string) {
var (
// One of the siblings is the hypha with name `hyphaName`
siblings = findSiblings(hyphaName)
subhyphaePool = make(map[string]bool)
I int
)
for h := range hyphae.YieldExistingHyphae() {
for _, s := range siblings {
s.checkThisChild(h.Name)
}
t.descendants = append(t.descendants, subt)
return subt
}
// Compare current prev next hyphae and decide if any of them should be set to `name2`.
func (t *tree) prevNextDetermine(name2 string) {
if name2 < t.name && (name2 > t.prevSibling || t.prevSibling == "") {
t.prevSibling = name2
} else if name2 > t.name && (name2 < t.nextSibling || t.nextSibling == "") {
t.nextSibling = name2
if strings.HasPrefix(h.Name, hyphaName+"/") {
subhyphaePool[h.Name] = true
}
}
// Compares names and does something with them, may generate a subtree.
func (t *tree) compareNamesAndAppend(name2 string) {
switch {
case t.name == name2:
t.exists = true
case t.root && path.Dir(t.name) == path.Dir(name2):
t.prevNextDetermine(name2)
t.siblings = append(t.siblings, name2)
case t.name == path.Dir(name2):
t.fork(name2).fill()
}
}
// Fills t.siblings and t.descendants, sorts them and does the same to the descendants.
func (t *tree) fill() {
t.hyphaIterator(func(hyphaName string) {
t.compareNamesAndAppend(hyphaName)
})
sort.Strings(t.siblings)
sort.Slice(t.descendants, func(i, j int) bool {
return t.descendants[i].name < t.descendants[j].name
})
}
// asHtml returns HTML representation of a tree.
// It applies itself recursively on the tree's children.
func (t *tree) asHtml() (html string) {
if t.root {
html += navitreeEntry(t.name, "navitree__pagename")
for i, s := range siblings {
if s.name == hyphaName {
I = i
relatives += fmt.Sprintf(`<li class="navitree__entry navitree__entry_this"><span>%s</span></li>`, util.BeautifulName(path.Base(hyphaName)))
} else {
html += navitreeEntry(t.name, "navitree__name")
}
for _, subtree := range t.descendants {
html += subtree.asHtml()
}
if t.root {
for _, siblingName := range t.siblings {
html += navitreeEntry(siblingName, "navitree__sibling")
relatives += s.asHTML()
}
}
return `<ul class="navitree__node">` + html + `</ul>`
if I != 0 {
prev = siblings[I-1].name
}
if I != len(siblings)-1 {
next = siblings[I+1].name
}
return fmt.Sprintf(`<ul class="navitree">%s</ul>`, relatives), subhyphaeMatrix(hyphaName, subhyphaePool), prev, next
}
// Strip hypha name from all ancestor names, replace _ with spaces, title case
func beautifulName(uglyName string) string {
return strings.Title(strings.ReplaceAll(path.Base(uglyName), "_", " "))
}
// navitreeEntry is a small utility function that makes generating html easier.
func navitreeEntry(name, class string) string {
return fmt.Sprintf(`<li class="navitree__entry %s">
<a class="navitree__link" href="/page/%s">%s</a>
</li>
`, class, name, beautifulName(name))
func findSiblings(hyphaName string) []*sibling {
siblings := []*sibling{&sibling{name: hyphaName, hasChildren: true}}
for h := range hyphae.YieldExistingHyphae() {
if path.Dir(hyphaName) == path.Dir(h.Name) && hyphaName != h.Name {
siblings = append(siblings, &sibling{name: h.Name, hasChildren: false})
}
}
sort.Slice(siblings, func(i, j int) bool {
return siblings[i].name < siblings[j].name
})
return siblings
}

View File

@ -26,6 +26,7 @@ var minimalRights = map[string]int{
"delete-ask": 3,
"delete-confirm": 3,
"reindex": 4,
"admin/shutdown": 4,
}
// Group — Right

View File

@ -8,16 +8,25 @@ var AuthUsed bool
var users sync.Map
var tokens sync.Map
func ListUsersWithGroup(group string) []string {
usersWithTheGroup := []string{}
func YieldUsers() chan *User {
ch := make(chan *User)
go func(ch chan *User) {
users.Range(func(_, v interface{}) bool {
userobj := v.(*User)
if userobj.Group == group {
usersWithTheGroup = append(usersWithTheGroup, userobj.Name)
}
ch <- v.(*User)
return true
})
close(ch)
}(ch)
return ch
}
func ListUsersWithGroup(group string) []string {
usersWithTheGroup := []string{}
for u := range YieldUsers() {
if u.Group == group {
usersWithTheGroup = append(usersWithTheGroup, u.Name)
}
}
return usersWithTheGroup
}

View File

@ -4,7 +4,9 @@ import (
"crypto/rand"
"encoding/hex"
"net/http"
"regexp"
"strings"
"unicode"
)
var (
@ -18,8 +20,27 @@ var (
HeaderLinksHypha string
AuthMethod string
FixedCredentialsPath string
GeminiCertPath string
)
// LettersNumbersOnly keeps letters and numbers only in the given string.
func LettersNumbersOnly(s string) string {
var (
ret strings.Builder
usedUnderscore bool
)
for _, r := range s {
if unicode.IsLetter(r) || unicode.IsNumber(r) {
ret.WriteRune(r)
usedUnderscore = false
} else if !usedUnderscore {
ret.WriteRune('_')
usedUnderscore = true
}
}
return strings.Trim(ret.String(), "_")
}
// ShorterPath is used by handlerList to display shorter path to the files. It simply strips WikiDir.
func ShorterPath(path string) string {
if strings.HasPrefix(path, WikiDir) {
@ -39,17 +60,6 @@ func HTTP200Page(w http.ResponseWriter, page string) {
w.Write([]byte(page))
}
// FindSubhyphae finds names of existing hyphae given the `hyphaIterator`.
func FindSubhyphae(hyphaName string, hyphaIterator func(func(string))) []string {
subhyphae := make([]string, 0)
hyphaIterator(func(otherHyphaName string) {
if strings.HasPrefix(otherHyphaName, hyphaName+"/") {
subhyphae = append(subhyphae, otherHyphaName)
}
})
return subhyphae
}
func RandomString(n int) (string, error) {
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
@ -65,3 +75,16 @@ func BeautifulName(uglyName string) string {
}
return strings.Title(strings.ReplaceAll(uglyName, "_", " "))
}
// CanonicalName makes sure the `name` is canonical. A name is canonical if it is lowercase and all spaces are replaced with underscores.
func CanonicalName(name string) string {
return strings.ToLower(strings.ReplaceAll(name, " ", "_"))
}
// HyphaPattern is a pattern which all hyphae must match.
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
// IsCanonicalName checks if the `name` is canonical.
func IsCanonicalName(name string) bool {
return HyphaPattern.MatchString(name)
}

68
views/auth.qtpl Normal file
View File

@ -0,0 +1,68 @@
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% func LoginHTML() %}
<div class="layout">
<main class="main-width">
<section>
{% if user.AuthUsed %}
<form class="modal" method="post" action="/login-data" id="login-form" enctype="multipart/form-data" autocomplete="on">
<fieldset class="modal__fieldset">
<legend class="modal__title">Log in to {%s util.SiteName %}</legend>
<p>Use the data you were given by an administrator.</p>
<label for="login-form__username">Username</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
<br>
<label for="login-form__password">Password</label>
<br>
<input type="password" required name="password" autocomplete="current-password">
<p>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.</p>
<input class="modal__action modal__submit" type="submit">
<a class="modal__action modal__cancel" href="/">Cancel</a>
</fieldset>
</form>
{% else %}
<p>Administrators of this wiki have not configured any authorization method. You can make edits anonymously.</p>
<p><a class="modal__cancel" href="/">← Go home</a></p>
{% endif %}
</section>
</main>
</div>
{% endfunc %}
{% func LoginErrorHTML(err string) %}
<div class="layout">
<main class="main-width">
<section>
{% switch err %}
{% case "unknown username" %}
<p class="error">Unknown username.</p>
{% case "wrong password" %}
<p class="error">Wrong password.</p>
{% default %}
<p class="error">{%s err %}</p>
{% endswitch %}
<p><a href="/login">← Try again</a></p>
</section>
</main>
</div>
{% endfunc %}
{% func LogoutHTML(can bool) %}
<div class="layout">
<main class="main-width">
<section>
{% if can %}
<h1>Log out?</h1>
<p><a href="/logout-confirm"><strong>Confirm</strong></a></p>
<p><a href="/">Cancel</a></p>
{% else %}
<p>You cannot log out because you are not logged in.</p>
<p><a href="/login">Login</a></p>
<p><a href="/login">← Home</a></p>
{% endif %}
</section>
</main>
</div>
{% endfunc %}

232
views/auth.qtpl.go Normal file
View File

@ -0,0 +1,232 @@
// Code generated by qtc from "auth.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line views/auth.qtpl:1
package views
//line views/auth.qtpl:1
import "github.com/bouncepaw/mycorrhiza/user"
//line views/auth.qtpl:2
import "github.com/bouncepaw/mycorrhiza/util"
//line views/auth.qtpl:4
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line views/auth.qtpl:4
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line views/auth.qtpl:4
func StreamLoginHTML(qw422016 *qt422016.Writer) {
//line views/auth.qtpl:4
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<section>
`)
//line views/auth.qtpl:8
if user.AuthUsed {
//line views/auth.qtpl:8
qw422016.N().S(`
<form class="modal" method="post" action="/login-data" id="login-form" enctype="multipart/form-data" autocomplete="on">
<fieldset class="modal__fieldset">
<legend class="modal__title">Log in to `)
//line views/auth.qtpl:11
qw422016.E().S(util.SiteName)
//line views/auth.qtpl:11
qw422016.N().S(`</legend>
<p>Use the data you were given by an administrator.</p>
<label for="login-form__username">Username</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
<br>
<label for="login-form__password">Password</label>
<br>
<input type="password" required name="password" autocomplete="current-password">
<p>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.</p>
<input class="modal__action modal__submit" type="submit">
<a class="modal__action modal__cancel" href="/">Cancel</a>
</fieldset>
</form>
`)
//line views/auth.qtpl:25
} else {
//line views/auth.qtpl:25
qw422016.N().S(`
<p>Administrators of this wiki have not configured any authorization method. You can make edits anonymously.</p>
<p><a class="modal__cancel" href="/"> Go home</a></p>
`)
//line views/auth.qtpl:28
}
//line views/auth.qtpl:28
qw422016.N().S(`
</section>
</main>
</div>
`)
//line views/auth.qtpl:32
}
//line views/auth.qtpl:32
func WriteLoginHTML(qq422016 qtio422016.Writer) {
//line views/auth.qtpl:32
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/auth.qtpl:32
StreamLoginHTML(qw422016)
//line views/auth.qtpl:32
qt422016.ReleaseWriter(qw422016)
//line views/auth.qtpl:32
}
//line views/auth.qtpl:32
func LoginHTML() string {
//line views/auth.qtpl:32
qb422016 := qt422016.AcquireByteBuffer()
//line views/auth.qtpl:32
WriteLoginHTML(qb422016)
//line views/auth.qtpl:32
qs422016 := string(qb422016.B)
//line views/auth.qtpl:32
qt422016.ReleaseByteBuffer(qb422016)
//line views/auth.qtpl:32
return qs422016
//line views/auth.qtpl:32
}
//line views/auth.qtpl:34
func StreamLoginErrorHTML(qw422016 *qt422016.Writer, err string) {
//line views/auth.qtpl:34
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<section>
`)
//line views/auth.qtpl:38
switch err {
//line views/auth.qtpl:39
case "unknown username":
//line views/auth.qtpl:39
qw422016.N().S(`
<p class="error">Unknown username.</p>
`)
//line views/auth.qtpl:41
case "wrong password":
//line views/auth.qtpl:41
qw422016.N().S(`
<p class="error">Wrong password.</p>
`)
//line views/auth.qtpl:43
default:
//line views/auth.qtpl:43
qw422016.N().S(`
<p class="error">`)
//line views/auth.qtpl:44
qw422016.E().S(err)
//line views/auth.qtpl:44
qw422016.N().S(`</p>
`)
//line views/auth.qtpl:45
}
//line views/auth.qtpl:45
qw422016.N().S(`
<p><a href="/login"> Try again</a></p>
</section>
</main>
</div>
`)
//line views/auth.qtpl:50
}
//line views/auth.qtpl:50
func WriteLoginErrorHTML(qq422016 qtio422016.Writer, err string) {
//line views/auth.qtpl:50
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/auth.qtpl:50
StreamLoginErrorHTML(qw422016, err)
//line views/auth.qtpl:50
qt422016.ReleaseWriter(qw422016)
//line views/auth.qtpl:50
}
//line views/auth.qtpl:50
func LoginErrorHTML(err string) string {
//line views/auth.qtpl:50
qb422016 := qt422016.AcquireByteBuffer()
//line views/auth.qtpl:50
WriteLoginErrorHTML(qb422016, err)
//line views/auth.qtpl:50
qs422016 := string(qb422016.B)
//line views/auth.qtpl:50
qt422016.ReleaseByteBuffer(qb422016)
//line views/auth.qtpl:50
return qs422016
//line views/auth.qtpl:50
}
//line views/auth.qtpl:52
func StreamLogoutHTML(qw422016 *qt422016.Writer, can bool) {
//line views/auth.qtpl:52
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<section>
`)
//line views/auth.qtpl:56
if can {
//line views/auth.qtpl:56
qw422016.N().S(`
<h1>Log out?</h1>
<p><a href="/logout-confirm"><strong>Confirm</strong></a></p>
<p><a href="/">Cancel</a></p>
`)
//line views/auth.qtpl:60
} else {
//line views/auth.qtpl:60
qw422016.N().S(`
<p>You cannot log out because you are not logged in.</p>
<p><a href="/login">Login</a></p>
<p><a href="/login"> Home</a></p>
`)
//line views/auth.qtpl:64
}
//line views/auth.qtpl:64
qw422016.N().S(`
</section>
</main>
</div>
`)
//line views/auth.qtpl:68
}
//line views/auth.qtpl:68
func WriteLogoutHTML(qq422016 qtio422016.Writer, can bool) {
//line views/auth.qtpl:68
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/auth.qtpl:68
StreamLogoutHTML(qw422016, can)
//line views/auth.qtpl:68
qt422016.ReleaseWriter(qw422016)
//line views/auth.qtpl:68
}
//line views/auth.qtpl:68
func LogoutHTML(can bool) string {
//line views/auth.qtpl:68
qb422016 := qt422016.AcquireByteBuffer()
//line views/auth.qtpl:68
WriteLogoutHTML(qb422016, can)
//line views/auth.qtpl:68
qs422016 := string(qb422016.B)
//line views/auth.qtpl:68
qt422016.ReleaseByteBuffer(qb422016)
//line views/auth.qtpl:68
return qs422016
//line views/auth.qtpl:68
}

View File

@ -1,5 +1,11 @@
{% func RecentChangesHTML(changes []string, n int) %}
<main class="recent-changes">
{% import "net/http" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% import "github.com/bouncepaw/mycorrhiza/history" %}
{% func RecentChangesHTML(n int) %}
<div class="layout">
<main class="main-width recent-changes">
<h1>Recent Changes</h1>
<p><a href="/">← Back</a></p>
@ -27,6 +33,9 @@
How come? I'll add the role anyway. -- bouncepaw
{% endcomment %}
{% code
changes := history.RecentChanges(n)
%}
<section class="recent-changes__list" role="feed">
{% if len(changes) == 0 %}
<p>Could not find any recent changes.</p>
@ -34,10 +43,30 @@
{% for i, entry := range changes %}
<ul class="recent-changes__entry rc-entry" role="article"
aria-setsize="{%d n %}" aria-posinset="{%d i %}">
{%s= entry %}
{%s= recentChangesEntry(entry) %}
</ul>
{% endfor %}
{% endif %}
</section>
</main>
</div>
{% endfunc %}
{% func recentChangesEntry(rev history.Revision) %}
<li class="rc-entry__time"><time>{%s rev.TimeString() %}</time></li>
<li class="rc-entry__hash">{%s rev.Hash %}</li>
<li class="rc-entry__links">{%s= rev.HyphaeLinksHTML() %}</li>
<li class="rc-entry__msg">{%s rev.Message %} {% if rev.Username != "anon" %}<span class="rc-entry__author">by <a href="/hypha/{%s util.UserHypha %}/{%s rev.Username %}" rel="author">{%s rev.Username %}</a></span>{% endif %}</li>
{% endfunc %}
{% func HistoryHTML(rq *http.Request, hyphaName, list string) %}
{%= NavHTML(rq, hyphaName, "history") %}
<div class="layout">
<main class="main-width">
<article class="history">
<h1>History of {%s util.BeautifulName(hyphaName) %}</h1>
{%s= list %}
</article>
</main>
</div>
{% endfunc %}

305
views/history.qtpl.go Normal file
View File

@ -0,0 +1,305 @@
// Code generated by qtc from "history.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line views/history.qtpl:1
package views
//line views/history.qtpl:1
import "net/http"
//line views/history.qtpl:3
import "github.com/bouncepaw/mycorrhiza/util"
//line views/history.qtpl:4
import "github.com/bouncepaw/mycorrhiza/history"
//line views/history.qtpl:6
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line views/history.qtpl:6
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line views/history.qtpl:6
func StreamRecentChangesHTML(qw422016 *qt422016.Writer, n int) {
//line views/history.qtpl:6
qw422016.N().S(`
<div class="layout">
<main class="main-width recent-changes">
<h1>Recent Changes</h1>
<p><a href="/"> Back</a></p>
<nav class="recent-changes__count">
See
`)
//line views/history.qtpl:14
for _, m := range []int{20, 0, 50, 0, 100} {
//line views/history.qtpl:14
qw422016.N().S(`
`)
//line views/history.qtpl:15
switch m {
//line views/history.qtpl:16
case 0:
//line views/history.qtpl:16
qw422016.N().S(`
<span aria-hidden="true">|</span>
`)
//line views/history.qtpl:18
case n:
//line views/history.qtpl:18
qw422016.N().S(`
<b>`)
//line views/history.qtpl:19
qw422016.N().D(n)
//line views/history.qtpl:19
qw422016.N().S(`</b>
`)
//line views/history.qtpl:20
default:
//line views/history.qtpl:20
qw422016.N().S(`
<a href="/recent-changes/`)
//line views/history.qtpl:21
qw422016.N().D(m)
//line views/history.qtpl:21
qw422016.N().S(`">`)
//line views/history.qtpl:21
qw422016.N().D(m)
//line views/history.qtpl:21
qw422016.N().S(`</a>
`)
//line views/history.qtpl:22
}
//line views/history.qtpl:22
qw422016.N().S(`
`)
//line views/history.qtpl:23
}
//line views/history.qtpl:23
qw422016.N().S(`
recent changes
</nav>
<p><img class="icon" width="20" height="20" src="https://upload.wikimedia.org/wikipedia/commons/4/46/Generic_Feed-icon.svg">Subscribe via <a href="/recent-changes-rss">RSS</a>, <a href="/recent-changes-atom">Atom</a> or <a href="/recent-changes-json">JSON feed</a>.</p>
`)
//line views/history.qtpl:34
qw422016.N().S(`
`)
//line views/history.qtpl:37
changes := history.RecentChanges(n)
//line views/history.qtpl:38
qw422016.N().S(`
<section class="recent-changes__list" role="feed">
`)
//line views/history.qtpl:40
if len(changes) == 0 {
//line views/history.qtpl:40
qw422016.N().S(`
<p>Could not find any recent changes.</p>
`)
//line views/history.qtpl:42
} else {
//line views/history.qtpl:42
qw422016.N().S(`
`)
//line views/history.qtpl:43
for i, entry := range changes {
//line views/history.qtpl:43
qw422016.N().S(`
<ul class="recent-changes__entry rc-entry" role="article"
aria-setsize="`)
//line views/history.qtpl:45
qw422016.N().D(n)
//line views/history.qtpl:45
qw422016.N().S(`" aria-posinset="`)
//line views/history.qtpl:45
qw422016.N().D(i)
//line views/history.qtpl:45
qw422016.N().S(`">
`)
//line views/history.qtpl:46
qw422016.N().S(recentChangesEntry(entry))
//line views/history.qtpl:46
qw422016.N().S(`
</ul>
`)
//line views/history.qtpl:48
}
//line views/history.qtpl:48
qw422016.N().S(`
`)
//line views/history.qtpl:49
}
//line views/history.qtpl:49
qw422016.N().S(`
</section>
</main>
</div>
`)
//line views/history.qtpl:53
}
//line views/history.qtpl:53
func WriteRecentChangesHTML(qq422016 qtio422016.Writer, n int) {
//line views/history.qtpl:53
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/history.qtpl:53
StreamRecentChangesHTML(qw422016, n)
//line views/history.qtpl:53
qt422016.ReleaseWriter(qw422016)
//line views/history.qtpl:53
}
//line views/history.qtpl:53
func RecentChangesHTML(n int) string {
//line views/history.qtpl:53
qb422016 := qt422016.AcquireByteBuffer()
//line views/history.qtpl:53
WriteRecentChangesHTML(qb422016, n)
//line views/history.qtpl:53
qs422016 := string(qb422016.B)
//line views/history.qtpl:53
qt422016.ReleaseByteBuffer(qb422016)
//line views/history.qtpl:53
return qs422016
//line views/history.qtpl:53
}
//line views/history.qtpl:55
func streamrecentChangesEntry(qw422016 *qt422016.Writer, rev history.Revision) {
//line views/history.qtpl:55
qw422016.N().S(`
<li class="rc-entry__time"><time>`)
//line views/history.qtpl:56
qw422016.E().S(rev.TimeString())
//line views/history.qtpl:56
qw422016.N().S(`</time></li>
<li class="rc-entry__hash">`)
//line views/history.qtpl:57
qw422016.E().S(rev.Hash)
//line views/history.qtpl:57
qw422016.N().S(`</li>
<li class="rc-entry__links">`)
//line views/history.qtpl:58
qw422016.N().S(rev.HyphaeLinksHTML())
//line views/history.qtpl:58
qw422016.N().S(`</li>
<li class="rc-entry__msg">`)
//line views/history.qtpl:59
qw422016.E().S(rev.Message)
//line views/history.qtpl:59
qw422016.N().S(` `)
//line views/history.qtpl:59
if rev.Username != "anon" {
//line views/history.qtpl:59
qw422016.N().S(`<span class="rc-entry__author">by <a href="/hypha/`)
//line views/history.qtpl:59
qw422016.E().S(util.UserHypha)
//line views/history.qtpl:59
qw422016.N().S(`/`)
//line views/history.qtpl:59
qw422016.E().S(rev.Username)
//line views/history.qtpl:59
qw422016.N().S(`" rel="author">`)
//line views/history.qtpl:59
qw422016.E().S(rev.Username)
//line views/history.qtpl:59
qw422016.N().S(`</a></span>`)
//line views/history.qtpl:59
}
//line views/history.qtpl:59
qw422016.N().S(`</li>
`)
//line views/history.qtpl:60
}
//line views/history.qtpl:60
func writerecentChangesEntry(qq422016 qtio422016.Writer, rev history.Revision) {
//line views/history.qtpl:60
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/history.qtpl:60
streamrecentChangesEntry(qw422016, rev)
//line views/history.qtpl:60
qt422016.ReleaseWriter(qw422016)
//line views/history.qtpl:60
}
//line views/history.qtpl:60
func recentChangesEntry(rev history.Revision) string {
//line views/history.qtpl:60
qb422016 := qt422016.AcquireByteBuffer()
//line views/history.qtpl:60
writerecentChangesEntry(qb422016, rev)
//line views/history.qtpl:60
qs422016 := string(qb422016.B)
//line views/history.qtpl:60
qt422016.ReleaseByteBuffer(qb422016)
//line views/history.qtpl:60
return qs422016
//line views/history.qtpl:60
}
//line views/history.qtpl:62
func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, list string) {
//line views/history.qtpl:62
qw422016.N().S(`
`)
//line views/history.qtpl:63
StreamNavHTML(qw422016, rq, hyphaName, "history")
//line views/history.qtpl:63
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<article class="history">
<h1>History of `)
//line views/history.qtpl:67
qw422016.E().S(util.BeautifulName(hyphaName))
//line views/history.qtpl:67
qw422016.N().S(`</h1>
`)
//line views/history.qtpl:68
qw422016.N().S(list)
//line views/history.qtpl:68
qw422016.N().S(`
</article>
</main>
</div>
`)
//line views/history.qtpl:72
}
//line views/history.qtpl:72
func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, list string) {
//line views/history.qtpl:72
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/history.qtpl:72
StreamHistoryHTML(qw422016, rq, hyphaName, list)
//line views/history.qtpl:72
qt422016.ReleaseWriter(qw422016)
//line views/history.qtpl:72
}
//line views/history.qtpl:72
func HistoryHTML(rq *http.Request, hyphaName, list string) string {
//line views/history.qtpl:72
qb422016 := qt422016.AcquireByteBuffer()
//line views/history.qtpl:72
WriteHistoryHTML(qb422016, rq, hyphaName, list)
//line views/history.qtpl:72
qs422016 := string(qb422016.B)
//line views/history.qtpl:72
qt422016.ReleaseByteBuffer(qb422016)
//line views/history.qtpl:72
return qs422016
//line views/history.qtpl:72
}

81
views/hypha.qtpl Normal file
View File

@ -0,0 +1,81 @@
{% import "path/filepath" %}
{% import "strings" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% func NaviTitleHTML(h *hyphae.Hypha) %}
{% code
var (
prevAcc = "/hypha/"
parts = strings.Split(h.Name, "/")
)
%}
<h1 class="navi-title">
{% stripspace %}
<a href="/hypha/{%s util.HomePage %}">
{%-s= util.SiteNavIcon -%}
<span aria-hidden="true" class="navi-title__colon">:</span>
</a>
{% for i, part := range parts %}
{% if i > 0 %}
<span aria-hidden="true" class="navi-title__separator">/</span>
{% endif %}
<a href="{%s prevAcc + part %}"
rel="{% if i == len(parts) - 1 %}bookmark{% else %}up{% endif %}">
{%s= util.BeautifulName(part) %}
</a>
{% code prevAcc += part + "/" %}
{% endfor %}
{% endstripspace %}
</h1>
{% endfunc %}
{% func BackLinksHTML(h *hyphae.Hypha) %}
<aside class="backlinks layout-card">
<h2 class="backlinks__title layout-card__title">Backlinks</h2>
<nav class="backlinks__nav">
<ul class="backlinks__list">
{% for _, backlink := range h.BackLinks %}
<li class="backlinks__entry">
<a class="backlinks__link" href="/hypha/{%s backlink.Name %}">
{%s util.BeautifulName(filepath.Base(backlink.Name)) %}
</a>
</li>
{% endfor %}
</ul>
</nav>
</aside>
{% endfunc %}
{% func AttachmentHTML(h *hyphae.Hypha) %}
{% switch filepath.Ext(h.BinaryPath) %}
{% case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico" %}
<div class="binary-container binary-container_with-img">
<a href="/binary/{%s= h.Name %}"><img src="/binary/{%s= h.Name %}"/></a>
</div>
{% case ".ogg", ".webm", ".mp4" %}
<div class="binary-container binary-container_with-video">
<video controls>
<source src="/binary/{%s= h.Name %}"/>
<p>Your browser does not support video. <a href="/binary/{%s= h.Name %}">Download video</a></p>
</video>
</div>
{% case ".mp3" %}
<div class="binary-container binary-container_with-audio">
<audio controls>
<source src="/binary/{%s= h.Name %}"/>
<p>Your browser does not support audio. <a href="/binary/{%s= h.Name %}">Download audio</a></p>
</audio>
</div>
{% default %}
<div class="binary-container binary-container_with-nothing">
<p><a href="/binary/{%s= h.Name %}">Download media</a></p>
</div>
{% endswitch %}
{% endfunc %}

297
views/hypha.qtpl.go Normal file
View File

@ -0,0 +1,297 @@
// Code generated by qtc from "hypha.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line views/hypha.qtpl:1
package views
//line views/hypha.qtpl:1
import "path/filepath"
//line views/hypha.qtpl:2
import "strings"
//line views/hypha.qtpl:3
import "github.com/bouncepaw/mycorrhiza/hyphae"
//line views/hypha.qtpl:4
import "github.com/bouncepaw/mycorrhiza/util"
//line views/hypha.qtpl:6
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line views/hypha.qtpl:6
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line views/hypha.qtpl:6
func StreamNaviTitleHTML(qw422016 *qt422016.Writer, h *hyphae.Hypha) {
//line views/hypha.qtpl:6
qw422016.N().S(`
`)
//line views/hypha.qtpl:8
var (
prevAcc = "/hypha/"
parts = strings.Split(h.Name, "/")
)
//line views/hypha.qtpl:12
qw422016.N().S(`
<h1 class="navi-title">
`)
//line views/hypha.qtpl:14
qw422016.N().S(`<a href="/hypha/`)
//line views/hypha.qtpl:15
qw422016.E().S(util.HomePage)
//line views/hypha.qtpl:15
qw422016.N().S(`">`)
//line views/hypha.qtpl:16
qw422016.N().S(util.SiteNavIcon)
//line views/hypha.qtpl:16
qw422016.N().S(`<span aria-hidden="true" class="navi-title__colon">:</span></a>`)
//line views/hypha.qtpl:20
for i, part := range parts {
//line views/hypha.qtpl:21
if i > 0 {
//line views/hypha.qtpl:21
qw422016.N().S(`<span aria-hidden="true" class="navi-title__separator">/</span>`)
//line views/hypha.qtpl:23
}
//line views/hypha.qtpl:23
qw422016.N().S(`<a href="`)
//line views/hypha.qtpl:25
qw422016.E().S(prevAcc + part)
//line views/hypha.qtpl:25
qw422016.N().S(`"rel="`)
//line views/hypha.qtpl:26
if i == len(parts)-1 {
//line views/hypha.qtpl:26
qw422016.N().S(`bookmark`)
//line views/hypha.qtpl:26
} else {
//line views/hypha.qtpl:26
qw422016.N().S(`up`)
//line views/hypha.qtpl:26
}
//line views/hypha.qtpl:26
qw422016.N().S(`">`)
//line views/hypha.qtpl:27
qw422016.N().S(util.BeautifulName(part))
//line views/hypha.qtpl:27
qw422016.N().S(`</a>`)
//line views/hypha.qtpl:29
prevAcc += part + "/"
//line views/hypha.qtpl:30
}
//line views/hypha.qtpl:31
qw422016.N().S(`
</h1>
`)
//line views/hypha.qtpl:33
}
//line views/hypha.qtpl:33
func WriteNaviTitleHTML(qq422016 qtio422016.Writer, h *hyphae.Hypha) {
//line views/hypha.qtpl:33
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/hypha.qtpl:33
StreamNaviTitleHTML(qw422016, h)
//line views/hypha.qtpl:33
qt422016.ReleaseWriter(qw422016)
//line views/hypha.qtpl:33
}
//line views/hypha.qtpl:33
func NaviTitleHTML(h *hyphae.Hypha) string {
//line views/hypha.qtpl:33
qb422016 := qt422016.AcquireByteBuffer()
//line views/hypha.qtpl:33
WriteNaviTitleHTML(qb422016, h)
//line views/hypha.qtpl:33
qs422016 := string(qb422016.B)
//line views/hypha.qtpl:33
qt422016.ReleaseByteBuffer(qb422016)
//line views/hypha.qtpl:33
return qs422016
//line views/hypha.qtpl:33
}
//line views/hypha.qtpl:35
func StreamBackLinksHTML(qw422016 *qt422016.Writer, h *hyphae.Hypha) {
//line views/hypha.qtpl:35
qw422016.N().S(`
<aside class="backlinks layout-card">
<h2 class="backlinks__title layout-card__title">Backlinks</h2>
<nav class="backlinks__nav">
<ul class="backlinks__list">
`)
//line views/hypha.qtpl:40
for _, backlink := range h.BackLinks {
//line views/hypha.qtpl:40
qw422016.N().S(`
<li class="backlinks__entry">
<a class="backlinks__link" href="/hypha/`)
//line views/hypha.qtpl:42
qw422016.E().S(backlink.Name)
//line views/hypha.qtpl:42
qw422016.N().S(`">
`)
//line views/hypha.qtpl:43
qw422016.E().S(util.BeautifulName(filepath.Base(backlink.Name)))
//line views/hypha.qtpl:43
qw422016.N().S(`
</a>
</li>
`)
//line views/hypha.qtpl:46
}
//line views/hypha.qtpl:46
qw422016.N().S(`
</ul>
</nav>
</aside>
`)
//line views/hypha.qtpl:50
}
//line views/hypha.qtpl:50
func WriteBackLinksHTML(qq422016 qtio422016.Writer, h *hyphae.Hypha) {
//line views/hypha.qtpl:50
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/hypha.qtpl:50
StreamBackLinksHTML(qw422016, h)
//line views/hypha.qtpl:50
qt422016.ReleaseWriter(qw422016)
//line views/hypha.qtpl:50
}
//line views/hypha.qtpl:50
func BackLinksHTML(h *hyphae.Hypha) string {
//line views/hypha.qtpl:50
qb422016 := qt422016.AcquireByteBuffer()
//line views/hypha.qtpl:50
WriteBackLinksHTML(qb422016, h)
//line views/hypha.qtpl:50
qs422016 := string(qb422016.B)
//line views/hypha.qtpl:50
qt422016.ReleaseByteBuffer(qb422016)
//line views/hypha.qtpl:50
return qs422016
//line views/hypha.qtpl:50
}
//line views/hypha.qtpl:52
func StreamAttachmentHTML(qw422016 *qt422016.Writer, h *hyphae.Hypha) {
//line views/hypha.qtpl:52
qw422016.N().S(`
`)
//line views/hypha.qtpl:53
switch filepath.Ext(h.BinaryPath) {
//line views/hypha.qtpl:55
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
//line views/hypha.qtpl:55
qw422016.N().S(`
<div class="binary-container binary-container_with-img">
<a href="/binary/`)
//line views/hypha.qtpl:57
qw422016.N().S(h.Name)
//line views/hypha.qtpl:57
qw422016.N().S(`"><img src="/binary/`)
//line views/hypha.qtpl:57
qw422016.N().S(h.Name)
//line views/hypha.qtpl:57
qw422016.N().S(`"/></a>
</div>
`)
//line views/hypha.qtpl:60
case ".ogg", ".webm", ".mp4":
//line views/hypha.qtpl:60
qw422016.N().S(`
<div class="binary-container binary-container_with-video">
<video controls>
<source src="/binary/`)
//line views/hypha.qtpl:63
qw422016.N().S(h.Name)
//line views/hypha.qtpl:63
qw422016.N().S(`"/>
<p>Your browser does not support video. <a href="/binary/`)
//line views/hypha.qtpl:64
qw422016.N().S(h.Name)
//line views/hypha.qtpl:64
qw422016.N().S(`">Download video</a></p>
</video>
</div>
`)
//line views/hypha.qtpl:68
case ".mp3":
//line views/hypha.qtpl:68
qw422016.N().S(`
<div class="binary-container binary-container_with-audio">
<audio controls>
<source src="/binary/`)
//line views/hypha.qtpl:71
qw422016.N().S(h.Name)
//line views/hypha.qtpl:71
qw422016.N().S(`"/>
<p>Your browser does not support audio. <a href="/binary/`)
//line views/hypha.qtpl:72
qw422016.N().S(h.Name)
//line views/hypha.qtpl:72
qw422016.N().S(`">Download audio</a></p>
</audio>
</div>
`)
//line views/hypha.qtpl:76
default:
//line views/hypha.qtpl:76
qw422016.N().S(`
<div class="binary-container binary-container_with-nothing">
<p><a href="/binary/`)
//line views/hypha.qtpl:78
qw422016.N().S(h.Name)
//line views/hypha.qtpl:78
qw422016.N().S(`">Download media</a></p>
</div>
`)
//line views/hypha.qtpl:80
}
//line views/hypha.qtpl:80
qw422016.N().S(`
`)
//line views/hypha.qtpl:81
}
//line views/hypha.qtpl:81
func WriteAttachmentHTML(qq422016 qtio422016.Writer, h *hyphae.Hypha) {
//line views/hypha.qtpl:81
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/hypha.qtpl:81
StreamAttachmentHTML(qw422016, h)
//line views/hypha.qtpl:81
qt422016.ReleaseWriter(qw422016)
//line views/hypha.qtpl:81
}
//line views/hypha.qtpl:81
func AttachmentHTML(h *hyphae.Hypha) string {
//line views/hypha.qtpl:81
qb422016 := qt422016.AcquireByteBuffer()
//line views/hypha.qtpl:81
WriteAttachmentHTML(qb422016, h)
//line views/hypha.qtpl:81
qs422016 := string(qb422016.B)
//line views/hypha.qtpl:81
qt422016.ReleaseByteBuffer(qb422016)
//line views/hypha.qtpl:81
return qs422016
//line views/hypha.qtpl:81
}

63
views/modal.qtpl Normal file
View File

@ -0,0 +1,63 @@
{% import "net/http" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
{%= NavHTML(rq, hyphaName, "delete-ask") %}
{%= modalBegin(
"delete-confirm",
hyphaName,
"",
"Delete "+util.BeautifulName(hyphaName)+"?") %}
{%= modalReallyWant(hyphaName, "unattach") %}
<p>In this version of MycorrhizaWiki you cannot undelete a deleted hypha but the history can still be accessed.</p>
{%= modalEnd(hyphaName, true) %}
{% endfunc %}
{% func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
{%= NavHTML(rq, hyphaName, "unattach-ask") %}
{%= modalBegin(
"unattach",
hyphaName,
"",
"Unattach "+util.BeautifulName(hyphaName)+"?") %}
{%= modalReallyWant(hyphaName, "unattach") %}
{%= modalEnd(hyphaName, true) %}
{% endfunc %}
{% func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
{%= NavHTML(rq, hyphaName, "rename-ask") %}
{%= modalBegin(
"rename-confirm",
hyphaName,
` method="post" enctype="multipart/form-data"`,
"Rename "+util.BeautifulName(hyphaName)) %}
<label for="new-name">New name</label>
<input type="text" value="{%s hyphaName %}" required autofocus id="new-name" name="new-name"/>
<input type="checkbox" id="recursive" name="recursive" value="true" checked/>
<label for="recursive">Rename subhyphae too</label>
<p>If you rename this hypha, all incoming links and all relative outcoming links will break. You will also lose all history for the new name. Rename carefully.</p>
{%= modalEnd(hyphaName, false) %}
{% endfunc %}
{% func modalReallyWant(hyphaName, verb string) %}
<p class="modal__confirmation-msg">Do you really want to {%s verb %} hypha <em>{%s hyphaName %}</em>?</p>
{% endfunc %}
{% func modalBegin(path, hyphaName, formAttrs, legend string) %}
<div class="layout">
<main class="main-width">
<form class="modal" action="/{%s path %}/{%s hyphaName %}"{%s= formAttrs %}>
<fieldset class="modal__fieldset">
<legend class="modal__title">{%s= legend %}</legend>
{% endfunc %}
{% func modalEnd(hyphaName string, shouldFocusOnConfirm bool) %}
<input type="submit" value="Confirm" class="modal__action modal__submit" {% if shouldFocusOnConfirm %}autofocus{% endif %}>
<a href="/hypha/{%s hyphaName %}" class="modal__action modal__cancel">Cancel</a>
</fieldset>
</form>
</main>
</div>
{% endfunc %}

355
views/modal.qtpl.go Normal file
View File

@ -0,0 +1,355 @@
// Code generated by qtc from "modal.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line views/modal.qtpl:1
package views
//line views/modal.qtpl:1
import "net/http"
//line views/modal.qtpl:2
import "github.com/bouncepaw/mycorrhiza/util"
//line views/modal.qtpl:4
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line views/modal.qtpl:4
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line views/modal.qtpl:4
func StreamDeleteAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line views/modal.qtpl:4
qw422016.N().S(`
`)
//line views/modal.qtpl:5
StreamNavHTML(qw422016, rq, hyphaName, "delete-ask")
//line views/modal.qtpl:5
qw422016.N().S(`
`)
//line views/modal.qtpl:6
streammodalBegin(qw422016,
"delete-confirm",
hyphaName,
"",
"Delete "+util.BeautifulName(hyphaName)+"?")
//line views/modal.qtpl:10
qw422016.N().S(`
`)
//line views/modal.qtpl:11
streammodalReallyWant(qw422016, hyphaName, "unattach")
//line views/modal.qtpl:11
qw422016.N().S(`
<p>In this version of MycorrhizaWiki you cannot undelete a deleted hypha but the history can still be accessed.</p>
`)
//line views/modal.qtpl:13
streammodalEnd(qw422016, hyphaName, true)
//line views/modal.qtpl:13
qw422016.N().S(`
`)
//line views/modal.qtpl:14
}
//line views/modal.qtpl:14
func WriteDeleteAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line views/modal.qtpl:14
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/modal.qtpl:14
StreamDeleteAskHTML(qw422016, rq, hyphaName, isOld)
//line views/modal.qtpl:14
qt422016.ReleaseWriter(qw422016)
//line views/modal.qtpl:14
}
//line views/modal.qtpl:14
func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) string {
//line views/modal.qtpl:14
qb422016 := qt422016.AcquireByteBuffer()
//line views/modal.qtpl:14
WriteDeleteAskHTML(qb422016, rq, hyphaName, isOld)
//line views/modal.qtpl:14
qs422016 := string(qb422016.B)
//line views/modal.qtpl:14
qt422016.ReleaseByteBuffer(qb422016)
//line views/modal.qtpl:14
return qs422016
//line views/modal.qtpl:14
}
//line views/modal.qtpl:16
func StreamUnattachAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line views/modal.qtpl:16
qw422016.N().S(`
`)
//line views/modal.qtpl:17
StreamNavHTML(qw422016, rq, hyphaName, "unattach-ask")
//line views/modal.qtpl:17
qw422016.N().S(`
`)
//line views/modal.qtpl:18
streammodalBegin(qw422016,
"unattach",
hyphaName,
"",
"Unattach "+util.BeautifulName(hyphaName)+"?")
//line views/modal.qtpl:22
qw422016.N().S(`
`)
//line views/modal.qtpl:23
streammodalReallyWant(qw422016, hyphaName, "unattach")
//line views/modal.qtpl:23
qw422016.N().S(`
`)
//line views/modal.qtpl:24
streammodalEnd(qw422016, hyphaName, true)
//line views/modal.qtpl:24
qw422016.N().S(`
`)
//line views/modal.qtpl:25
}
//line views/modal.qtpl:25
func WriteUnattachAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line views/modal.qtpl:25
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/modal.qtpl:25
StreamUnattachAskHTML(qw422016, rq, hyphaName, isOld)
//line views/modal.qtpl:25
qt422016.ReleaseWriter(qw422016)
//line views/modal.qtpl:25
}
//line views/modal.qtpl:25
func UnattachAskHTML(rq *http.Request, hyphaName string, isOld bool) string {
//line views/modal.qtpl:25
qb422016 := qt422016.AcquireByteBuffer()
//line views/modal.qtpl:25
WriteUnattachAskHTML(qb422016, rq, hyphaName, isOld)
//line views/modal.qtpl:25
qs422016 := string(qb422016.B)
//line views/modal.qtpl:25
qt422016.ReleaseByteBuffer(qb422016)
//line views/modal.qtpl:25
return qs422016
//line views/modal.qtpl:25
}
//line views/modal.qtpl:27
func StreamRenameAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line views/modal.qtpl:27
qw422016.N().S(`
`)
//line views/modal.qtpl:28
StreamNavHTML(qw422016, rq, hyphaName, "rename-ask")
//line views/modal.qtpl:28
qw422016.N().S(`
`)
//line views/modal.qtpl:29
streammodalBegin(qw422016,
"rename-confirm",
hyphaName,
` method="post" enctype="multipart/form-data"`,
"Rename "+util.BeautifulName(hyphaName))
//line views/modal.qtpl:33
qw422016.N().S(`
<label for="new-name">New name</label>
<input type="text" value="`)
//line views/modal.qtpl:35
qw422016.E().S(hyphaName)
//line views/modal.qtpl:35
qw422016.N().S(`" required autofocus id="new-name" name="new-name"/>
<input type="checkbox" id="recursive" name="recursive" value="true" checked/>
<label for="recursive">Rename subhyphae too</label>
<p>If you rename this hypha, all incoming links and all relative outcoming links will break. You will also lose all history for the new name. Rename carefully.</p>
`)
//line views/modal.qtpl:41
streammodalEnd(qw422016, hyphaName, false)
//line views/modal.qtpl:41
qw422016.N().S(`
`)
//line views/modal.qtpl:42
}
//line views/modal.qtpl:42
func WriteRenameAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line views/modal.qtpl:42
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/modal.qtpl:42
StreamRenameAskHTML(qw422016, rq, hyphaName, isOld)
//line views/modal.qtpl:42
qt422016.ReleaseWriter(qw422016)
//line views/modal.qtpl:42
}
//line views/modal.qtpl:42
func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) string {
//line views/modal.qtpl:42
qb422016 := qt422016.AcquireByteBuffer()
//line views/modal.qtpl:42
WriteRenameAskHTML(qb422016, rq, hyphaName, isOld)
//line views/modal.qtpl:42
qs422016 := string(qb422016.B)
//line views/modal.qtpl:42
qt422016.ReleaseByteBuffer(qb422016)
//line views/modal.qtpl:42
return qs422016
//line views/modal.qtpl:42
}
//line views/modal.qtpl:44
func streammodalReallyWant(qw422016 *qt422016.Writer, hyphaName, verb string) {
//line views/modal.qtpl:44
qw422016.N().S(`
<p class="modal__confirmation-msg">Do you really want to `)
//line views/modal.qtpl:45
qw422016.E().S(verb)
//line views/modal.qtpl:45
qw422016.N().S(` hypha <em>`)
//line views/modal.qtpl:45
qw422016.E().S(hyphaName)
//line views/modal.qtpl:45
qw422016.N().S(`</em>?</p>
`)
//line views/modal.qtpl:46
}
//line views/modal.qtpl:46
func writemodalReallyWant(qq422016 qtio422016.Writer, hyphaName, verb string) {
//line views/modal.qtpl:46
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/modal.qtpl:46
streammodalReallyWant(qw422016, hyphaName, verb)
//line views/modal.qtpl:46
qt422016.ReleaseWriter(qw422016)
//line views/modal.qtpl:46
}
//line views/modal.qtpl:46
func modalReallyWant(hyphaName, verb string) string {
//line views/modal.qtpl:46
qb422016 := qt422016.AcquireByteBuffer()
//line views/modal.qtpl:46
writemodalReallyWant(qb422016, hyphaName, verb)
//line views/modal.qtpl:46
qs422016 := string(qb422016.B)
//line views/modal.qtpl:46
qt422016.ReleaseByteBuffer(qb422016)
//line views/modal.qtpl:46
return qs422016
//line views/modal.qtpl:46
}
//line views/modal.qtpl:48
func streammodalBegin(qw422016 *qt422016.Writer, path, hyphaName, formAttrs, legend string) {
//line views/modal.qtpl:48
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<form class="modal" action="/`)
//line views/modal.qtpl:51
qw422016.E().S(path)
//line views/modal.qtpl:51
qw422016.N().S(`/`)
//line views/modal.qtpl:51
qw422016.E().S(hyphaName)
//line views/modal.qtpl:51
qw422016.N().S(`"`)
//line views/modal.qtpl:51
qw422016.N().S(formAttrs)
//line views/modal.qtpl:51
qw422016.N().S(`>
<fieldset class="modal__fieldset">
<legend class="modal__title">`)
//line views/modal.qtpl:53
qw422016.N().S(legend)
//line views/modal.qtpl:53
qw422016.N().S(`</legend>
`)
//line views/modal.qtpl:54
}
//line views/modal.qtpl:54
func writemodalBegin(qq422016 qtio422016.Writer, path, hyphaName, formAttrs, legend string) {
//line views/modal.qtpl:54
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/modal.qtpl:54
streammodalBegin(qw422016, path, hyphaName, formAttrs, legend)
//line views/modal.qtpl:54
qt422016.ReleaseWriter(qw422016)
//line views/modal.qtpl:54
}
//line views/modal.qtpl:54
func modalBegin(path, hyphaName, formAttrs, legend string) string {
//line views/modal.qtpl:54
qb422016 := qt422016.AcquireByteBuffer()
//line views/modal.qtpl:54
writemodalBegin(qb422016, path, hyphaName, formAttrs, legend)
//line views/modal.qtpl:54
qs422016 := string(qb422016.B)
//line views/modal.qtpl:54
qt422016.ReleaseByteBuffer(qb422016)
//line views/modal.qtpl:54
return qs422016
//line views/modal.qtpl:54
}
//line views/modal.qtpl:56
func streammodalEnd(qw422016 *qt422016.Writer, hyphaName string, shouldFocusOnConfirm bool) {
//line views/modal.qtpl:56
qw422016.N().S(`
<input type="submit" value="Confirm" class="modal__action modal__submit" `)
//line views/modal.qtpl:57
if shouldFocusOnConfirm {
//line views/modal.qtpl:57
qw422016.N().S(`autofocus`)
//line views/modal.qtpl:57
}
//line views/modal.qtpl:57
qw422016.N().S(`>
<a href="/hypha/`)
//line views/modal.qtpl:58
qw422016.E().S(hyphaName)
//line views/modal.qtpl:58
qw422016.N().S(`" class="modal__action modal__cancel">Cancel</a>
</fieldset>
</form>
</main>
</div>
`)
//line views/modal.qtpl:63
}
//line views/modal.qtpl:63
func writemodalEnd(qq422016 qtio422016.Writer, hyphaName string, shouldFocusOnConfirm bool) {
//line views/modal.qtpl:63
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/modal.qtpl:63
streammodalEnd(qw422016, hyphaName, shouldFocusOnConfirm)
//line views/modal.qtpl:63
qt422016.ReleaseWriter(qw422016)
//line views/modal.qtpl:63
}
//line views/modal.qtpl:63
func modalEnd(hyphaName string, shouldFocusOnConfirm bool) string {
//line views/modal.qtpl:63
qb422016 := qt422016.AcquireByteBuffer()
//line views/modal.qtpl:63
writemodalEnd(qb422016, hyphaName, shouldFocusOnConfirm)
//line views/modal.qtpl:63
qs422016 := string(qb422016.B)
//line views/modal.qtpl:63
qt422016.ReleaseByteBuffer(qb422016)
//line views/modal.qtpl:63
return qs422016
//line views/modal.qtpl:63
}

View File

@ -1,9 +1,11 @@
{% import "net/http" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) %}
{%s= navHTML(rq, hyphaName, "edit") %}
<main class="edit edit_no-preview">
<h1>Edit {%s hyphaName %}</h1>
{%s= NavHTML(rq, hyphaName, "edit") %}
<div class="layout">
<main class="main-width edit edit_no-preview">
<h1 class="edit__title">Edit {%s util.BeautifulName(hyphaName) %}</h1>
{%s= warning %}
<form method="post" class="edit-form"
action="/upload-text/{%s hyphaName %}">
@ -14,12 +16,14 @@
<a href="/page/{%s hyphaName %}" class="edit-form__cancel">Cancel</a>
</form>
</main>
</div>
{% endfunc %}
{% func PreviewHTML(rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) %}
{%s= navHTML(rq, hyphaName, "edit") %}
<main class="edit edit_with-preview">
<h1>Edit {%s hyphaName %} (preview)</h1>
{%s= NavHTML(rq, hyphaName, "edit") %}
<div class="layout">
<main class="main-width edit edit_with-preview">
<h1>Edit {%s util.BeautifulName(hyphaName) %} (preview)</h1>
{%s= warning %}
<form method="post" class="edit-form"
action="/upload-text/{%s hyphaName %}">
@ -32,4 +36,5 @@
<p class="warning">Note that the hypha is not saved yet. You can preview the changes ↓</p>
<section class="edit__preview">{%s= renderedPage %}</section>
</main>
</div>
{% endfunc %}

View File

@ -1,169 +1,176 @@
// Code generated by qtc from "http_mutators.qtpl". DO NOT EDIT.
// Code generated by qtc from "mutators.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line templates/http_mutators.qtpl:1
package templates
//line views/mutators.qtpl:1
package views
//line templates/http_mutators.qtpl:1
//line views/mutators.qtpl:1
import "net/http"
//line templates/http_mutators.qtpl:3
//line views/mutators.qtpl:2
import "github.com/bouncepaw/mycorrhiza/util"
//line views/mutators.qtpl:4
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line templates/http_mutators.qtpl:3
//line views/mutators.qtpl:4
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line templates/http_mutators.qtpl:3
//line views/mutators.qtpl:4
func StreamEditHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
//line templates/http_mutators.qtpl:3
//line views/mutators.qtpl:4
qw422016.N().S(`
`)
//line templates/http_mutators.qtpl:4
qw422016.N().S(navHTML(rq, hyphaName, "edit"))
//line templates/http_mutators.qtpl:4
//line views/mutators.qtpl:5
qw422016.N().S(NavHTML(rq, hyphaName, "edit"))
//line views/mutators.qtpl:5
qw422016.N().S(`
<main class="edit edit_no-preview">
<h1>Edit `)
//line templates/http_mutators.qtpl:6
qw422016.E().S(hyphaName)
//line templates/http_mutators.qtpl:6
<div class="layout">
<main class="main-width edit edit_no-preview">
<h1 class="edit__title">Edit `)
//line views/mutators.qtpl:8
qw422016.E().S(util.BeautifulName(hyphaName))
//line views/mutators.qtpl:8
qw422016.N().S(`</h1>
`)
//line templates/http_mutators.qtpl:7
//line views/mutators.qtpl:9
qw422016.N().S(warning)
//line templates/http_mutators.qtpl:7
//line views/mutators.qtpl:9
qw422016.N().S(`
<form method="post" class="edit-form"
action="/upload-text/`)
//line templates/http_mutators.qtpl:9
//line views/mutators.qtpl:11
qw422016.E().S(hyphaName)
//line templates/http_mutators.qtpl:9
//line views/mutators.qtpl:11
qw422016.N().S(`">
<textarea name="text">`)
//line templates/http_mutators.qtpl:10
//line views/mutators.qtpl:12
qw422016.E().S(textAreaFill)
//line templates/http_mutators.qtpl:10
//line views/mutators.qtpl:12
qw422016.N().S(`</textarea>
<br/>
<input type="submit" name="action" value="Save" class="edit-form__save"/>
<input type="submit" name="action" value="Preview" class="edit-form__preview">
<a href="/page/`)
//line templates/http_mutators.qtpl:14
//line views/mutators.qtpl:16
qw422016.E().S(hyphaName)
//line templates/http_mutators.qtpl:14
//line views/mutators.qtpl:16
qw422016.N().S(`" class="edit-form__cancel">Cancel</a>
</form>
</main>
</div>
`)
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
}
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
func WriteEditHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
StreamEditHTML(qw422016, rq, hyphaName, textAreaFill, warning)
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
qt422016.ReleaseWriter(qw422016)
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
}
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) string {
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
qb422016 := qt422016.AcquireByteBuffer()
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
WriteEditHTML(qb422016, rq, hyphaName, textAreaFill, warning)
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
qs422016 := string(qb422016.B)
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
return qs422016
//line templates/http_mutators.qtpl:17
//line views/mutators.qtpl:20
}
//line templates/http_mutators.qtpl:19
//line views/mutators.qtpl:22
func StreamPreviewHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) {
//line templates/http_mutators.qtpl:19
//line views/mutators.qtpl:22
qw422016.N().S(`
`)
//line templates/http_mutators.qtpl:20
qw422016.N().S(navHTML(rq, hyphaName, "edit"))
//line templates/http_mutators.qtpl:20
//line views/mutators.qtpl:23
qw422016.N().S(NavHTML(rq, hyphaName, "edit"))
//line views/mutators.qtpl:23
qw422016.N().S(`
<main class="edit edit_with-preview">
<div class="layout">
<main class="main-width edit edit_with-preview">
<h1>Edit `)
//line templates/http_mutators.qtpl:22
qw422016.E().S(hyphaName)
//line templates/http_mutators.qtpl:22
//line views/mutators.qtpl:26
qw422016.E().S(util.BeautifulName(hyphaName))
//line views/mutators.qtpl:26
qw422016.N().S(` (preview)</h1>
`)
//line templates/http_mutators.qtpl:23
//line views/mutators.qtpl:27
qw422016.N().S(warning)
//line templates/http_mutators.qtpl:23
//line views/mutators.qtpl:27
qw422016.N().S(`
<form method="post" class="edit-form"
action="/upload-text/`)
//line templates/http_mutators.qtpl:25
//line views/mutators.qtpl:29
qw422016.E().S(hyphaName)
//line templates/http_mutators.qtpl:25
//line views/mutators.qtpl:29
qw422016.N().S(`">
<textarea name="text">`)
//line templates/http_mutators.qtpl:26
//line views/mutators.qtpl:30
qw422016.E().S(textAreaFill)
//line templates/http_mutators.qtpl:26
//line views/mutators.qtpl:30
qw422016.N().S(`</textarea>
<br/>
<input type="submit" name="action" value="Save" class="edit-form__save"/>
<input type="submit" name="action" value="Preview" class="edit-form__preview">
<a href="/page/`)
//line templates/http_mutators.qtpl:30
//line views/mutators.qtpl:34
qw422016.E().S(hyphaName)
//line templates/http_mutators.qtpl:30
//line views/mutators.qtpl:34
qw422016.N().S(`" class="edit-form__cancel">Cancel</a>
</form>
<p class="warning">Note that the hypha is not saved yet. You can preview the changes </p>
<section class="edit__preview">`)
//line templates/http_mutators.qtpl:33
//line views/mutators.qtpl:37
qw422016.N().S(renderedPage)
//line templates/http_mutators.qtpl:33
//line views/mutators.qtpl:37
qw422016.N().S(`</section>
</main>
</div>
`)
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
}
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
func WritePreviewHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) {
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
StreamPreviewHTML(qw422016, rq, hyphaName, textAreaFill, warning, renderedPage)
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
qt422016.ReleaseWriter(qw422016)
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
}
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
func PreviewHTML(rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) string {
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
qb422016 := qt422016.AcquireByteBuffer()
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
WritePreviewHTML(qb422016, rq, hyphaName, textAreaFill, warning, renderedPage)
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
qs422016 := string(qb422016.B)
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
return qs422016
//line templates/http_mutators.qtpl:35
//line views/mutators.qtpl:40
}

View File

@ -1,4 +1,5 @@
{% import "net/http" %}
{% import "strings" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
@ -11,33 +12,33 @@ type navEntry struct {
var navEntries = []navEntry{
{"page", "Hypha"},
{"edit", "Edit"},
{"text", "Raw text"},
{"attachment", "Attachment"},
{"history", "History"},
{"revision", "NOT REACHED"},
{"rename-ask", "Rename"},
{"delete-ask", "Delete"},
{"text", "Raw text"},
}
%}
{% func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %}
{% func NavHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %}
{% code
u := user.FromRequest(rq)
%}
<nav class="hypha-tabs">
<nav class="hypha-tabs main-width">
<ul class="hypha-tabs__flex">
{%- for _, entry := range navEntries -%}
{%- if navType == "revision" && entry.path == "revision" -%}
<li class="hypha-tabs__tab hypha-tabs__tab_active">
{%s revisionHash[0] %}
<span class="hypha-tabs__selection">{%s revisionHash[0] %}</span>
</li>
{%- elseif navType == entry.path -%}
<li class="hypha-tabs__tab hypha-tabs__tab_active">
{%s entry.title %}
<span class="hypha-tabs__selection">{%s entry.title %}</span>
</li>
{%- elseif entry.path != "revision" && u.CanProceed(entry.path) -%}
<li class="hypha-tabs__tab">
<a href="/{%s entry.path %}/{%s hyphaName %}">{%s entry.title %}</a>
<a class="hypha-tabs__link" href="/{%s entry.path %}/{%s hyphaName %}">{%s entry.title %}</a>
</li>
{%- endif -%}
{%- endfor -%}
@ -45,7 +46,7 @@ var navEntries = []navEntry{
</nav>
{% endfunc %}
{% func userMenuHTML(u *user.User) %}
{% func UserMenuHTML(u *user.User) %}
{% if user.AuthUsed %}
<li class="header-links__entry header-links__entry_user">
{% if u.Group == "anon" %}
@ -57,3 +58,22 @@ var navEntries = []navEntry{
{% endif %}
{% endfunc %}
{% func RelativeHyphaeHTML(relatives string) %}
<aside class="relative-hyphae layout-card">
<h2 class="relative-hyphae__title layout-card__title">Relative hyphae</h2>
{%s= relatives %}
</aside>
{% endfunc %}
{% func SubhyphaeHTML(subhyphae string) %}
{% if strings.TrimSpace(subhyphae) != "" %}
<section class="subhyphae">
<h2 class="subhyphae__title">Subhyphae</h2>
<nav class="subhyphae__nav">
<ul class="subhyphae__list">
{%s= subhyphae %}
</ul>
</nav>
</section>
{% endif %}
{% endfunc %}

315
views/nav.qtpl.go Normal file
View File

@ -0,0 +1,315 @@
// Code generated by qtc from "nav.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line views/nav.qtpl:1
package views
//line views/nav.qtpl:1
import "net/http"
//line views/nav.qtpl:2
import "strings"
//line views/nav.qtpl:3
import "github.com/bouncepaw/mycorrhiza/user"
//line views/nav.qtpl:4
import "github.com/bouncepaw/mycorrhiza/util"
// This is the <nav> seen on top of many pages.
//line views/nav.qtpl:7
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line views/nav.qtpl:7
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line views/nav.qtpl:8
type navEntry struct {
path string
title string
}
var navEntries = []navEntry{
{"page", "Hypha"},
{"edit", "Edit"},
{"attachment", "Attachment"},
{"history", "History"},
{"revision", "NOT REACHED"},
{"rename-ask", "Rename"},
{"delete-ask", "Delete"},
{"text", "Raw text"},
}
//line views/nav.qtpl:24
func StreamNavHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
//line views/nav.qtpl:24
qw422016.N().S(`
`)
//line views/nav.qtpl:26
u := user.FromRequest(rq)
//line views/nav.qtpl:27
qw422016.N().S(`
<nav class="hypha-tabs main-width">
<ul class="hypha-tabs__flex">
`)
//line views/nav.qtpl:30
for _, entry := range navEntries {
//line views/nav.qtpl:31
if navType == "revision" && entry.path == "revision" {
//line views/nav.qtpl:31
qw422016.N().S(` <li class="hypha-tabs__tab hypha-tabs__tab_active">
<span class="hypha-tabs__selection">`)
//line views/nav.qtpl:33
qw422016.E().S(revisionHash[0])
//line views/nav.qtpl:33
qw422016.N().S(`</span>
</li>
`)
//line views/nav.qtpl:35
} else if navType == entry.path {
//line views/nav.qtpl:35
qw422016.N().S(` <li class="hypha-tabs__tab hypha-tabs__tab_active">
<span class="hypha-tabs__selection">`)
//line views/nav.qtpl:37
qw422016.E().S(entry.title)
//line views/nav.qtpl:37
qw422016.N().S(`</span>
</li>
`)
//line views/nav.qtpl:39
} else if entry.path != "revision" && u.CanProceed(entry.path) {
//line views/nav.qtpl:39
qw422016.N().S(` <li class="hypha-tabs__tab">
<a class="hypha-tabs__link" href="/`)
//line views/nav.qtpl:41
qw422016.E().S(entry.path)
//line views/nav.qtpl:41
qw422016.N().S(`/`)
//line views/nav.qtpl:41
qw422016.E().S(hyphaName)
//line views/nav.qtpl:41
qw422016.N().S(`">`)
//line views/nav.qtpl:41
qw422016.E().S(entry.title)
//line views/nav.qtpl:41
qw422016.N().S(`</a>
</li>
`)
//line views/nav.qtpl:43
}
//line views/nav.qtpl:44
}
//line views/nav.qtpl:44
qw422016.N().S(` </ul>
</nav>
`)
//line views/nav.qtpl:47
}
//line views/nav.qtpl:47
func WriteNavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
//line views/nav.qtpl:47
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/nav.qtpl:47
StreamNavHTML(qw422016, rq, hyphaName, navType, revisionHash...)
//line views/nav.qtpl:47
qt422016.ReleaseWriter(qw422016)
//line views/nav.qtpl:47
}
//line views/nav.qtpl:47
func NavHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string {
//line views/nav.qtpl:47
qb422016 := qt422016.AcquireByteBuffer()
//line views/nav.qtpl:47
WriteNavHTML(qb422016, rq, hyphaName, navType, revisionHash...)
//line views/nav.qtpl:47
qs422016 := string(qb422016.B)
//line views/nav.qtpl:47
qt422016.ReleaseByteBuffer(qb422016)
//line views/nav.qtpl:47
return qs422016
//line views/nav.qtpl:47
}
//line views/nav.qtpl:49
func StreamUserMenuHTML(qw422016 *qt422016.Writer, u *user.User) {
//line views/nav.qtpl:49
qw422016.N().S(`
`)
//line views/nav.qtpl:50
if user.AuthUsed {
//line views/nav.qtpl:50
qw422016.N().S(`
<li class="header-links__entry header-links__entry_user">
`)
//line views/nav.qtpl:52
if u.Group == "anon" {
//line views/nav.qtpl:52
qw422016.N().S(`
<a href="/login" class="header-links__link">Login</a>
`)
//line views/nav.qtpl:54
} else {
//line views/nav.qtpl:54
qw422016.N().S(`
<a href="/page/`)
//line views/nav.qtpl:55
qw422016.E().S(util.UserHypha)
//line views/nav.qtpl:55
qw422016.N().S(`/`)
//line views/nav.qtpl:55
qw422016.E().S(u.Name)
//line views/nav.qtpl:55
qw422016.N().S(`" class="header-links__link">`)
//line views/nav.qtpl:55
qw422016.E().S(u.Name)
//line views/nav.qtpl:55
qw422016.N().S(`</a>
`)
//line views/nav.qtpl:56
}
//line views/nav.qtpl:56
qw422016.N().S(`
</li>
`)
//line views/nav.qtpl:58
}
//line views/nav.qtpl:58
qw422016.N().S(`
`)
//line views/nav.qtpl:59
}
//line views/nav.qtpl:59
func WriteUserMenuHTML(qq422016 qtio422016.Writer, u *user.User) {
//line views/nav.qtpl:59
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/nav.qtpl:59
StreamUserMenuHTML(qw422016, u)
//line views/nav.qtpl:59
qt422016.ReleaseWriter(qw422016)
//line views/nav.qtpl:59
}
//line views/nav.qtpl:59
func UserMenuHTML(u *user.User) string {
//line views/nav.qtpl:59
qb422016 := qt422016.AcquireByteBuffer()
//line views/nav.qtpl:59
WriteUserMenuHTML(qb422016, u)
//line views/nav.qtpl:59
qs422016 := string(qb422016.B)
//line views/nav.qtpl:59
qt422016.ReleaseByteBuffer(qb422016)
//line views/nav.qtpl:59
return qs422016
//line views/nav.qtpl:59
}
//line views/nav.qtpl:61
func StreamRelativeHyphaeHTML(qw422016 *qt422016.Writer, relatives string) {
//line views/nav.qtpl:61
qw422016.N().S(`
<aside class="relative-hyphae layout-card">
<h2 class="relative-hyphae__title layout-card__title">Relative hyphae</h2>
`)
//line views/nav.qtpl:64
qw422016.N().S(relatives)
//line views/nav.qtpl:64
qw422016.N().S(`
</aside>
`)
//line views/nav.qtpl:66
}
//line views/nav.qtpl:66
func WriteRelativeHyphaeHTML(qq422016 qtio422016.Writer, relatives string) {
//line views/nav.qtpl:66
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/nav.qtpl:66
StreamRelativeHyphaeHTML(qw422016, relatives)
//line views/nav.qtpl:66
qt422016.ReleaseWriter(qw422016)
//line views/nav.qtpl:66
}
//line views/nav.qtpl:66
func RelativeHyphaeHTML(relatives string) string {
//line views/nav.qtpl:66
qb422016 := qt422016.AcquireByteBuffer()
//line views/nav.qtpl:66
WriteRelativeHyphaeHTML(qb422016, relatives)
//line views/nav.qtpl:66
qs422016 := string(qb422016.B)
//line views/nav.qtpl:66
qt422016.ReleaseByteBuffer(qb422016)
//line views/nav.qtpl:66
return qs422016
//line views/nav.qtpl:66
}
//line views/nav.qtpl:68
func StreamSubhyphaeHTML(qw422016 *qt422016.Writer, subhyphae string) {
//line views/nav.qtpl:68
qw422016.N().S(`
`)
//line views/nav.qtpl:69
if strings.TrimSpace(subhyphae) != "" {
//line views/nav.qtpl:69
qw422016.N().S(`
<section class="subhyphae">
<h2 class="subhyphae__title">Subhyphae</h2>
<nav class="subhyphae__nav">
<ul class="subhyphae__list">
`)
//line views/nav.qtpl:74
qw422016.N().S(subhyphae)
//line views/nav.qtpl:74
qw422016.N().S(`
</ul>
</nav>
</section>
`)
//line views/nav.qtpl:78
}
//line views/nav.qtpl:78
qw422016.N().S(`
`)
//line views/nav.qtpl:79
}
//line views/nav.qtpl:79
func WriteSubhyphaeHTML(qq422016 qtio422016.Writer, subhyphae string) {
//line views/nav.qtpl:79
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/nav.qtpl:79
StreamSubhyphaeHTML(qw422016, subhyphae)
//line views/nav.qtpl:79
qt422016.ReleaseWriter(qw422016)
//line views/nav.qtpl:79
}
//line views/nav.qtpl:79
func SubhyphaeHTML(subhyphae string) string {
//line views/nav.qtpl:79
qb422016 := qt422016.AcquireByteBuffer()
//line views/nav.qtpl:79
WriteSubhyphaeHTML(qb422016, subhyphae)
//line views/nav.qtpl:79
qs422016 := string(qb422016.B)
//line views/nav.qtpl:79
qt422016.ReleaseByteBuffer(qb422016)
//line views/nav.qtpl:79
return qs422016
//line views/nav.qtpl:79
}

131
views/readers.qtpl Normal file
View File

@ -0,0 +1,131 @@
{% import "net/http" %}
{% import "strings" %}
{% import "path" %}
{% import "os" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/mimetype" %}
{% import "github.com/bouncepaw/mycorrhiza/tree" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% func AttachmentMenuHTML(rq *http.Request, h *hyphae.Hypha, u *user.User) %}
{%= NavHTML(rq, h.Name, "attachment") %}
<div class="layout">
<main class="main-width">
<h1>Attachment of {%s util.BeautifulName(h.Name) %}</h1>
{% if h.BinaryPath == "" %}
<p class="warning">This hypha has no attachment, you can upload it here.</p>
{% else %}
<p class="warning">You can manage the hypha's attachment on this page.</p>
{% endif %}
<section class="amnt-grid">
{% if h.BinaryPath != "" %}
{% code
mime := mimetype.FromExtension(path.Ext(h.BinaryPath))
fileinfo, err := os.Stat(h.BinaryPath) %}
{% if err == nil %}
<fieldset class="amnt-menu-block">
<legend class="modal__title modal__title_small">Stat</legend>
<p class="modal__confirmation-msg"><b>File size:</b> {%dl fileinfo.Size() %} bytes</p>
<p><b>MIME type:</b> {%s mime %}</p>
</fieldset>
{% endif %}
{% if strings.HasPrefix(mime, "image/") %}
<fieldset class="amnt-menu-block">
<legend class="modal__title modal__title_small">Include</legend>
<p class="modal__confirmation-msg">This attachment is an image. To include it n a hypha, use a syntax like this:</p>
<pre class="codebleck"><code>img { {%s h.Name %} }</code></pre>
</fieldset>
{% endif %}
{% endif %}
{% if u.CanProceed("upload-binary") %}
<form action="/upload-binary/{%s h.Name %}"
method="post" enctype="multipart/form-data"
class="modal amnt-menu-block">
<fieldset class="modal__fieldset upload-binary">
<legend class="modal__title modal__title_small">Attach</legend>
<p class="modal__confirmation-msg">You can upload a new attachment. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.</p>
<input type="file" class="upload-binary__input" name="binary">
<input type="submit" class="modal__action modal__submit">
</fieldset>
</form>
{% endif %}
{% if h.BinaryPath != "" && u.CanProceed("unattach-confirm") %}
<form action="/unattach-confirm/{%s h.Name %}" method="post" class="modal amnt-menu-block">
<fieldset class="modal__fieldset">
<legend class="modal__title modal__title_small">Unattach</legend>
<p class="modal__confirmation-msg">Please note that you don't have to unattach before uploading a new attachment.</p>
<input type="submit" class="modal__action modal__submit">
</fieldset>
</form>
{% endif %}
</section>
</main>
</div>
{% endfunc %}
If `contents` == "", a helpful message is shown instead.
{% func HyphaHTML(rq *http.Request, h *hyphae.Hypha, contents string) %}
{% code
relatives, subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.Name)
%}
{%= NavHTML(rq, h.Name, "page") %}
<div class="layout">
<main class="main-width">
<article>
{%s= NaviTitleHTML(h) %}
{% if contents == "" %}
<p>This hypha has no text. Why not <a href="/edit/{%s h.Name %}">create it</a>?</p>
{% if u := user.FromRequest(rq); (!user.AuthUsed || u.Group != "anon") && !h.Exists %}
<form action="/upload-binary/{%s h.Name %}"
method="post" enctype="multipart/form-data"
class="upload-binary">
<label for="upload-binary__input">Upload an attachment:</label>
<input type="file" id="upload-binary__input" name="binary">
<input type="submit">
</form>
<br>
{% endif %}
{% else %}
{%s= contents %}
{% endif %}
</article>
<section class="prevnext">
{% if prevHyphaName != "" %}
<a class="prevnext__el prevnext__prev" href="/hypha/{%s prevHyphaName %}" rel="prev">← {%s util.BeautifulName(path.Base(prevHyphaName)) %}</a>
{% endif %}
{% if nextHyphaName != "" %}
<a class="prevnext__el prevnext__next" href="/hypha/{%s nextHyphaName %}" rel="next">{%s util.BeautifulName(path.Base(nextHyphaName)) %} →</a>
{% endif %}
</section>
{%= SubhyphaeHTML(subhyphae) %}
</main>
{%= RelativeHyphaeHTML(relatives) %}
{%= BackLinksHTML(h) %}
</div>
{% endfunc %}
{% func RevisionHTML(rq *http.Request, h *hyphae.Hypha, contents, revHash string) %}
{% code
relatives, subhyphae, _, _ := tree.Tree(h.Name)
%}
{%= NavHTML(rq, h.Name, "revision", revHash) %}
<div class="layout">
<main class="main-width">
<article>
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
{%s= NaviTitleHTML(h) %}
{%s= contents %}
</article>
{%= SubhyphaeHTML(subhyphae) %}
</main>
{%= RelativeHyphaeHTML(relatives) %}
</div>
{% endfunc %}

454
views/readers.qtpl.go Normal file
View File

@ -0,0 +1,454 @@
// Code generated by qtc from "readers.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line views/readers.qtpl:1
package views
//line views/readers.qtpl:1
import "net/http"
//line views/readers.qtpl:2
import "strings"
//line views/readers.qtpl:3
import "path"
//line views/readers.qtpl:4
import "os"
//line views/readers.qtpl:6
import "github.com/bouncepaw/mycorrhiza/hyphae"
//line views/readers.qtpl:7
import "github.com/bouncepaw/mycorrhiza/mimetype"
//line views/readers.qtpl:8
import "github.com/bouncepaw/mycorrhiza/tree"
//line views/readers.qtpl:9
import "github.com/bouncepaw/mycorrhiza/user"
//line views/readers.qtpl:10
import "github.com/bouncepaw/mycorrhiza/util"
//line views/readers.qtpl:12
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line views/readers.qtpl:12
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line views/readers.qtpl:12
func StreamAttachmentMenuHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User) {
//line views/readers.qtpl:12
qw422016.N().S(`
`)
//line views/readers.qtpl:13
StreamNavHTML(qw422016, rq, h.Name, "attachment")
//line views/readers.qtpl:13
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<h1>Attachment of `)
//line views/readers.qtpl:16
qw422016.E().S(util.BeautifulName(h.Name))
//line views/readers.qtpl:16
qw422016.N().S(`</h1>
`)
//line views/readers.qtpl:17
if h.BinaryPath == "" {
//line views/readers.qtpl:17
qw422016.N().S(`
<p class="warning">This hypha has no attachment, you can upload it here.</p>
`)
//line views/readers.qtpl:19
} else {
//line views/readers.qtpl:19
qw422016.N().S(`
<p class="warning">You can manage the hypha's attachment on this page.</p>
`)
//line views/readers.qtpl:21
}
//line views/readers.qtpl:21
qw422016.N().S(`
<section class="amnt-grid">
`)
//line views/readers.qtpl:25
if h.BinaryPath != "" {
//line views/readers.qtpl:25
qw422016.N().S(`
`)
//line views/readers.qtpl:27
mime := mimetype.FromExtension(path.Ext(h.BinaryPath))
fileinfo, err := os.Stat(h.BinaryPath)
//line views/readers.qtpl:28
qw422016.N().S(`
`)
//line views/readers.qtpl:29
if err == nil {
//line views/readers.qtpl:29
qw422016.N().S(`
<fieldset class="amnt-menu-block">
<legend class="modal__title modal__title_small">Stat</legend>
<p class="modal__confirmation-msg"><b>File size:</b> `)
//line views/readers.qtpl:32
qw422016.N().DL(fileinfo.Size())
//line views/readers.qtpl:32
qw422016.N().S(` bytes</p>
<p><b>MIME type:</b> `)
//line views/readers.qtpl:33
qw422016.E().S(mime)
//line views/readers.qtpl:33
qw422016.N().S(`</p>
</fieldset>
`)
//line views/readers.qtpl:35
}
//line views/readers.qtpl:35
qw422016.N().S(`
`)
//line views/readers.qtpl:37
if strings.HasPrefix(mime, "image/") {
//line views/readers.qtpl:37
qw422016.N().S(`
<fieldset class="amnt-menu-block">
<legend class="modal__title modal__title_small">Include</legend>
<p class="modal__confirmation-msg">This attachment is an image. To include it n a hypha, use a syntax like this:</p>
<pre class="codebleck"><code>img { `)
//line views/readers.qtpl:41
qw422016.E().S(h.Name)
//line views/readers.qtpl:41
qw422016.N().S(` }</code></pre>
</fieldset>
`)
//line views/readers.qtpl:43
}
//line views/readers.qtpl:43
qw422016.N().S(`
`)
//line views/readers.qtpl:44
}
//line views/readers.qtpl:44
qw422016.N().S(`
`)
//line views/readers.qtpl:46
if u.CanProceed("upload-binary") {
//line views/readers.qtpl:46
qw422016.N().S(`
<form action="/upload-binary/`)
//line views/readers.qtpl:47
qw422016.E().S(h.Name)
//line views/readers.qtpl:47
qw422016.N().S(`"
method="post" enctype="multipart/form-data"
class="modal amnt-menu-block">
<fieldset class="modal__fieldset upload-binary">
<legend class="modal__title modal__title_small">Attach</legend>
<p class="modal__confirmation-msg">You can upload a new attachment. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.</p>
<input type="file" class="upload-binary__input" name="binary">
<input type="submit" class="modal__action modal__submit">
</fieldset>
</form>
`)
//line views/readers.qtpl:57
}
//line views/readers.qtpl:57
qw422016.N().S(`
`)
//line views/readers.qtpl:59
if h.BinaryPath != "" && u.CanProceed("unattach-confirm") {
//line views/readers.qtpl:59
qw422016.N().S(`
<form action="/unattach-confirm/`)
//line views/readers.qtpl:60
qw422016.E().S(h.Name)
//line views/readers.qtpl:60
qw422016.N().S(`" method="post" class="modal amnt-menu-block">
<fieldset class="modal__fieldset">
<legend class="modal__title modal__title_small">Unattach</legend>
<p class="modal__confirmation-msg">Please note that you don't have to unattach before uploading a new attachment.</p>
<input type="submit" class="modal__action modal__submit">
</fieldset>
</form>
`)
//line views/readers.qtpl:67
}
//line views/readers.qtpl:67
qw422016.N().S(`
</section>
</main>
</div>
`)
//line views/readers.qtpl:72
}
//line views/readers.qtpl:72
func WriteAttachmentMenuHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User) {
//line views/readers.qtpl:72
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:72
StreamAttachmentMenuHTML(qw422016, rq, h, u)
//line views/readers.qtpl:72
qt422016.ReleaseWriter(qw422016)
//line views/readers.qtpl:72
}
//line views/readers.qtpl:72
func AttachmentMenuHTML(rq *http.Request, h *hyphae.Hypha, u *user.User) string {
//line views/readers.qtpl:72
qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:72
WriteAttachmentMenuHTML(qb422016, rq, h, u)
//line views/readers.qtpl:72
qs422016 := string(qb422016.B)
//line views/readers.qtpl:72
qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:72
return qs422016
//line views/readers.qtpl:72
}
// If `contents` == "", a helpful message is shown instead.
//line views/readers.qtpl:75
func StreamHyphaHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, contents string) {
//line views/readers.qtpl:75
qw422016.N().S(`
`)
//line views/readers.qtpl:77
relatives, subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.Name)
//line views/readers.qtpl:78
qw422016.N().S(`
`)
//line views/readers.qtpl:79
StreamNavHTML(qw422016, rq, h.Name, "page")
//line views/readers.qtpl:79
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<article>
`)
//line views/readers.qtpl:83
qw422016.N().S(NaviTitleHTML(h))
//line views/readers.qtpl:83
qw422016.N().S(`
`)
//line views/readers.qtpl:84
if contents == "" {
//line views/readers.qtpl:84
qw422016.N().S(`
<p>This hypha has no text. Why not <a href="/edit/`)
//line views/readers.qtpl:85
qw422016.E().S(h.Name)
//line views/readers.qtpl:85
qw422016.N().S(`">create it</a>?</p>
`)
//line views/readers.qtpl:86
if u := user.FromRequest(rq); (!user.AuthUsed || u.Group != "anon") && !h.Exists {
//line views/readers.qtpl:86
qw422016.N().S(`
<form action="/upload-binary/`)
//line views/readers.qtpl:87
qw422016.E().S(h.Name)
//line views/readers.qtpl:87
qw422016.N().S(`"
method="post" enctype="multipart/form-data"
class="upload-binary">
<label for="upload-binary__input">Upload an attachment:</label>
<input type="file" id="upload-binary__input" name="binary">
<input type="submit">
</form>
<br>
`)
//line views/readers.qtpl:95
}
//line views/readers.qtpl:95
qw422016.N().S(`
`)
//line views/readers.qtpl:96
} else {
//line views/readers.qtpl:96
qw422016.N().S(`
`)
//line views/readers.qtpl:97
qw422016.N().S(contents)
//line views/readers.qtpl:97
qw422016.N().S(`
`)
//line views/readers.qtpl:98
}
//line views/readers.qtpl:98
qw422016.N().S(`
</article>
<section class="prevnext">
`)
//line views/readers.qtpl:101
if prevHyphaName != "" {
//line views/readers.qtpl:101
qw422016.N().S(`
<a class="prevnext__el prevnext__prev" href="/hypha/`)
//line views/readers.qtpl:102
qw422016.E().S(prevHyphaName)
//line views/readers.qtpl:102
qw422016.N().S(`" rel="prev">← `)
//line views/readers.qtpl:102
qw422016.E().S(util.BeautifulName(path.Base(prevHyphaName)))
//line views/readers.qtpl:102
qw422016.N().S(`</a>
`)
//line views/readers.qtpl:103
}
//line views/readers.qtpl:103
qw422016.N().S(`
`)
//line views/readers.qtpl:104
if nextHyphaName != "" {
//line views/readers.qtpl:104
qw422016.N().S(`
<a class="prevnext__el prevnext__next" href="/hypha/`)
//line views/readers.qtpl:105
qw422016.E().S(nextHyphaName)
//line views/readers.qtpl:105
qw422016.N().S(`" rel="next">`)
//line views/readers.qtpl:105
qw422016.E().S(util.BeautifulName(path.Base(nextHyphaName)))
//line views/readers.qtpl:105
qw422016.N().S(` </a>
`)
//line views/readers.qtpl:106
}
//line views/readers.qtpl:106
qw422016.N().S(`
</section>
`)
//line views/readers.qtpl:108
StreamSubhyphaeHTML(qw422016, subhyphae)
//line views/readers.qtpl:108
qw422016.N().S(`
</main>
`)
//line views/readers.qtpl:110
StreamRelativeHyphaeHTML(qw422016, relatives)
//line views/readers.qtpl:110
qw422016.N().S(`
`)
//line views/readers.qtpl:111
StreamBackLinksHTML(qw422016, h)
//line views/readers.qtpl:111
qw422016.N().S(`
</div>
`)
//line views/readers.qtpl:113
}
//line views/readers.qtpl:113
func WriteHyphaHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, contents string) {
//line views/readers.qtpl:113
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:113
StreamHyphaHTML(qw422016, rq, h, contents)
//line views/readers.qtpl:113
qt422016.ReleaseWriter(qw422016)
//line views/readers.qtpl:113
}
//line views/readers.qtpl:113
func HyphaHTML(rq *http.Request, h *hyphae.Hypha, contents string) string {
//line views/readers.qtpl:113
qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:113
WriteHyphaHTML(qb422016, rq, h, contents)
//line views/readers.qtpl:113
qs422016 := string(qb422016.B)
//line views/readers.qtpl:113
qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:113
return qs422016
//line views/readers.qtpl:113
}
//line views/readers.qtpl:115
func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, contents, revHash string) {
//line views/readers.qtpl:115
qw422016.N().S(`
`)
//line views/readers.qtpl:117
relatives, subhyphae, _, _ := tree.Tree(h.Name)
//line views/readers.qtpl:118
qw422016.N().S(`
`)
//line views/readers.qtpl:119
StreamNavHTML(qw422016, rq, h.Name, "revision", revHash)
//line views/readers.qtpl:119
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<article>
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
`)
//line views/readers.qtpl:124
qw422016.N().S(NaviTitleHTML(h))
//line views/readers.qtpl:124
qw422016.N().S(`
`)
//line views/readers.qtpl:125
qw422016.N().S(contents)
//line views/readers.qtpl:125
qw422016.N().S(`
</article>
`)
//line views/readers.qtpl:127
StreamSubhyphaeHTML(qw422016, subhyphae)
//line views/readers.qtpl:127
qw422016.N().S(`
</main>
`)
//line views/readers.qtpl:129
StreamRelativeHyphaeHTML(qw422016, relatives)
//line views/readers.qtpl:129
qw422016.N().S(`
</div>
`)
//line views/readers.qtpl:131
}
//line views/readers.qtpl:131
func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, contents, revHash string) {
//line views/readers.qtpl:131
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/readers.qtpl:131
StreamRevisionHTML(qw422016, rq, h, contents, revHash)
//line views/readers.qtpl:131
qt422016.ReleaseWriter(qw422016)
//line views/readers.qtpl:131
}
//line views/readers.qtpl:131
func RevisionHTML(rq *http.Request, h *hyphae.Hypha, contents, revHash string) string {
//line views/readers.qtpl:131
qb422016 := qt422016.AcquireByteBuffer()
//line views/readers.qtpl:131
WriteRevisionHTML(qb422016, rq, h, contents, revHash)
//line views/readers.qtpl:131
qs422016 := string(qb422016.B)
//line views/readers.qtpl:131
qt422016.ReleaseByteBuffer(qb422016)
//line views/readers.qtpl:131
return qs422016
//line views/readers.qtpl:131
}

147
views/stuff.qtpl Normal file
View File

@ -0,0 +1,147 @@
{% import "path/filepath" %}
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
{% func BaseHTML(title, body string, u *user.User, headElements ...string) %}
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="/static/common.css">
<title>{%s title %}</title>
{% for _, el := range headElements %}{%s= el %}{% endfor %}
</head>
<body>
<header>
<nav class="header-links main-width">
<ul class="header-links__list">
{%- for _, link := range util.HeaderLinks -%}
<li class="header-links__entry"><a class="header-links__link" href="{%s link.Href %}">{%s link.Display %}</a></li>
{%- endfor -%}
{%s= UserMenuHTML(u) %}
</ul>
</nav>
</header>
{%s= body %}
</body>
</html>
{% endfunc %}
{% func UserListHTML() %}
<div class="layout">
<main class="main-width user-list">
<h1>List of users</h1>
{% code
var (
admins = make([]string, 0)
moderators = make([]string, 0)
editors = make([]string, 0)
)
for u := range user.YieldUsers() {
switch u.Group {
case "admin":
admins = append(admins, u.Name)
case "moderator":
moderators = append(moderators, u.Name)
case "editor", "trusted":
editors = append(editors, u.Name)
}
}
%}
<section>
<h2>Admins</h2>
<ol>{% for _, name := range admins %}
<li><a href="/page/{%s util.UserHypha %}/{%s name %}">{%s name %}</a></li>
{% endfor %}</ol>
</section>
<section>
<h2>Moderators</h2>
<ol>{% for _, name := range moderators %}
<li><a href="/page/{%s util.UserHypha %}/{%s name %}">{%s name %}</a></li>
{% endfor %}</ol>
</section>
<section>
<h2>Editors</h2>
<ol>{% for _, name := range editors %}
<li><a href="/page/{%s util.UserHypha %}/{%s name %}">{%s name %}</a></li>
{% endfor %}</ol>
</section>
</main>
</div>
{% endfunc %}
{% func HyphaListHTML() %}
<div class="layout">
<main class="main-width">
<h1>List of hyphae</h1>
<p>This wiki has {%d hyphae.Count() %} hyphae.</p>
<ul class="hypha-list">
{% for h := range hyphae.YieldExistingHyphae() %}
<li class="hypha-list__entry">
<a class="hypha-list__link" href="/hypha/{%s h.Name %}">{%s util.BeautifulName(h.Name) %}</a>
{% if h.BinaryPath != "" %}
<span class="hypha-list__amnt-type">{%s filepath.Ext(h.BinaryPath)[1:] %}</span>
{% endif %}
</li>
{% endfor %}
</ul>
</main>
</div>
{% endfunc %}
{% func AboutHTML() %}
<div class="layout">
<main class="main-width">
<section>
<h1>About {%s util.SiteName %}</h1>
<ul>
<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> β 0.13</li>
{%- if user.AuthUsed -%}
<li><b>User count:</b> {%d user.Count() %}</li>
<li><b>Home page:</b> <a href="/">{%s util.HomePage %}</a></li>
<li><b>Administrators:</b> {%- for i, username := range user.ListUsersWithGroup("admin") -%}
{%- if i > 0 -%}<span aria-hidden="true">, </span>
{%- endif -%}
<a href="/page/{%s util.UserHypha %}/{%s username %}">{%s username %}</a>{%- endfor -%}</li>
{%- else -%}
<li>This wiki does not use authorization</li>
{%- endif -%}
</ul>
<p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p>
</section>
</main>
</div>
{% endfunc %}
{% func AdminPanelHTML() %}
<div class="layout">
<main class="main-width">
<h1>Admininstrative functions</h1>
<section>
<h2>Safe things</h2>
<ul>
<li><a href="/about">About this wiki<a></li>
<li><a href="/user-list">User list</a></li>
<li><a href="/update-header-links">Update header links</a></li>
</ul>
</section>
<section>
<h2>Dangerous things</h2>
<form action="/admin/shutdown" method="POST" style="float:left">
<fieldset>
<legend>Shutdown wiki</legend>
<input type="submit">
</fieldset>
</form>
<form action="/reindex" method="GET" style="float:left">
<fieldset>
<legend>Reindex hyphae</legend>
<input type="submit">
</fieldset>
</form>
</section>
</main>
</div>
{% endfunc %}

502
views/stuff.qtpl.go Normal file
View File

@ -0,0 +1,502 @@
// Code generated by qtc from "stuff.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line views/stuff.qtpl:1
package views
//line views/stuff.qtpl:1
import "path/filepath"
//line views/stuff.qtpl:2
import "github.com/bouncepaw/mycorrhiza/hyphae"
//line views/stuff.qtpl:3
import "github.com/bouncepaw/mycorrhiza/user"
//line views/stuff.qtpl:4
import "github.com/bouncepaw/mycorrhiza/util"
//line views/stuff.qtpl:6
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line views/stuff.qtpl:6
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line views/stuff.qtpl:6
func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User, headElements ...string) {
//line views/stuff.qtpl:6
qw422016.N().S(`
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="/static/common.css">
<title>`)
//line views/stuff.qtpl:13
qw422016.E().S(title)
//line views/stuff.qtpl:13
qw422016.N().S(`</title>
`)
//line views/stuff.qtpl:14
for _, el := range headElements {
//line views/stuff.qtpl:14
qw422016.N().S(el)
//line views/stuff.qtpl:14
}
//line views/stuff.qtpl:14
qw422016.N().S(`
</head>
<body>
<header>
<nav class="header-links main-width">
<ul class="header-links__list">
`)
//line views/stuff.qtpl:20
for _, link := range util.HeaderLinks {
//line views/stuff.qtpl:20
qw422016.N().S(` <li class="header-links__entry"><a class="header-links__link" href="`)
//line views/stuff.qtpl:21
qw422016.E().S(link.Href)
//line views/stuff.qtpl:21
qw422016.N().S(`">`)
//line views/stuff.qtpl:21
qw422016.E().S(link.Display)
//line views/stuff.qtpl:21
qw422016.N().S(`</a></li>
`)
//line views/stuff.qtpl:22
}
//line views/stuff.qtpl:22
qw422016.N().S(` `)
//line views/stuff.qtpl:23
qw422016.N().S(UserMenuHTML(u))
//line views/stuff.qtpl:23
qw422016.N().S(`
</ul>
</nav>
</header>
`)
//line views/stuff.qtpl:27
qw422016.N().S(body)
//line views/stuff.qtpl:27
qw422016.N().S(`
</body>
</html>
`)
//line views/stuff.qtpl:30
}
//line views/stuff.qtpl:30
func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string, u *user.User, headElements ...string) {
//line views/stuff.qtpl:30
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:30
StreamBaseHTML(qw422016, title, body, u, headElements...)
//line views/stuff.qtpl:30
qt422016.ReleaseWriter(qw422016)
//line views/stuff.qtpl:30
}
//line views/stuff.qtpl:30
func BaseHTML(title, body string, u *user.User, headElements ...string) string {
//line views/stuff.qtpl:30
qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:30
WriteBaseHTML(qb422016, title, body, u, headElements...)
//line views/stuff.qtpl:30
qs422016 := string(qb422016.B)
//line views/stuff.qtpl:30
qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:30
return qs422016
//line views/stuff.qtpl:30
}
//line views/stuff.qtpl:32
func StreamUserListHTML(qw422016 *qt422016.Writer) {
//line views/stuff.qtpl:32
qw422016.N().S(`
<div class="layout">
<main class="main-width user-list">
<h1>List of users</h1>
`)
//line views/stuff.qtpl:37
var (
admins = make([]string, 0)
moderators = make([]string, 0)
editors = make([]string, 0)
)
for u := range user.YieldUsers() {
switch u.Group {
case "admin":
admins = append(admins, u.Name)
case "moderator":
moderators = append(moderators, u.Name)
case "editor", "trusted":
editors = append(editors, u.Name)
}
}
//line views/stuff.qtpl:52
qw422016.N().S(`
<section>
<h2>Admins</h2>
<ol>`)
//line views/stuff.qtpl:55
for _, name := range admins {
//line views/stuff.qtpl:55
qw422016.N().S(`
<li><a href="/page/`)
//line views/stuff.qtpl:56
qw422016.E().S(util.UserHypha)
//line views/stuff.qtpl:56
qw422016.N().S(`/`)
//line views/stuff.qtpl:56
qw422016.E().S(name)
//line views/stuff.qtpl:56
qw422016.N().S(`">`)
//line views/stuff.qtpl:56
qw422016.E().S(name)
//line views/stuff.qtpl:56
qw422016.N().S(`</a></li>
`)
//line views/stuff.qtpl:57
}
//line views/stuff.qtpl:57
qw422016.N().S(`</ol>
</section>
<section>
<h2>Moderators</h2>
<ol>`)
//line views/stuff.qtpl:61
for _, name := range moderators {
//line views/stuff.qtpl:61
qw422016.N().S(`
<li><a href="/page/`)
//line views/stuff.qtpl:62
qw422016.E().S(util.UserHypha)
//line views/stuff.qtpl:62
qw422016.N().S(`/`)
//line views/stuff.qtpl:62
qw422016.E().S(name)
//line views/stuff.qtpl:62
qw422016.N().S(`">`)
//line views/stuff.qtpl:62
qw422016.E().S(name)
//line views/stuff.qtpl:62
qw422016.N().S(`</a></li>
`)
//line views/stuff.qtpl:63
}
//line views/stuff.qtpl:63
qw422016.N().S(`</ol>
</section>
<section>
<h2>Editors</h2>
<ol>`)
//line views/stuff.qtpl:67
for _, name := range editors {
//line views/stuff.qtpl:67
qw422016.N().S(`
<li><a href="/page/`)
//line views/stuff.qtpl:68
qw422016.E().S(util.UserHypha)
//line views/stuff.qtpl:68
qw422016.N().S(`/`)
//line views/stuff.qtpl:68
qw422016.E().S(name)
//line views/stuff.qtpl:68
qw422016.N().S(`">`)
//line views/stuff.qtpl:68
qw422016.E().S(name)
//line views/stuff.qtpl:68
qw422016.N().S(`</a></li>
`)
//line views/stuff.qtpl:69
}
//line views/stuff.qtpl:69
qw422016.N().S(`</ol>
</section>
</main>
</div>
`)
//line views/stuff.qtpl:73
}
//line views/stuff.qtpl:73
func WriteUserListHTML(qq422016 qtio422016.Writer) {
//line views/stuff.qtpl:73
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:73
StreamUserListHTML(qw422016)
//line views/stuff.qtpl:73
qt422016.ReleaseWriter(qw422016)
//line views/stuff.qtpl:73
}
//line views/stuff.qtpl:73
func UserListHTML() string {
//line views/stuff.qtpl:73
qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:73
WriteUserListHTML(qb422016)
//line views/stuff.qtpl:73
qs422016 := string(qb422016.B)
//line views/stuff.qtpl:73
qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:73
return qs422016
//line views/stuff.qtpl:73
}
//line views/stuff.qtpl:75
func StreamHyphaListHTML(qw422016 *qt422016.Writer) {
//line views/stuff.qtpl:75
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<h1>List of hyphae</h1>
<p>This wiki has `)
//line views/stuff.qtpl:79
qw422016.N().D(hyphae.Count())
//line views/stuff.qtpl:79
qw422016.N().S(` hyphae.</p>
<ul class="hypha-list">
`)
//line views/stuff.qtpl:81
for h := range hyphae.YieldExistingHyphae() {
//line views/stuff.qtpl:81
qw422016.N().S(`
<li class="hypha-list__entry">
<a class="hypha-list__link" href="/hypha/`)
//line views/stuff.qtpl:83
qw422016.E().S(h.Name)
//line views/stuff.qtpl:83
qw422016.N().S(`">`)
//line views/stuff.qtpl:83
qw422016.E().S(util.BeautifulName(h.Name))
//line views/stuff.qtpl:83
qw422016.N().S(`</a>
`)
//line views/stuff.qtpl:84
if h.BinaryPath != "" {
//line views/stuff.qtpl:84
qw422016.N().S(`
<span class="hypha-list__amnt-type">`)
//line views/stuff.qtpl:85
qw422016.E().S(filepath.Ext(h.BinaryPath)[1:])
//line views/stuff.qtpl:85
qw422016.N().S(`</span>
`)
//line views/stuff.qtpl:86
}
//line views/stuff.qtpl:86
qw422016.N().S(`
</li>
`)
//line views/stuff.qtpl:88
}
//line views/stuff.qtpl:88
qw422016.N().S(`
</ul>
</main>
</div>
`)
//line views/stuff.qtpl:92
}
//line views/stuff.qtpl:92
func WriteHyphaListHTML(qq422016 qtio422016.Writer) {
//line views/stuff.qtpl:92
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:92
StreamHyphaListHTML(qw422016)
//line views/stuff.qtpl:92
qt422016.ReleaseWriter(qw422016)
//line views/stuff.qtpl:92
}
//line views/stuff.qtpl:92
func HyphaListHTML() string {
//line views/stuff.qtpl:92
qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:92
WriteHyphaListHTML(qb422016)
//line views/stuff.qtpl:92
qs422016 := string(qb422016.B)
//line views/stuff.qtpl:92
qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:92
return qs422016
//line views/stuff.qtpl:92
}
//line views/stuff.qtpl:94
func StreamAboutHTML(qw422016 *qt422016.Writer) {
//line views/stuff.qtpl:94
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<section>
<h1>About `)
//line views/stuff.qtpl:98
qw422016.E().S(util.SiteName)
//line views/stuff.qtpl:98
qw422016.N().S(`</h1>
<ul>
<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> β 0.13 indev</li>
`)
//line views/stuff.qtpl:101
if user.AuthUsed {
//line views/stuff.qtpl:101
qw422016.N().S(` <li><b>User count:</b> `)
//line views/stuff.qtpl:102
qw422016.N().D(user.Count())
//line views/stuff.qtpl:102
qw422016.N().S(`</li>
<li><b>Home page:</b> <a href="/">`)
//line views/stuff.qtpl:103
qw422016.E().S(util.HomePage)
//line views/stuff.qtpl:103
qw422016.N().S(`</a></li>
<li><b>Administrators:</b>`)
//line views/stuff.qtpl:104
for i, username := range user.ListUsersWithGroup("admin") {
//line views/stuff.qtpl:105
if i > 0 {
//line views/stuff.qtpl:105
qw422016.N().S(`<span aria-hidden="true">, </span>
`)
//line views/stuff.qtpl:106
}
//line views/stuff.qtpl:106
qw422016.N().S(` <a href="/page/`)
//line views/stuff.qtpl:107
qw422016.E().S(util.UserHypha)
//line views/stuff.qtpl:107
qw422016.N().S(`/`)
//line views/stuff.qtpl:107
qw422016.E().S(username)
//line views/stuff.qtpl:107
qw422016.N().S(`">`)
//line views/stuff.qtpl:107
qw422016.E().S(username)
//line views/stuff.qtpl:107
qw422016.N().S(`</a>`)
//line views/stuff.qtpl:107
}
//line views/stuff.qtpl:107
qw422016.N().S(`</li>
`)
//line views/stuff.qtpl:108
} else {
//line views/stuff.qtpl:108
qw422016.N().S(` <li>This wiki does not use authorization</li>
`)
//line views/stuff.qtpl:110
}
//line views/stuff.qtpl:110
qw422016.N().S(` </ul>
<p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p>
</section>
</main>
</div>
`)
//line views/stuff.qtpl:116
}
//line views/stuff.qtpl:116
func WriteAboutHTML(qq422016 qtio422016.Writer) {
//line views/stuff.qtpl:116
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:116
StreamAboutHTML(qw422016)
//line views/stuff.qtpl:116
qt422016.ReleaseWriter(qw422016)
//line views/stuff.qtpl:116
}
//line views/stuff.qtpl:116
func AboutHTML() string {
//line views/stuff.qtpl:116
qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:116
WriteAboutHTML(qb422016)
//line views/stuff.qtpl:116
qs422016 := string(qb422016.B)
//line views/stuff.qtpl:116
qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:116
return qs422016
//line views/stuff.qtpl:116
}
//line views/stuff.qtpl:118
func StreamAdminPanelHTML(qw422016 *qt422016.Writer) {
//line views/stuff.qtpl:118
qw422016.N().S(`
<div class="layout">
<main class="main-width">
<h1>Admininstrative functions</h1>
<section>
<h2>Safe things</h2>
<ul>
<li><a href="/about">About this wiki<a></li>
<li><a href="/user-list">User list</a></li>
<li><a href="/update-header-links">Update header links</a></li>
</ul>
</section>
<section>
<h2>Dangerous things</h2>
<form action="/admin/shutdown" method="POST" style="float:left">
<fieldset>
<legend>Shutdown wiki</legend>
<input type="submit">
</fieldset>
</form>
<form action="/reindex" method="GET" style="float:left">
<fieldset>
<legend>Reindex hyphae</legend>
<input type="submit">
</fieldset>
</form>
</section>
</main>
</div>
`)
//line views/stuff.qtpl:147
}
//line views/stuff.qtpl:147
func WriteAdminPanelHTML(qq422016 qtio422016.Writer) {
//line views/stuff.qtpl:147
qw422016 := qt422016.AcquireWriter(qq422016)
//line views/stuff.qtpl:147
StreamAdminPanelHTML(qw422016)
//line views/stuff.qtpl:147
qt422016.ReleaseWriter(qw422016)
//line views/stuff.qtpl:147
}
//line views/stuff.qtpl:147
func AdminPanelHTML() string {
//line views/stuff.qtpl:147
qb422016 := qt422016.AcquireByteBuffer()
//line views/stuff.qtpl:147
WriteAdminPanelHTML(qb422016)
//line views/stuff.qtpl:147
qs422016 := string(qb422016.B)
//line views/stuff.qtpl:147
qt422016.ReleaseByteBuffer(qb422016)
//line views/stuff.qtpl:147
return qs422016
//line views/stuff.qtpl:147
}