mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2024-10-30 11:46:16 +00:00
commit
24d79dac8c
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,6 @@
|
||||
mycorrhiza
|
||||
hyphae/*.gog
|
||||
|
||||
# go editors and IDEA folders
|
||||
.idea/
|
||||
.vscode/
|
||||
|
7
Makefile
7
Makefile
@ -1,11 +1,8 @@
|
||||
run: build
|
||||
./mycorrhiza metarrhiza
|
||||
|
||||
auth_run: build
|
||||
./mycorrhiza -auth-method fixed metarrhiza
|
||||
|
||||
gemini_run: build
|
||||
./mycorrhiza -gemini-cert-path "." metarrhiza
|
||||
config_run: build
|
||||
./mycorrhiza -config-path "assets/config.ini" metarrhiza
|
||||
|
||||
build:
|
||||
go generate
|
||||
|
28
README.md
28
README.md
@ -1,10 +1,10 @@
|
||||
# 🍄 MycorrhizaWiki 0.13
|
||||
# 🍄 MycorrhizaWiki
|
||||
A wiki engine.
|
||||
|
||||
[Main wiki](https://mycorrhiza.lesarbr.es)
|
||||
|
||||
## Building
|
||||
Also see [detailed instructions](https://mycorrhiza.lesarbr.es/page/deploy) on wiki.
|
||||
Also see [detailed instructions](https://mycorrhiza.lesarbr.es/hypha/deploy) on wiki.
|
||||
```sh
|
||||
git clone --recurse-submodules https://github.com/bouncepaw/mycorrhiza
|
||||
cd mycorrhiza
|
||||
@ -21,26 +21,10 @@ mycorrhiza [OPTIONS...] WIKI_PATH
|
||||
WIKI_PATH must be a path to git repository which you want to be a wiki.
|
||||
|
||||
Options:
|
||||
-auth-method string
|
||||
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
|
||||
The home page (default "home")
|
||||
-icon string
|
||||
What to show in the navititle in the beginning, before the colon (default "🍄")
|
||||
-name string
|
||||
What is the name of your wiki (default "wiki")
|
||||
-port string
|
||||
Port to serve the wiki at (default "1737")
|
||||
-url string
|
||||
URL at which your wiki can be found. Used to generate feeds (default "http://0.0.0.0:$port")
|
||||
-user-hypha string
|
||||
Hypha which is a superhypha of all user pages (default "u")
|
||||
-config-path string
|
||||
Path to a configuration file. Leave empty if you don't want to use it.
|
||||
-print-example-config
|
||||
If true, print an example configuration file contents and exit. You can save the output to a file and base your own configuration on it.
|
||||
```
|
||||
|
||||
## Features
|
||||
|
@ -1,3 +1,11 @@
|
||||
{%- func HelpMessage() -%}
|
||||
Usage of %s:
|
||||
{%- endfunc -%}
|
||||
|
||||
{%- func ExampleConfig() -%}
|
||||
{% cat "config.ini" %}
|
||||
{%- endfunc -%}
|
||||
|
||||
{% func DefaultCSS() %}
|
||||
{% cat "default.css" %}
|
||||
{% endfunc %}
|
||||
|
@ -18,11 +18,97 @@ var (
|
||||
)
|
||||
|
||||
//line assets/assets.qtpl:1
|
||||
func StreamDefaultCSS(qw422016 *qt422016.Writer) {
|
||||
func StreamHelpMessage(qw422016 *qt422016.Writer) {
|
||||
//line assets/assets.qtpl:1
|
||||
qw422016.N().S(`Usage of %s:
|
||||
`)
|
||||
//line assets/assets.qtpl:3
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:3
|
||||
func WriteHelpMessage(qq422016 qtio422016.Writer) {
|
||||
//line assets/assets.qtpl:3
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line assets/assets.qtpl:3
|
||||
StreamHelpMessage(qw422016)
|
||||
//line assets/assets.qtpl:3
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line assets/assets.qtpl:3
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:3
|
||||
func HelpMessage() string {
|
||||
//line assets/assets.qtpl:3
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line assets/assets.qtpl:3
|
||||
WriteHelpMessage(qb422016)
|
||||
//line assets/assets.qtpl:3
|
||||
qs422016 := string(qb422016.B)
|
||||
//line assets/assets.qtpl:3
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line assets/assets.qtpl:3
|
||||
return qs422016
|
||||
//line assets/assets.qtpl:3
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:5
|
||||
func StreamExampleConfig(qw422016 *qt422016.Writer) {
|
||||
//line assets/assets.qtpl:6
|
||||
qw422016.N().S(`WikiName = My wiki
|
||||
NaviTitleIcon = 🐑
|
||||
|
||||
[Hyphae]
|
||||
HomeHypha = home
|
||||
UserHypha = u
|
||||
HeaderLinksHypha = header-links
|
||||
|
||||
[Network]
|
||||
HTTPPort = 8080
|
||||
URL = https://wiki
|
||||
GeminiCertificatePath = /home/wiki/gemcerts
|
||||
|
||||
[Authorization]
|
||||
UseFixedAuth = true
|
||||
FixedAuthCredentialsPath = /home/wiki/mycocredentials.json
|
||||
`)
|
||||
//line assets/assets.qtpl:6
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:2
|
||||
//line assets/assets.qtpl:7
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:7
|
||||
func WriteExampleConfig(qq422016 qtio422016.Writer) {
|
||||
//line assets/assets.qtpl:7
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line assets/assets.qtpl:7
|
||||
StreamExampleConfig(qw422016)
|
||||
//line assets/assets.qtpl:7
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line assets/assets.qtpl:7
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:7
|
||||
func ExampleConfig() string {
|
||||
//line assets/assets.qtpl:7
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line assets/assets.qtpl:7
|
||||
WriteExampleConfig(qb422016)
|
||||
//line assets/assets.qtpl:7
|
||||
qs422016 := string(qb422016.B)
|
||||
//line assets/assets.qtpl:7
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line assets/assets.qtpl:7
|
||||
return qs422016
|
||||
//line assets/assets.qtpl:7
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:9
|
||||
func StreamDefaultCSS(qw422016 *qt422016.Writer) {
|
||||
//line assets/assets.qtpl:9
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:10
|
||||
qw422016.N().S(`.amnt-grid { display: grid; grid-template-columns: 1fr 1fr; }
|
||||
.upload-binary__input { display: block; margin: .25rem 0; }
|
||||
|
||||
@ -61,7 +147,7 @@ header { width: 100%; margin-bottom: 1rem; }
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.amnt-grid { grid-template-columns: 1fr; }
|
||||
.layout { grid-template-column: auto; grid-template-row: auto auto auto; }
|
||||
.layout { grid-template-columns: auto; grid-template-rows: auto auto auto; }
|
||||
.main-width { width: 100%; }
|
||||
main { padding: 1rem; margin: 0; }
|
||||
}
|
||||
@ -178,7 +264,7 @@ figcaption { padding-bottom: .5rem; }
|
||||
.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; }
|
||||
.prevnext__el { display: inline-block; min-width: 40%; padding: .5rem; margin-bottom: .25rem; text-decoration: none; border-radius: .25rem; }
|
||||
.prevnext__prev { float: left; }
|
||||
.prevnext__next { float: right; text-align: right; }
|
||||
|
||||
@ -295,171 +381,169 @@ mark { background: rgba(130, 80, 30, 5); color: inherit; }
|
||||
.hypha-tabs { background-color: #232323; }
|
||||
}
|
||||
}
|
||||
|
||||
.backlinks { display: none; }
|
||||
`)
|
||||
//line assets/assets.qtpl:2
|
||||
//line assets/assets.qtpl:10
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
func WriteDefaultCSS(qq422016 qtio422016.Writer) {
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
StreamDefaultCSS(qw422016)
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
func DefaultCSS() string {
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
WriteDefaultCSS(qb422016)
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
qs422016 := string(qb422016.B)
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
return qs422016
|
||||
//line assets/assets.qtpl:3
|
||||
//line assets/assets.qtpl:11
|
||||
}
|
||||
|
||||
// Next three are from https://remixicon.com/
|
||||
|
||||
//line assets/assets.qtpl:6
|
||||
//line assets/assets.qtpl:14
|
||||
func StreamIconHTTP(qw422016 *qt422016.Writer) {
|
||||
//line assets/assets.qtpl:6
|
||||
//line assets/assets.qtpl:14
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:7
|
||||
//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="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 assets/assets.qtpl:7
|
||||
//line assets/assets.qtpl:15
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
func WriteIconHTTP(qq422016 qtio422016.Writer) {
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
StreamIconHTTP(qw422016)
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
func IconHTTP() string {
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
WriteIconHTTP(qb422016)
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
qs422016 := string(qb422016.B)
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
return qs422016
|
||||
//line assets/assets.qtpl:8
|
||||
//line assets/assets.qtpl:16
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:10
|
||||
//line assets/assets.qtpl:18
|
||||
func StreamIconGemini(qw422016 *qt422016.Writer) {
|
||||
//line assets/assets.qtpl:10
|
||||
//line assets/assets.qtpl:18
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:11
|
||||
//line assets/assets.qtpl:19
|
||||
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 assets/assets.qtpl:11
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:12
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:12
|
||||
func WriteIconGemini(qq422016 qtio422016.Writer) {
|
||||
//line assets/assets.qtpl:12
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line assets/assets.qtpl:12
|
||||
StreamIconGemini(qw422016)
|
||||
//line assets/assets.qtpl:12
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line assets/assets.qtpl:12
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:12
|
||||
func IconGemini() string {
|
||||
//line assets/assets.qtpl:12
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line assets/assets.qtpl:12
|
||||
WriteIconGemini(qb422016)
|
||||
//line assets/assets.qtpl:12
|
||||
qs422016 := string(qb422016.B)
|
||||
//line assets/assets.qtpl:12
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line assets/assets.qtpl:12
|
||||
return qs422016
|
||||
//line assets/assets.qtpl:12
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:14
|
||||
func StreamIconMailto(qw422016 *qt422016.Writer) {
|
||||
//line assets/assets.qtpl:14
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//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 assets/assets.qtpl:15
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:16
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:16
|
||||
func WriteIconMailto(qq422016 qtio422016.Writer) {
|
||||
//line assets/assets.qtpl:16
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line assets/assets.qtpl:16
|
||||
StreamIconMailto(qw422016)
|
||||
//line assets/assets.qtpl:16
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line assets/assets.qtpl:16
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:16
|
||||
func IconMailto() string {
|
||||
//line assets/assets.qtpl:16
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line assets/assets.qtpl:16
|
||||
WriteIconMailto(qb422016)
|
||||
//line assets/assets.qtpl:16
|
||||
qs422016 := string(qb422016.B)
|
||||
//line assets/assets.qtpl:16
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line assets/assets.qtpl:16
|
||||
return qs422016
|
||||
//line assets/assets.qtpl:16
|
||||
}
|
||||
|
||||
// This is a modified version of https://www.svgrepo.com/svg/232085/rat
|
||||
|
||||
//line assets/assets.qtpl:19
|
||||
func StreamIconGopher(qw422016 *qt422016.Writer) {
|
||||
//line assets/assets.qtpl:19
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:20
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:20
|
||||
func WriteIconGemini(qq422016 qtio422016.Writer) {
|
||||
//line assets/assets.qtpl:20
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line assets/assets.qtpl:20
|
||||
StreamIconGemini(qw422016)
|
||||
//line assets/assets.qtpl:20
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line assets/assets.qtpl:20
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:20
|
||||
func IconGemini() string {
|
||||
//line assets/assets.qtpl:20
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line assets/assets.qtpl:20
|
||||
WriteIconGemini(qb422016)
|
||||
//line assets/assets.qtpl:20
|
||||
qs422016 := string(qb422016.B)
|
||||
//line assets/assets.qtpl:20
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line assets/assets.qtpl:20
|
||||
return qs422016
|
||||
//line assets/assets.qtpl:20
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:22
|
||||
func StreamIconMailto(qw422016 *qt422016.Writer) {
|
||||
//line assets/assets.qtpl:22
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:23
|
||||
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 assets/assets.qtpl:23
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:24
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:24
|
||||
func WriteIconMailto(qq422016 qtio422016.Writer) {
|
||||
//line assets/assets.qtpl:24
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line assets/assets.qtpl:24
|
||||
StreamIconMailto(qw422016)
|
||||
//line assets/assets.qtpl:24
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line assets/assets.qtpl:24
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:24
|
||||
func IconMailto() string {
|
||||
//line assets/assets.qtpl:24
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line assets/assets.qtpl:24
|
||||
WriteIconMailto(qb422016)
|
||||
//line assets/assets.qtpl:24
|
||||
qs422016 := string(qb422016.B)
|
||||
//line assets/assets.qtpl:24
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line assets/assets.qtpl:24
|
||||
return qs422016
|
||||
//line assets/assets.qtpl:24
|
||||
}
|
||||
|
||||
// This is a modified version of https://www.svgrepo.com/svg/232085/rat
|
||||
|
||||
//line assets/assets.qtpl:27
|
||||
func StreamIconGopher(qw422016 *qt422016.Writer) {
|
||||
//line assets/assets.qtpl:27
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:28
|
||||
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
|
||||
@ -472,34 +556,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 assets/assets.qtpl:20
|
||||
//line assets/assets.qtpl:28
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
func WriteIconGopher(qq422016 qtio422016.Writer) {
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
StreamIconGopher(qw422016)
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
}
|
||||
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
func IconGopher() string {
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
WriteIconGopher(qb422016)
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
qs422016 := string(qb422016.B)
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
return qs422016
|
||||
//line assets/assets.qtpl:21
|
||||
//line assets/assets.qtpl:29
|
||||
}
|
||||
|
16
assets/config.ini
Normal file
16
assets/config.ini
Normal file
@ -0,0 +1,16 @@
|
||||
WikiName = My wiki
|
||||
NaviTitleIcon = 🐑
|
||||
|
||||
[Hyphae]
|
||||
HomeHypha = home
|
||||
UserHypha = u
|
||||
HeaderLinksHypha = header-links
|
||||
|
||||
[Network]
|
||||
HTTPPort = 8080
|
||||
URL = https://wiki
|
||||
GeminiCertificatePath = /home/wiki/gemcerts
|
||||
|
||||
[Authorization]
|
||||
UseFixedAuth = true
|
||||
FixedAuthCredentialsPath = /home/wiki/mycocredentials.json
|
@ -36,7 +36,7 @@ header { width: 100%; margin-bottom: 1rem; }
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.amnt-grid { grid-template-columns: 1fr; }
|
||||
.layout { grid-template-column: auto; grid-template-row: auto auto auto; }
|
||||
.layout { grid-template-columns: auto; grid-template-rows: auto auto auto; }
|
||||
.main-width { width: 100%; }
|
||||
main { padding: 1rem; margin: 0; }
|
||||
}
|
||||
@ -153,7 +153,7 @@ figcaption { padding-bottom: .5rem; }
|
||||
.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; }
|
||||
.prevnext__el { display: inline-block; min-width: 40%; padding: .5rem; margin-bottom: .25rem; text-decoration: none; border-radius: .25rem; }
|
||||
.prevnext__prev { float: left; }
|
||||
.prevnext__next { float: right; text-align: right; }
|
||||
|
||||
@ -270,5 +270,3 @@ mark { background: rgba(130, 80, 30, 5); color: inherit; }
|
||||
.hypha-tabs { background-color: #232323; }
|
||||
}
|
||||
}
|
||||
|
||||
.backlinks { display: none; }
|
||||
|
54
flag.go
54
flag.go
@ -2,24 +2,39 @@ package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/assets"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
var printExampleConfig bool
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&util.URL, "url", "http://0.0.0.0:$port", "URL at which your wiki can be found. Used to generate feeds and social media previews")
|
||||
flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at using HTTP")
|
||||
flag.StringVar(&util.HomePage, "home", "home", "The home page name")
|
||||
flag.StringVar(&util.SiteNavIcon, "icon", "🍄", "What to show in the navititle in the beginning, before the colon")
|
||||
flag.StringVar(&util.SiteName, "name", "wiki", "What is the name of your wiki")
|
||||
flag.StringVar(&util.UserHypha, "user-hypha", "u", "Hypha which is a superhypha of all user pages")
|
||||
flag.StringVar(&util.AuthMethod, "auth-method", "none", "What auth method to use. Variants: \"none\", \"fixed\"")
|
||||
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.")
|
||||
// flag.StringVar(&util.URL, "url", "http://0.0.0.0:$port", "URL at which your wiki can be found. Used to generate feeds and social media previews")
|
||||
// flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at using HTTP")
|
||||
// flag.StringVar(&util.HomePage, "home", "home", "The home page name")
|
||||
// flag.StringVar(&util.SiteNavIcon, "icon", "🍄", "What to show in the navititle in the beginning, before the colon")
|
||||
// flag.StringVar(&util.SiteName, "name", "wiki", "What is the name of your wiki")
|
||||
// flag.StringVar(&util.UserHypha, "user-hypha", "u", "Hypha which is a superhypha of all user pages")
|
||||
// flag.StringVar(&util.AuthMethod, "auth-method", "none", "What auth method to use. Variants: \"none\", \"fixed\"")
|
||||
// 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.")
|
||||
flag.StringVar(&util.ConfigFilePath, "config-path", "", "Path to a configuration file. Leave empty if you don't want to use it.")
|
||||
flag.BoolVar(&printExampleConfig, "print-example-config", false, "If true, print an example configuration file contents and exit. You can save the output to a file and base your own configuration on it.")
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(
|
||||
flag.CommandLine.Output(),
|
||||
assets.HelpMessage(),
|
||||
os.Args[0],
|
||||
)
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
}
|
||||
|
||||
// Do the things related to cli args and die maybe
|
||||
@ -27,10 +42,18 @@ func parseCliArgs() {
|
||||
flag.Parse()
|
||||
|
||||
args := flag.Args()
|
||||
if printExampleConfig {
|
||||
fmt.Printf(assets.ExampleConfig())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
log.Fatal("Error: pass a wiki directory")
|
||||
}
|
||||
|
||||
// It is ok if the path is ""
|
||||
util.ReadConfigFile(util.ConfigFilePath)
|
||||
|
||||
var err error
|
||||
WikiDir, err = filepath.Abs(args[0])
|
||||
util.WikiDir = WikiDir
|
||||
@ -38,20 +61,15 @@ func parseCliArgs() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if util.URL == "http://0.0.0.0:$port" {
|
||||
if util.URL == "" {
|
||||
util.URL = "http://0.0.0.0:" + util.ServerPort
|
||||
}
|
||||
|
||||
util.HomePage = util.CanonicalName(util.HomePage)
|
||||
util.UserHypha = util.CanonicalName(util.UserHypha)
|
||||
util.HeaderLinksHypha = util.CanonicalName(util.HeaderLinksHypha)
|
||||
|
||||
switch util.AuthMethod {
|
||||
case "none":
|
||||
case "fixed":
|
||||
user.AuthUsed = true
|
||||
user.AuthUsed = util.UseFixedAuth
|
||||
if user.AuthUsed && util.FixedCredentialsPath != "" {
|
||||
user.ReadUsersFromFilesystem()
|
||||
default:
|
||||
log.Fatal("Error: unknown auth method:", util.AuthMethod)
|
||||
}
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -5,8 +5,8 @@ go 1.14
|
||||
require (
|
||||
git.sr.ht/~adnano/go-gemini v0.1.13
|
||||
github.com/adrg/xdg v0.2.2
|
||||
github.com/go-ini/ini v1.62.0 // indirect
|
||||
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
4
go.sum
@ -5,6 +5,8 @@ github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ
|
||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-ini/ini v1.62.0 h1:7VJT/ZXjzqSrvtraFp4ONq80hTcRQth1c9ZnQ3uNQvU=
|
||||
github.com/go-ini/ini v1.62.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
||||
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
@ -35,5 +37,3 @@ 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=
|
||||
|
@ -13,11 +13,22 @@ import (
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
// Path to git executable. Set at init()
|
||||
var gitpath string
|
||||
|
||||
var renameMsgPattern = regexp.MustCompile(`^Rename ‘(.*)’ to ‘.*’`)
|
||||
|
||||
// Start initializes git credentials.
|
||||
// Start finds git and initializes git credentials.
|
||||
func Start(wikiDir string) {
|
||||
_, err := gitsh("config", "user.name", "wikimind")
|
||||
path, err := exec.LookPath("git")
|
||||
if err != nil {
|
||||
log.Fatal("Cound not find the git executable. Check your $PATH.")
|
||||
} else {
|
||||
log.Println("Git path is", path)
|
||||
}
|
||||
gitpath = path
|
||||
|
||||
_, err = gitsh("config", "user.name", "wikimind")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -110,20 +121,6 @@ func (rev *Revision) bestLink() string {
|
||||
}
|
||||
}
|
||||
|
||||
// Path to git executable. Set at init()
|
||||
var gitpath string
|
||||
|
||||
func init() {
|
||||
path, err := exec.LookPath("git")
|
||||
if err != nil {
|
||||
log.Fatal("Cound not find the git executable. Check your $PATH.")
|
||||
} else {
|
||||
log.Println("Git path is", path)
|
||||
}
|
||||
gitpath = path
|
||||
|
||||
}
|
||||
|
||||
// I pronounce it as [gɪt͡ʃ].
|
||||
// gitsh is async-safe, therefore all other git-related functions in this module are too.
|
||||
func gitsh(args ...string) (out bytes.Buffer, err error) {
|
||||
|
@ -143,7 +143,7 @@ func (rev *Revision) asHistoryEntry(hyphaName string) (html string) {
|
||||
<li class="history__entry">
|
||||
<a class="history-entry" href="/rev/%[3]s/%[1]s">
|
||||
<time class="history-entry__time">%[2]s</time>
|
||||
<span class="history-entry__hash">%[3]s</span>
|
||||
<span class="history-entry__hash"><a href="/primitive-diff/%[3]s/%[1]s">%[3]s</a></span>
|
||||
<span class="history-entry__msg">%[4]s</span>
|
||||
</a>%[5]s
|
||||
</li>
|
||||
@ -175,3 +175,8 @@ func FileAtRevision(filepath, hash string) (string, error) {
|
||||
out, err := gitsh("show", hash+":"+strings.TrimPrefix(filepath, util.WikiDir+"/"))
|
||||
return out.String(), err
|
||||
}
|
||||
|
||||
func PrimitiveDiffAtRevision(filepath, hash string) (string, error) {
|
||||
out, err := gitsh("diff", "--unified=1", "--no-color", hash+"~", hash, "--", filepath)
|
||||
return out.String(), err
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
"github.com/bouncepaw/mycorrhiza/views"
|
||||
)
|
||||
|
||||
@ -13,6 +14,7 @@ func initAdmin() {
|
||||
if user.AuthUsed {
|
||||
http.HandleFunc("/admin", handlerAdmin)
|
||||
http.HandleFunc("/admin/shutdown", handlerAdminShutdown)
|
||||
http.HandleFunc("/admin/reindex-users", handlerAdminReindexUsers)
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,3 +33,11 @@ func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Fatal("An admin commanded the wiki to shutdown")
|
||||
}
|
||||
}
|
||||
|
||||
func handlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
if user.CanProceed(rq, "admin") && rq.Method == "POST" {
|
||||
user.ReadUsersFromFilesystem()
|
||||
http.Redirect(w, rq, "/hypha/"+util.UserHypha, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ func init() {
|
||||
http.HandleFunc("/text/", handlerText)
|
||||
http.HandleFunc("/binary/", handlerBinary)
|
||||
http.HandleFunc("/rev/", handlerRevision)
|
||||
http.HandleFunc("/primitive-diff/", handlerPrimitiveDiff)
|
||||
http.HandleFunc("/attachment/", handlerAttachment)
|
||||
}
|
||||
|
||||
@ -41,6 +42,23 @@ func handlerAttachment(w http.ResponseWriter, rq *http.Request) {
|
||||
u))
|
||||
}
|
||||
|
||||
func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/primitive-diff/")
|
||||
firstSlashIndex = strings.IndexRune(shorterUrl, '/')
|
||||
revHash = shorterUrl[:firstSlashIndex]
|
||||
hyphaName = util.CanonicalName(shorterUrl[firstSlashIndex+1:])
|
||||
h = hyphae.ByName(hyphaName)
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
util.HTTP200Page(w,
|
||||
views.BaseHTML(
|
||||
fmt.Sprintf("Diff of %s at %s", hyphaName, revHash),
|
||||
views.PrimitiveDiffHTML(rq, h, u, revHash),
|
||||
u))
|
||||
}
|
||||
|
||||
// handlerRevision displays a specific revision of text part a page
|
||||
func handlerRevision(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
@ -112,10 +130,19 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) {
|
||||
contents = views.AttachmentHTML(h) + contents
|
||||
}
|
||||
}
|
||||
util.HTTP200Page(w,
|
||||
views.BaseHTML(
|
||||
util.BeautifulName(hyphaName),
|
||||
views.HyphaHTML(rq, h, contents),
|
||||
u,
|
||||
openGraph))
|
||||
if contents == "" {
|
||||
util.HTTP404Page(w,
|
||||
views.BaseHTML(
|
||||
util.BeautifulName(hyphaName),
|
||||
views.HyphaHTML(rq, h, contents),
|
||||
u,
|
||||
openGraph))
|
||||
} else {
|
||||
util.HTTP200Page(w,
|
||||
views.BaseHTML(
|
||||
util.BeautifulName(hyphaName),
|
||||
views.HyphaHTML(rq, h, contents),
|
||||
u,
|
||||
openGraph))
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,6 @@ import (
|
||||
|
||||
// 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)
|
||||
|
||||
@ -23,11 +21,10 @@ func Index(path string) {
|
||||
|
||||
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)
|
||||
if oh := ByName(h.Name); oh.Exists {
|
||||
oh.MergeIn(h)
|
||||
} else {
|
||||
byNames[h.Name] = h
|
||||
IncrementCount()
|
||||
h.Insert()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -40,7 +37,7 @@ func indexHelper(path string, nestLevel uint, ch chan *Hypha) {
|
||||
}
|
||||
|
||||
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 this hypha looks like it can be a hypha path, go deeper. Do not touch the .git and static folders for they have an administrative importance!
|
||||
if node.IsDir() &&
|
||||
util.IsCanonicalName(node.Name()) &&
|
||||
node.Name() != ".git" &&
|
||||
|
122
hyphae/hyphae.go
122
hyphae/hyphae.go
@ -13,12 +13,10 @@ var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
|
||||
type Hypha struct {
|
||||
sync.RWMutex
|
||||
|
||||
Name string
|
||||
Name string // Canonical name
|
||||
Exists bool
|
||||
TextPath string
|
||||
BinaryPath string
|
||||
OutLinks []*Hypha
|
||||
BackLinks []*Hypha
|
||||
TextPath string // == "" => no text part
|
||||
BinaryPath string // == "" => no attachment
|
||||
}
|
||||
|
||||
var byNames = make(map[string]*Hypha)
|
||||
@ -31,56 +29,48 @@ func EmptyHypha(hyphaName string) *Hypha {
|
||||
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).
|
||||
// ByName returns a hypha by name. It may have been recorded to the storage.
|
||||
func ByName(hyphaName string) (h *Hypha) {
|
||||
h, exists := byNames[hyphaName]
|
||||
if exists {
|
||||
h, recorded := byNames[hyphaName]
|
||||
if recorded {
|
||||
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)
|
||||
|
||||
func storeHypha(h *Hypha) {
|
||||
byNamesMutex.Lock()
|
||||
defer byNamesMutex.Unlock()
|
||||
if hp.Exists {
|
||||
hp = h
|
||||
byNames[h.Name] = h
|
||||
byNamesMutex.Unlock()
|
||||
|
||||
h.Lock()
|
||||
h.Exists = true
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
// Insert inserts the hypha into the storage. A previous record is used if possible. Count incrementation is done if needed.
|
||||
func (h *Hypha) Insert() (justRecorded bool) {
|
||||
hp, recorded := byNames[h.Name]
|
||||
if recorded {
|
||||
hp.MergeIn(h)
|
||||
} else {
|
||||
h.Exists = true
|
||||
byNames[h.Name] = h
|
||||
storeHypha(h)
|
||||
IncrementCount()
|
||||
}
|
||||
|
||||
return !hp.Exists
|
||||
return !recorded
|
||||
}
|
||||
|
||||
func (h *Hypha) InsertIfNew() (justCreated bool) {
|
||||
func (h *Hypha) InsertIfNew() (justRecorded 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()
|
||||
@ -88,10 +78,6 @@ func (h *Hypha) Delete() {
|
||||
DecrementCount()
|
||||
byNamesMutex.Unlock()
|
||||
h.Unlock()
|
||||
|
||||
for _, outlinkHypha := range h.OutLinks {
|
||||
outlinkHypha.DropBackLink(h)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Hypha) RenameTo(newName string) {
|
||||
@ -106,6 +92,10 @@ func (h *Hypha) RenameTo(newName string) {
|
||||
|
||||
// MergeIn merges in content file paths from a different hypha object. Prints warnings sometimes.
|
||||
func (h *Hypha) MergeIn(oh *Hypha) {
|
||||
if h == oh {
|
||||
return
|
||||
}
|
||||
h.Lock()
|
||||
if h.TextPath == "" && oh.TextPath != "" {
|
||||
h.TextPath = oh.TextPath
|
||||
}
|
||||
@ -115,61 +105,5 @@ func (h *Hypha) MergeIn(oh *Hypha) {
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
h.Unlock()
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
type LinkType int
|
||||
|
||||
const (
|
||||
LinkInavild LinkType = iota
|
||||
LinkInvalid LinkType = iota
|
||||
// LinkLocalRoot is a link like "/list", "/user-list", etc.
|
||||
LinkLocalRoot
|
||||
// LinkLocalHypha is a link like "test", "../test", etc.
|
||||
@ -97,6 +97,11 @@ func From(address, display, hyphaName string) *Link {
|
||||
link.Display = strings.TrimSpace(display)
|
||||
}
|
||||
|
||||
if pos := strings.IndexRune(address, '#'); pos != -1 {
|
||||
link.Anchor = address[pos:]
|
||||
address = address[:pos]
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.ContainsRune(address, ':'):
|
||||
pos := strings.IndexRune(address, ':')
|
||||
|
16
main.go
16
main.go
@ -85,8 +85,16 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
|
||||
// Redirect to a random hypha.
|
||||
func handlerRandom(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var randomHyphaName string
|
||||
i := rand.Intn(hyphae.Count())
|
||||
var (
|
||||
randomHyphaName string
|
||||
amountOfHyphae int = hyphae.Count()
|
||||
)
|
||||
if amountOfHyphae == 0 {
|
||||
HttpErr(w, http.StatusNotFound, util.HomePage, "There are no hyphae",
|
||||
"It is not possible to display a random hypha because the wiki does not contain any hyphae")
|
||||
return
|
||||
}
|
||||
i := rand.Intn(amountOfHyphae)
|
||||
for h := range hyphae.YieldExistingHyphae() {
|
||||
if i == 0 {
|
||||
randomHyphaName = h.Name
|
||||
@ -160,16 +168,14 @@ Crawl-delay: 5`))
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.Println("Running MycorrhizaWiki β")
|
||||
parseCliArgs()
|
||||
log.Println("Running MycorrhizaWiki β")
|
||||
if err := os.Chdir(WikiDir); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Wiki storage directory is", WikiDir)
|
||||
hyphae.Index(WikiDir)
|
||||
log.Println("Indexed", hyphae.Count(), "hyphae")
|
||||
shroom.FindAllBacklinks()
|
||||
log.Println("Found all backlinks")
|
||||
|
||||
history.Start(WikiDir)
|
||||
shroom.SetHeaderLinks()
|
||||
|
@ -1,47 +0,0 @@
|
||||
package markup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseStartOfEntry(t *testing.T) {
|
||||
img := ImgFromFirstLine("img {", "h")
|
||||
tests := []struct {
|
||||
line string
|
||||
entry imgEntry
|
||||
followedByDesc bool
|
||||
}{
|
||||
{"apple", imgEntry{"/binary/apple", "", "", ""}, false},
|
||||
{"pear|", imgEntry{"/binary/pear", "", "", ""}, false},
|
||||
{"яблоко| 30*60", imgEntry{"/binary/яблоко", "30", "60", ""}, false},
|
||||
{"груша | 65 ", imgEntry{"/binary/груша", "65", "", ""}, false},
|
||||
{"жеронимо | 30 { full desc }", imgEntry{"/binary/жеронимо", "30", "", " full desc "}, false},
|
||||
{"жорно жованна | *5555 {partial description", imgEntry{"/binary/жорно_жованна", "", "5555", "partial description"}, true},
|
||||
{"иноске | {full}", imgEntry{"/binary/иноске", "", "", "full"}, false},
|
||||
{"j|{partial", imgEntry{"/binary/j", "", "", "partial"}, true},
|
||||
}
|
||||
for _, triplet := range tests {
|
||||
entry, followedByDesc := img.parseStartOfEntry(triplet.line)
|
||||
if entry != triplet.entry || followedByDesc != triplet.followedByDesc {
|
||||
t.Error(fmt.Sprintf("%q:%q != %q; %v != %v", triplet.line, entry, triplet.entry, followedByDesc, triplet.followedByDesc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDimensions(t *testing.T) {
|
||||
tests := [][]string{
|
||||
{"500", "500", ""},
|
||||
{"3em", "3em", ""},
|
||||
{"500*", "500", ""},
|
||||
{"*500", "", "500"},
|
||||
{"800*520", "800", "520"},
|
||||
{"17%*5rem", "17%", "5rem"},
|
||||
}
|
||||
for _, triplet := range tests {
|
||||
sizeH, sizeV := parseDimensions(triplet[0])
|
||||
if sizeH != triplet[1] || sizeV != triplet[2] {
|
||||
t.Error(sizeH, "*", sizeV, " != ", triplet[1], "*", triplet[2])
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
package markup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TODO: move test markup docs to files, perhaps? These strings sure are ugly
|
||||
func TestLex(t *testing.T) {
|
||||
check := func(name, content string, expectedAst []Line) {
|
||||
if ast := lex(name, content); !reflect.DeepEqual(ast, expectedAst) {
|
||||
if len(ast) != len(expectedAst) {
|
||||
t.Error("Expected and generated AST length of", name, "do not match. Printed generated AST.")
|
||||
for _, l := range ast {
|
||||
fmt.Printf("%d: %s\n", l.id, l.contents)
|
||||
}
|
||||
return
|
||||
}
|
||||
for i, e := range ast {
|
||||
if !reflect.DeepEqual(e, expectedAst[i]) {
|
||||
t.Error(fmt.Sprintf("Expected: %q\nGot:%q", expectedAst[i], e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
contentsB, err := ioutil.ReadFile("testdata/test.myco")
|
||||
if err != nil {
|
||||
t.Error("Could not read test markup file!")
|
||||
}
|
||||
contents := string(contentsB)
|
||||
check("Apple", contents, []Line{
|
||||
{1, "<h1 id='1'>1</h1>"},
|
||||
{2, "<h2 id='2'>2</h2>"},
|
||||
{3, "<h3 id='3'>3</h3>"},
|
||||
{4, "<blockquote id='4'>quote</blockquote>"},
|
||||
{5, `<ul id='5'>
|
||||
<li>li 1</li>
|
||||
<li>li 2</li>
|
||||
</ul>`},
|
||||
{6, "<p id='6'>text</p>"},
|
||||
{7, "<p id='7'>more text</p>"},
|
||||
{8, `<p><a id='8' class='rocketlink wikilink_internal' href="/page/pear">some link</a></p>`},
|
||||
{9, `<ul id='9'>
|
||||
<li>lin"+</li>
|
||||
</ul>`},
|
||||
{10, `<pre id='10' alt='alt text goes here' class='codeblock'><code>=> preformatted text
|
||||
where markup is not lexed</code></pre>`},
|
||||
{11, `<p><a id='11' class='rocketlink wikilink_internal' href="/page/linking">linking</a></p>`},
|
||||
{12, "<p id='12'>text</p>"},
|
||||
{13, `<pre id='13' alt='' class='codeblock'><code>()
|
||||
/\</code></pre>`},
|
||||
{14, Transclusion{"apple", 1, 3}},
|
||||
{15, Img{
|
||||
hyphaName: "Apple",
|
||||
inDesc: false,
|
||||
entries: []imgEntry{
|
||||
{"/binary/hypha1", "", "", ""},
|
||||
{"/binary/hypha2", "", "", ""},
|
||||
{"/binary/hypha3", "60", "", ""},
|
||||
{"/binary/hypha4", "", "", " line1\nline2\n"},
|
||||
{"/binary/hypha5", "", "", "\nstate of minnesota\n"},
|
||||
},
|
||||
}},
|
||||
})
|
||||
}
|
@ -13,6 +13,7 @@ import (
|
||||
// * Rocketlinks
|
||||
// * Transclusion
|
||||
// * Image galleries
|
||||
// Not needed anymore, I guess.
|
||||
func (md *MycoDoc) OutLinks() chan string {
|
||||
ch := make(chan string)
|
||||
if !md.parsedAlready {
|
||||
|
@ -1,50 +0,0 @@
|
||||
package markup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
/*
|
||||
func TestGetTextNode(t *testing.T) {
|
||||
tests := [][]string{
|
||||
// input textNode rest
|
||||
{"barab", "barab", ""},
|
||||
{"test, ", "test", ", "},
|
||||
{"/test/", "", "/test/"},
|
||||
{"\\/test/", "/test", "/"},
|
||||
{"test \\/ar", "test /ar", ""},
|
||||
{"test //italian// test", "test ", "//italian// test"},
|
||||
}
|
||||
for _, triplet := range tests {
|
||||
a, b := getTextNode([]byte(triplet[0]))
|
||||
if a != triplet[1] || string(b) != triplet[2] {
|
||||
t.Error(fmt.Sprintf("Wanted: %q\nGot: %q %q", triplet, a, b))
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func TestParagraphToHtml(t *testing.T) {
|
||||
tests := [][]string{
|
||||
{"a simple paragraph", "a simple paragraph"},
|
||||
{"//italic//", "<em>italic</em>"},
|
||||
{"Embedded //italic//", "Embedded <em>italic</em>"},
|
||||
{"double //italian// //text//", "double <em>italian</em> <em>text</em>"},
|
||||
{"it has `mono`", "it has <code>mono</code>"},
|
||||
{"it has ~~strike~~", "it has <s>strike</s>"},
|
||||
{"this is a left **bold", "this is a left <strong>bold</strong>"},
|
||||
{"this line has a ,comma, two of them", "this line has a ,comma, two of them"},
|
||||
{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},
|
||||
{"A [[simple]] link", `A <a href="/page/simple" class="wikilink_internal">simple</a> link`},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if ParagraphToHtml("Apple", test[0]) != test[1] {
|
||||
t.Error(fmt.Sprintf("%q: Wanted %q, got %q", test[0], test[1], ParagraphToHtml("Apple", test[0])))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
HyphaExists = func(_ string) bool { return true }
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package markup
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseTransclusion(t *testing.T) {
|
||||
check := func(line string, expectedXclusion Transclusion) {
|
||||
if xcl := parseTransclusion(line, "t"); xcl != expectedXclusion {
|
||||
t.Error(line, "; got:", xcl, "wanted:", expectedXclusion)
|
||||
}
|
||||
}
|
||||
check("<= ", Transclusion{"", -9, -9})
|
||||
check("<=hypha", Transclusion{"hypha", 0, 0})
|
||||
check("<= hypha\t", Transclusion{"hypha", 0, 0})
|
||||
check("<= hypha :", Transclusion{"hypha", 0, 0})
|
||||
check("<= hypha : ..", Transclusion{"hypha", 0, 0})
|
||||
check("<= hypha : 3", Transclusion{"hypha", 3, 3})
|
||||
check("<= hypha : 3..", Transclusion{"hypha", 3, 0})
|
||||
check("<= hypha : ..3", Transclusion{"hypha", 0, 3})
|
||||
check("<= hypha : 3..4", Transclusion{"hypha", 3, 4})
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit e7040f3e0dc41809063b77fcbc12fe33b234ea87
|
||||
Subproject commit 133b2689fbd67cad274f1c10fc6cbe8adbfc156a
|
@ -1,36 +0,0 @@
|
||||
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())
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ func canFactory(
|
||||
dispatcher func(*hyphae.Hypha, *user.User) (string, string),
|
||||
noRightsMsg string,
|
||||
notExistsMsg string,
|
||||
careAboutExistince bool,
|
||||
careAboutExistence bool,
|
||||
) func(*user.User, *hyphae.Hypha) (error, string) {
|
||||
return func(u *user.User, h *hyphae.Hypha) (error, string) {
|
||||
if !u.CanProceed(action) {
|
||||
@ -21,7 +21,7 @@ func canFactory(
|
||||
return errors.New(noRightsMsg), "Not enough rights"
|
||||
}
|
||||
|
||||
if careAboutExistince && !h.Exists {
|
||||
if careAboutExistence && !h.Exists {
|
||||
rejectLogger(h, u, "does not exist")
|
||||
return errors.New(notExistsMsg), "Does not exist"
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
@ -62,10 +63,10 @@ func readTokensToUsers() {
|
||||
func tokenStoragePath() string {
|
||||
dir, err := xdg.DataFile("mycorrhiza/tokens.json")
|
||||
if err != nil {
|
||||
// Yes, it is unix-only, but function above rarely fails, so this block is probably never reached.
|
||||
path := "/home/" + os.Getenv("HOME") + "/.local/share/mycorrhiza/tokens.json"
|
||||
os.MkdirAll(path, 0777)
|
||||
return path
|
||||
log.Fatal(err)
|
||||
}
|
||||
if strings.HasPrefix(dir, util.WikiDir) {
|
||||
log.Fatal("Error: Wiki storage directory includes private config files")
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ var minimalRights = map[string]int{
|
||||
"delete-ask": 3,
|
||||
"delete-confirm": 3,
|
||||
"reindex": 4,
|
||||
"admin": 4,
|
||||
"admin/shutdown": 4,
|
||||
}
|
||||
|
||||
|
@ -66,10 +66,10 @@ func userByName(username string) *User {
|
||||
|
||||
func commenceSession(username, token string) {
|
||||
tokens.Store(token, username)
|
||||
go dumpTokens()
|
||||
dumpTokens()
|
||||
}
|
||||
|
||||
func terminateSession(token string) {
|
||||
tokens.Delete(token)
|
||||
go dumpTokens()
|
||||
dumpTokens()
|
||||
}
|
||||
|
73
util/config.go
Normal file
73
util/config.go
Normal file
@ -0,0 +1,73 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-ini/ini"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
WikiName string
|
||||
NaviTitleIcon string
|
||||
Hyphae
|
||||
Network
|
||||
Authorization
|
||||
}
|
||||
|
||||
type Hyphae struct {
|
||||
HomeHypha string
|
||||
UserHypha string
|
||||
HeaderLinksHypha string
|
||||
}
|
||||
|
||||
type Network struct {
|
||||
HTTPPort uint64
|
||||
URL string
|
||||
GeminiCertificatePath string
|
||||
}
|
||||
|
||||
type Authorization struct {
|
||||
UseFixedAuth bool
|
||||
FixedAuthCredentialsPath string
|
||||
}
|
||||
|
||||
func ReadConfigFile(path string) {
|
||||
cfg := &Config{
|
||||
WikiName: "MycorrhizaWiki",
|
||||
NaviTitleIcon: "🍄",
|
||||
Hyphae: Hyphae{
|
||||
HomeHypha: "home",
|
||||
UserHypha: "u",
|
||||
HeaderLinksHypha: "",
|
||||
},
|
||||
Network: Network{
|
||||
HTTPPort: 1737,
|
||||
URL: "",
|
||||
GeminiCertificatePath: "",
|
||||
},
|
||||
Authorization: Authorization{
|
||||
UseFixedAuth: false,
|
||||
FixedAuthCredentialsPath: "",
|
||||
},
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
log.Println("Loading config at", path)
|
||||
err := ini.MapTo(cfg, path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
SiteName = cfg.WikiName
|
||||
SiteNavIcon = cfg.NaviTitleIcon
|
||||
HomePage = cfg.HomeHypha
|
||||
UserHypha = cfg.UserHypha
|
||||
HeaderLinksHypha = cfg.HeaderLinksHypha
|
||||
ServerPort = strconv.FormatUint(cfg.HTTPPort, 10)
|
||||
URL = cfg.URL
|
||||
GeminiCertPath = cfg.GeminiCertificatePath
|
||||
UseFixedAuth = cfg.UseFixedAuth
|
||||
FixedCredentialsPath = cfg.FixedAuthCredentialsPath
|
||||
}
|
33
util/util.go
33
util/util.go
@ -9,18 +9,24 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// TODO: make names match to fields of config file
|
||||
var (
|
||||
URL string
|
||||
ServerPort string
|
||||
HomePage string
|
||||
SiteNavIcon string
|
||||
SiteName string
|
||||
WikiDir string
|
||||
UserHypha string
|
||||
HeaderLinksHypha string
|
||||
AuthMethod string
|
||||
SiteName string
|
||||
SiteNavIcon string
|
||||
|
||||
HomePage string
|
||||
UserHypha string
|
||||
HeaderLinksHypha string
|
||||
|
||||
ServerPort string
|
||||
URL string
|
||||
GeminiCertPath string
|
||||
|
||||
UseFixedAuth bool
|
||||
FixedCredentialsPath string
|
||||
GeminiCertPath string
|
||||
|
||||
WikiDir string
|
||||
ConfigFilePath string
|
||||
)
|
||||
|
||||
// LettersNumbersOnly keeps letters and numbers only in the given string.
|
||||
@ -53,6 +59,13 @@ func ShorterPath(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
// HTTP404Page writes a 404 error in the status, needed when no content is found on the page.
|
||||
func HTTP404Page(w http.ResponseWriter, page string) {
|
||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(page))
|
||||
}
|
||||
|
||||
// HTTP200Page wraps some frequently used things for successful 200 responses.
|
||||
func HTTP200Page(w http.ResponseWriter, page string) {
|
||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||
|
@ -1,8 +1,29 @@
|
||||
{% import "net/http" %}
|
||||
|
||||
{% import "github.com/bouncepaw/mycorrhiza/util" %}
|
||||
{% import "github.com/bouncepaw/mycorrhiza/user" %}
|
||||
{% import "github.com/bouncepaw/mycorrhiza/hyphae" %}
|
||||
{% import "github.com/bouncepaw/mycorrhiza/history" %}
|
||||
|
||||
|
||||
{% func PrimitiveDiffHTML(rq *http.Request, h *hyphae.Hypha, u *user.User, hash string) %}
|
||||
{% code
|
||||
text, err := history.PrimitiveDiffAtRevision(h.TextPath, hash)
|
||||
if err != nil {
|
||||
text = err.Error()
|
||||
}
|
||||
%}
|
||||
{%= NavHTML(rq, h.Name, "history") %}
|
||||
<div class="layout">
|
||||
<main class="main-width">
|
||||
<article>
|
||||
<h1>Diff {%s util.BeautifulName(h.Name) %} at {%s hash %}</h1>
|
||||
<pre class="codeblock"><code>{%s text %}</code></pre>
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
{% endfunc %}
|
||||
|
||||
{% func RecentChangesHTML(n int) %}
|
||||
<div class="layout">
|
||||
<main class="main-width recent-changes">
|
||||
@ -27,7 +48,7 @@
|
||||
<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>
|
||||
|
||||
{% comment %}
|
||||
Here I am, willing to add some accesibility using ARIA. Turns out,
|
||||
Here I am, willing to add some accessibility using ARIA. Turns out,
|
||||
role="feed" is not supported in any screen reader as of September
|
||||
2020. At least web search says so. Even JAWS doesn't support it!
|
||||
How come? I'll add the role anyway. -- bouncepaw
|
||||
|
@ -11,24 +11,98 @@ import "net/http"
|
||||
import "github.com/bouncepaw/mycorrhiza/util"
|
||||
|
||||
//line views/history.qtpl:4
|
||||
import "github.com/bouncepaw/mycorrhiza/history"
|
||||
import "github.com/bouncepaw/mycorrhiza/user"
|
||||
|
||||
//line views/history.qtpl:5
|
||||
import "github.com/bouncepaw/mycorrhiza/hyphae"
|
||||
|
||||
//line views/history.qtpl:6
|
||||
import "github.com/bouncepaw/mycorrhiza/history"
|
||||
|
||||
//line views/history.qtpl:9
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line views/history.qtpl:6
|
||||
//line views/history.qtpl:9
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line views/history.qtpl:6
|
||||
//line views/history.qtpl:9
|
||||
func StreamPrimitiveDiffHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User, hash string) {
|
||||
//line views/history.qtpl:9
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line views/history.qtpl:11
|
||||
text, err := history.PrimitiveDiffAtRevision(h.TextPath, hash)
|
||||
if err != nil {
|
||||
text = err.Error()
|
||||
}
|
||||
|
||||
//line views/history.qtpl:15
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line views/history.qtpl:16
|
||||
StreamNavHTML(qw422016, rq, h.Name, "history")
|
||||
//line views/history.qtpl:16
|
||||
qw422016.N().S(`
|
||||
<div class="layout">
|
||||
<main class="main-width">
|
||||
<article>
|
||||
<h1>Diff `)
|
||||
//line views/history.qtpl:20
|
||||
qw422016.E().S(util.BeautifulName(h.Name))
|
||||
//line views/history.qtpl:20
|
||||
qw422016.N().S(` at `)
|
||||
//line views/history.qtpl:20
|
||||
qw422016.E().S(hash)
|
||||
//line views/history.qtpl:20
|
||||
qw422016.N().S(`</h1>
|
||||
<pre class="codeblock"><code>`)
|
||||
//line views/history.qtpl:21
|
||||
qw422016.E().S(text)
|
||||
//line views/history.qtpl:21
|
||||
qw422016.N().S(`</code></pre>
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
`)
|
||||
//line views/history.qtpl:25
|
||||
}
|
||||
|
||||
//line views/history.qtpl:25
|
||||
func WritePrimitiveDiffHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, u *user.User, hash string) {
|
||||
//line views/history.qtpl:25
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line views/history.qtpl:25
|
||||
StreamPrimitiveDiffHTML(qw422016, rq, h, u, hash)
|
||||
//line views/history.qtpl:25
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line views/history.qtpl:25
|
||||
}
|
||||
|
||||
//line views/history.qtpl:25
|
||||
func PrimitiveDiffHTML(rq *http.Request, h *hyphae.Hypha, u *user.User, hash string) string {
|
||||
//line views/history.qtpl:25
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line views/history.qtpl:25
|
||||
WritePrimitiveDiffHTML(qb422016, rq, h, u, hash)
|
||||
//line views/history.qtpl:25
|
||||
qs422016 := string(qb422016.B)
|
||||
//line views/history.qtpl:25
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line views/history.qtpl:25
|
||||
return qs422016
|
||||
//line views/history.qtpl:25
|
||||
}
|
||||
|
||||
//line views/history.qtpl:27
|
||||
func StreamRecentChangesHTML(qw422016 *qt422016.Writer, n int) {
|
||||
//line views/history.qtpl:6
|
||||
//line views/history.qtpl:27
|
||||
qw422016.N().S(`
|
||||
<div class="layout">
|
||||
<main class="main-width recent-changes">
|
||||
@ -38,51 +112,51 @@ func StreamRecentChangesHTML(qw422016 *qt422016.Writer, n int) {
|
||||
<nav class="recent-changes__count">
|
||||
See
|
||||
`)
|
||||
//line views/history.qtpl:14
|
||||
//line views/history.qtpl:35
|
||||
for _, m := range []int{20, 0, 50, 0, 100} {
|
||||
//line views/history.qtpl:14
|
||||
//line views/history.qtpl:35
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line views/history.qtpl:15
|
||||
//line views/history.qtpl:36
|
||||
switch m {
|
||||
//line views/history.qtpl:16
|
||||
//line views/history.qtpl:37
|
||||
case 0:
|
||||
//line views/history.qtpl:16
|
||||
//line views/history.qtpl:37
|
||||
qw422016.N().S(`
|
||||
<span aria-hidden="true">|</span>
|
||||
`)
|
||||
//line views/history.qtpl:18
|
||||
//line views/history.qtpl:39
|
||||
case n:
|
||||
//line views/history.qtpl:18
|
||||
//line views/history.qtpl:39
|
||||
qw422016.N().S(`
|
||||
<b>`)
|
||||
//line views/history.qtpl:19
|
||||
//line views/history.qtpl:40
|
||||
qw422016.N().D(n)
|
||||
//line views/history.qtpl:19
|
||||
//line views/history.qtpl:40
|
||||
qw422016.N().S(`</b>
|
||||
`)
|
||||
//line views/history.qtpl:20
|
||||
//line views/history.qtpl:41
|
||||
default:
|
||||
//line views/history.qtpl:20
|
||||
//line views/history.qtpl:41
|
||||
qw422016.N().S(`
|
||||
<a href="/recent-changes/`)
|
||||
//line views/history.qtpl:21
|
||||
//line views/history.qtpl:42
|
||||
qw422016.N().D(m)
|
||||
//line views/history.qtpl:21
|
||||
//line views/history.qtpl:42
|
||||
qw422016.N().S(`">`)
|
||||
//line views/history.qtpl:21
|
||||
//line views/history.qtpl:42
|
||||
qw422016.N().D(m)
|
||||
//line views/history.qtpl:21
|
||||
//line views/history.qtpl:42
|
||||
qw422016.N().S(`</a>
|
||||
`)
|
||||
//line views/history.qtpl:22
|
||||
//line views/history.qtpl:43
|
||||
}
|
||||
//line views/history.qtpl:22
|
||||
//line views/history.qtpl:43
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line views/history.qtpl:23
|
||||
//line views/history.qtpl:44
|
||||
}
|
||||
//line views/history.qtpl:23
|
||||
//line views/history.qtpl:44
|
||||
qw422016.N().S(`
|
||||
recent changes
|
||||
</nav>
|
||||
@ -90,216 +164,216 @@ func StreamRecentChangesHTML(qw422016 *qt422016.Writer, n int) {
|
||||
<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
|
||||
//line views/history.qtpl:55
|
||||
qw422016.N().S(`
|
||||
|
||||
`)
|
||||
//line views/history.qtpl:37
|
||||
//line views/history.qtpl:58
|
||||
changes := history.RecentChanges(n)
|
||||
|
||||
//line views/history.qtpl:38
|
||||
//line views/history.qtpl:59
|
||||
qw422016.N().S(`
|
||||
<section class="recent-changes__list" role="feed">
|
||||
`)
|
||||
//line views/history.qtpl:40
|
||||
//line views/history.qtpl:61
|
||||
if len(changes) == 0 {
|
||||
//line views/history.qtpl:40
|
||||
//line views/history.qtpl:61
|
||||
qw422016.N().S(`
|
||||
<p>Could not find any recent changes.</p>
|
||||
`)
|
||||
//line views/history.qtpl:42
|
||||
//line views/history.qtpl:63
|
||||
} else {
|
||||
//line views/history.qtpl:42
|
||||
//line views/history.qtpl:63
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line views/history.qtpl:43
|
||||
//line views/history.qtpl:64
|
||||
for i, entry := range changes {
|
||||
//line views/history.qtpl:43
|
||||
//line views/history.qtpl:64
|
||||
qw422016.N().S(`
|
||||
<ul class="recent-changes__entry rc-entry" role="article"
|
||||
aria-setsize="`)
|
||||
//line views/history.qtpl:45
|
||||
//line views/history.qtpl:66
|
||||
qw422016.N().D(n)
|
||||
//line views/history.qtpl:45
|
||||
//line views/history.qtpl:66
|
||||
qw422016.N().S(`" aria-posinset="`)
|
||||
//line views/history.qtpl:45
|
||||
//line views/history.qtpl:66
|
||||
qw422016.N().D(i)
|
||||
//line views/history.qtpl:45
|
||||
//line views/history.qtpl:66
|
||||
qw422016.N().S(`">
|
||||
`)
|
||||
//line views/history.qtpl:46
|
||||
//line views/history.qtpl:67
|
||||
qw422016.N().S(recentChangesEntry(entry))
|
||||
//line views/history.qtpl:46
|
||||
//line views/history.qtpl:67
|
||||
qw422016.N().S(`
|
||||
</ul>
|
||||
`)
|
||||
//line views/history.qtpl:48
|
||||
//line views/history.qtpl:69
|
||||
}
|
||||
//line views/history.qtpl:48
|
||||
//line views/history.qtpl:69
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line views/history.qtpl:49
|
||||
//line views/history.qtpl:70
|
||||
}
|
||||
//line views/history.qtpl:49
|
||||
//line views/history.qtpl:70
|
||||
qw422016.N().S(`
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
`)
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
}
|
||||
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
func WriteRecentChangesHTML(qq422016 qtio422016.Writer, n int) {
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
StreamRecentChangesHTML(qw422016, n)
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
}
|
||||
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
func RecentChangesHTML(n int) string {
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
WriteRecentChangesHTML(qb422016, n)
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
qs422016 := string(qb422016.B)
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
return qs422016
|
||||
//line views/history.qtpl:53
|
||||
//line views/history.qtpl:74
|
||||
}
|
||||
|
||||
//line views/history.qtpl:55
|
||||
//line views/history.qtpl:76
|
||||
func streamrecentChangesEntry(qw422016 *qt422016.Writer, rev history.Revision) {
|
||||
//line views/history.qtpl:55
|
||||
//line views/history.qtpl:76
|
||||
qw422016.N().S(`
|
||||
<li class="rc-entry__time"><time>`)
|
||||
//line views/history.qtpl:56
|
||||
//line views/history.qtpl:77
|
||||
qw422016.E().S(rev.TimeString())
|
||||
//line views/history.qtpl:56
|
||||
//line views/history.qtpl:77
|
||||
qw422016.N().S(`</time></li>
|
||||
<li class="rc-entry__hash">`)
|
||||
//line views/history.qtpl:57
|
||||
//line views/history.qtpl:78
|
||||
qw422016.E().S(rev.Hash)
|
||||
//line views/history.qtpl:57
|
||||
//line views/history.qtpl:78
|
||||
qw422016.N().S(`</li>
|
||||
<li class="rc-entry__links">`)
|
||||
//line views/history.qtpl:58
|
||||
//line views/history.qtpl:79
|
||||
qw422016.N().S(rev.HyphaeLinksHTML())
|
||||
//line views/history.qtpl:58
|
||||
//line views/history.qtpl:79
|
||||
qw422016.N().S(`</li>
|
||||
<li class="rc-entry__msg">`)
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
qw422016.E().S(rev.Message)
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
qw422016.N().S(` `)
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
if rev.Username != "anon" {
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
qw422016.N().S(`<span class="rc-entry__author">by <a href="/hypha/`)
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
qw422016.E().S(util.UserHypha)
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
qw422016.N().S(`/`)
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
qw422016.E().S(rev.Username)
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
qw422016.N().S(`" rel="author">`)
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
qw422016.E().S(rev.Username)
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
qw422016.N().S(`</a></span>`)
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
}
|
||||
//line views/history.qtpl:59
|
||||
//line views/history.qtpl:80
|
||||
qw422016.N().S(`</li>
|
||||
`)
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
}
|
||||
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
func writerecentChangesEntry(qq422016 qtio422016.Writer, rev history.Revision) {
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
streamrecentChangesEntry(qw422016, rev)
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
}
|
||||
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
func recentChangesEntry(rev history.Revision) string {
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
writerecentChangesEntry(qb422016, rev)
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
qs422016 := string(qb422016.B)
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
return qs422016
|
||||
//line views/history.qtpl:60
|
||||
//line views/history.qtpl:81
|
||||
}
|
||||
|
||||
//line views/history.qtpl:62
|
||||
//line views/history.qtpl:83
|
||||
func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, list string) {
|
||||
//line views/history.qtpl:62
|
||||
//line views/history.qtpl:83
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line views/history.qtpl:63
|
||||
//line views/history.qtpl:84
|
||||
StreamNavHTML(qw422016, rq, hyphaName, "history")
|
||||
//line views/history.qtpl:63
|
||||
//line views/history.qtpl:84
|
||||
qw422016.N().S(`
|
||||
<div class="layout">
|
||||
<main class="main-width">
|
||||
<article class="history">
|
||||
<h1>History of `)
|
||||
//line views/history.qtpl:67
|
||||
//line views/history.qtpl:88
|
||||
qw422016.E().S(util.BeautifulName(hyphaName))
|
||||
//line views/history.qtpl:67
|
||||
//line views/history.qtpl:88
|
||||
qw422016.N().S(`</h1>
|
||||
`)
|
||||
//line views/history.qtpl:68
|
||||
//line views/history.qtpl:89
|
||||
qw422016.N().S(list)
|
||||
//line views/history.qtpl:68
|
||||
//line views/history.qtpl:89
|
||||
qw422016.N().S(`
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
`)
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
}
|
||||
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, list string) {
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
StreamHistoryHTML(qw422016, rq, hyphaName, list)
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
}
|
||||
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
func HistoryHTML(rq *http.Request, hyphaName, list string) string {
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
WriteHistoryHTML(qb422016, rq, hyphaName, list)
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
qs422016 := string(qb422016.B)
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
return qs422016
|
||||
//line views/history.qtpl:72
|
||||
//line views/history.qtpl:93
|
||||
}
|
||||
|
@ -32,23 +32,6 @@
|
||||
</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) %}
|
||||
|
||||
|
@ -123,175 +123,111 @@ func NaviTitleHTML(h *hyphae.Hypha) string {
|
||||
}
|
||||
|
||||
//line views/hypha.qtpl:35
|
||||
func StreamBackLinksHTML(qw422016 *qt422016.Writer, h *hyphae.Hypha) {
|
||||
func StreamAttachmentHTML(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
|
||||
//line views/hypha.qtpl:36
|
||||
switch filepath.Ext(h.BinaryPath) {
|
||||
//line views/hypha.qtpl:55
|
||||
//line views/hypha.qtpl:38
|
||||
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
|
||||
//line views/hypha.qtpl:55
|
||||
//line views/hypha.qtpl:38
|
||||
qw422016.N().S(`
|
||||
<div class="binary-container binary-container_with-img">
|
||||
<a href="/binary/`)
|
||||
//line views/hypha.qtpl:57
|
||||
//line views/hypha.qtpl:40
|
||||
qw422016.N().S(h.Name)
|
||||
//line views/hypha.qtpl:57
|
||||
//line views/hypha.qtpl:40
|
||||
qw422016.N().S(`"><img src="/binary/`)
|
||||
//line views/hypha.qtpl:57
|
||||
//line views/hypha.qtpl:40
|
||||
qw422016.N().S(h.Name)
|
||||
//line views/hypha.qtpl:57
|
||||
//line views/hypha.qtpl:40
|
||||
qw422016.N().S(`"/></a>
|
||||
</div>
|
||||
|
||||
`)
|
||||
//line views/hypha.qtpl:60
|
||||
//line views/hypha.qtpl:43
|
||||
case ".ogg", ".webm", ".mp4":
|
||||
//line views/hypha.qtpl:60
|
||||
//line views/hypha.qtpl:43
|
||||
qw422016.N().S(`
|
||||
<div class="binary-container binary-container_with-video">
|
||||
<video controls>
|
||||
<source src="/binary/`)
|
||||
//line views/hypha.qtpl:63
|
||||
//line views/hypha.qtpl:46
|
||||
qw422016.N().S(h.Name)
|
||||
//line views/hypha.qtpl:63
|
||||
//line views/hypha.qtpl:46
|
||||
qw422016.N().S(`"/>
|
||||
<p>Your browser does not support video. <a href="/binary/`)
|
||||
//line views/hypha.qtpl:64
|
||||
//line views/hypha.qtpl:47
|
||||
qw422016.N().S(h.Name)
|
||||
//line views/hypha.qtpl:64
|
||||
//line views/hypha.qtpl:47
|
||||
qw422016.N().S(`">Download video</a></p>
|
||||
</video>
|
||||
</div>
|
||||
|
||||
`)
|
||||
//line views/hypha.qtpl:68
|
||||
//line views/hypha.qtpl:51
|
||||
case ".mp3":
|
||||
//line views/hypha.qtpl:68
|
||||
//line views/hypha.qtpl:51
|
||||
qw422016.N().S(`
|
||||
<div class="binary-container binary-container_with-audio">
|
||||
<audio controls>
|
||||
<source src="/binary/`)
|
||||
//line views/hypha.qtpl:71
|
||||
//line views/hypha.qtpl:54
|
||||
qw422016.N().S(h.Name)
|
||||
//line views/hypha.qtpl:71
|
||||
//line views/hypha.qtpl:54
|
||||
qw422016.N().S(`"/>
|
||||
<p>Your browser does not support audio. <a href="/binary/`)
|
||||
//line views/hypha.qtpl:72
|
||||
//line views/hypha.qtpl:55
|
||||
qw422016.N().S(h.Name)
|
||||
//line views/hypha.qtpl:72
|
||||
//line views/hypha.qtpl:55
|
||||
qw422016.N().S(`">Download audio</a></p>
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
`)
|
||||
//line views/hypha.qtpl:76
|
||||
//line views/hypha.qtpl:59
|
||||
default:
|
||||
//line views/hypha.qtpl:76
|
||||
//line views/hypha.qtpl:59
|
||||
qw422016.N().S(`
|
||||
<div class="binary-container binary-container_with-nothing">
|
||||
<p><a href="/binary/`)
|
||||
//line views/hypha.qtpl:78
|
||||
//line views/hypha.qtpl:61
|
||||
qw422016.N().S(h.Name)
|
||||
//line views/hypha.qtpl:78
|
||||
//line views/hypha.qtpl:61
|
||||
qw422016.N().S(`">Download media</a></p>
|
||||
</div>
|
||||
`)
|
||||
//line views/hypha.qtpl:80
|
||||
//line views/hypha.qtpl:63
|
||||
}
|
||||
//line views/hypha.qtpl:80
|
||||
//line views/hypha.qtpl:63
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
}
|
||||
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
func WriteAttachmentHTML(qq422016 qtio422016.Writer, h *hyphae.Hypha) {
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
StreamAttachmentHTML(qw422016, h)
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
}
|
||||
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
func AttachmentHTML(h *hyphae.Hypha) string {
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
WriteAttachmentHTML(qb422016, h)
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
qs422016 := string(qb422016.B)
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
return qs422016
|
||||
//line views/hypha.qtpl:81
|
||||
//line views/hypha.qtpl:64
|
||||
}
|
||||
|
@ -34,7 +34,7 @@
|
||||
<a href="/page/{%s hyphaName %}" 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">{%s= renderedPage %}</section>
|
||||
<article class="edit__preview">{%s= renderedPage %}</article>
|
||||
</main>
|
||||
</div>
|
||||
{% endfunc %}
|
||||
|
@ -138,11 +138,11 @@ func StreamPreviewHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, t
|
||||
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">`)
|
||||
<article class="edit__preview">`)
|
||||
//line views/mutators.qtpl:37
|
||||
qw422016.N().S(renderedPage)
|
||||
//line views/mutators.qtpl:37
|
||||
qw422016.N().S(`</section>
|
||||
qw422016.N().S(`</article>
|
||||
</main>
|
||||
</div>
|
||||
`)
|
||||
|
@ -108,7 +108,6 @@ If `contents` == "", a helpful message is shown instead.
|
||||
{%= SubhyphaeHTML(subhyphae) %}
|
||||
</main>
|
||||
{%= RelativeHyphaeHTML(relatives) %}
|
||||
{%= BackLinksHTML(h) %}
|
||||
</div>
|
||||
{% endfunc %}
|
||||
|
||||
|
@ -345,110 +345,105 @@ func StreamHyphaHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hyph
|
||||
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:112
|
||||
}
|
||||
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
func WriteHyphaHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, contents string) {
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
StreamHyphaHTML(qw422016, rq, h, contents)
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
}
|
||||
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
func HyphaHTML(rq *http.Request, h *hyphae.Hypha, contents string) string {
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
WriteHyphaHTML(qb422016, rq, h, contents)
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
qs422016 := string(qb422016.B)
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
return qs422016
|
||||
//line views/readers.qtpl:113
|
||||
//line views/readers.qtpl:112
|
||||
}
|
||||
|
||||
//line views/readers.qtpl:115
|
||||
//line views/readers.qtpl:114
|
||||
func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, h *hyphae.Hypha, contents, revHash string) {
|
||||
//line views/readers.qtpl:115
|
||||
//line views/readers.qtpl:114
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line views/readers.qtpl:117
|
||||
//line views/readers.qtpl:116
|
||||
relatives, subhyphae, _, _ := tree.Tree(h.Name)
|
||||
|
||||
//line views/readers.qtpl:118
|
||||
//line views/readers.qtpl:117
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line views/readers.qtpl:119
|
||||
//line views/readers.qtpl:118
|
||||
StreamNavHTML(qw422016, rq, h.Name, "revision", revHash)
|
||||
//line views/readers.qtpl:119
|
||||
//line views/readers.qtpl:118
|
||||
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
|
||||
//line views/readers.qtpl:123
|
||||
qw422016.N().S(NaviTitleHTML(h))
|
||||
//line views/readers.qtpl:124
|
||||
//line views/readers.qtpl:123
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line views/readers.qtpl:125
|
||||
//line views/readers.qtpl:124
|
||||
qw422016.N().S(contents)
|
||||
//line views/readers.qtpl:125
|
||||
//line views/readers.qtpl:124
|
||||
qw422016.N().S(`
|
||||
</article>
|
||||
`)
|
||||
//line views/readers.qtpl:127
|
||||
//line views/readers.qtpl:126
|
||||
StreamSubhyphaeHTML(qw422016, subhyphae)
|
||||
//line views/readers.qtpl:127
|
||||
//line views/readers.qtpl:126
|
||||
qw422016.N().S(`
|
||||
</main>
|
||||
`)
|
||||
//line views/readers.qtpl:129
|
||||
//line views/readers.qtpl:128
|
||||
StreamRelativeHyphaeHTML(qw422016, relatives)
|
||||
//line views/readers.qtpl:129
|
||||
//line views/readers.qtpl:128
|
||||
qw422016.N().S(`
|
||||
</div>
|
||||
`)
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
}
|
||||
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, h *hyphae.Hypha, contents, revHash string) {
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
StreamRevisionHTML(qw422016, rq, h, contents, revHash)
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
}
|
||||
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
func RevisionHTML(rq *http.Request, h *hyphae.Hypha, contents, revHash string) string {
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
WriteRevisionHTML(qb422016, rq, h, contents, revHash)
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
qs422016 := string(qb422016.B)
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
return qs422016
|
||||
//line views/readers.qtpl:131
|
||||
//line views/readers.qtpl:130
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ for u := range user.YieldUsers() {
|
||||
<section>
|
||||
<h1>About {%s util.SiteName %}</h1>
|
||||
<ul>
|
||||
<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> β 0.13</li>
|
||||
<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> 1.0.0</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>
|
||||
@ -118,7 +118,7 @@ for u := range user.YieldUsers() {
|
||||
{% func AdminPanelHTML() %}
|
||||
<div class="layout">
|
||||
<main class="main-width">
|
||||
<h1>Admininstrative functions</h1>
|
||||
<h1>Administrative functions</h1>
|
||||
<section>
|
||||
<h2>Safe things</h2>
|
||||
<ul>
|
||||
@ -141,6 +141,12 @@ for u := range user.YieldUsers() {
|
||||
<input type="submit">
|
||||
</fieldset>
|
||||
</form>
|
||||
<form action="/admin/reindex-users" method="POST" style="float:left">
|
||||
<fieldset>
|
||||
<legend>Reindex users</legend>
|
||||
<input type="submit">
|
||||
</fieldset>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
@ -352,7 +352,7 @@ func StreamAboutHTML(qw422016 *qt422016.Writer) {
|
||||
//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>
|
||||
<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> β 0.13</li>
|
||||
`)
|
||||
//line views/stuff.qtpl:101
|
||||
if user.AuthUsed {
|
||||
@ -445,7 +445,7 @@ func StreamAdminPanelHTML(qw422016 *qt422016.Writer) {
|
||||
qw422016.N().S(`
|
||||
<div class="layout">
|
||||
<main class="main-width">
|
||||
<h1>Admininstrative functions</h1>
|
||||
<h1>Administrative functions</h1>
|
||||
<section>
|
||||
<h2>Safe things</h2>
|
||||
<ul>
|
||||
@ -468,35 +468,41 @@ func StreamAdminPanelHTML(qw422016 *qt422016.Writer) {
|
||||
<input type="submit">
|
||||
</fieldset>
|
||||
</form>
|
||||
<form action="/admin/reindex-users" method="POST" style="float:left">
|
||||
<fieldset>
|
||||
<legend>Reindex users</legend>
|
||||
<input type="submit">
|
||||
</fieldset>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
`)
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
}
|
||||
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
func WriteAdminPanelHTML(qq422016 qtio422016.Writer) {
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
StreamAdminPanelHTML(qw422016)
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
}
|
||||
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
func AdminPanelHTML() string {
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
WriteAdminPanelHTML(qb422016)
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
qs422016 := string(qb422016.B)
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
return qs422016
|
||||
//line views/stuff.qtpl:147
|
||||
//line views/stuff.qtpl:153
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user