mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2024-12-12 05:20:26 +00:00
commit
2a5d7d580c
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
|||||||
hypha
|
|
||||||
mycorrhiza
|
mycorrhiza
|
||||||
|
35
README.md
35
README.md
@ -1,7 +1,10 @@
|
|||||||
# 🍄 MycorrhizaWiki 0.11
|
# 🍄 MycorrhizaWiki 0.12
|
||||||
A wiki engine.
|
A wiki engine.
|
||||||
|
|
||||||
|
[Main wiki](https://mycorrhiza.lesarbr.es)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
Also see [detailed instructions](https://mycorrhiza.lesarbr.es/page/deploy) on wiki.
|
||||||
```sh
|
```sh
|
||||||
git clone --recurse-submodules https://github.com/bouncepaw/mycorrhiza
|
git clone --recurse-submodules https://github.com/bouncepaw/mycorrhiza
|
||||||
cd mycorrhiza
|
cd mycorrhiza
|
||||||
@ -22,36 +25,40 @@ Options:
|
|||||||
What auth method to use. Variants: "none", "fixed" (default "none")
|
What auth method to use. Variants: "none", "fixed" (default "none")
|
||||||
-fixed-credentials-path string
|
-fixed-credentials-path string
|
||||||
Used when -auth-method=fixed. Path to file with user credentials. (default "mycocredentials.json")
|
Used when -auth-method=fixed. Path to file with user credentials. (default "mycocredentials.json")
|
||||||
|
-header-links-hypha string
|
||||||
|
Optional hypha that overrides the header links
|
||||||
-home string
|
-home string
|
||||||
The home page (default "home")
|
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 string
|
||||||
Port to serve the wiki at (default "1737")
|
Port to serve the wiki at (default "1737")
|
||||||
-title string
|
-url string
|
||||||
How to call your wiki in the navititle (default "🍄")
|
URL at which your wiki can be found. Used to generate feeds (default "http://0.0.0.0:$port")
|
||||||
-user-tree string
|
-user-hypha string
|
||||||
Hypha which is a superhypha of all user pages (default "u")
|
Hypha which is a superhypha of all user pages (default "u")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* Edit pages through html forms
|
* Edit pages through html forms, graphical preview
|
||||||
* Responsive design
|
* Responsive design, dark theme (synced with system theme)
|
||||||
* Works in text browsers
|
* Works in text browsers
|
||||||
* Wiki pages (called hyphae) are written in mycomarkup
|
* Wiki pages (called hyphae) are written in mycomarkup
|
||||||
* Everything is stored as simple files, no database required. You can run a wiki on almost any directory and get something to work with.
|
* Everything is stored as simple files, no database required. You can run a wiki on almost any directory and get something to work with
|
||||||
* Page trees
|
* Page trees; links to previous and next pages
|
||||||
* Changes are saved to git
|
* Changes are saved to git
|
||||||
* List of hyphae page
|
* List of hyphae page
|
||||||
* History page
|
* History page
|
||||||
* Random page
|
* Random page
|
||||||
* Recent changes page
|
* Recent changes page; RSS, Atom and JSON feeds available
|
||||||
* Hyphae can be deleted (while still preserving history)
|
* Hyphae can be deleted (while still preserving history)
|
||||||
* Hyphae can be renamed (recursive renaming of subhyphae is also supported)
|
* Hyphae can be renamed (recursive renaming of subhyphae is also supported)
|
||||||
* Light on resources: I run a home wiki on this engine 24/7 at an [Orange π Lite](http://www.orangepi.org/orangepilite/).
|
* Light on resources
|
||||||
* Authorization with pre-set credentials
|
* Authorization with pre-set credentials
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where some development is coordinated. Feel free to open an issue or contact me.
|
Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where some development is coordinated. You can also sponsor on [boosty](https://boosty.to/bouncepaw). Feel free to open an issue or contact directly.
|
||||||
|
|
||||||
## Future plans
|
You can view list of all planned features on [our kanban board](https://github.com/bouncepaw/mycorrhiza/projects/1).
|
||||||
* Tagging system
|
|
||||||
* Better history viewing
|
|
||||||
|
23
flag.go
23
flag.go
@ -10,12 +10,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at")
|
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.HomePage, "home", "home", "The home page")
|
flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at using HTTP")
|
||||||
flag.StringVar(&util.SiteTitle, "title", "🍄", "How to call your wiki in the navititle")
|
flag.StringVar(&util.HomePage, "home", "home", "The home page name")
|
||||||
flag.StringVar(&util.UserTree, "user-tree", "u", "Hypha which is a superhypha of all user pages")
|
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.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.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")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the things related to cli args and die maybe
|
// Do the things related to cli args and die maybe
|
||||||
@ -34,19 +37,19 @@ func parseCliArgs() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isCanonicalName(util.HomePage) {
|
if util.URL == "http://0.0.0.0:$port" {
|
||||||
log.Fatal("Error: you must use a proper name for the homepage")
|
util.URL = "http://0.0.0.0:" + util.ServerPort
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isCanonicalName(util.UserTree) {
|
util.HomePage = CanonicalName(util.HomePage)
|
||||||
log.Fatal("Error: you must use a proper name for user tree")
|
util.UserHypha = CanonicalName(util.UserHypha)
|
||||||
}
|
util.HeaderLinksHypha = CanonicalName(util.HeaderLinksHypha)
|
||||||
|
|
||||||
switch util.AuthMethod {
|
switch util.AuthMethod {
|
||||||
case "none":
|
case "none":
|
||||||
case "fixed":
|
case "fixed":
|
||||||
user.AuthUsed = true
|
user.AuthUsed = true
|
||||||
user.PopulateFixedUserStorage()
|
user.ReadUsersFromFilesystem()
|
||||||
default:
|
default:
|
||||||
log.Fatal("Error: unknown auth method:", util.AuthMethod)
|
log.Fatal("Error: unknown auth method:", util.AuthMethod)
|
||||||
}
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -4,5 +4,7 @@ go 1.14
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/adrg/xdg v0.2.2
|
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
|
github.com/valyala/quicktemplate v1.6.3
|
||||||
)
|
)
|
||||||
|
12
go.sum
12
go.sum
@ -1,11 +1,21 @@
|
|||||||
github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
|
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/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/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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
||||||
|
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||||
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
@ -19,5 +29,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/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=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -13,6 +14,8 @@ import (
|
|||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var renameMsgPattern = regexp.MustCompile(`^Rename ‘(.*)’ to ‘.*’`)
|
||||||
|
|
||||||
// Start initializes git credentials.
|
// Start initializes git credentials.
|
||||||
func Start(wikiDir string) {
|
func Start(wikiDir string) {
|
||||||
_, err := gitsh("config", "user.name", "wikimind")
|
_, err := gitsh("config", "user.name", "wikimind")
|
||||||
@ -31,6 +34,42 @@ type Revision struct {
|
|||||||
Username string
|
Username string
|
||||||
Time time.Time
|
Time time.Time
|
||||||
Message string
|
Message string
|
||||||
|
hyphaeAffectedBuf []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine what hyphae were affected by this revision
|
||||||
|
func (rev *Revision) hyphaeAffected() (hyphae []string) {
|
||||||
|
if nil != rev.hyphaeAffectedBuf {
|
||||||
|
return rev.hyphaeAffectedBuf
|
||||||
|
}
|
||||||
|
hyphae = make([]string, 0)
|
||||||
|
var (
|
||||||
|
// List of files affected by this revision, one per line.
|
||||||
|
out, err = gitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash)
|
||||||
|
// set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most currently).
|
||||||
|
set = make(map[string]bool)
|
||||||
|
isNewName = func(hyphaName string) bool {
|
||||||
|
if _, present := set[hyphaName]; present {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
set[hyphaName] = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return hyphae
|
||||||
|
}
|
||||||
|
for _, filename := range strings.Split(out.String(), "\n") {
|
||||||
|
if strings.IndexRune(filename, '.') >= 0 {
|
||||||
|
dotPos := strings.LastIndexByte(filename, '.')
|
||||||
|
hyphaName := string([]byte(filename)[0:dotPos]) // is it safe?
|
||||||
|
if isNewName(hyphaName) {
|
||||||
|
hyphae = append(hyphae, hyphaName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rev.hyphaeAffectedBuf = hyphae
|
||||||
|
return hyphae
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimeString returns a human readable time representation.
|
// TimeString returns a human readable time representation.
|
||||||
@ -40,42 +79,38 @@ func (rev Revision) TimeString() string {
|
|||||||
|
|
||||||
// HyphaeLinks returns a comma-separated list of hyphae that were affected by this revision as HTML string.
|
// HyphaeLinks returns a comma-separated list of hyphae that were affected by this revision as HTML string.
|
||||||
func (rev Revision) HyphaeLinks() (html string) {
|
func (rev Revision) HyphaeLinks() (html string) {
|
||||||
// diff-tree --no-commit-id --name-only -r
|
hyphae := rev.hyphaeAffected()
|
||||||
var (
|
for i, hyphaName := range hyphae {
|
||||||
// List of files affected by this revision, one per line.
|
if i > 0 {
|
||||||
out, err = gitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash)
|
|
||||||
// set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most).
|
|
||||||
set = make(map[string]bool)
|
|
||||||
isNewName = func(hyphaName string) bool {
|
|
||||||
if _, present := set[hyphaName]; present {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
set[hyphaName] = true
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
for _, filename := range strings.Split(out.String(), "\n") {
|
|
||||||
// If filename has an ampersand:
|
|
||||||
if strings.IndexRune(filename, '.') >= 0 {
|
|
||||||
// Remove ampersanded suffix from filename:
|
|
||||||
ampersandPos := strings.LastIndexByte(filename, '.')
|
|
||||||
hyphaName := string([]byte(filename)[0:ampersandPos]) // is it safe?
|
|
||||||
if isNewName(hyphaName) {
|
|
||||||
// Entries are separated by commas
|
|
||||||
if len(set) > 1 {
|
|
||||||
html += `<span aria-hidden="true">, </span>`
|
html += `<span aria-hidden="true">, </span>`
|
||||||
}
|
}
|
||||||
html += fmt.Sprintf(`<a href="/page/%[1]s">%[1]s</a>`, hyphaName)
|
html += fmt.Sprintf(`<a href="/page/%[1]s">%[1]s</a>`, hyphaName)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rev *Revision) descriptionForFeed() (html string) {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
`<p>%s</p>
|
||||||
|
<p><b>Hyphae affected:</b> %s</p>`, rev.Message, rev.HyphaeLinks())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try and guess what link is the most important by looking at the message.
|
||||||
|
func (rev *Revision) bestLink() string {
|
||||||
|
var (
|
||||||
|
revs = rev.hyphaeAffected()
|
||||||
|
renameRes = renameMsgPattern.FindStringSubmatch(rev.Message)
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case renameRes != nil:
|
||||||
|
return "/page/" + renameRes[1]
|
||||||
|
case len(revs) == 0:
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
return "/page/" + revs[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (rev Revision) RecentChangesEntry() (html string) {
|
func (rev Revision) RecentChangesEntry() (html string) {
|
||||||
if user.AuthUsed && rev.Username != "anon" {
|
if user.AuthUsed && rev.Username != "anon" {
|
||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
@ -83,7 +118,7 @@ func (rev Revision) RecentChangesEntry() (html string) {
|
|||||||
<li class="rc-entry__hash">%[2]s</li>
|
<li class="rc-entry__hash">%[2]s</li>
|
||||||
<li class="rc-entry__links">%[5]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>
|
<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.UserTree, rev.Username, rev.HyphaeLinks(), rev.Message)
|
`, rev.TimeString(), rev.Hash, util.UserHypha, rev.Username, rev.HyphaeLinks(), rev.Message)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
<li class="rc-entry__time"><time>%[1]s</time></li>
|
<li class="rc-entry__time"><time>%[1]s</time></li>
|
||||||
|
@ -7,10 +7,60 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/templates"
|
"github.com/bouncepaw/mycorrhiza/templates"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
"github.com/gorilla/feeds"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func recentChangesFeed() *feeds.Feed {
|
||||||
|
feed := &feeds.Feed{
|
||||||
|
Title: "Recent changes",
|
||||||
|
Link: &feeds.Link{Href: util.URL},
|
||||||
|
Description: "List of 30 recent changes on the wiki",
|
||||||
|
Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"},
|
||||||
|
Updated: time.Now(),
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
out, err = gitsh(
|
||||||
|
"log", "--oneline", "--no-merges",
|
||||||
|
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
|
||||||
|
"--max-count=30",
|
||||||
|
)
|
||||||
|
revs []Revision
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
for _, line := range strings.Split(out.String(), "\n") {
|
||||||
|
revs = append(revs, parseRevisionLine(line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, rev := range revs {
|
||||||
|
feed.Add(&feeds.Item{
|
||||||
|
Title: rev.Message,
|
||||||
|
Author: &feeds.Author{Name: rev.Username},
|
||||||
|
Id: rev.Hash,
|
||||||
|
Description: rev.descriptionForFeed(),
|
||||||
|
Created: rev.Time,
|
||||||
|
Updated: rev.Time,
|
||||||
|
Link: &feeds.Link{Href: util.URL + rev.bestLink()},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return feed
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecentChangesRSS() (string, error) {
|
||||||
|
return recentChangesFeed().ToRss()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecentChangesAtom() (string, error) {
|
||||||
|
return recentChangesFeed().ToAtom()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecentChangesJSON() (string, error) {
|
||||||
|
return recentChangesFeed().ToJSON()
|
||||||
|
}
|
||||||
|
|
||||||
func RecentChanges(n int) string {
|
func RecentChanges(n int) string {
|
||||||
var (
|
var (
|
||||||
out, err = gitsh(
|
out, err = gitsh(
|
||||||
@ -32,13 +82,19 @@ func RecentChanges(n int) string {
|
|||||||
return templates.RecentChangesHTML(entries, n)
|
return templates.RecentChangesHTML(entries, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FileChanged tells you if the file has been changed.
|
||||||
|
func FileChanged(path string) bool {
|
||||||
|
_, err := gitsh("diff", "--exit-code", path)
|
||||||
|
return err != nil
|
||||||
|
}
|
||||||
|
|
||||||
// Revisions returns slice of revisions for the given hypha name.
|
// Revisions returns slice of revisions for the given hypha name.
|
||||||
func Revisions(hyphaName string) ([]Revision, error) {
|
func Revisions(hyphaName string) ([]Revision, error) {
|
||||||
var (
|
var (
|
||||||
out, err = gitsh(
|
out, err = gitsh(
|
||||||
"log", "--oneline", "--no-merges",
|
"log", "--oneline", "--no-merges",
|
||||||
// Hash, Commiter email, Commiter time, Commit msg separated by tab
|
// Hash, author email, author time, commit msg separated by tab
|
||||||
"--pretty=format:\"%h\t%ce\t%ct\t%s\"",
|
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
|
||||||
"--", hyphaName+".*",
|
"--", hyphaName+".*",
|
||||||
)
|
)
|
||||||
revs []Revision
|
revs []Revision
|
||||||
@ -53,6 +109,59 @@ func Revisions(hyphaName string) ([]Revision, error) {
|
|||||||
return revs, err
|
return revs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HistoryWithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
|
||||||
|
func HistoryWithRevisions(hyphaName string, revs []Revision) (html string) {
|
||||||
|
var (
|
||||||
|
currentYear int
|
||||||
|
currentMonth time.Month
|
||||||
|
)
|
||||||
|
for i, rev := range revs {
|
||||||
|
if rev.Time.Month() != currentMonth || rev.Time.Year() != currentYear {
|
||||||
|
currentYear = rev.Time.Year()
|
||||||
|
currentMonth = rev.Time.Month()
|
||||||
|
if i != 0 {
|
||||||
|
html += `
|
||||||
|
</ul>
|
||||||
|
</section>`
|
||||||
|
}
|
||||||
|
html += fmt.Sprintf(`
|
||||||
|
<section class="history__month">
|
||||||
|
<a href="#%[1]d-%[2]d" class="history__month-anchor">
|
||||||
|
<h2 id="%[1]d-%[2]d" class="history__month-title">%[3]s</h2>
|
||||||
|
</a>
|
||||||
|
<ul class="history__entries">`,
|
||||||
|
currentYear, currentMonth,
|
||||||
|
strconv.Itoa(currentYear)+" "+rev.Time.Month().String())
|
||||||
|
}
|
||||||
|
html += rev.asHistoryEntry(hyphaName)
|
||||||
|
}
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rev *Revision) asHistoryEntry(hyphaName string) (html string) {
|
||||||
|
author := ""
|
||||||
|
if rev.Username != "anon" {
|
||||||
|
author = fmt.Sprintf(`
|
||||||
|
<span class="history-entry__author">by <a href="/page/%[1]s/%[2]s" rel="author">%[2]s</span>`, util.UserHypha, rev.Username)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
<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__msg">%[4]s</span>
|
||||||
|
</a>%[5]s
|
||||||
|
</li>
|
||||||
|
`, hyphaName, rev.timeToDisplay(), rev.Hash, rev.Message, author)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return time like mm-dd 13:42
|
||||||
|
func (rev *Revision) timeToDisplay() string {
|
||||||
|
D := rev.Time.Day()
|
||||||
|
h, m, _ := rev.Time.Clock()
|
||||||
|
return fmt.Sprintf("%02d — %02d:%02d", D, h, m)
|
||||||
|
}
|
||||||
|
|
||||||
// This regex is wrapped in "". For some reason, these quotes appear at some time and we have to get rid of them.
|
// This regex is wrapped in "". For some reason, these quotes appear at some time and we have to get rid of them.
|
||||||
var revisionLinePattern = regexp.MustCompile("\"(.*)\t(.*)@.*\t(.*)\t(.*)\"")
|
var revisionLinePattern = regexp.MustCompile("\"(.*)\t(.*)@.*\t(.*)\t(.*)\"")
|
||||||
|
|
||||||
@ -66,16 +175,6 @@ func parseRevisionLine(line string) Revision {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represent revision as a table row.
|
|
||||||
func (rev *Revision) AsHtmlTableRow(hyphaName string) string {
|
|
||||||
return fmt.Sprintf(`
|
|
||||||
<tr>
|
|
||||||
<td><time>%s</time></td>
|
|
||||||
<td><a href="/rev/%s/%s">%s</a></td>
|
|
||||||
<td>%s</td>
|
|
||||||
</tr>`, rev.TimeString(), rev.Hash, hyphaName, rev.Hash, rev.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// See how the file with `filepath` looked at commit with `hash`.
|
// See how the file with `filepath` looked at commit with `hash`.
|
||||||
func FileAtRevision(filepath, hash string) (string, error) {
|
func FileAtRevision(filepath, hash string) (string, error) {
|
||||||
out, err := gitsh("show", hash+":"+filepath)
|
out, err := gitsh("show", hash+":"+filepath)
|
||||||
|
@ -24,6 +24,7 @@ const (
|
|||||||
TypeEditBinary
|
TypeEditBinary
|
||||||
TypeDeleteHypha
|
TypeDeleteHypha
|
||||||
TypeRenameHypha
|
TypeRenameHypha
|
||||||
|
TypeUnattachHypha
|
||||||
)
|
)
|
||||||
|
|
||||||
// HistoryOp is an object representing a history operation.
|
// HistoryOp is an object representing a history operation.
|
||||||
@ -108,6 +109,12 @@ func (hop *HistoryOp) Apply() *HistoryOp {
|
|||||||
return hop
|
return hop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Abort aborts the history operation.
|
||||||
|
func (hop *HistoryOp) Abort() *HistoryOp {
|
||||||
|
gitMutex.Unlock()
|
||||||
|
return hop
|
||||||
|
}
|
||||||
|
|
||||||
// WithMsg sets what message will be used for the future commit. If user message exceeds one line, it is stripped down.
|
// WithMsg sets what message will be used for the future commit. If user message exceeds one line, it is stripped down.
|
||||||
func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
|
func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
|
||||||
for _, ch := range userMsg {
|
for _, ch := range userMsg {
|
||||||
@ -121,7 +128,7 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
|
|||||||
|
|
||||||
// WithUser sets a user for the commit.
|
// WithUser sets a user for the commit.
|
||||||
func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
|
func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
|
||||||
if u.Group != user.UserAnon {
|
if u.Group != "anon" {
|
||||||
hop.name = u.Name
|
hop.name = u.Name
|
||||||
hop.email = u.Name + "@mycorrhiza"
|
hop.email = u.Name + "@mycorrhiza"
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ func handlerLogout(w http.ResponseWriter, rq *http.Request) {
|
|||||||
log.Println("Unknown user tries to log out")
|
log.Println("Unknown user tries to log out")
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
}
|
}
|
||||||
w.Write([]byte(base("Logout?", templates.LogoutHTML(can))))
|
w.Write([]byte(base("Logout?", templates.LogoutHTML(can), u)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) {
|
func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) {
|
||||||
@ -44,7 +44,7 @@ func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
|
|||||||
err = user.LoginDataHTTP(w, rq, username, password)
|
err = user.LoginDataHTTP(w, rq, username, password)
|
||||||
)
|
)
|
||||||
if err != "" {
|
if err != "" {
|
||||||
w.Write([]byte(base(err, templates.LoginErrorHTML(err))))
|
w.Write([]byte(base(err, templates.LoginErrorHTML(err), user.EmptyUser())))
|
||||||
} else {
|
} else {
|
||||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
@ -58,5 +58,5 @@ func handlerLogin(w http.ResponseWriter, rq *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
}
|
}
|
||||||
w.Write([]byte(base("Login", templates.LoginHTML())))
|
w.Write([]byte(base("Login", templates.LoginHTML(), user.EmptyUser())))
|
||||||
}
|
}
|
||||||
|
78
http_history.go
Normal file
78
http_history.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/templates"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
http.HandleFunc("/history/", handlerHistory)
|
||||||
|
http.HandleFunc("/recent-changes/", handlerRecentChanges)
|
||||||
|
http.HandleFunc("/recent-changes-rss", handlerRecentChangesRSS)
|
||||||
|
http.HandleFunc("/recent-changes-atom", handlerRecentChangesAtom)
|
||||||
|
http.HandleFunc("/recent-changes-json", handlerRecentChangesJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlerHistory lists all revisions of a hypha
|
||||||
|
func handlerHistory(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
log.Println(rq.URL)
|
||||||
|
hyphaName := HyphaNameFromRq(rq, "history")
|
||||||
|
var list string
|
||||||
|
|
||||||
|
// History can be found for files that do not exist anymore.
|
||||||
|
revs, err := history.Revisions(hyphaName)
|
||||||
|
if err == nil {
|
||||||
|
list = history.HistoryWithRevisions(hyphaName, revs)
|
||||||
|
}
|
||||||
|
log.Println("Found", len(revs), "revisions for", hyphaName)
|
||||||
|
|
||||||
|
util.HTTP200Page(w,
|
||||||
|
base(hyphaName, templates.HistoryHTML(rq, hyphaName, list), user.FromRequest(rq)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recent changes
|
||||||
|
func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
log.Println(rq.URL)
|
||||||
|
var (
|
||||||
|
noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/")
|
||||||
|
n, err = strconv.Atoi(noPrefix)
|
||||||
|
)
|
||||||
|
if err == nil && n < 101 {
|
||||||
|
util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", history.RecentChanges(n), user.FromRequest(rq)))
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func genericHandlerOfFeeds(w http.ResponseWriter, rq *http.Request, f func() (string, error), name string) {
|
||||||
|
log.Println(rq.URL)
|
||||||
|
if content, err := f(); err != nil {
|
||||||
|
w.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprint(w, "An error while generating "+name+": "+err.Error())
|
||||||
|
} else {
|
||||||
|
w.Header().Set("Content-Type", "application/rss+xml")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlerRecentChangesRSS(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
genericHandlerOfFeeds(w, rq, history.RecentChangesRSS, "RSS")
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlerRecentChangesAtom(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
genericHandlerOfFeeds(w, rq, history.RecentChangesAtom, "Atom")
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlerRecentChangesJSON(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
genericHandlerOfFeeds(w, rq, history.RecentChangesJSON, "JSON feed")
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/markup"
|
||||||
"github.com/bouncepaw/mycorrhiza/templates"
|
"github.com/bouncepaw/mycorrhiza/templates"
|
||||||
"github.com/bouncepaw/mycorrhiza/user"
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
@ -15,11 +16,65 @@ func init() {
|
|||||||
http.HandleFunc("/edit/", handlerEdit)
|
http.HandleFunc("/edit/", handlerEdit)
|
||||||
http.HandleFunc("/delete-ask/", handlerDeleteAsk)
|
http.HandleFunc("/delete-ask/", handlerDeleteAsk)
|
||||||
http.HandleFunc("/rename-ask/", handlerRenameAsk)
|
http.HandleFunc("/rename-ask/", handlerRenameAsk)
|
||||||
|
http.HandleFunc("/unattach-ask/", handlerUnattachAsk)
|
||||||
// And those that do mutate something:
|
// And those that do mutate something:
|
||||||
http.HandleFunc("/upload-binary/", handlerUploadBinary)
|
http.HandleFunc("/upload-binary/", handlerUploadBinary)
|
||||||
http.HandleFunc("/upload-text/", handlerUploadText)
|
http.HandleFunc("/upload-text/", handlerUploadText)
|
||||||
http.HandleFunc("/delete-confirm/", handlerDeleteConfirm)
|
http.HandleFunc("/delete-confirm/", handlerDeleteConfirm)
|
||||||
http.HandleFunc("/rename-confirm/", handlerRenameConfirm)
|
http.HandleFunc("/rename-confirm/", handlerRenameConfirm)
|
||||||
|
http.HandleFunc("/unattach-confirm/", handlerUnattachConfirm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlerUnattachAsk(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 != ""
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if hop := hyphaData.UnattachHypha(hyphaName, u); len(hop.Errs) != 0 {
|
||||||
|
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))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) {
|
func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) {
|
||||||
@ -27,13 +82,14 @@ func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) {
|
|||||||
var (
|
var (
|
||||||
hyphaName = HyphaNameFromRq(rq, "rename-ask")
|
hyphaName = HyphaNameFromRq(rq, "rename-ask")
|
||||||
_, isOld = HyphaStorage[hyphaName]
|
_, isOld = HyphaStorage[hyphaName]
|
||||||
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
if ok := user.CanProceed(rq, "rename-confirm"); !ok {
|
if !u.CanProceed("rename-confirm") {
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.")
|
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.")
|
||||||
log.Println("Rejected", rq.URL)
|
log.Println("Rejected", rq.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
util.HTTP200Page(w, base("Rename "+hyphaName+"?", templates.RenameAskHTML(rq, hyphaName, isOld)))
|
util.HTTP200Page(w, base("Rename "+hyphaName+"?", templates.RenameAskHTML(rq, hyphaName, isOld), u))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
|
func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
|
||||||
@ -44,7 +100,7 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
|
|||||||
newName = CanonicalName(rq.PostFormValue("new-name"))
|
newName = CanonicalName(rq.PostFormValue("new-name"))
|
||||||
_, newNameIsUsed = HyphaStorage[newName]
|
_, newNameIsUsed = HyphaStorage[newName]
|
||||||
recursive = rq.PostFormValue("recursive") == "true"
|
recursive = rq.PostFormValue("recursive") == "true"
|
||||||
u = user.FromRequest(rq).OrAnon()
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
switch {
|
switch {
|
||||||
case !u.CanProceed("rename-confirm"):
|
case !u.CanProceed("rename-confirm"):
|
||||||
@ -79,13 +135,14 @@ func handlerDeleteAsk(w http.ResponseWriter, rq *http.Request) {
|
|||||||
var (
|
var (
|
||||||
hyphaName = HyphaNameFromRq(rq, "delete-ask")
|
hyphaName = HyphaNameFromRq(rq, "delete-ask")
|
||||||
_, isOld = HyphaStorage[hyphaName]
|
_, isOld = HyphaStorage[hyphaName]
|
||||||
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
if ok := user.CanProceed(rq, "delete-ask"); !ok {
|
if !u.CanProceed("delete-ask") {
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.")
|
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.")
|
||||||
log.Println("Rejected", rq.URL)
|
log.Println("Rejected", rq.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
util.HTTP200Page(w, base("Delete "+hyphaName+"?", templates.DeleteAskHTML(rq, hyphaName, isOld)))
|
util.HTTP200Page(w, base("Delete "+hyphaName+"?", templates.DeleteAskHTML(rq, hyphaName, isOld), u))
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlerDeleteConfirm deletes a hypha for sure
|
// handlerDeleteConfirm deletes a hypha for sure
|
||||||
@ -126,8 +183,9 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
|||||||
warning string
|
warning string
|
||||||
textAreaFill string
|
textAreaFill string
|
||||||
err error
|
err error
|
||||||
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
if ok := user.CanProceed(rq, "edit"); !ok {
|
if !u.CanProceed("edit") {
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
|
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
|
||||||
log.Println("Rejected", rq.URL)
|
log.Println("Rejected", rq.URL)
|
||||||
return
|
return
|
||||||
@ -142,7 +200,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
warning = `<p>You are creating a new hypha.</p>`
|
warning = `<p>You are creating a new hypha.</p>`
|
||||||
}
|
}
|
||||||
util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(rq, hyphaName, textAreaFill, warning)))
|
util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(rq, hyphaName, textAreaFill, warning), u))
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlerUploadText uploads a new text part for the hypha.
|
// handlerUploadText uploads a new text part for the hypha.
|
||||||
@ -151,9 +209,10 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
|
|||||||
var (
|
var (
|
||||||
hyphaName = HyphaNameFromRq(rq, "upload-text")
|
hyphaName = HyphaNameFromRq(rq, "upload-text")
|
||||||
textData = rq.PostFormValue("text")
|
textData = rq.PostFormValue("text")
|
||||||
u = user.FromRequest(rq).OrAnon()
|
action = rq.PostFormValue("action")
|
||||||
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
if ok := user.CanProceed(rq, "upload-text"); !ok {
|
if !u.CanProceed("upload-text") {
|
||||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
|
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
|
||||||
log.Println("Rejected", rq.URL)
|
log.Println("Rejected", rq.URL)
|
||||||
return
|
return
|
||||||
@ -162,7 +221,9 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
|
|||||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
|
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if hop := UploadText(hyphaName, textData, u); len(hop.Errs) != 0 {
|
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())
|
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
|
||||||
} else {
|
} else {
|
||||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/bouncepaw/mycorrhiza/markup"
|
"github.com/bouncepaw/mycorrhiza/markup"
|
||||||
"github.com/bouncepaw/mycorrhiza/templates"
|
"github.com/bouncepaw/mycorrhiza/templates"
|
||||||
"github.com/bouncepaw/mycorrhiza/tree"
|
"github.com/bouncepaw/mycorrhiza/tree"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,7 +21,6 @@ func init() {
|
|||||||
http.HandleFunc("/page/", handlerPage)
|
http.HandleFunc("/page/", handlerPage)
|
||||||
http.HandleFunc("/text/", handlerText)
|
http.HandleFunc("/text/", handlerText)
|
||||||
http.HandleFunc("/binary/", handlerBinary)
|
http.HandleFunc("/binary/", handlerBinary)
|
||||||
http.HandleFunc("/history/", handlerHistory)
|
|
||||||
http.HandleFunc("/rev/", handlerRevision)
|
http.HandleFunc("/rev/", handlerRevision)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,40 +35,23 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
|
|||||||
contents = fmt.Sprintf(`<p>This hypha had no text at this revision.</p>`)
|
contents = fmt.Sprintf(`<p>This hypha had no text at this revision.</p>`)
|
||||||
textPath = hyphaName + ".myco"
|
textPath = hyphaName + ".myco"
|
||||||
textContents, err = history.FileAtRevision(textPath, revHash)
|
textContents, err = history.FileAtRevision(textPath, revHash)
|
||||||
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
contents = markup.ToHtml(hyphaName, textContents)
|
contents = markup.Doc(hyphaName, textContents).AsHTML()
|
||||||
}
|
}
|
||||||
|
treeHTML, _, _ := tree.Tree(hyphaName, IterateHyphaNamesWith)
|
||||||
page := templates.RevisionHTML(
|
page := templates.RevisionHTML(
|
||||||
rq,
|
rq,
|
||||||
hyphaName,
|
hyphaName,
|
||||||
naviTitle(hyphaName),
|
naviTitle(hyphaName),
|
||||||
contents,
|
contents,
|
||||||
tree.TreeAsHtml(hyphaName, IterateHyphaNamesWith),
|
treeHTML,
|
||||||
revHash,
|
revHash,
|
||||||
)
|
)
|
||||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte(base(hyphaName, page)))
|
w.Write([]byte(base(hyphaName, page, u)))
|
||||||
}
|
|
||||||
|
|
||||||
// handlerHistory lists all revisions of a hypha
|
|
||||||
func handlerHistory(w http.ResponseWriter, rq *http.Request) {
|
|
||||||
log.Println(rq.URL)
|
|
||||||
hyphaName := HyphaNameFromRq(rq, "history")
|
|
||||||
var tbody string
|
|
||||||
|
|
||||||
// History can be found for files that do not exist anymore.
|
|
||||||
revs, err := history.Revisions(hyphaName)
|
|
||||||
if err == nil {
|
|
||||||
for _, rev := range revs {
|
|
||||||
tbody += rev.AsHtmlTableRow(hyphaName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Println("Found", len(revs), "revisions for", hyphaName)
|
|
||||||
|
|
||||||
util.HTTP200Page(w,
|
|
||||||
base(hyphaName, templates.HistoryHTML(rq, hyphaName, tbody)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handlerText serves raw source text of the hypha.
|
// handlerText serves raw source text of the hypha.
|
||||||
@ -99,20 +82,32 @@ func handlerPage(w http.ResponseWriter, rq *http.Request) {
|
|||||||
var (
|
var (
|
||||||
hyphaName = HyphaNameFromRq(rq, "page")
|
hyphaName = HyphaNameFromRq(rq, "page")
|
||||||
data, hyphaExists = HyphaStorage[hyphaName]
|
data, hyphaExists = HyphaStorage[hyphaName]
|
||||||
|
hasAmnt = hyphaExists && data.binaryPath != ""
|
||||||
contents string
|
contents string
|
||||||
|
openGraph string
|
||||||
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
if hyphaExists {
|
if hyphaExists {
|
||||||
fileContentsT, errT := ioutil.ReadFile(data.textPath)
|
fileContentsT, errT := ioutil.ReadFile(data.textPath)
|
||||||
_, errB := os.Stat(data.binaryPath)
|
_, errB := os.Stat(data.binaryPath)
|
||||||
if errT == nil {
|
if errT == nil {
|
||||||
contents = markup.ToHtml(hyphaName, string(fileContentsT))
|
md := markup.Doc(hyphaName, string(fileContentsT))
|
||||||
|
contents = md.AsHTML()
|
||||||
|
openGraph = md.OpenGraphHTML()
|
||||||
}
|
}
|
||||||
if !os.IsNotExist(errB) {
|
if !os.IsNotExist(errB) {
|
||||||
contents = binaryHtmlBlock(hyphaName, data) + contents
|
contents = binaryHtmlBlock(hyphaName, data) + contents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
util.HTTP200Page(w, base(hyphaName, templates.PageHTML(rq, hyphaName,
|
treeHTML, prevHypha, nextHypha := tree.Tree(hyphaName, IterateHyphaNamesWith)
|
||||||
|
util.HTTP200Page(w,
|
||||||
|
templates.BaseHTML(
|
||||||
|
hyphaName,
|
||||||
|
templates.PageHTML(rq, hyphaName,
|
||||||
naviTitle(hyphaName),
|
naviTitle(hyphaName),
|
||||||
contents,
|
contents,
|
||||||
tree.TreeAsHtml(hyphaName, IterateHyphaNamesWith))))
|
treeHTML, prevHypha, nextHypha,
|
||||||
|
hasAmnt),
|
||||||
|
u,
|
||||||
|
openGraph))
|
||||||
}
|
}
|
||||||
|
57
hypha.go
57
hypha.go
@ -8,9 +8,10 @@ import (
|
|||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"regexp"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/markup"
|
"github.com/bouncepaw/mycorrhiza/markup"
|
||||||
"github.com/bouncepaw/mycorrhiza/user"
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
@ -33,6 +34,12 @@ func init() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
markup.HyphaIterate = IterateHyphaNamesWith
|
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.
|
// 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.
|
||||||
@ -78,8 +85,12 @@ func uploadHelp(hop *history.HistoryOp, hyphaName, ext string, data []byte, u *u
|
|||||||
// New hyphae must be added to the hypha storage
|
// New hyphae must be added to the hypha storage
|
||||||
if !isOld {
|
if !isOld {
|
||||||
HyphaStorage[hyphaName] = hyphaData
|
HyphaStorage[hyphaName] = hyphaData
|
||||||
|
hyphae.IncrementCount()
|
||||||
}
|
}
|
||||||
*originalFullPath = fullPath
|
*originalFullPath = fullPath
|
||||||
|
if isOld && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) {
|
||||||
|
return hop.Abort()
|
||||||
|
}
|
||||||
return hop.WithFiles(fullPath).
|
return hop.WithFiles(fullPath).
|
||||||
WithUser(u).
|
WithUser(u).
|
||||||
Apply()
|
Apply()
|
||||||
@ -115,6 +126,30 @@ func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.Histor
|
|||||||
Apply()
|
Apply()
|
||||||
if len(hop.Errs) == 0 {
|
if len(hop.Errs) == 0 {
|
||||||
delete(HyphaStorage, hyphaName)
|
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
|
return hop
|
||||||
}
|
}
|
||||||
@ -160,8 +195,9 @@ func relocateHyphaData(hyphaNames []string, replaceName func(string) string) {
|
|||||||
// 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.
|
// 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 {
|
func RenameHypha(hyphaName, newName string, recursive bool, u *user.User) *history.HistoryOp {
|
||||||
var (
|
var (
|
||||||
|
re = regexp.MustCompile(`(?i)` + hyphaName)
|
||||||
replaceName = func(str string) string {
|
replaceName = func(str string) string {
|
||||||
return strings.Replace(str, hyphaName, newName, 1)
|
return re.ReplaceAllString(CanonicalName(str), newName)
|
||||||
}
|
}
|
||||||
hyphaNames = findHyphaeToRename(hyphaName, recursive)
|
hyphaNames = findHyphaeToRename(hyphaName, recursive)
|
||||||
renameMap, err = renamingPairs(hyphaNames, replaceName)
|
renameMap, err = renamingPairs(hyphaNames, replaceName)
|
||||||
@ -212,7 +248,7 @@ func binaryHtmlBlock(hyphaName string, hd *HyphaData) string {
|
|||||||
default:
|
default:
|
||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
<div class="binary-container binary-container_with-nothing">
|
<div class="binary-container binary-container_with-nothing">
|
||||||
<p>This hypha's media cannot be rendered. Access it <a href="/binary/%s">directly</a></p>
|
<p>This hypha's media cannot be rendered. <a href="/binary/%s">Download it</a></p>
|
||||||
</div>
|
</div>
|
||||||
`, hyphaName)
|
`, hyphaName)
|
||||||
}
|
}
|
||||||
@ -244,6 +280,7 @@ func Index(path string) {
|
|||||||
} else {
|
} else {
|
||||||
hyphaData = &HyphaData{}
|
hyphaData = &HyphaData{}
|
||||||
HyphaStorage[hyphaName] = hyphaData
|
HyphaStorage[hyphaName] = hyphaData
|
||||||
|
hyphae.IncrementCount()
|
||||||
}
|
}
|
||||||
if isText {
|
if isText {
|
||||||
hyphaData.textPath = hyphaPartPath
|
hyphaData.textPath = hyphaPartPath
|
||||||
@ -276,3 +313,17 @@ func FetchTextPart(d *HyphaData) (string, error) {
|
|||||||
}
|
}
|
||||||
return string(text), nil
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
38
hyphae/count.go
Normal file
38
hyphae/count.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package hyphae
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Its value is number of all existing hyphae. Hypha mutators are expected to manipulate the value. It is concurrent-safe.
|
||||||
|
var count = struct {
|
||||||
|
value int
|
||||||
|
sync.Mutex
|
||||||
|
}{}
|
||||||
|
|
||||||
|
// Set the value of hyphae count to zero.
|
||||||
|
func ResetCount() {
|
||||||
|
count.Lock()
|
||||||
|
count.value = 0
|
||||||
|
count.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment the value of hyphae count.
|
||||||
|
func IncrementCount() {
|
||||||
|
count.Lock()
|
||||||
|
count.value++
|
||||||
|
count.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement the value of hyphae count.
|
||||||
|
func DecrementCount() {
|
||||||
|
count.Lock()
|
||||||
|
count.value--
|
||||||
|
count.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count how many hyphae there are.
|
||||||
|
func Count() int {
|
||||||
|
// it is concurrent-safe to not lock here, right?
|
||||||
|
return count.value
|
||||||
|
}
|
21
hyphae/hypha.go
Normal file
21
hyphae/hypha.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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) {
|
||||||
|
}
|
113
main.go
113
main.go
@ -4,16 +4,17 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/history"
|
"github.com/bouncepaw/mycorrhiza/history"
|
||||||
|
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||||
"github.com/bouncepaw/mycorrhiza/templates"
|
"github.com/bouncepaw/mycorrhiza/templates"
|
||||||
"github.com/bouncepaw/mycorrhiza/user"
|
"github.com/bouncepaw/mycorrhiza/user"
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
@ -40,9 +41,18 @@ func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) {
|
|||||||
log.Println(errMsg, "for", name)
|
log.Println(errMsg, "for", name)
|
||||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
fmt.Fprint(w, base(title, fmt.Sprintf(
|
fmt.Fprint(
|
||||||
|
w,
|
||||||
|
base(
|
||||||
|
title,
|
||||||
|
fmt.Sprintf(
|
||||||
`<main><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`,
|
`<main><p>%s. <a href="/page/%s">Go back to the hypha.<a></p></main>`,
|
||||||
errMsg, name)))
|
errMsg,
|
||||||
|
name,
|
||||||
|
),
|
||||||
|
user.EmptyUser(),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show all hyphae
|
// Show all hyphae
|
||||||
@ -50,12 +60,13 @@ func handlerList(w http.ResponseWriter, rq *http.Request) {
|
|||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
var (
|
var (
|
||||||
tbody string
|
tbody string
|
||||||
pageCount = len(HyphaStorage)
|
pageCount = hyphae.Count()
|
||||||
|
u = user.FromRequest(rq)
|
||||||
)
|
)
|
||||||
for hyphaName, data := range HyphaStorage {
|
for hyphaName, data := range HyphaStorage {
|
||||||
tbody += templates.HyphaListRowHTML(hyphaName, ExtensionToMime(filepath.Ext(data.binaryPath)), data.binaryPath != "")
|
tbody += templates.HyphaListRowHTML(hyphaName, ExtensionToMime(filepath.Ext(data.binaryPath)), data.binaryPath != "")
|
||||||
}
|
}
|
||||||
util.HTTP200Page(w, base("List of pages", templates.HyphaListHTML(tbody, pageCount)))
|
util.HTTP200Page(w, base("List of pages", templates.HyphaListHTML(tbody, pageCount), u))
|
||||||
}
|
}
|
||||||
|
|
||||||
// This part is present in all html documents.
|
// This part is present in all html documents.
|
||||||
@ -69,18 +80,32 @@ func handlerReindex(w http.ResponseWriter, rq *http.Request) {
|
|||||||
log.Println("Rejected", rq.URL)
|
log.Println("Rejected", rq.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
hyphae.ResetCount()
|
||||||
HyphaStorage = make(map[string]*HyphaData)
|
HyphaStorage = make(map[string]*HyphaData)
|
||||||
log.Println("Wiki storage directory is", WikiDir)
|
log.Println("Wiki storage directory is", WikiDir)
|
||||||
log.Println("Start indexing hyphae...")
|
log.Println("Start indexing hyphae...")
|
||||||
Index(WikiDir)
|
Index(WikiDir)
|
||||||
log.Println("Indexed", len(HyphaStorage), "hyphae")
|
log.Println("Indexed", hyphae.Count(), "hyphae")
|
||||||
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if ok := user.CanProceed(rq, "update-header-links"); !ok {
|
||||||
|
HttpErr(w, http.StatusForbidden, util.HomePage, "Not enough rights", "You must be a moderator to update header links.")
|
||||||
|
log.Println("Rejected", rq.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setHeaderLinks()
|
||||||
|
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to a random hypha.
|
// Redirect to a random hypha.
|
||||||
func handlerRandom(w http.ResponseWriter, rq *http.Request) {
|
func handlerRandom(w http.ResponseWriter, rq *http.Request) {
|
||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
var randomHyphaName string
|
var randomHyphaName string
|
||||||
i := rand.Intn(len(HyphaStorage))
|
i := rand.Intn(hyphae.Count())
|
||||||
for hyphaName := range HyphaStorage {
|
for hyphaName := range HyphaStorage {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
randomHyphaName = hyphaName
|
randomHyphaName = hyphaName
|
||||||
@ -91,20 +116,6 @@ func handlerRandom(w http.ResponseWriter, rq *http.Request) {
|
|||||||
http.Redirect(w, rq, "/page/"+randomHyphaName, http.StatusSeeOther)
|
http.Redirect(w, rq, "/page/"+randomHyphaName, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recent changes
|
|
||||||
func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
|
|
||||||
log.Println(rq.URL)
|
|
||||||
var (
|
|
||||||
noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/")
|
|
||||||
n, err = strconv.Atoi(noPrefix)
|
|
||||||
)
|
|
||||||
if err == nil && n < 101 {
|
|
||||||
util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", history.RecentChanges(n)))
|
|
||||||
} else {
|
|
||||||
http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
||||||
log.Println(rq.URL)
|
log.Println(rq.URL)
|
||||||
if _, err := os.Stat(WikiDir + "/static/common.css"); err == nil {
|
if _, err := os.Stat(WikiDir + "/static/common.css"); err == nil {
|
||||||
@ -115,6 +126,50 @@ func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handlerIcon(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
iconName := strings.TrimPrefix(rq.URL.Path, "/static/icon/")
|
||||||
|
if iconName == "https" {
|
||||||
|
iconName = "http"
|
||||||
|
}
|
||||||
|
files, err := ioutil.ReadDir(WikiDir + "/static/icon")
|
||||||
|
if err == nil {
|
||||||
|
for _, f := range files {
|
||||||
|
if strings.HasPrefix(f.Name(), iconName+"-protocol-icon") {
|
||||||
|
http.ServeFile(w, rq, WikiDir+"/static/icon/"+f.Name())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "image/svg+xml")
|
||||||
|
switch iconName {
|
||||||
|
case "gemini":
|
||||||
|
w.Write([]byte(templates.IconGemini()))
|
||||||
|
case "mailto":
|
||||||
|
w.Write([]byte(templates.IconMailto()))
|
||||||
|
case "gopher":
|
||||||
|
w.Write([]byte(templates.IconGopher()))
|
||||||
|
default:
|
||||||
|
w.Write([]byte(templates.IconHTTP()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func 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))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(
|
||||||
|
`User-agent: *
|
||||||
|
Allow: /page/
|
||||||
|
Allow: /recent-changes
|
||||||
|
Disallow: /
|
||||||
|
Crawl-delay: 5`))
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Println("Running MycorrhizaWiki β")
|
log.Println("Running MycorrhizaWiki β")
|
||||||
parseCliArgs()
|
parseCliArgs()
|
||||||
@ -122,26 +177,30 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Println("Wiki storage directory is", WikiDir)
|
log.Println("Wiki storage directory is", WikiDir)
|
||||||
log.Println("Start indexing hyphae...")
|
|
||||||
Index(WikiDir)
|
Index(WikiDir)
|
||||||
log.Println("Indexed", len(HyphaStorage), "hyphae")
|
log.Println("Indexed", hyphae.Count(), "hyphae")
|
||||||
|
|
||||||
history.Start(WikiDir)
|
history.Start(WikiDir)
|
||||||
|
setHeaderLinks()
|
||||||
|
|
||||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static"))))
|
// See http_readers.go for /page/, /text/, /binary/
|
||||||
// See http_readers.go for /page/, /text/, /binary/, /history/.
|
// See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/, /unattach-ask/, /unattach-confirm/
|
||||||
// See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/.
|
|
||||||
// See http_auth.go for /login, /login-data, /logout, /logout-confirm
|
// See http_auth.go for /login, /login-data, /logout, /logout-confirm
|
||||||
|
// See http_history.go for /history/, /recent-changes
|
||||||
http.HandleFunc("/list", handlerList)
|
http.HandleFunc("/list", handlerList)
|
||||||
http.HandleFunc("/reindex", handlerReindex)
|
http.HandleFunc("/reindex", handlerReindex)
|
||||||
|
http.HandleFunc("/update-header-links", handlerUpdateHeaderLinks)
|
||||||
http.HandleFunc("/random", handlerRandom)
|
http.HandleFunc("/random", handlerRandom)
|
||||||
http.HandleFunc("/recent-changes/", handlerRecentChanges)
|
http.HandleFunc("/about", handlerAbout)
|
||||||
|
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static"))))
|
||||||
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) {
|
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, rq *http.Request) {
|
||||||
http.ServeFile(w, rq, WikiDir+"/static/favicon.ico")
|
http.ServeFile(w, rq, WikiDir+"/static/favicon.ico")
|
||||||
})
|
})
|
||||||
http.HandleFunc("/static/common.css", handlerStyle)
|
http.HandleFunc("/static/common.css", handlerStyle)
|
||||||
|
http.HandleFunc("/static/icon/", handlerIcon)
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
|
||||||
http.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther)
|
http.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther)
|
||||||
})
|
})
|
||||||
|
http.HandleFunc("/robots.txt", handlerRobotsTxt)
|
||||||
log.Fatal(http.ListenAndServe("0.0.0.0:"+util.ServerPort, nil))
|
log.Fatal(http.ListenAndServe("0.0.0.0:"+util.ServerPort, nil))
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var imgRe = regexp.MustCompile(`^img\s+{`)
|
var imgRe = regexp.MustCompile(`^img\s+{`)
|
||||||
@ -184,6 +186,14 @@ func (img *Img) binaryPathFor(path string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (img *Img) ogBinaryPathFor(path string) string {
|
||||||
|
path = img.binaryPathFor(path)
|
||||||
|
if strings.HasPrefix(path, "/binary/") {
|
||||||
|
return util.URL + path
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
func (img *Img) pagePathFor(path string) string {
|
func (img *Img) pagePathFor(path string) string {
|
||||||
path = strings.TrimSpace(path)
|
path = strings.TrimSpace(path)
|
||||||
if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
|
if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
|
||||||
@ -218,7 +228,7 @@ func (img *Img) checkLinks() map[string]bool {
|
|||||||
}
|
}
|
||||||
HyphaIterate(func(hyphaName string) {
|
HyphaIterate(func(hyphaName string) {
|
||||||
for _, entry := range img.entries {
|
for _, entry := range img.entries {
|
||||||
if hyphaName == entry.trimmedPath {
|
if hyphaName == xclCanonicalName(img.hyphaName, entry.trimmedPath) {
|
||||||
m[entry.trimmedPath] = true
|
m[entry.trimmedPath] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,9 @@ import (
|
|||||||
// HyphaExists holds function that checks that a hypha is present.
|
// HyphaExists holds function that checks that a hypha is present.
|
||||||
var HyphaExists func(string) bool
|
var HyphaExists func(string) bool
|
||||||
|
|
||||||
|
//
|
||||||
|
var HyphaImageForOG func(string) string
|
||||||
|
|
||||||
// HyphaAccess holds function that accesses a hypha by its name.
|
// HyphaAccess holds function that accesses a hypha by its name.
|
||||||
var HyphaAccess func(string) (rawText, binaryHtml string, err error)
|
var HyphaAccess func(string) (rawText, binaryHtml string, err error)
|
||||||
|
|
||||||
@ -25,28 +28,36 @@ type GemLexerState struct {
|
|||||||
buf string
|
buf string
|
||||||
// Temporaries
|
// Temporaries
|
||||||
img *Img
|
img *Img
|
||||||
|
table *Table
|
||||||
}
|
}
|
||||||
|
|
||||||
type Line struct {
|
type Line struct {
|
||||||
id int
|
id int
|
||||||
// interface{} may be bad. What I need is a sum of string and Transclusion
|
// interface{} may be bad. TODO: a proper type
|
||||||
contents interface{}
|
contents interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lex(name, content string) (ast []Line) {
|
func (md *MycoDoc) lex() (ast []Line) {
|
||||||
var state = GemLexerState{name: name}
|
var state = GemLexerState{name: md.hyphaName}
|
||||||
|
|
||||||
for _, line := range append(strings.Split(content, "\n"), "") {
|
for _, line := range append(strings.Split(md.contents, "\n"), "") {
|
||||||
geminiLineToAST(line, &state, &ast)
|
lineToAST(line, &state, &ast)
|
||||||
}
|
}
|
||||||
return ast
|
return ast
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lex `line` in markup and save it to `ast` using `state`.
|
// Lex `line` in markup and save it to `ast` using `state`.
|
||||||
func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
|
func lineToAST(line string, state *GemLexerState, ast *[]Line) {
|
||||||
addLine := func(text interface{}) {
|
addLine := func(text interface{}) {
|
||||||
*ast = append(*ast, Line{id: state.id, contents: text})
|
*ast = append(*ast, Line{id: state.id, contents: text})
|
||||||
}
|
}
|
||||||
|
addParagraphIfNeeded := func() {
|
||||||
|
if state.where == "p" {
|
||||||
|
state.where = ""
|
||||||
|
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, strings.ReplaceAll(ParagraphToHtml(state.name, state.buf), "\n", "<br>")))
|
||||||
|
state.buf = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Process empty lines depending on the current state
|
// Process empty lines depending on the current state
|
||||||
if "" == strings.TrimSpace(line) {
|
if "" == strings.TrimSpace(line) {
|
||||||
@ -59,6 +70,11 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
|
|||||||
addLine(state.buf + "</ol>")
|
addLine(state.buf + "</ol>")
|
||||||
case "pre":
|
case "pre":
|
||||||
state.buf += "\n"
|
state.buf += "\n"
|
||||||
|
case "launchpad":
|
||||||
|
state.where = ""
|
||||||
|
addLine(state.buf + "</ul>")
|
||||||
|
case "p":
|
||||||
|
addParagraphIfNeeded()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -74,13 +90,17 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
|
|||||||
switch state.where {
|
switch state.where {
|
||||||
case "img":
|
case "img":
|
||||||
goto imgState
|
goto imgState
|
||||||
|
case "table":
|
||||||
|
goto tableState
|
||||||
case "pre":
|
case "pre":
|
||||||
goto preformattedState
|
goto preformattedState
|
||||||
case "list":
|
case "list":
|
||||||
goto listState
|
goto listState
|
||||||
case "number":
|
case "number":
|
||||||
goto numberState
|
goto numberState
|
||||||
default:
|
case "launchpad":
|
||||||
|
goto launchpadState
|
||||||
|
default: // "p" or ""
|
||||||
goto normalState
|
goto normalState
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +111,13 @@ imgState:
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
|
tableState:
|
||||||
|
if shouldGoBackToNormal := state.table.Process(line); shouldGoBackToNormal {
|
||||||
|
state.where = ""
|
||||||
|
addLine(*state.table)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
preformattedState:
|
preformattedState:
|
||||||
switch {
|
switch {
|
||||||
case startsWith("```"):
|
case startsWith("```"):
|
||||||
@ -135,48 +162,84 @@ numberState:
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
|
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)
|
||||||
|
case startsWith("```"):
|
||||||
|
state.where = "pre"
|
||||||
|
addLine(state.buf + "</ul>")
|
||||||
|
state.id++
|
||||||
|
state.buf = fmt.Sprintf("<pre id='%d' alt='%s' class='codeblock'><code>", state.id, strings.TrimPrefix(line, "```"))
|
||||||
|
default:
|
||||||
|
state.where = ""
|
||||||
|
addLine(state.buf + "</ul>")
|
||||||
|
goto normalState
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
normalState:
|
normalState:
|
||||||
state.id++
|
state.id++
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
case startsWith("```"):
|
case startsWith("```"):
|
||||||
|
addParagraphIfNeeded()
|
||||||
state.where = "pre"
|
state.where = "pre"
|
||||||
state.buf = fmt.Sprintf("<pre id='%d' alt='%s' class='codeblock'><code>", state.id, strings.TrimPrefix(line, "```"))
|
state.buf = fmt.Sprintf("<pre id='%d' alt='%s' class='codeblock'><code>", state.id, strings.TrimPrefix(line, "```"))
|
||||||
case startsWith("* "):
|
case startsWith("* "):
|
||||||
|
addParagraphIfNeeded()
|
||||||
state.where = "list"
|
state.where = "list"
|
||||||
state.buf = fmt.Sprintf("<ul id='%d'>\n", state.id)
|
state.buf = fmt.Sprintf("<ul id='%d'>\n", state.id)
|
||||||
goto listState
|
goto listState
|
||||||
case startsWith("*. "):
|
case startsWith("*. "):
|
||||||
|
addParagraphIfNeeded()
|
||||||
state.where = "number"
|
state.where = "number"
|
||||||
state.buf = fmt.Sprintf("<ol id='%d'>\n", state.id)
|
state.buf = fmt.Sprintf("<ol id='%d'>\n", state.id)
|
||||||
goto numberState
|
goto numberState
|
||||||
|
|
||||||
case startsWith("###### "):
|
case startsWith("###### "):
|
||||||
|
addParagraphIfNeeded()
|
||||||
addHeading(6)
|
addHeading(6)
|
||||||
case startsWith("##### "):
|
case startsWith("##### "):
|
||||||
|
addParagraphIfNeeded()
|
||||||
addHeading(5)
|
addHeading(5)
|
||||||
case startsWith("#### "):
|
case startsWith("#### "):
|
||||||
|
addParagraphIfNeeded()
|
||||||
addHeading(4)
|
addHeading(4)
|
||||||
case startsWith("### "):
|
case startsWith("### "):
|
||||||
|
addParagraphIfNeeded()
|
||||||
addHeading(3)
|
addHeading(3)
|
||||||
case startsWith("## "):
|
case startsWith("## "):
|
||||||
|
addParagraphIfNeeded()
|
||||||
addHeading(2)
|
addHeading(2)
|
||||||
case startsWith("# "):
|
case startsWith("# "):
|
||||||
|
addParagraphIfNeeded()
|
||||||
addHeading(1)
|
addHeading(1)
|
||||||
|
|
||||||
case startsWith(">"):
|
case startsWith(">"):
|
||||||
addLine(fmt.Sprintf(
|
addParagraphIfNeeded()
|
||||||
"<blockquote id='%d'>%s</blockquote>", state.id, remover(">")(line)))
|
addLine(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"<blockquote id='%d'>%s</blockquote>",
|
||||||
|
state.id,
|
||||||
|
ParagraphToHtml(state.name, remover(">")(line)),
|
||||||
|
),
|
||||||
|
)
|
||||||
case startsWith("=>"):
|
case startsWith("=>"):
|
||||||
href, text, class := Rocketlink(line, state.name)
|
addParagraphIfNeeded()
|
||||||
addLine(fmt.Sprintf(
|
state.where = "launchpad"
|
||||||
`<p><a id='%d' class='rocketlink %s' href="%s">%s</a></p>`, state.id, class, href, text))
|
state.buf = fmt.Sprintf("<ul class='launchpad' id='%d'>\n", state.id)
|
||||||
|
goto launchpadState
|
||||||
|
|
||||||
case startsWith("<="):
|
case startsWith("<="):
|
||||||
|
addParagraphIfNeeded()
|
||||||
addLine(parseTransclusion(line, state.name))
|
addLine(parseTransclusion(line, state.name))
|
||||||
case line == "----":
|
case line == "----":
|
||||||
|
addParagraphIfNeeded()
|
||||||
*ast = append(*ast, Line{id: -1, contents: "<hr/>"})
|
*ast = append(*ast, Line{id: -1, contents: "<hr/>"})
|
||||||
case MatchesImg(line):
|
case MatchesImg(line):
|
||||||
|
addParagraphIfNeeded()
|
||||||
img, shouldGoBackToNormal := ImgFromFirstLine(line, state.name)
|
img, shouldGoBackToNormal := ImgFromFirstLine(line, state.name)
|
||||||
if shouldGoBackToNormal {
|
if shouldGoBackToNormal {
|
||||||
addLine(*img)
|
addLine(*img)
|
||||||
@ -184,7 +247,15 @@ normalState:
|
|||||||
state.where = "img"
|
state.where = "img"
|
||||||
state.img = img
|
state.img = img
|
||||||
}
|
}
|
||||||
|
case MatchesTable(line):
|
||||||
|
addParagraphIfNeeded()
|
||||||
|
state.where = "table"
|
||||||
|
state.table = TableFromFirstLine(line, state.name)
|
||||||
|
|
||||||
|
case state.where == "p":
|
||||||
|
state.buf += "\n" + line
|
||||||
default:
|
default:
|
||||||
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, ParagraphToHtml(state.name, line)))
|
state.where = "p"
|
||||||
|
state.buf = line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package markup
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -15,11 +16,19 @@ func LinkParts(addr, display, hyphaName string) (href, text, class string) {
|
|||||||
} else {
|
} else {
|
||||||
text = strings.TrimSpace(display)
|
text = strings.TrimSpace(display)
|
||||||
}
|
}
|
||||||
class = "wikilink_internal"
|
class = "wikilink wikilink_internal"
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.ContainsRune(addr, ':'):
|
case strings.ContainsRune(addr, ':'):
|
||||||
return addr, text, "wikilink_external"
|
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, "/"):
|
case strings.HasPrefix(addr, "/"):
|
||||||
return addr, text, class
|
return addr, text, class
|
||||||
case strings.HasPrefix(addr, "./"):
|
case strings.HasPrefix(addr, "./"):
|
||||||
|
@ -2,8 +2,12 @@
|
|||||||
package markup
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Mycomarkup-formatted document
|
// A Mycomarkup-formatted document
|
||||||
@ -11,26 +15,79 @@ type MycoDoc struct {
|
|||||||
// data
|
// data
|
||||||
hyphaName string
|
hyphaName string
|
||||||
contents string
|
contents string
|
||||||
|
// indicators
|
||||||
// state
|
parsedAlready bool
|
||||||
recursionDepth int
|
|
||||||
|
|
||||||
// results
|
// results
|
||||||
|
ast []Line
|
||||||
|
html string
|
||||||
|
firstImageURL string
|
||||||
|
description string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
func Doc(hyphaName, contents string) *MycoDoc {
|
func Doc(hyphaName, contents string) *MycoDoc {
|
||||||
return &MycoDoc{
|
md := &MycoDoc{
|
||||||
hyphaName: hyphaName,
|
hyphaName: hyphaName,
|
||||||
contents: contents,
|
contents: contents,
|
||||||
}
|
}
|
||||||
|
return md
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MycoDoc) Lex(recursionLevel int) *MycoDoc {
|
||||||
|
if !md.parsedAlready {
|
||||||
|
md.ast = md.lex()
|
||||||
|
}
|
||||||
|
md.parsedAlready = true
|
||||||
|
return md
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsHtml returns an html representation of the document
|
// AsHtml returns an html representation of the document
|
||||||
func (md *MycoDoc) AsHtml() string {
|
func (md *MycoDoc) AsHTML() string {
|
||||||
return ""
|
md.html = Parse(md.Lex(0).ast, 0, 0, 0)
|
||||||
|
return md.html
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to clear opengraph description from html tags. This method is usually bad because of dangers of malformed HTML, but I'm going to use it only for Mycorrhiza-generated HTML, so it's okay. The question mark is required; without it the whole string is eaten away.
|
||||||
|
var htmlTagRe = regexp.MustCompile(`<.*?>`)
|
||||||
|
|
||||||
|
// OpenGraphHTML returns an html representation of og: meta tags.
|
||||||
|
func (md *MycoDoc) OpenGraphHTML() string {
|
||||||
|
md.ogFillVars()
|
||||||
|
return strings.Join([]string{
|
||||||
|
ogTag("title", md.hyphaName),
|
||||||
|
ogTag("type", "article"),
|
||||||
|
ogTag("image", md.firstImageURL),
|
||||||
|
ogTag("url", util.URL+"/page/"+md.hyphaName),
|
||||||
|
ogTag("determiner", ""),
|
||||||
|
ogTag("description", htmlTagRe.ReplaceAllString(md.description, "")),
|
||||||
|
}, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MycoDoc) ogFillVars() *MycoDoc {
|
||||||
|
foundDesc := false
|
||||||
|
md.firstImageURL = HyphaImageForOG(md.hyphaName)
|
||||||
|
for _, line := range md.ast {
|
||||||
|
switch v := line.contents.(type) {
|
||||||
|
case string:
|
||||||
|
if !foundDesc {
|
||||||
|
md.description = v
|
||||||
|
foundDesc = true
|
||||||
|
}
|
||||||
|
case Img:
|
||||||
|
if len(v.entries) > 0 {
|
||||||
|
md.firstImageURL = v.entries[0].path.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return md
|
||||||
|
}
|
||||||
|
|
||||||
|
func ogTag(property, content string) string {
|
||||||
|
return fmt.Sprintf(`<meta property="og:%s" content="%s"/>`, property, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The rest of this file is currently unused. TODO: use it I guess */
|
||||||
|
|
||||||
type BlockType int
|
type BlockType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
type spanTokenType int
|
type spanTokenType int
|
||||||
@ -34,8 +35,10 @@ func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, o
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLinkNode(input *bytes.Buffer, hyphaName string) string {
|
func getLinkNode(input *bytes.Buffer, hyphaName string, isBracketedLink bool) string {
|
||||||
input.Next(2)
|
if isBracketedLink {
|
||||||
|
input.Next(2) // drop those [[
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
escaping = false
|
escaping = false
|
||||||
addrBuf = bytes.Buffer{}
|
addrBuf = bytes.Buffer{}
|
||||||
@ -47,11 +50,13 @@ func getLinkNode(input *bytes.Buffer, hyphaName string) string {
|
|||||||
if escaping {
|
if escaping {
|
||||||
currBuf.WriteByte(b)
|
currBuf.WriteByte(b)
|
||||||
escaping = false
|
escaping = false
|
||||||
} else if b == '|' && currBuf == &addrBuf {
|
} else if isBracketedLink && b == '|' && currBuf == &addrBuf {
|
||||||
currBuf = &displayBuf
|
currBuf = &displayBuf
|
||||||
} else if b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) {
|
} else if isBracketedLink && b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) {
|
||||||
input.Next(1)
|
input.Next(1)
|
||||||
break
|
break
|
||||||
|
} else if !isBracketedLink && unicode.IsSpace(rune(b)) {
|
||||||
|
break
|
||||||
} else {
|
} else {
|
||||||
currBuf.WriteByte(b)
|
currBuf.WriteByte(b)
|
||||||
}
|
}
|
||||||
@ -65,6 +70,12 @@ func getTextNode(input *bytes.Buffer) string {
|
|||||||
var (
|
var (
|
||||||
textNodeBuffer = bytes.Buffer{}
|
textNodeBuffer = bytes.Buffer{}
|
||||||
escaping = false
|
escaping = false
|
||||||
|
startsWith = func(t string) bool {
|
||||||
|
return bytes.HasPrefix(input.Bytes(), []byte(t))
|
||||||
|
}
|
||||||
|
couldBeLinkStart = func() bool {
|
||||||
|
return startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://")
|
||||||
|
}
|
||||||
)
|
)
|
||||||
// Always read the first byte in advance to avoid endless loops that kill computers (sad experience)
|
// Always read the first byte in advance to avoid endless loops that kill computers (sad experience)
|
||||||
if input.Len() != 0 {
|
if input.Len() != 0 {
|
||||||
@ -82,6 +93,9 @@ func getTextNode(input *bytes.Buffer) string {
|
|||||||
} else if strings.IndexByte("/*`^,![~", b) >= 0 {
|
} else if strings.IndexByte("/*`^,![~", b) >= 0 {
|
||||||
input.UnreadByte()
|
input.UnreadByte()
|
||||||
break
|
break
|
||||||
|
} else if couldBeLinkStart() {
|
||||||
|
textNodeBuffer.WriteByte(b)
|
||||||
|
break
|
||||||
} else {
|
} else {
|
||||||
textNodeBuffer.WriteByte(b)
|
textNodeBuffer.WriteByte(b)
|
||||||
}
|
}
|
||||||
@ -106,6 +120,9 @@ func ParagraphToHtml(hyphaName, input string) string {
|
|||||||
startsWith = func(t string) bool {
|
startsWith = func(t string) bool {
|
||||||
return bytes.HasPrefix(p.Bytes(), []byte(t))
|
return bytes.HasPrefix(p.Bytes(), []byte(t))
|
||||||
}
|
}
|
||||||
|
noTagsActive = func() bool {
|
||||||
|
return !(tagState[spanItalic] || tagState[spanBold] || tagState[spanMono] || tagState[spanSuper] || tagState[spanSub] || tagState[spanMark] || tagState[spanLink])
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
for p.Len() != 0 {
|
for p.Len() != 0 {
|
||||||
@ -132,7 +149,9 @@ func ParagraphToHtml(hyphaName, input string) string {
|
|||||||
ret.WriteString(tagFromState(spanMark, tagState, "s", "~~"))
|
ret.WriteString(tagFromState(spanMark, tagState, "s", "~~"))
|
||||||
p.Next(2)
|
p.Next(2)
|
||||||
case startsWith("[["):
|
case startsWith("[["):
|
||||||
ret.WriteString(getLinkNode(p, hyphaName))
|
ret.WriteString(getLinkNode(p, hyphaName, true))
|
||||||
|
case (startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://")) && noTagsActive():
|
||||||
|
ret.WriteString(getLinkNode(p, hyphaName, false))
|
||||||
default:
|
default:
|
||||||
ret.WriteString(html.EscapeString(getTextNode(p)))
|
ret.WriteString(html.EscapeString(getTextNode(p)))
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,26 @@
|
|||||||
package markup
|
package markup
|
||||||
|
|
||||||
import ()
|
|
||||||
|
|
||||||
const maxRecursionLevel = 3
|
const maxRecursionLevel = 3
|
||||||
|
|
||||||
type GemParserState struct {
|
func Parse(ast []Line, from, to int, recursionLevel int) (html string) {
|
||||||
recursionLevel int
|
if recursionLevel > maxRecursionLevel {
|
||||||
}
|
|
||||||
|
|
||||||
func Parse(ast []Line, from, to int, state GemParserState) (html string) {
|
|
||||||
if state.recursionLevel > maxRecursionLevel {
|
|
||||||
return "Transclusion depth limit"
|
return "Transclusion depth limit"
|
||||||
}
|
}
|
||||||
for _, line := range ast {
|
for _, line := range ast {
|
||||||
if line.id >= from && (line.id <= to || to == 0) || line.id == -1 {
|
if line.id >= from && (line.id <= to || to == 0) || line.id == -1 {
|
||||||
switch v := line.contents.(type) {
|
switch v := line.contents.(type) {
|
||||||
case Transclusion:
|
case Transclusion:
|
||||||
html += Transclude(v, state)
|
html += Transclude(v, recursionLevel)
|
||||||
case Img:
|
case Img:
|
||||||
html += v.ToHtml()
|
html += v.ToHtml()
|
||||||
|
case Table:
|
||||||
|
html += v.asHtml()
|
||||||
case string:
|
case string:
|
||||||
html += v
|
html += v
|
||||||
default:
|
default:
|
||||||
html += "Unknown"
|
html += "<b class='error'>Unknown element.</b>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToHtml(name, text string) string {
|
|
||||||
state := GemParserState{}
|
|
||||||
return Parse(lex(name, text), 0, 0, state)
|
|
||||||
}
|
|
||||||
|
231
markup/table.go
Normal file
231
markup/table.go
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
package markup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
// "github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tableRe = regexp.MustCompile(`^table\s+{`)
|
||||||
|
|
||||||
|
func MatchesTable(line string) bool {
|
||||||
|
return tableRe.MatchString(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TableFromFirstLine(line, hyphaName string) *Table {
|
||||||
|
return &Table{
|
||||||
|
hyphaName: hyphaName,
|
||||||
|
caption: line[strings.IndexRune(line, '{')+1:],
|
||||||
|
rows: make([]*tableRow, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) Process(line string) (shouldGoBackToNormal bool) {
|
||||||
|
if strings.TrimSpace(line) == "}" && !t.inMultiline {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !t.inMultiline {
|
||||||
|
t.pushRow()
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
inLink bool
|
||||||
|
skipNext bool
|
||||||
|
escaping bool
|
||||||
|
lookingForNonSpace = !t.inMultiline
|
||||||
|
countingColspan bool
|
||||||
|
)
|
||||||
|
for i, r := range line {
|
||||||
|
switch {
|
||||||
|
case skipNext:
|
||||||
|
skipNext = false
|
||||||
|
continue
|
||||||
|
|
||||||
|
case lookingForNonSpace && unicode.IsSpace(r):
|
||||||
|
case lookingForNonSpace && (r == '!' || r == '|'):
|
||||||
|
t.currCellMarker = r
|
||||||
|
t.currColspan = 1
|
||||||
|
lookingForNonSpace = false
|
||||||
|
countingColspan = true
|
||||||
|
case lookingForNonSpace:
|
||||||
|
t.currCellMarker = '^' // ^ represents implicit |, not part of syntax
|
||||||
|
t.currColspan = 1
|
||||||
|
lookingForNonSpace = false
|
||||||
|
t.currCellBuilder.WriteRune(r)
|
||||||
|
|
||||||
|
case escaping:
|
||||||
|
t.currCellBuilder.WriteRune(r)
|
||||||
|
case inLink && r == ']' && len(line)-1 > i && line[i+1] == ']':
|
||||||
|
t.currCellBuilder.WriteString("]]")
|
||||||
|
inLink = false
|
||||||
|
skipNext = true
|
||||||
|
case inLink:
|
||||||
|
t.currCellBuilder.WriteRune(r)
|
||||||
|
|
||||||
|
case t.inMultiline && r == '}':
|
||||||
|
t.inMultiline = false
|
||||||
|
case t.inMultiline && i == len(line)-1:
|
||||||
|
t.currCellBuilder.WriteRune('\n')
|
||||||
|
case t.inMultiline:
|
||||||
|
t.currCellBuilder.WriteRune(r)
|
||||||
|
|
||||||
|
// Not in multiline:
|
||||||
|
case (r == '|' || r == '!') && !countingColspan:
|
||||||
|
t.pushCell()
|
||||||
|
t.currCellMarker = r
|
||||||
|
t.currColspan = 1
|
||||||
|
countingColspan = true
|
||||||
|
case r == t.currCellMarker && (r == '|' || r == '!') && countingColspan:
|
||||||
|
t.currColspan++
|
||||||
|
case r == '{':
|
||||||
|
t.inMultiline = true
|
||||||
|
countingColspan = false
|
||||||
|
case r == '[' && len(line)-1 > i && line[i+1] == '[':
|
||||||
|
t.currCellBuilder.WriteString("[[")
|
||||||
|
inLink = true
|
||||||
|
skipNext = true
|
||||||
|
case i == len(line)-1:
|
||||||
|
t.pushCell()
|
||||||
|
default:
|
||||||
|
t.currCellBuilder.WriteRune(r)
|
||||||
|
countingColspan = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type Table struct {
|
||||||
|
// data
|
||||||
|
hyphaName string
|
||||||
|
caption string
|
||||||
|
rows []*tableRow
|
||||||
|
// state
|
||||||
|
inMultiline bool
|
||||||
|
// tmp
|
||||||
|
currCellMarker rune
|
||||||
|
currColspan uint
|
||||||
|
currCellBuilder strings.Builder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) pushRow() {
|
||||||
|
t.rows = append(t.rows, &tableRow{
|
||||||
|
cells: make([]*tableCell, 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) pushCell() {
|
||||||
|
tc := &tableCell{
|
||||||
|
content: t.currCellBuilder.String(),
|
||||||
|
colspan: t.currColspan,
|
||||||
|
}
|
||||||
|
switch t.currCellMarker {
|
||||||
|
case '|', '^':
|
||||||
|
tc.kind = tableCellDatum
|
||||||
|
case '!':
|
||||||
|
tc.kind = tableCellHeader
|
||||||
|
}
|
||||||
|
// We expect the table to have at least one row ready, so no nil-checking
|
||||||
|
tr := t.rows[len(t.rows)-1]
|
||||||
|
tr.cells = append(tr.cells, tc)
|
||||||
|
t.currCellBuilder = strings.Builder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) asHtml() (html string) {
|
||||||
|
if t.caption != "" {
|
||||||
|
html += fmt.Sprintf("<caption>%s</caption>", t.caption)
|
||||||
|
}
|
||||||
|
if len(t.rows) > 0 && t.rows[0].looksLikeThead() {
|
||||||
|
html += fmt.Sprintf("<thead>%s</thead>", t.rows[0].asHtml(t.hyphaName))
|
||||||
|
t.rows = t.rows[1:]
|
||||||
|
}
|
||||||
|
html += "\n<tbody>\n"
|
||||||
|
for _, tr := range t.rows {
|
||||||
|
html += tr.asHtml(t.hyphaName)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`<table>%s</tbody></table>`, html)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tableRow struct {
|
||||||
|
cells []*tableCell
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *tableRow) asHtml(hyphaName string) (html string) {
|
||||||
|
for _, tc := range tr.cells {
|
||||||
|
html += tc.asHtml(hyphaName)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("<tr>%s</tr>\n", html)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most likely, rows with more than two header cells are theads. I allow one extra datum cell for tables like this:
|
||||||
|
// | ! a ! b
|
||||||
|
// ! c | d | e
|
||||||
|
// ! f | g | h
|
||||||
|
func (tr *tableRow) looksLikeThead() bool {
|
||||||
|
var (
|
||||||
|
headerAmount = 0
|
||||||
|
datumAmount = 0
|
||||||
|
)
|
||||||
|
for _, tc := range tr.cells {
|
||||||
|
switch tc.kind {
|
||||||
|
case tableCellHeader:
|
||||||
|
headerAmount++
|
||||||
|
case tableCellDatum:
|
||||||
|
datumAmount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return headerAmount >= 2 && datumAmount <= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
type tableCell struct {
|
||||||
|
kind tableCellKind
|
||||||
|
colspan uint
|
||||||
|
content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *tableCell) asHtml(hyphaName string) string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"<%[1]s %[2]s>%[3]s</%[1]s>\n",
|
||||||
|
tc.kind.tagName(),
|
||||||
|
tc.colspanAttribute(),
|
||||||
|
tc.contentAsHtml(hyphaName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *tableCell) colspanAttribute() string {
|
||||||
|
if tc.colspan <= 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`colspan="%d"`, tc.colspan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *tableCell) contentAsHtml(hyphaName string) (html string) {
|
||||||
|
for _, line := range strings.Split(tc.content, "\n") {
|
||||||
|
if line = strings.TrimSpace(line); line != "" {
|
||||||
|
if html != "" {
|
||||||
|
html += `<br>`
|
||||||
|
}
|
||||||
|
html += ParagraphToHtml(hyphaName, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
type tableCellKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
tableCellUnknown tableCellKind = iota
|
||||||
|
tableCellHeader
|
||||||
|
tableCellDatum
|
||||||
|
)
|
||||||
|
|
||||||
|
func (tck tableCellKind) tagName() string {
|
||||||
|
switch tck {
|
||||||
|
case tableCellHeader:
|
||||||
|
return "th"
|
||||||
|
case tableCellDatum:
|
||||||
|
return "td"
|
||||||
|
default:
|
||||||
|
return "p"
|
||||||
|
}
|
||||||
|
}
|
@ -17,14 +17,14 @@ type Transclusion struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Transclude transcludes `xcl` and returns html representation.
|
// Transclude transcludes `xcl` and returns html representation.
|
||||||
func Transclude(xcl Transclusion, state GemParserState) (html string) {
|
func Transclude(xcl Transclusion, recursionLevel int) (html string) {
|
||||||
state.recursionLevel++
|
recursionLevel++
|
||||||
tmptOk := `<section class="transclusion transclusion_ok">
|
tmptOk := `<section class="transclusion transclusion_ok">
|
||||||
<a class="transclusion__link" href="/page/%s">%s</a>
|
<a class="transclusion__link" href="/page/%s">%s</a>
|
||||||
<div class="transclusion__content">%s</div>
|
<div class="transclusion__content">%s</div>
|
||||||
</section>`
|
</section>`
|
||||||
tmptFailed := `<section class="transclusion transclusion_failed">
|
tmptFailed := `<section class="transclusion transclusion_failed">
|
||||||
<p>Failed to transclude <a href="/page/%s">%s</a></p>
|
<p class="error">Hypha <a class="wikilink_new" href="/page/%s">%s</a> does not exist</p>
|
||||||
</section>`
|
</section>`
|
||||||
if xcl.from == xclError || xcl.to == xclError || xcl.from > xcl.to {
|
if xcl.from == xclError || xcl.to == xclError || xcl.from > xcl.to {
|
||||||
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
|
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
|
||||||
@ -34,7 +34,8 @@ func Transclude(xcl Transclusion, state GemParserState) (html string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
|
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
|
||||||
}
|
}
|
||||||
xclText := Parse(lex(xcl.name, rawText), xcl.from, xcl.to, state)
|
md := Doc(xcl.name, rawText)
|
||||||
|
xclText := Parse(md.lex(), xcl.from, xcl.to, recursionLevel)
|
||||||
return fmt.Sprintf(tmptOk, xcl.name, xcl.name, binaryHtml+xclText)
|
return fmt.Sprintf(tmptOk, xcl.name, xcl.name, binaryHtml+xclText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 7828352598c19afe5f2e13df0219656ac7b44c9c
|
Subproject commit be5b922e9b564551601d21ed45bf7d9ced65c6bb
|
20
name.go
20
name.go
@ -23,16 +23,24 @@ func CanonicalName(name string) string {
|
|||||||
func naviTitle(canonicalName string) string {
|
func naviTitle(canonicalName string) string {
|
||||||
var (
|
var (
|
||||||
html = fmt.Sprintf(`<h1 class="navi-title" id="navi-title">
|
html = fmt.Sprintf(`<h1 class="navi-title" id="navi-title">
|
||||||
<a href="/page/%s">%s</a>`, util.HomePage, util.SiteTitle)
|
<a href="/page/%s">%s</a><span aria-hidden="true" class="navi-title__colon">:</span>`, util.HomePage, util.SiteNavIcon)
|
||||||
prevAcc = `/page/`
|
prevAcc = `/page/`
|
||||||
parts = strings.Split(canonicalName, "/")
|
parts = strings.Split(canonicalName, "/")
|
||||||
|
rel = "up"
|
||||||
)
|
)
|
||||||
for _, part := range parts {
|
for i, part := range parts {
|
||||||
html += fmt.Sprintf(`
|
if i > 0 {
|
||||||
<span aria-hidden="true">/</span>
|
html += `<span aria-hidden="true" class="navi-title__separator">/</span>`
|
||||||
<a href="%s">%s</a>`,
|
}
|
||||||
|
if i == len(parts)-1 {
|
||||||
|
rel = "bookmark"
|
||||||
|
}
|
||||||
|
html += fmt.Sprintf(
|
||||||
|
`<a href="%s" rel="%s">%s</a>`,
|
||||||
prevAcc+part,
|
prevAcc+part,
|
||||||
strings.Title(part))
|
rel,
|
||||||
|
util.BeautifulName(part),
|
||||||
|
)
|
||||||
prevAcc += part + "/"
|
prevAcc += part + "/"
|
||||||
}
|
}
|
||||||
return html + "</h1>"
|
return html + "</h1>"
|
||||||
|
21
templates/asset.qtpl
Normal file
21
templates/asset.qtpl
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{% func DefaultCSS() %}
|
||||||
|
{% cat "default.css" %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
Next three are from https://remixicon.com/
|
||||||
|
{% func IconHTTP() %}
|
||||||
|
{% cat "icon/http-protocol-icon.svg" %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func IconGemini() %}
|
||||||
|
{% cat "icon/gemini-protocol-icon.svg" %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
{% func IconMailto() %}
|
||||||
|
{% cat "icon/mailto-protocol-icon.svg" %}
|
||||||
|
{% endfunc %}
|
||||||
|
|
||||||
|
This is a modified version of https://www.svgrepo.com/svg/232085/rat
|
||||||
|
{% func IconGopher() %}
|
||||||
|
{% cat "icon/gopher-protocol-icon.svg" %}
|
||||||
|
{% endfunc %}
|
414
templates/asset.qtpl.go
Normal file
414
templates/asset.qtpl.go
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
// Code generated by qtc from "asset.qtpl". DO NOT EDIT.
|
||||||
|
// See https://github.com/valyala/quicktemplate for details.
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:1
|
||||||
|
package templates
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:1
|
||||||
|
import (
|
||||||
|
qtio422016 "io"
|
||||||
|
|
||||||
|
qt422016 "github.com/valyala/quicktemplate"
|
||||||
|
)
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:1
|
||||||
|
var (
|
||||||
|
_ = qtio422016.Copy
|
||||||
|
_ = qt422016.AcquireByteBuffer
|
||||||
|
)
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:1
|
||||||
|
func StreamDefaultCSS(qw422016 *qt422016.Writer) {
|
||||||
|
//line templates/asset.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; }
|
||||||
|
.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; }
|
||||||
|
}
|
||||||
|
@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; }
|
||||||
|
}
|
||||||
|
*, *::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; }
|
||||||
|
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__preview { border: 2px dashed #ddd; }
|
||||||
|
.edit-form {height:90%;}
|
||||||
|
.edit-form textarea {width:100%;height:90%;}
|
||||||
|
.edit-form__save { font-weight: bold; }
|
||||||
|
.icon {margin-right: .25rem; vertical-align: bottom; }
|
||||||
|
|
||||||
|
main h1:not(.navi-title) {font-size:1.7rem;}
|
||||||
|
blockquote { margin-left: 0; padding-left: 1rem; }
|
||||||
|
.wikilink_external::before { display: inline-block; width: 18px; height: 16px; vertical-align: sub; }
|
||||||
|
/* .wikilink_external { padding-left: 16px; } */
|
||||||
|
.wikilink_gopher::before { content: url("/static/icon/gopher"); }
|
||||||
|
.wikilink_http::before { content: url("/static/icon/http"); }
|
||||||
|
.wikilink_https::before { content: url("/static/icon/http"); }
|
||||||
|
/* .wikilink_https { background: transparent url("/static/icon/http") center left no-repeat; } */
|
||||||
|
.wikilink_gemini::before { content: url("/static/icon/gemini"); }
|
||||||
|
.wikilink_mailto::before { content: url("/static/icon/mailto"); }
|
||||||
|
|
||||||
|
article { overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; line-height: 150%; }
|
||||||
|
article h1, article h2, article h3, article h4, article h5, article h6 { margin: 1.5rem 0 0 0; }
|
||||||
|
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%; }
|
||||||
|
article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25rem;}
|
||||||
|
.codeblock code {padding:0; font-size:15px;}
|
||||||
|
.transclusion { border-radius: .25rem; }
|
||||||
|
.transclusion__content > *:not(.binary-container) {margin: 0.5rem; }
|
||||||
|
.transclusion__link {display: block; text-align: right; font-style: italic; margin-top: .5rem; margin-right: .25rem; text-decoration: none;}
|
||||||
|
.transclusion__link::before {content: "⇐ ";}
|
||||||
|
|
||||||
|
/* Derived from https://commons.wikimedia.org/wiki/File:U%2B21D2.svg */
|
||||||
|
.launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath fill='%23999' d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); }
|
||||||
|
|
||||||
|
.binary-container_with-img img,
|
||||||
|
.binary-container_with-video video,
|
||||||
|
.binary-container_with-audio audio {width: 100%}
|
||||||
|
|
||||||
|
.navi-title { padding-bottom: .5rem; margin: .25rem 0; }
|
||||||
|
.navi-title a {text-decoration:none; }
|
||||||
|
.navi-title__separator { margin: 0 .25rem; }
|
||||||
|
.navi-title__colon { margin-right: .5rem; }
|
||||||
|
.upload-amnt { clear: both; padding: .5rem; border-radius: .25rem; }
|
||||||
|
.upload-amnt__unattach { display: block; }
|
||||||
|
aside { clear: both; }
|
||||||
|
|
||||||
|
.img-gallery { text-align: center; margin-top: .25rem; margin-bottom: .25rem; }
|
||||||
|
.img-gallery_many-images { border-radius: .25rem; padding: .5rem; }
|
||||||
|
.img-gallery img { max-width: 100%; max-height: 50vh; }
|
||||||
|
figure { margin: 0; }
|
||||||
|
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__time { font-style: italic; }
|
||||||
|
.rc-entry__hash { font-style: italic; text-align: right; }
|
||||||
|
.rc-entry__links { grid-column: 1 / span 2; }
|
||||||
|
.rc-entry__author { font-style: italic; }
|
||||||
|
|
||||||
|
.prevnext__el { display: block-inline; min-width: 40%; padding: .5rem; margin-bottom: .25rem; text-decoration: none; border-radius: .25rem; }
|
||||||
|
.prevnext__prev { float: left; }
|
||||||
|
.prevnext__next { float: right; text-align: right; }
|
||||||
|
|
||||||
|
.page-separator { clear: both; }
|
||||||
|
.history__entries { background-color: #eee; margin: 0; padding: 0; border-radius: .25rem; }
|
||||||
|
.history__month-anchor { text-decoration: none; color: inherit; }
|
||||||
|
.history__entry { list-style-type: none; padding: .25rem; }
|
||||||
|
.history-entry { padding: .25rem; }
|
||||||
|
.history-entry__time { font-weight: bold; }
|
||||||
|
.history-entry__author { font-style: italic; }
|
||||||
|
|
||||||
|
table { border: #ddd 1px solid; border-radius: .25rem; min-width: 4rem; }
|
||||||
|
td { padding: .25rem; }
|
||||||
|
caption { caption-side: top; font-size: small; }
|
||||||
|
|
||||||
|
/* Color stuff */
|
||||||
|
/* Lighter stuff #eee */
|
||||||
|
article code,
|
||||||
|
article .codeblock,
|
||||||
|
.transclusion,
|
||||||
|
.img-gallery_many-images,
|
||||||
|
.rc-entry,
|
||||||
|
.prevnext__el,
|
||||||
|
table { background-color: #eee; }
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.hypha-tabs { background-color: white; }
|
||||||
|
.hypha-tabs__tab { box-shadow: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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");
|
||||||
|
} /* heropatterns.com */
|
||||||
|
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; }
|
||||||
|
|
||||||
|
blockquote { border-left: 4px black solid; }
|
||||||
|
.wikilink_new {color:#a55858;}
|
||||||
|
.transclusion code, .transclusion .codeblock {background-color:#ddd;}
|
||||||
|
.transclusion__link { color: black; }
|
||||||
|
.wikilink_new:visited {color:#a55858;}
|
||||||
|
.navi-title { border-bottom: #eee 1px solid; }
|
||||||
|
.upload-amnt { border: #eee 1px solid; }
|
||||||
|
td { border: #ddd 1px solid; }
|
||||||
|
|
||||||
|
/* Dark theme! */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
html { background: #222; color: #ddd; }
|
||||||
|
main, article, .hypha-tabs__tab, header { background-color: #343434; color: #ddd; }
|
||||||
|
|
||||||
|
a, .wikilink_external { color: #f1fa8c; }
|
||||||
|
a:visited, .wikilink_external:visited { color: #ffb86c; }
|
||||||
|
.wikilink_new, .wikilink_new:visited { color: #dd4444; }
|
||||||
|
|
||||||
|
.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; }
|
||||||
|
|
||||||
|
blockquote { border-left: 4px #ddd solid; }
|
||||||
|
|
||||||
|
.transclusion .transclusion__link { color: #ddd; }
|
||||||
|
article code,
|
||||||
|
article .codeblock,
|
||||||
|
.transclusion,
|
||||||
|
.img-gallery_many-images,
|
||||||
|
.rc-entry,
|
||||||
|
.history__entry,
|
||||||
|
.prevnext__el,
|
||||||
|
.upload-amnt,
|
||||||
|
textarea,
|
||||||
|
table { border: 0; background-color: #444444; color: #ddd; }
|
||||||
|
.transclusion code,
|
||||||
|
.transclusion .codeblock { background-color: #454545; }
|
||||||
|
mark { background: rgba(130, 80, 30, 5); color: inherit; }
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.hypha-tabs { background-color: #232323; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
`)
|
||||||
|
//line templates/asset.qtpl:2
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
func WriteDefaultCSS(qq422016 qtio422016.Writer) {
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
StreamDefaultCSS(qw422016)
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
func DefaultCSS() string {
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
WriteDefaultCSS(qb422016)
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
return qs422016
|
||||||
|
//line templates/asset.qtpl:3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next three are from https://remixicon.com/
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:6
|
||||||
|
func StreamIconHTTP(qw422016 *qt422016.Writer) {
|
||||||
|
//line templates/asset.qtpl:6
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line templates/asset.qtpl:7
|
||||||
|
qw422016.N().S(`<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
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
func WriteIconHTTP(qq422016 qtio422016.Writer) {
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
StreamIconHTTP(qw422016)
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
func IconHTTP() string {
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
WriteIconHTTP(qb422016)
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
return qs422016
|
||||||
|
//line templates/asset.qtpl:8
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:10
|
||||||
|
func StreamIconGemini(qw422016 *qt422016.Writer) {
|
||||||
|
//line templates/asset.qtpl:10
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line templates/asset.qtpl:11
|
||||||
|
qw422016.N().S(`<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
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
func WriteIconGemini(qq422016 qtio422016.Writer) {
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
StreamIconGemini(qw422016)
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
func IconGemini() string {
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
WriteIconGemini(qb422016)
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
return qs422016
|
||||||
|
//line templates/asset.qtpl:12
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:14
|
||||||
|
func StreamIconMailto(qw422016 *qt422016.Writer) {
|
||||||
|
//line templates/asset.qtpl:14
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line templates/asset.qtpl:15
|
||||||
|
qw422016.N().S(`<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
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
func WriteIconMailto(qq422016 qtio422016.Writer) {
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
StreamIconMailto(qw422016)
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
func IconMailto() string {
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
WriteIconMailto(qb422016)
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
return qs422016
|
||||||
|
//line templates/asset.qtpl:16
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a modified version of https://www.svgrepo.com/svg/232085/rat
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:19
|
||||||
|
func StreamIconGopher(qw422016 *qt422016.Writer) {
|
||||||
|
//line templates/asset.qtpl:19
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line templates/asset.qtpl:20
|
||||||
|
qw422016.N().S(`<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
|
||||||
|
c8.836,0,16-7.164,16-16c0-8.836-7.164-16-16-16H81.051c-21.441,0-39.7-13.836-46.351-33.044H496c8.836,0,16-7.164,16-16
|
||||||
|
C512,271.82,486.82,228.692,447.238,204.944z M415.238,153.216v37.805c-10.318-2.946-19.556-4.305-29.342-4.937
|
||||||
|
C390.355,168.611,402.006,157.881,415.238,153.216z M295.484,303.435L295.484,303.435c-7.562-41.495-43.948-73.062-87.593-73.062
|
||||||
|
c-8.836,0-16,7.164-16,16c0,8.836,7.164,16,16,16c25.909,0,47.826,17.364,54.76,41.062H32.722
|
||||||
|
c14.415-159.15,218.064-217.856,315.136-90.512c3.545,4.649,9.345,6.995,15.124,6.118
|
||||||
|
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
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
func WriteIconGopher(qq422016 qtio422016.Writer) {
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
StreamIconGopher(qw422016)
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
func IconGopher() string {
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
WriteIconGopher(qb422016)
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
return qs422016
|
||||||
|
//line templates/asset.qtpl:21
|
||||||
|
}
|
@ -6,7 +6,7 @@
|
|||||||
{% if user.AuthUsed %}
|
{% if user.AuthUsed %}
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data">
|
<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data">
|
||||||
<p>Use the data you were given by the administrator.</p>
|
<p>Use the data you were given by an administrator.</p>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Username</legend>
|
<legend>Username</legend>
|
||||||
<input type="text" required autofocus name="username" autocomplete="on">
|
<input type="text" required autofocus name="username" autocomplete="on">
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<legend>Password</legend>
|
<legend>Password</legend>
|
||||||
<input type="password" required name="password" autocomplete="on">
|
<input type="password" required name="password" autocomplete="on">
|
||||||
</fieldset>
|
</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.</p>
|
<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">
|
<input type="submit">
|
||||||
<a href="/">Cancel</a>
|
<a href="/">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
|
@ -33,7 +33,7 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) {
|
|||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data">
|
<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data">
|
||||||
<p>Use the data you were given by the administrator.</p>
|
<p>Use the data you were given by an administrator.</p>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Username</legend>
|
<legend>Username</legend>
|
||||||
<input type="text" required autofocus name="username" autocomplete="on">
|
<input type="text" required autofocus name="username" autocomplete="on">
|
||||||
@ -42,7 +42,7 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) {
|
|||||||
<legend>Password</legend>
|
<legend>Password</legend>
|
||||||
<input type="password" required name="password" autocomplete="on">
|
<input type="password" required name="password" autocomplete="on">
|
||||||
</fieldset>
|
</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.</p>
|
<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">
|
<input type="submit">
|
||||||
<a href="/">Cancel</a>
|
<a href="/">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
|
@ -21,31 +21,39 @@ var navEntries = []navEntry{
|
|||||||
|
|
||||||
{% func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %}
|
{% func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %}
|
||||||
{% code
|
{% code
|
||||||
u := user.FromRequest(rq).OrAnon()
|
u := user.FromRequest(rq)
|
||||||
%}
|
%}
|
||||||
<nav class="navlinks">
|
|
||||||
<ul>
|
<nav class="hypha-tabs">
|
||||||
|
<ul class="hypha-tabs__flex">
|
||||||
{%- for _, entry := range navEntries -%}
|
{%- for _, entry := range navEntries -%}
|
||||||
{%- if navType == "revision" && entry.path == "revision" -%}
|
{%- if navType == "revision" && entry.path == "revision" -%}
|
||||||
<li><b>{%s revisionHash[0] %}</b></li>
|
<li class="hypha-tabs__tab hypha-tabs__tab_active">
|
||||||
|
{%s revisionHash[0] %}
|
||||||
|
</li>
|
||||||
{%- elseif navType == entry.path -%}
|
{%- elseif navType == entry.path -%}
|
||||||
<li><b>{%s entry.title %}</b></li>
|
<li class="hypha-tabs__tab hypha-tabs__tab_active">
|
||||||
{%- elseif entry.path != "revision" && u.Group.CanAccessRoute(entry.path) -%}
|
{%s entry.title %}
|
||||||
<li><a href="/{%s entry.path %}/{%s hyphaName %}">{%s entry.title %}</a></li>
|
</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>
|
||||||
|
</li>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
{%s= userMenuHTML(u) %}
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
{% endfunc %}
|
{% endfunc %}
|
||||||
|
|
||||||
{% func userMenuHTML(u *user.User) %}
|
{% func userMenuHTML(u *user.User) %}
|
||||||
<li class="navlinks__user">
|
{% if user.AuthUsed %}
|
||||||
{% if u.Group == user.UserAnon %}
|
<li class="header-links__entry header-links__entry_user">
|
||||||
<a href="/login">Login</a>
|
{% if u.Group == "anon" %}
|
||||||
|
<a href="/login" class="header-links__link">Login</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/page/{%s util.UserTree %}/{%s u.Name %}">{%s u.Name %}</a>
|
<a href="/page/{%s util.UserHypha %}/{%s u.Name %}" class="header-links__link">{%s u.Name %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% endfunc %}
|
{% endfunc %}
|
||||||
|
|
||||||
|
@ -50,153 +50,165 @@ func streamnavHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navTy
|
|||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:24
|
//line templates/common.qtpl:24
|
||||||
u := user.FromRequest(rq).OrAnon()
|
u := user.FromRequest(rq)
|
||||||
|
|
||||||
//line templates/common.qtpl:25
|
//line templates/common.qtpl:25
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<nav class="navlinks">
|
|
||||||
<ul>
|
<nav class="hypha-tabs">
|
||||||
|
<ul class="hypha-tabs__flex">
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:28
|
//line templates/common.qtpl:29
|
||||||
for _, entry := range navEntries {
|
for _, entry := range navEntries {
|
||||||
//line templates/common.qtpl:29
|
//line templates/common.qtpl:30
|
||||||
if navType == "revision" && entry.path == "revision" {
|
if navType == "revision" && entry.path == "revision" {
|
||||||
//line templates/common.qtpl:29
|
|
||||||
qw422016.N().S(` <li><b>`)
|
|
||||||
//line templates/common.qtpl:30
|
//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])
|
qw422016.E().S(revisionHash[0])
|
||||||
//line templates/common.qtpl:30
|
|
||||||
qw422016.N().S(`</b></li>
|
|
||||||
`)
|
|
||||||
//line templates/common.qtpl:31
|
|
||||||
} else if navType == entry.path {
|
|
||||||
//line templates/common.qtpl:31
|
|
||||||
qw422016.N().S(` <li><b>`)
|
|
||||||
//line templates/common.qtpl:32
|
//line templates/common.qtpl:32
|
||||||
qw422016.E().S(entry.title)
|
|
||||||
//line templates/common.qtpl:32
|
|
||||||
qw422016.N().S(`</b></li>
|
|
||||||
`)
|
|
||||||
//line templates/common.qtpl:33
|
|
||||||
} else if entry.path != "revision" && u.Group.CanAccessRoute(entry.path) {
|
|
||||||
//line templates/common.qtpl:33
|
|
||||||
qw422016.N().S(` <li><a href="/`)
|
|
||||||
//line templates/common.qtpl:34
|
|
||||||
qw422016.E().S(entry.path)
|
|
||||||
//line templates/common.qtpl:34
|
|
||||||
qw422016.N().S(`/`)
|
|
||||||
//line templates/common.qtpl:34
|
|
||||||
qw422016.E().S(hyphaName)
|
|
||||||
//line templates/common.qtpl:34
|
|
||||||
qw422016.N().S(`">`)
|
|
||||||
//line templates/common.qtpl:34
|
|
||||||
qw422016.E().S(entry.title)
|
|
||||||
//line templates/common.qtpl:34
|
|
||||||
qw422016.N().S(`</a></li>
|
|
||||||
`)
|
|
||||||
//line templates/common.qtpl:35
|
|
||||||
}
|
|
||||||
//line templates/common.qtpl:36
|
|
||||||
}
|
|
||||||
//line templates/common.qtpl:36
|
|
||||||
qw422016.N().S(` `)
|
|
||||||
//line templates/common.qtpl:37
|
|
||||||
qw422016.N().S(userMenuHTML(u))
|
|
||||||
//line templates/common.qtpl:37
|
|
||||||
qw422016.N().S(`
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
`)
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
}
|
|
||||||
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
func writenavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
streamnavHTML(qw422016, rq, hyphaName, navType, revisionHash...)
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
qt422016.ReleaseWriter(qw422016)
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
}
|
|
||||||
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string {
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
writenavHTML(qb422016, rq, hyphaName, navType, revisionHash...)
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
qs422016 := string(qb422016.B)
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
return qs422016
|
|
||||||
//line templates/common.qtpl:40
|
|
||||||
}
|
|
||||||
|
|
||||||
//line templates/common.qtpl:42
|
|
||||||
func streamuserMenuHTML(qw422016 *qt422016.Writer, u *user.User) {
|
|
||||||
//line templates/common.qtpl:42
|
|
||||||
qw422016.N().S(`
|
|
||||||
<li class="navlinks__user">
|
|
||||||
`)
|
|
||||||
//line templates/common.qtpl:44
|
|
||||||
if u.Group == user.UserAnon {
|
|
||||||
//line templates/common.qtpl:44
|
|
||||||
qw422016.N().S(`
|
|
||||||
<a href="/login">Login</a>
|
|
||||||
`)
|
|
||||||
//line templates/common.qtpl:46
|
|
||||||
} else {
|
|
||||||
//line templates/common.qtpl:46
|
|
||||||
qw422016.N().S(`
|
|
||||||
<a href="/page/`)
|
|
||||||
//line templates/common.qtpl:47
|
|
||||||
qw422016.E().S(util.UserTree)
|
|
||||||
//line templates/common.qtpl:47
|
|
||||||
qw422016.N().S(`/`)
|
|
||||||
//line templates/common.qtpl:47
|
|
||||||
qw422016.E().S(u.Name)
|
|
||||||
//line templates/common.qtpl:47
|
|
||||||
qw422016.N().S(`">`)
|
|
||||||
//line templates/common.qtpl:47
|
|
||||||
qw422016.E().S(u.Name)
|
|
||||||
//line templates/common.qtpl:47
|
|
||||||
qw422016.N().S(`</a>
|
|
||||||
`)
|
|
||||||
//line templates/common.qtpl:48
|
|
||||||
}
|
|
||||||
//line templates/common.qtpl:48
|
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</li>
|
</li>
|
||||||
`)
|
`)
|
||||||
//line templates/common.qtpl:50
|
//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:50
|
//line templates/common.qtpl:46
|
||||||
func writeuserMenuHTML(qq422016 qtio422016.Writer, u *user.User) {
|
func writenavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
|
||||||
//line templates/common.qtpl:50
|
//line templates/common.qtpl:46
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line templates/common.qtpl:50
|
//line templates/common.qtpl:46
|
||||||
streamuserMenuHTML(qw422016, u)
|
streamnavHTML(qw422016, rq, hyphaName, navType, revisionHash...)
|
||||||
//line templates/common.qtpl:50
|
//line templates/common.qtpl:46
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line templates/common.qtpl:50
|
//line templates/common.qtpl:46
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/common.qtpl:50
|
//line templates/common.qtpl:46
|
||||||
func userMenuHTML(u *user.User) string {
|
func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string {
|
||||||
//line templates/common.qtpl:50
|
//line templates/common.qtpl:46
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line templates/common.qtpl:50
|
//line templates/common.qtpl:46
|
||||||
writeuserMenuHTML(qb422016, u)
|
writenavHTML(qb422016, rq, hyphaName, navType, revisionHash...)
|
||||||
//line templates/common.qtpl:50
|
//line templates/common.qtpl:46
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line templates/common.qtpl:50
|
//line templates/common.qtpl:46
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line templates/common.qtpl:50
|
//line templates/common.qtpl:46
|
||||||
return qs422016
|
return qs422016
|
||||||
//line templates/common.qtpl:50
|
//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
|
||||||
}
|
}
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
{% func DefaultCSS() %}
|
|
||||||
@media screen and (min-width: 700px) {
|
|
||||||
main {margin: 0 auto; width: 700px;}
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 700px) {
|
|
||||||
main {margin: 0; width: 100%;}
|
|
||||||
}
|
|
||||||
*, *::before, *::after {box-sizing: border-box;}
|
|
||||||
html {height:100%; padding:0; background-color:#ddd;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox='0 0 42 44' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='brick-wall' fill='%23bbbbbb' fill-opacity='0.4'%3E%3Cpath d='M0 0h42v44H0V0zm1 1h40v20H1V1zM0 23h20v20H0V23zm22 0h20v20H22V23z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");} /* heropatterns.com */
|
|
||||||
body {height:100%; margin:0; font-size:16px; font-family:sans-serif;}
|
|
||||||
main {padding:1rem; background-color: white; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); }
|
|
||||||
main > form {margin-bottom:1rem;}
|
|
||||||
textarea {font-size:15px;}
|
|
||||||
.edit {height:100%;}
|
|
||||||
.edit-form {height:90%;}
|
|
||||||
.edit-form textarea {width:100%;height:90%;}
|
|
||||||
|
|
||||||
main h1:not(.navi-title) {font-size:1.7rem;}
|
|
||||||
blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;}
|
|
||||||
.wikilink_new {color:#a55858;}
|
|
||||||
.wikilink_new:visited {color:#a55858;}
|
|
||||||
.wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;}
|
|
||||||
article code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; }
|
|
||||||
article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wrap; border-radius: .25rem;}
|
|
||||||
.codeblock code {padding:0; font-size:15px;}
|
|
||||||
.transclusion code, .transclusion .codeblock {background-color:#ddd;}
|
|
||||||
.transclusion {background-color:#eee; border-radius: .25rem; }
|
|
||||||
.transclusion__content > *:not(.binary-container) {margin: 0.5rem; }
|
|
||||||
.transclusion__link {display: block; text-align: right; font-style: italic; margin-top: 0.5rem; color: black; text-decoration: none;}
|
|
||||||
.transclusion__link::before {content: "⇐ ";}
|
|
||||||
|
|
||||||
.binary-container_with-img img,
|
|
||||||
.binary-container_with-video video,
|
|
||||||
.binary-container_with-audio audio {width: 100%}
|
|
||||||
.navi-title a {text-decoration:none;}
|
|
||||||
.img-gallery { text-align: center; margin-top: .25rem; }
|
|
||||||
.img-gallery_many-images { background-color: #eee; border-radius: .25rem; padding: .5rem; }
|
|
||||||
.img-gallery img { max-width: 100%; max-height: 50vh; }
|
|
||||||
figure { margin: 0; }
|
|
||||||
figcaption { padding-bottom: .5rem; }
|
|
||||||
|
|
||||||
nav ul {display:flex; padding-left:0; flex-wrap:wrap; margin-top:0;}
|
|
||||||
nav ul li {list-style-type:none;margin-right:1rem;}
|
|
||||||
|
|
||||||
#new-name {width:100%;}
|
|
||||||
.navlinks__user {font-style:italic;}
|
|
||||||
|
|
||||||
.rc-entry { display: grid; list-style-type: none; padding: .25rem; background-color: #eee; grid-template-columns: 1fr 1fr; }
|
|
||||||
.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__author { font-style: italic; }
|
|
||||||
{% endfunc %}
|
|
@ -1,104 +0,0 @@
|
|||||||
// Code generated by qtc from "css.qtpl". DO NOT EDIT.
|
|
||||||
// See https://github.com/valyala/quicktemplate for details.
|
|
||||||
|
|
||||||
//line templates/css.qtpl:1
|
|
||||||
package templates
|
|
||||||
|
|
||||||
//line templates/css.qtpl:1
|
|
||||||
import (
|
|
||||||
qtio422016 "io"
|
|
||||||
|
|
||||||
qt422016 "github.com/valyala/quicktemplate"
|
|
||||||
)
|
|
||||||
|
|
||||||
//line templates/css.qtpl:1
|
|
||||||
var (
|
|
||||||
_ = qtio422016.Copy
|
|
||||||
_ = qt422016.AcquireByteBuffer
|
|
||||||
)
|
|
||||||
|
|
||||||
//line templates/css.qtpl:1
|
|
||||||
func StreamDefaultCSS(qw422016 *qt422016.Writer) {
|
|
||||||
//line templates/css.qtpl:1
|
|
||||||
qw422016.N().S(`
|
|
||||||
@media screen and (min-width: 700px) {
|
|
||||||
main {margin: 0 auto; width: 700px;}
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 700px) {
|
|
||||||
main {margin: 0; width: 100%;}
|
|
||||||
}
|
|
||||||
*, *::before, *::after {box-sizing: border-box;}
|
|
||||||
html {height:100%; padding:0; background-color:#ddd;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox='0 0 42 44' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='brick-wall' fill='%23bbbbbb' fill-opacity='0.4'%3E%3Cpath d='M0 0h42v44H0V0zm1 1h40v20H1V1zM0 23h20v20H0V23zm22 0h20v20H22V23z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");} /* heropatterns.com */
|
|
||||||
body {height:100%; margin:0; font-size:16px; font-family:sans-serif;}
|
|
||||||
main {padding:1rem; background-color: white; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2); }
|
|
||||||
main > form {margin-bottom:1rem;}
|
|
||||||
textarea {font-size:15px;}
|
|
||||||
.edit {height:100%;}
|
|
||||||
.edit-form {height:90%;}
|
|
||||||
.edit-form textarea {width:100%;height:90%;}
|
|
||||||
|
|
||||||
main h1:not(.navi-title) {font-size:1.7rem;}
|
|
||||||
blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;}
|
|
||||||
.wikilink_new {color:#a55858;}
|
|
||||||
.wikilink_new:visited {color:#a55858;}
|
|
||||||
.wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;}
|
|
||||||
article code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; }
|
|
||||||
article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wrap; border-radius: .25rem;}
|
|
||||||
.codeblock code {padding:0; font-size:15px;}
|
|
||||||
.transclusion code, .transclusion .codeblock {background-color:#ddd;}
|
|
||||||
.transclusion {background-color:#eee; border-radius: .25rem; }
|
|
||||||
.transclusion__content > *:not(.binary-container) {margin: 0.5rem; }
|
|
||||||
.transclusion__link {display: block; text-align: right; font-style: italic; margin-top: 0.5rem; color: black; text-decoration: none;}
|
|
||||||
.transclusion__link::before {content: "⇐ ";}
|
|
||||||
|
|
||||||
.binary-container_with-img img,
|
|
||||||
.binary-container_with-video video,
|
|
||||||
.binary-container_with-audio audio {width: 100%}
|
|
||||||
.navi-title a {text-decoration:none;}
|
|
||||||
.img-gallery { text-align: center; margin-top: .25rem; }
|
|
||||||
.img-gallery_many-images { background-color: #eee; border-radius: .25rem; padding: .5rem; }
|
|
||||||
.img-gallery img { max-width: 100%; max-height: 50vh; }
|
|
||||||
figure { margin: 0; }
|
|
||||||
figcaption { padding-bottom: .5rem; }
|
|
||||||
|
|
||||||
nav ul {display:flex; padding-left:0; flex-wrap:wrap; margin-top:0;}
|
|
||||||
nav ul li {list-style-type:none;margin-right:1rem;}
|
|
||||||
|
|
||||||
#new-name {width:100%;}
|
|
||||||
.navlinks__user {font-style:italic;}
|
|
||||||
|
|
||||||
.rc-entry { display: grid; list-style-type: none; padding: .25rem; background-color: #eee; grid-template-columns: 1fr 1fr; }
|
|
||||||
.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__author { font-style: italic; }
|
|
||||||
`)
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
}
|
|
||||||
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
func WriteDefaultCSS(qq422016 qtio422016.Writer) {
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
StreamDefaultCSS(qw422016)
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
qt422016.ReleaseWriter(qw422016)
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
}
|
|
||||||
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
func DefaultCSS() string {
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
WriteDefaultCSS(qb422016)
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
qs422016 := string(qb422016.B)
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
return qs422016
|
|
||||||
//line templates/css.qtpl:54
|
|
||||||
}
|
|
183
templates/default.css
Normal file
183
templates/default.css
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/* 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; }
|
||||||
|
.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; }
|
||||||
|
}
|
||||||
|
@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; }
|
||||||
|
}
|
||||||
|
*, *::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; }
|
||||||
|
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__preview { border: 2px dashed #ddd; }
|
||||||
|
.edit-form {height:90%;}
|
||||||
|
.edit-form textarea {width:100%;height:90%;}
|
||||||
|
.edit-form__save { font-weight: bold; }
|
||||||
|
.icon {margin-right: .25rem; vertical-align: bottom; }
|
||||||
|
|
||||||
|
main h1:not(.navi-title) {font-size:1.7rem;}
|
||||||
|
blockquote { margin-left: 0; padding-left: 1rem; }
|
||||||
|
.wikilink_external::before { display: inline-block; width: 18px; height: 16px; vertical-align: sub; }
|
||||||
|
/* .wikilink_external { padding-left: 16px; } */
|
||||||
|
.wikilink_gopher::before { content: url("/static/icon/gopher"); }
|
||||||
|
.wikilink_http::before { content: url("/static/icon/http"); }
|
||||||
|
.wikilink_https::before { content: url("/static/icon/http"); }
|
||||||
|
/* .wikilink_https { background: transparent url("/static/icon/http") center left no-repeat; } */
|
||||||
|
.wikilink_gemini::before { content: url("/static/icon/gemini"); }
|
||||||
|
.wikilink_mailto::before { content: url("/static/icon/mailto"); }
|
||||||
|
|
||||||
|
article { overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; line-height: 150%; }
|
||||||
|
article h1, article h2, article h3, article h4, article h5, article h6 { margin: 1.5rem 0 0 0; }
|
||||||
|
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%; }
|
||||||
|
article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25rem;}
|
||||||
|
.codeblock code {padding:0; font-size:15px;}
|
||||||
|
.transclusion { border-radius: .25rem; }
|
||||||
|
.transclusion__content > *:not(.binary-container) {margin: 0.5rem; }
|
||||||
|
.transclusion__link {display: block; text-align: right; font-style: italic; margin-top: .5rem; margin-right: .25rem; text-decoration: none;}
|
||||||
|
.transclusion__link::before {content: "⇐ ";}
|
||||||
|
|
||||||
|
/* Derived from https://commons.wikimedia.org/wiki/File:U%2B21D2.svg */
|
||||||
|
.launchpad__entry { list-style-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.0' width='25' height='12'%3E%3Cg transform='scale(0.7,0.8) translate(-613.21429,-421)'%3E%3Cpath fill='%23999' d='M 638.06773,429.49751 L 631.01022,436.87675 L 630.1898,436.02774 L 632.416,433.30375 L 613.46876,433.30375 L 613.46876,431.66382 L 633.82089,431.66382 L 635.57789,429.5261 L 633.79229,427.35979 L 613.46876,427.35979 L 613.46876,425.71985 L 632.416,425.71985 L 630.1898,422.99587 L 631.01022,422.08788 L 638.06773,429.49751 z '/%3E%3C/g%3E%3C/svg%3E"); }
|
||||||
|
|
||||||
|
.binary-container_with-img img,
|
||||||
|
.binary-container_with-video video,
|
||||||
|
.binary-container_with-audio audio {width: 100%}
|
||||||
|
|
||||||
|
.navi-title { padding-bottom: .5rem; margin: .25rem 0; }
|
||||||
|
.navi-title a {text-decoration:none; }
|
||||||
|
.navi-title__separator { margin: 0 .25rem; }
|
||||||
|
.navi-title__colon { margin-right: .5rem; }
|
||||||
|
.upload-amnt { clear: both; padding: .5rem; border-radius: .25rem; }
|
||||||
|
.upload-amnt__unattach { display: block; }
|
||||||
|
aside { clear: both; }
|
||||||
|
|
||||||
|
.img-gallery { text-align: center; margin-top: .25rem; margin-bottom: .25rem; }
|
||||||
|
.img-gallery_many-images { border-radius: .25rem; padding: .5rem; }
|
||||||
|
.img-gallery img { max-width: 100%; max-height: 50vh; }
|
||||||
|
figure { margin: 0; }
|
||||||
|
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__time { font-style: italic; }
|
||||||
|
.rc-entry__hash { font-style: italic; text-align: right; }
|
||||||
|
.rc-entry__links { grid-column: 1 / span 2; }
|
||||||
|
.rc-entry__author { font-style: italic; }
|
||||||
|
|
||||||
|
.prevnext__el { display: block-inline; min-width: 40%; padding: .5rem; margin-bottom: .25rem; text-decoration: none; border-radius: .25rem; }
|
||||||
|
.prevnext__prev { float: left; }
|
||||||
|
.prevnext__next { float: right; text-align: right; }
|
||||||
|
|
||||||
|
.page-separator { clear: both; }
|
||||||
|
.history__entries { background-color: #eee; margin: 0; padding: 0; border-radius: .25rem; }
|
||||||
|
.history__month-anchor { text-decoration: none; color: inherit; }
|
||||||
|
.history__entry { list-style-type: none; padding: .25rem; }
|
||||||
|
.history-entry { padding: .25rem; }
|
||||||
|
.history-entry__time { font-weight: bold; }
|
||||||
|
.history-entry__author { font-style: italic; }
|
||||||
|
|
||||||
|
table { border: #ddd 1px solid; border-radius: .25rem; min-width: 4rem; }
|
||||||
|
td { padding: .25rem; }
|
||||||
|
caption { caption-side: top; font-size: small; }
|
||||||
|
|
||||||
|
/* Color stuff */
|
||||||
|
/* Lighter stuff #eee */
|
||||||
|
article code,
|
||||||
|
article .codeblock,
|
||||||
|
.transclusion,
|
||||||
|
.img-gallery_many-images,
|
||||||
|
.rc-entry,
|
||||||
|
.prevnext__el,
|
||||||
|
table { background-color: #eee; }
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.hypha-tabs { background-color: white; }
|
||||||
|
.hypha-tabs__tab { box-shadow: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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");
|
||||||
|
} /* heropatterns.com */
|
||||||
|
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; }
|
||||||
|
|
||||||
|
blockquote { border-left: 4px black solid; }
|
||||||
|
.wikilink_new {color:#a55858;}
|
||||||
|
.transclusion code, .transclusion .codeblock {background-color:#ddd;}
|
||||||
|
.transclusion__link { color: black; }
|
||||||
|
.wikilink_new:visited {color:#a55858;}
|
||||||
|
.navi-title { border-bottom: #eee 1px solid; }
|
||||||
|
.upload-amnt { border: #eee 1px solid; }
|
||||||
|
td { border: #ddd 1px solid; }
|
||||||
|
|
||||||
|
/* Dark theme! */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
html { background: #222; color: #ddd; }
|
||||||
|
main, article, .hypha-tabs__tab, header { background-color: #343434; color: #ddd; }
|
||||||
|
|
||||||
|
a, .wikilink_external { color: #f1fa8c; }
|
||||||
|
a:visited, .wikilink_external:visited { color: #ffb86c; }
|
||||||
|
.wikilink_new, .wikilink_new:visited { color: #dd4444; }
|
||||||
|
|
||||||
|
.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; }
|
||||||
|
|
||||||
|
blockquote { border-left: 4px #ddd solid; }
|
||||||
|
|
||||||
|
.transclusion .transclusion__link { color: #ddd; }
|
||||||
|
article code,
|
||||||
|
article .codeblock,
|
||||||
|
.transclusion,
|
||||||
|
.img-gallery_many-images,
|
||||||
|
.rc-entry,
|
||||||
|
.history__entry,
|
||||||
|
.prevnext__el,
|
||||||
|
.upload-amnt,
|
||||||
|
textarea,
|
||||||
|
table { border: 0; background-color: #444444; color: #ddd; }
|
||||||
|
.transclusion code,
|
||||||
|
.transclusion .codeblock { background-color: #454545; }
|
||||||
|
mark { background: rgba(130, 80, 30, 5); color: inherit; }
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.hypha-tabs { background-color: #232323; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
This dialog is to be shown to a user when they try to delete a hypha.
|
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) %}
|
{% func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
|
||||||
<main>
|
|
||||||
{%= navHTML(rq, hyphaName, "delete-ask") %}
|
{%= navHTML(rq, hyphaName, "delete-ask") %}
|
||||||
|
<main>
|
||||||
{% if isOld %}
|
{% if isOld %}
|
||||||
<section>
|
<section>
|
||||||
<h1>Delete {%s hyphaName %}?</h1>
|
<h1>Delete {%s hyphaName %}?</h1>
|
||||||
|
@ -26,12 +26,12 @@ var (
|
|||||||
func StreamDeleteAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
func StreamDeleteAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
||||||
//line templates/delete.qtpl:4
|
//line templates/delete.qtpl:4
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<main>
|
|
||||||
`)
|
`)
|
||||||
//line templates/delete.qtpl:6
|
//line templates/delete.qtpl:5
|
||||||
streamnavHTML(qw422016, rq, hyphaName, "delete-ask")
|
streamnavHTML(qw422016, rq, hyphaName, "delete-ask")
|
||||||
//line templates/delete.qtpl:6
|
//line templates/delete.qtpl:5
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
<main>
|
||||||
`)
|
`)
|
||||||
//line templates/delete.qtpl:7
|
//line templates/delete.qtpl:7
|
||||||
if isOld {
|
if isOld {
|
||||||
|
@ -1,16 +1,35 @@
|
|||||||
{% import "net/http" %}
|
{% import "net/http" %}
|
||||||
|
|
||||||
{% func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) %}
|
{% func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) %}
|
||||||
<main class="edit">
|
|
||||||
{%s= navHTML(rq, hyphaName, "edit") %}
|
{%s= navHTML(rq, hyphaName, "edit") %}
|
||||||
|
<main class="edit edit_no-preview">
|
||||||
<h1>Edit {%s hyphaName %}</h1>
|
<h1>Edit {%s hyphaName %}</h1>
|
||||||
{%s= warning %}
|
{%s= warning %}
|
||||||
<form method="post" class="edit-form"
|
<form method="post" class="edit-form"
|
||||||
action="/upload-text/{%s hyphaName %}">
|
action="/upload-text/{%s hyphaName %}">
|
||||||
<textarea name="text">{%s textAreaFill %}</textarea>
|
<textarea name="text">{%s textAreaFill %}</textarea>
|
||||||
<br/>
|
<br/>
|
||||||
<input type="submit"/>
|
<input type="submit" name="action" value="Save" class="edit-form__save"/>
|
||||||
<a href="/page/{%s hyphaName %}">Cancel</a>
|
<input type="submit" name="action" value="Preview" class="edit-form__preview">
|
||||||
|
<a href="/page/{%s hyphaName %}" class="edit-form__cancel">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
{% endfunc %}
|
{% 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= warning %}
|
||||||
|
<form method="post" class="edit-form"
|
||||||
|
action="/upload-text/{%s hyphaName %}">
|
||||||
|
<textarea name="text">{%s textAreaFill %}</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/{%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>
|
||||||
|
</main>
|
||||||
|
{% endfunc %}
|
||||||
|
@ -24,12 +24,12 @@ var (
|
|||||||
func StreamEditHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
|
func StreamEditHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
|
||||||
//line templates/http_mutators.qtpl:3
|
//line templates/http_mutators.qtpl:3
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<main class="edit">
|
|
||||||
`)
|
`)
|
||||||
//line templates/http_mutators.qtpl:5
|
//line templates/http_mutators.qtpl:4
|
||||||
qw422016.N().S(navHTML(rq, hyphaName, "edit"))
|
qw422016.N().S(navHTML(rq, hyphaName, "edit"))
|
||||||
//line templates/http_mutators.qtpl:5
|
//line templates/http_mutators.qtpl:4
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
<main class="edit edit_no-preview">
|
||||||
<h1>Edit `)
|
<h1>Edit `)
|
||||||
//line templates/http_mutators.qtpl:6
|
//line templates/http_mutators.qtpl:6
|
||||||
qw422016.E().S(hyphaName)
|
qw422016.E().S(hyphaName)
|
||||||
@ -52,40 +52,118 @@ func StreamEditHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, text
|
|||||||
//line templates/http_mutators.qtpl:10
|
//line templates/http_mutators.qtpl:10
|
||||||
qw422016.N().S(`</textarea>
|
qw422016.N().S(`</textarea>
|
||||||
<br/>
|
<br/>
|
||||||
<input type="submit"/>
|
<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/`)
|
<a href="/page/`)
|
||||||
//line templates/http_mutators.qtpl:13
|
//line templates/http_mutators.qtpl:14
|
||||||
qw422016.E().S(hyphaName)
|
qw422016.E().S(hyphaName)
|
||||||
//line templates/http_mutators.qtpl:13
|
//line templates/http_mutators.qtpl:14
|
||||||
qw422016.N().S(`">Cancel</a>
|
qw422016.N().S(`" class="edit-form__cancel">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
`)
|
`)
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
func WriteEditHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
|
func WriteEditHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
StreamEditHTML(qw422016, rq, hyphaName, textAreaFill, warning)
|
StreamEditHTML(qw422016, rq, hyphaName, textAreaFill, warning)
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) string {
|
func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) string {
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
WriteEditHTML(qb422016, rq, hyphaName, textAreaFill, warning)
|
WriteEditHTML(qb422016, rq, hyphaName, textAreaFill, warning)
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
return qs422016
|
return qs422016
|
||||||
//line templates/http_mutators.qtpl:16
|
//line templates/http_mutators.qtpl:17
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/http_mutators.qtpl:19
|
||||||
|
func StreamPreviewHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) {
|
||||||
|
//line templates/http_mutators.qtpl:19
|
||||||
|
qw422016.N().S(`
|
||||||
|
`)
|
||||||
|
//line templates/http_mutators.qtpl:20
|
||||||
|
qw422016.N().S(navHTML(rq, hyphaName, "edit"))
|
||||||
|
//line templates/http_mutators.qtpl:20
|
||||||
|
qw422016.N().S(`
|
||||||
|
<main class="edit edit_with-preview">
|
||||||
|
<h1>Edit `)
|
||||||
|
//line templates/http_mutators.qtpl:22
|
||||||
|
qw422016.E().S(hyphaName)
|
||||||
|
//line templates/http_mutators.qtpl:22
|
||||||
|
qw422016.N().S(` (preview)</h1>
|
||||||
|
`)
|
||||||
|
//line templates/http_mutators.qtpl:23
|
||||||
|
qw422016.N().S(warning)
|
||||||
|
//line templates/http_mutators.qtpl:23
|
||||||
|
qw422016.N().S(`
|
||||||
|
<form method="post" class="edit-form"
|
||||||
|
action="/upload-text/`)
|
||||||
|
//line templates/http_mutators.qtpl:25
|
||||||
|
qw422016.E().S(hyphaName)
|
||||||
|
//line templates/http_mutators.qtpl:25
|
||||||
|
qw422016.N().S(`">
|
||||||
|
<textarea name="text">`)
|
||||||
|
//line templates/http_mutators.qtpl:26
|
||||||
|
qw422016.E().S(textAreaFill)
|
||||||
|
//line templates/http_mutators.qtpl:26
|
||||||
|
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
|
||||||
|
qw422016.E().S(hyphaName)
|
||||||
|
//line templates/http_mutators.qtpl:30
|
||||||
|
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
|
||||||
|
qw422016.N().S(renderedPage)
|
||||||
|
//line templates/http_mutators.qtpl:33
|
||||||
|
qw422016.N().S(`</section>
|
||||||
|
</main>
|
||||||
|
`)
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
func WritePreviewHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) {
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
StreamPreviewHTML(qw422016, rq, hyphaName, textAreaFill, warning, renderedPage)
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
qt422016.ReleaseWriter(qw422016)
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
}
|
||||||
|
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
func PreviewHTML(rq *http.Request, hyphaName, textAreaFill, warning string, renderedPage string) string {
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
WritePreviewHTML(qb422016, rq, hyphaName, textAreaFill, warning, renderedPage)
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
qs422016 := string(qb422016.B)
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
|
return qs422016
|
||||||
|
//line templates/http_mutators.qtpl:35
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,20 @@
|
|||||||
{% import "net/http" %}
|
{% import "net/http" %}
|
||||||
|
{% import "path" %}
|
||||||
{% import "github.com/bouncepaw/mycorrhiza/user" %}
|
{% import "github.com/bouncepaw/mycorrhiza/user" %}
|
||||||
|
|
||||||
{% func HistoryHTML(rq *http.Request, hyphaName, tbody string) %}
|
{% func HistoryHTML(rq *http.Request, hyphaName, list string) %}
|
||||||
<main>
|
|
||||||
{%= navHTML(rq, hyphaName, "history") %}
|
{%= navHTML(rq, hyphaName, "history") %}
|
||||||
<table>
|
<main>
|
||||||
<thead>
|
<article class="history">
|
||||||
<tr>
|
<h1>History of {%s hyphaName %}</h1>
|
||||||
<th>Time</th>
|
{%s= list %}
|
||||||
<th>Hash</th>
|
</article>
|
||||||
<th>Message</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{%s= tbody %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</main>
|
</main>
|
||||||
{% endfunc %}
|
{% endfunc %}
|
||||||
|
|
||||||
{% func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) %}
|
{% func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) %}
|
||||||
<main>
|
|
||||||
{%= navHTML(rq, hyphaName, "revision", revHash) %}
|
{%= navHTML(rq, hyphaName, "revision", revHash) %}
|
||||||
|
<main>
|
||||||
<article>
|
<article>
|
||||||
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
|
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
|
||||||
{%s= naviTitle %}
|
{%s= naviTitle %}
|
||||||
@ -35,9 +28,9 @@
|
|||||||
{% endfunc %}
|
{% endfunc %}
|
||||||
|
|
||||||
If `contents` == "", a helpful message is shown instead.
|
If `contents` == "", a helpful message is shown instead.
|
||||||
{% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree string) %}
|
{% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) %}
|
||||||
<main>
|
|
||||||
{%= navHTML(rq, hyphaName, "page") %}
|
{%= navHTML(rq, hyphaName, "page") %}
|
||||||
|
<main>
|
||||||
<article>
|
<article>
|
||||||
{%s= naviTitle %}
|
{%s= naviTitle %}
|
||||||
{% if contents == "" %}
|
{% if contents == "" %}
|
||||||
@ -46,16 +39,26 @@ If `contents` == "", a helpful message is shown instead.
|
|||||||
{%s= contents %}
|
{%s= contents %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</article>
|
</article>
|
||||||
<hr/>
|
<section class="prevnext">
|
||||||
{% if u := user.FromRequest(rq).OrAnon(); !user.AuthUsed || u.Group > user.UserAnon %}
|
{% 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 %}"
|
<form action="/upload-binary/{%s hyphaName %}"
|
||||||
method="post" enctype="multipart/form-data">
|
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>
|
<label for="upload-binary__input">Upload a new attachment</label>
|
||||||
<br>
|
<br>
|
||||||
<input type="file" id="upload-binary__input" name="binary"/>
|
<input type="file" id="upload-binary__input" name="binary"/>
|
||||||
<input type="submit"/>
|
<input type="submit"/>
|
||||||
</form>
|
</form>
|
||||||
<hr/>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<aside>
|
<aside>
|
||||||
{%s= tree %}
|
{%s= tree %}
|
||||||
|
@ -8,188 +8,226 @@ package templates
|
|||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:2
|
//line templates/http_readers.qtpl:2
|
||||||
|
import "path"
|
||||||
|
|
||||||
|
//line templates/http_readers.qtpl:3
|
||||||
import "github.com/bouncepaw/mycorrhiza/user"
|
import "github.com/bouncepaw/mycorrhiza/user"
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:4
|
//line templates/http_readers.qtpl:5
|
||||||
import (
|
import (
|
||||||
qtio422016 "io"
|
qtio422016 "io"
|
||||||
|
|
||||||
qt422016 "github.com/valyala/quicktemplate"
|
qt422016 "github.com/valyala/quicktemplate"
|
||||||
)
|
)
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:4
|
//line templates/http_readers.qtpl:5
|
||||||
var (
|
var (
|
||||||
_ = qtio422016.Copy
|
_ = qtio422016.Copy
|
||||||
_ = qt422016.AcquireByteBuffer
|
_ = qt422016.AcquireByteBuffer
|
||||||
)
|
)
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:4
|
//line templates/http_readers.qtpl:5
|
||||||
func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, tbody string) {
|
func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, list string) {
|
||||||
//line templates/http_readers.qtpl:4
|
//line templates/http_readers.qtpl:5
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<main>
|
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:6
|
//line templates/http_readers.qtpl:6
|
||||||
streamnavHTML(qw422016, rq, hyphaName, "history")
|
streamnavHTML(qw422016, rq, hyphaName, "history")
|
||||||
//line templates/http_readers.qtpl:6
|
//line templates/http_readers.qtpl:6
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<table>
|
<main>
|
||||||
<thead>
|
<article class="history">
|
||||||
<tr>
|
<h1>History of `)
|
||||||
<th>Time</th>
|
//line templates/http_readers.qtpl:9
|
||||||
<th>Hash</th>
|
qw422016.E().S(hyphaName)
|
||||||
<th>Message</th>
|
//line templates/http_readers.qtpl:9
|
||||||
</tr>
|
qw422016.N().S(`</h1>
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:16
|
//line templates/http_readers.qtpl:10
|
||||||
qw422016.N().S(tbody)
|
qw422016.N().S(list)
|
||||||
//line templates/http_readers.qtpl:16
|
//line templates/http_readers.qtpl:10
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</tbody>
|
</article>
|
||||||
</table>
|
|
||||||
</main>
|
</main>
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, tbody string) {
|
func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, list string) {
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
StreamHistoryHTML(qw422016, rq, hyphaName, tbody)
|
StreamHistoryHTML(qw422016, rq, hyphaName, list)
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
func HistoryHTML(rq *http.Request, hyphaName, tbody string) string {
|
func HistoryHTML(rq *http.Request, hyphaName, list string) string {
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
WriteHistoryHTML(qb422016, rq, hyphaName, tbody)
|
WriteHistoryHTML(qb422016, rq, hyphaName, list)
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
return qs422016
|
return qs422016
|
||||||
//line templates/http_readers.qtpl:20
|
//line templates/http_readers.qtpl:13
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:22
|
//line templates/http_readers.qtpl:15
|
||||||
func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) {
|
func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) {
|
||||||
//line templates/http_readers.qtpl:22
|
//line templates/http_readers.qtpl: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(`
|
qw422016.N().S(`
|
||||||
<main>
|
<main>
|
||||||
`)
|
|
||||||
//line templates/http_readers.qtpl:24
|
|
||||||
streamnavHTML(qw422016, rq, hyphaName, "revision", revHash)
|
|
||||||
//line templates/http_readers.qtpl:24
|
|
||||||
qw422016.N().S(`
|
|
||||||
<article>
|
<article>
|
||||||
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
|
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:27
|
//line templates/http_readers.qtpl:20
|
||||||
qw422016.N().S(naviTitle)
|
qw422016.N().S(naviTitle)
|
||||||
//line templates/http_readers.qtpl:27
|
//line templates/http_readers.qtpl:20
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:28
|
//line templates/http_readers.qtpl:21
|
||||||
qw422016.N().S(contents)
|
qw422016.N().S(contents)
|
||||||
//line templates/http_readers.qtpl:28
|
//line templates/http_readers.qtpl:21
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</article>
|
</article>
|
||||||
<hr/>
|
<hr/>
|
||||||
<aside>
|
<aside>
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:32
|
//line templates/http_readers.qtpl:25
|
||||||
qw422016.N().S(tree)
|
qw422016.N().S(tree)
|
||||||
//line templates/http_readers.qtpl:32
|
//line templates/http_readers.qtpl:25
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</aside>
|
</aside>
|
||||||
</main>
|
</main>
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) {
|
func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) {
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
StreamRevisionHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, revHash)
|
StreamRevisionHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, revHash)
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) string {
|
func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) string {
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
WriteRevisionHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, revHash)
|
WriteRevisionHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, revHash)
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
return qs422016
|
return qs422016
|
||||||
//line templates/http_readers.qtpl:35
|
//line templates/http_readers.qtpl:28
|
||||||
}
|
}
|
||||||
|
|
||||||
// If `contents` == "", a helpful message is shown instead.
|
// If `contents` == "", a helpful message is shown instead.
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:38
|
//line templates/http_readers.qtpl:31
|
||||||
func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree string) {
|
func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) {
|
||||||
//line templates/http_readers.qtpl:38
|
//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(`
|
qw422016.N().S(`
|
||||||
<main>
|
<main>
|
||||||
`)
|
|
||||||
//line templates/http_readers.qtpl:40
|
|
||||||
streamnavHTML(qw422016, rq, hyphaName, "page")
|
|
||||||
//line templates/http_readers.qtpl:40
|
|
||||||
qw422016.N().S(`
|
|
||||||
<article>
|
<article>
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:42
|
//line templates/http_readers.qtpl:35
|
||||||
qw422016.N().S(naviTitle)
|
qw422016.N().S(naviTitle)
|
||||||
//line templates/http_readers.qtpl:42
|
//line templates/http_readers.qtpl:35
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:43
|
//line templates/http_readers.qtpl:36
|
||||||
if contents == "" {
|
if contents == "" {
|
||||||
//line templates/http_readers.qtpl:43
|
//line templates/http_readers.qtpl:36
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<p>This hypha has no text. Why not <a href="/edit/`)
|
<p>This hypha has no text. Why not <a href="/edit/`)
|
||||||
//line templates/http_readers.qtpl:44
|
//line templates/http_readers.qtpl:37
|
||||||
qw422016.E().S(hyphaName)
|
qw422016.E().S(hyphaName)
|
||||||
//line templates/http_readers.qtpl:44
|
//line templates/http_readers.qtpl:37
|
||||||
qw422016.N().S(`">create it</a>?</p>
|
qw422016.N().S(`">create it</a>?</p>
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:45
|
//line templates/http_readers.qtpl:38
|
||||||
} else {
|
} else {
|
||||||
//line templates/http_readers.qtpl:45
|
//line templates/http_readers.qtpl:38
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:46
|
//line templates/http_readers.qtpl:39
|
||||||
qw422016.N().S(contents)
|
qw422016.N().S(contents)
|
||||||
//line templates/http_readers.qtpl:46
|
//line templates/http_readers.qtpl:39
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:47
|
//line templates/http_readers.qtpl:40
|
||||||
}
|
}
|
||||||
//line templates/http_readers.qtpl:47
|
//line templates/http_readers.qtpl:40
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</article>
|
</article>
|
||||||
<hr/>
|
<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
|
//line templates/http_readers.qtpl:50
|
||||||
if u := user.FromRequest(rq).OrAnon(); !user.AuthUsed || u.Group > user.UserAnon {
|
if u := user.FromRequest(rq); !user.AuthUsed || u.Group != "anon" {
|
||||||
//line templates/http_readers.qtpl:50
|
//line templates/http_readers.qtpl:50
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<form action="/upload-binary/`)
|
<form action="/upload-binary/`)
|
||||||
@ -197,52 +235,67 @@ func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navi
|
|||||||
qw422016.E().S(hyphaName)
|
qw422016.E().S(hyphaName)
|
||||||
//line templates/http_readers.qtpl:51
|
//line templates/http_readers.qtpl:51
|
||||||
qw422016.N().S(`"
|
qw422016.N().S(`"
|
||||||
method="post" enctype="multipart/form-data">
|
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>
|
<label for="upload-binary__input">Upload a new attachment</label>
|
||||||
<br>
|
<br>
|
||||||
<input type="file" id="upload-binary__input" name="binary"/>
|
<input type="file" id="upload-binary__input" name="binary"/>
|
||||||
<input type="submit"/>
|
<input type="submit"/>
|
||||||
</form>
|
</form>
|
||||||
<hr/>
|
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:59
|
//line templates/http_readers.qtpl:62
|
||||||
}
|
}
|
||||||
//line templates/http_readers.qtpl:59
|
//line templates/http_readers.qtpl:62
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<aside>
|
<aside>
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:61
|
//line templates/http_readers.qtpl:64
|
||||||
qw422016.N().S(tree)
|
qw422016.N().S(tree)
|
||||||
//line templates/http_readers.qtpl:61
|
//line templates/http_readers.qtpl:64
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</aside>
|
</aside>
|
||||||
</main>
|
</main>
|
||||||
`)
|
`)
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree string) {
|
func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) {
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree)
|
StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt)
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree string) string {
|
func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) string {
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree)
|
WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt)
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
return qs422016
|
return qs422016
|
||||||
//line templates/http_readers.qtpl:64
|
//line templates/http_readers.qtpl:67
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,26 @@
|
|||||||
{% func BaseHTML(title, body string) %}
|
{% import "github.com/bouncepaw/mycorrhiza/util" %}
|
||||||
|
{% import "github.com/bouncepaw/mycorrhiza/user" %}
|
||||||
|
|
||||||
|
{% func BaseHTML(title, body string, u *user.User, headElements ...string) %}
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/common.css">
|
<link rel="stylesheet" type="text/css" href="/static/common.css">
|
||||||
<title>{%s title %}</title>
|
<title>{%s title %}</title>
|
||||||
|
{% for _, el := range headElements %}{%s= el %}{% endfor %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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 %}
|
{%s= body %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -40,3 +54,25 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfunc %}
|
{% 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 %}
|
||||||
|
@ -5,21 +5,27 @@
|
|||||||
package templates
|
package templates
|
||||||
|
|
||||||
//line templates/http_stuff.qtpl:1
|
//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 (
|
import (
|
||||||
qtio422016 "io"
|
qtio422016 "io"
|
||||||
|
|
||||||
qt422016 "github.com/valyala/quicktemplate"
|
qt422016 "github.com/valyala/quicktemplate"
|
||||||
)
|
)
|
||||||
|
|
||||||
//line templates/http_stuff.qtpl:1
|
//line templates/http_stuff.qtpl:4
|
||||||
var (
|
var (
|
||||||
_ = qtio422016.Copy
|
_ = qtio422016.Copy
|
||||||
_ = qt422016.AcquireByteBuffer
|
_ = qt422016.AcquireByteBuffer
|
||||||
)
|
)
|
||||||
|
|
||||||
//line templates/http_stuff.qtpl:1
|
//line templates/http_stuff.qtpl:4
|
||||||
func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string) {
|
func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User, headElements ...string) {
|
||||||
//line templates/http_stuff.qtpl:1
|
//line templates/http_stuff.qtpl:4
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
@ -27,59 +33,96 @@ func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string) {
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/common.css">
|
<link rel="stylesheet" type="text/css" href="/static/common.css">
|
||||||
<title>`)
|
<title>`)
|
||||||
//line templates/http_stuff.qtpl:7
|
//line templates/http_stuff.qtpl:10
|
||||||
qw422016.E().S(title)
|
qw422016.E().S(title)
|
||||||
//line templates/http_stuff.qtpl:7
|
//line templates/http_stuff.qtpl:10
|
||||||
qw422016.N().S(`</title>
|
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>
|
</head>
|
||||||
<body>
|
<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:10
|
//line templates/http_stuff.qtpl:24
|
||||||
qw422016.N().S(body)
|
qw422016.N().S(body)
|
||||||
//line templates/http_stuff.qtpl:10
|
//line templates/http_stuff.qtpl:24
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`)
|
`)
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string) {
|
func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string, u *user.User, headElements ...string) {
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
StreamBaseHTML(qw422016, title, body)
|
StreamBaseHTML(qw422016, title, body, u, headElements...)
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
func BaseHTML(title, body string) string {
|
func BaseHTML(title, body string, u *user.User, headElements ...string) string {
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
WriteBaseHTML(qb422016, title, body)
|
WriteBaseHTML(qb422016, title, body, u, headElements...)
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
return qs422016
|
return qs422016
|
||||||
//line templates/http_stuff.qtpl:13
|
//line templates/http_stuff.qtpl:27
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_stuff.qtpl:15
|
//line templates/http_stuff.qtpl:29
|
||||||
func StreamHyphaListHTML(qw422016 *qt422016.Writer, tbody string, pageCount int) {
|
func StreamHyphaListHTML(qw422016 *qt422016.Writer, tbody string, pageCount int) {
|
||||||
//line templates/http_stuff.qtpl:15
|
//line templates/http_stuff.qtpl:29
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<main>
|
<main>
|
||||||
<h1>List of hyphae</h1>
|
<h1>List of hyphae</h1>
|
||||||
<p>This wiki has `)
|
<p>This wiki has `)
|
||||||
//line templates/http_stuff.qtpl:18
|
//line templates/http_stuff.qtpl:32
|
||||||
qw422016.N().D(pageCount)
|
qw422016.N().D(pageCount)
|
||||||
//line templates/http_stuff.qtpl:18
|
//line templates/http_stuff.qtpl:32
|
||||||
qw422016.N().S(` hyphae.</p>
|
qw422016.N().S(` hyphae.</p>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@ -90,105 +133,203 @@ func StreamHyphaListHTML(qw422016 *qt422016.Writer, tbody string, pageCount int)
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
`)
|
`)
|
||||||
//line templates/http_stuff.qtpl:27
|
//line templates/http_stuff.qtpl:41
|
||||||
qw422016.N().S(tbody)
|
qw422016.N().S(tbody)
|
||||||
//line templates/http_stuff.qtpl:27
|
//line templates/http_stuff.qtpl:41
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</main>
|
</main>
|
||||||
`)
|
`)
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
func WriteHyphaListHTML(qq422016 qtio422016.Writer, tbody string, pageCount int) {
|
func WriteHyphaListHTML(qq422016 qtio422016.Writer, tbody string, pageCount int) {
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
StreamHyphaListHTML(qw422016, tbody, pageCount)
|
StreamHyphaListHTML(qw422016, tbody, pageCount)
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
func HyphaListHTML(tbody string, pageCount int) string {
|
func HyphaListHTML(tbody string, pageCount int) string {
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
WriteHyphaListHTML(qb422016, tbody, pageCount)
|
WriteHyphaListHTML(qb422016, tbody, pageCount)
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
return qs422016
|
return qs422016
|
||||||
//line templates/http_stuff.qtpl:31
|
//line templates/http_stuff.qtpl:45
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_stuff.qtpl:33
|
//line templates/http_stuff.qtpl:47
|
||||||
func StreamHyphaListRowHTML(qw422016 *qt422016.Writer, hyphaName, binaryMime string, binaryPresent bool) {
|
func StreamHyphaListRowHTML(qw422016 *qt422016.Writer, hyphaName, binaryMime string, binaryPresent bool) {
|
||||||
//line templates/http_stuff.qtpl:33
|
//line templates/http_stuff.qtpl:47
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/page/`)
|
<td><a href="/page/`)
|
||||||
//line templates/http_stuff.qtpl:35
|
//line templates/http_stuff.qtpl:49
|
||||||
qw422016.E().S(hyphaName)
|
qw422016.E().S(hyphaName)
|
||||||
//line templates/http_stuff.qtpl:35
|
//line templates/http_stuff.qtpl:49
|
||||||
qw422016.N().S(`">`)
|
qw422016.N().S(`">`)
|
||||||
//line templates/http_stuff.qtpl:35
|
//line templates/http_stuff.qtpl:49
|
||||||
qw422016.E().S(hyphaName)
|
qw422016.E().S(hyphaName)
|
||||||
//line templates/http_stuff.qtpl:35
|
//line templates/http_stuff.qtpl:49
|
||||||
qw422016.N().S(`</a></td>
|
qw422016.N().S(`</a></td>
|
||||||
`)
|
`)
|
||||||
//line templates/http_stuff.qtpl:36
|
//line templates/http_stuff.qtpl:50
|
||||||
if binaryPresent {
|
if binaryPresent {
|
||||||
//line templates/http_stuff.qtpl:36
|
//line templates/http_stuff.qtpl:50
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<td>`)
|
<td>`)
|
||||||
//line templates/http_stuff.qtpl:37
|
//line templates/http_stuff.qtpl:51
|
||||||
qw422016.E().S(binaryMime)
|
qw422016.E().S(binaryMime)
|
||||||
//line templates/http_stuff.qtpl:37
|
//line templates/http_stuff.qtpl:51
|
||||||
qw422016.N().S(`</td>
|
qw422016.N().S(`</td>
|
||||||
`)
|
`)
|
||||||
//line templates/http_stuff.qtpl:38
|
//line templates/http_stuff.qtpl:52
|
||||||
} else {
|
} else {
|
||||||
//line templates/http_stuff.qtpl:38
|
//line templates/http_stuff.qtpl:52
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<td></td>
|
<td></td>
|
||||||
`)
|
`)
|
||||||
//line templates/http_stuff.qtpl:40
|
//line templates/http_stuff.qtpl:54
|
||||||
}
|
}
|
||||||
//line templates/http_stuff.qtpl:40
|
//line templates/http_stuff.qtpl:54
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</tr>
|
</tr>
|
||||||
`)
|
`)
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
func WriteHyphaListRowHTML(qq422016 qtio422016.Writer, hyphaName, binaryMime string, binaryPresent bool) {
|
func WriteHyphaListRowHTML(qq422016 qtio422016.Writer, hyphaName, binaryMime string, binaryPresent bool) {
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
StreamHyphaListRowHTML(qw422016, hyphaName, binaryMime, binaryPresent)
|
StreamHyphaListRowHTML(qw422016, hyphaName, binaryMime, binaryPresent)
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
func HyphaListRowHTML(hyphaName, binaryMime string, binaryPresent bool) string {
|
func HyphaListRowHTML(hyphaName, binaryMime string, binaryPresent bool) string {
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
WriteHyphaListRowHTML(qb422016, hyphaName, binaryMime, binaryPresent)
|
WriteHyphaListRowHTML(qb422016, hyphaName, binaryMime, binaryPresent)
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line templates/http_stuff.qtpl:42
|
//line templates/http_stuff.qtpl:56
|
||||||
return qs422016
|
return qs422016
|
||||||
//line templates/http_stuff.qtpl:42
|
//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
|
||||||
}
|
}
|
||||||
|
1
templates/icon/gemini-protocol-icon.svg
Normal file
1
templates/icon/gemini-protocol-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 473 B |
11
templates/icon/gopher-protocol-icon.svg
Normal file
11
templates/icon/gopher-protocol-icon.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<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
|
||||||
|
c8.836,0,16-7.164,16-16c0-8.836-7.164-16-16-16H81.051c-21.441,0-39.7-13.836-46.351-33.044H496c8.836,0,16-7.164,16-16
|
||||||
|
C512,271.82,486.82,228.692,447.238,204.944z M415.238,153.216v37.805c-10.318-2.946-19.556-4.305-29.342-4.937
|
||||||
|
C390.355,168.611,402.006,157.881,415.238,153.216z M295.484,303.435L295.484,303.435c-7.562-41.495-43.948-73.062-87.593-73.062
|
||||||
|
c-8.836,0-16,7.164-16,16c0,8.836,7.164,16,16,16c25.909,0,47.826,17.364,54.76,41.062H32.722
|
||||||
|
c14.415-159.15,218.064-217.856,315.136-90.512c3.545,4.649,9.345,6.995,15.124,6.118
|
||||||
|
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>
|
After Width: | Height: | Size: 951 B |
1
templates/icon/http-protocol-icon.svg
Normal file
1
templates/icon/http-protocol-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 627 B |
1
templates/icon/mailto-protocol-icon.svg
Normal file
1
templates/icon/mailto-protocol-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 261 B |
@ -18,6 +18,8 @@
|
|||||||
recent changes
|
recent changes
|
||||||
</nav>
|
</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>
|
||||||
|
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Here I am, willing to add some accesibility using ARIA. Turns out,
|
Here I am, willing to add some accesibility using ARIA. Turns out,
|
||||||
role="feed" is not supported in any screen reader as of September
|
role="feed" is not supported in any screen reader as of September
|
||||||
|
@ -77,81 +77,83 @@ func StreamRecentChangesHTML(qw422016 *qt422016.Writer, changes []string, n int)
|
|||||||
recent changes
|
recent changes
|
||||||
</nav>
|
</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:26
|
//line templates/recent_changes.qtpl:28
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
|
||||||
<section class="recent-changes__list" role="feed">
|
<section class="recent-changes__list" role="feed">
|
||||||
`)
|
`)
|
||||||
//line templates/recent_changes.qtpl:29
|
//line templates/recent_changes.qtpl:31
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
//line templates/recent_changes.qtpl:29
|
//line templates/recent_changes.qtpl:31
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<p>Could not find any recent changes.</p>
|
<p>Could not find any recent changes.</p>
|
||||||
`)
|
`)
|
||||||
//line templates/recent_changes.qtpl:31
|
//line templates/recent_changes.qtpl:33
|
||||||
} else {
|
} else {
|
||||||
//line templates/recent_changes.qtpl:31
|
//line templates/recent_changes.qtpl:33
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line templates/recent_changes.qtpl:32
|
//line templates/recent_changes.qtpl:34
|
||||||
for i, entry := range changes {
|
for i, entry := range changes {
|
||||||
//line templates/recent_changes.qtpl:32
|
//line templates/recent_changes.qtpl:34
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<ul class="recent-changes__entry rc-entry" role="article"
|
<ul class="recent-changes__entry rc-entry" role="article"
|
||||||
aria-setsize="`)
|
aria-setsize="`)
|
||||||
//line templates/recent_changes.qtpl:34
|
//line templates/recent_changes.qtpl:36
|
||||||
qw422016.N().D(n)
|
qw422016.N().D(n)
|
||||||
//line templates/recent_changes.qtpl:34
|
//line templates/recent_changes.qtpl:36
|
||||||
qw422016.N().S(`" aria-posinset="`)
|
qw422016.N().S(`" aria-posinset="`)
|
||||||
//line templates/recent_changes.qtpl:34
|
//line templates/recent_changes.qtpl:36
|
||||||
qw422016.N().D(i)
|
qw422016.N().D(i)
|
||||||
//line templates/recent_changes.qtpl:34
|
//line templates/recent_changes.qtpl:36
|
||||||
qw422016.N().S(`">
|
qw422016.N().S(`">
|
||||||
`)
|
`)
|
||||||
//line templates/recent_changes.qtpl:35
|
//line templates/recent_changes.qtpl:37
|
||||||
qw422016.N().S(entry)
|
qw422016.N().S(entry)
|
||||||
//line templates/recent_changes.qtpl:35
|
//line templates/recent_changes.qtpl:37
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</ul>
|
</ul>
|
||||||
`)
|
`)
|
||||||
//line templates/recent_changes.qtpl:37
|
//line templates/recent_changes.qtpl:39
|
||||||
}
|
}
|
||||||
//line templates/recent_changes.qtpl:37
|
//line templates/recent_changes.qtpl:39
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
`)
|
`)
|
||||||
//line templates/recent_changes.qtpl:38
|
//line templates/recent_changes.qtpl:40
|
||||||
}
|
}
|
||||||
//line templates/recent_changes.qtpl:38
|
//line templates/recent_changes.qtpl:40
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
`)
|
`)
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
func WriteRecentChangesHTML(qq422016 qtio422016.Writer, changes []string, n int) {
|
func WriteRecentChangesHTML(qq422016 qtio422016.Writer, changes []string, n int) {
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
StreamRecentChangesHTML(qw422016, changes, n)
|
StreamRecentChangesHTML(qw422016, changes, n)
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
qt422016.ReleaseWriter(qw422016)
|
qt422016.ReleaseWriter(qw422016)
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
}
|
}
|
||||||
|
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
func RecentChangesHTML(changes []string, n int) string {
|
func RecentChangesHTML(changes []string, n int) string {
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
qb422016 := qt422016.AcquireByteBuffer()
|
qb422016 := qt422016.AcquireByteBuffer()
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
WriteRecentChangesHTML(qb422016, changes, n)
|
WriteRecentChangesHTML(qb422016, changes, n)
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
qs422016 := string(qb422016.B)
|
qs422016 := string(qb422016.B)
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
qt422016.ReleaseByteBuffer(qb422016)
|
qt422016.ReleaseByteBuffer(qb422016)
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
return qs422016
|
return qs422016
|
||||||
//line templates/recent_changes.qtpl:41
|
//line templates/recent_changes.qtpl:43
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{% import "net/http" %}
|
{% import "net/http" %}
|
||||||
This dialog is to be shown to a user when they try to rename a hypha.
|
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) %}
|
{% func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
|
||||||
<main>
|
|
||||||
{%= navHTML(rq, hyphaName, "rename-ask") %}
|
{%= navHTML(rq, hyphaName, "rename-ask") %}
|
||||||
|
<main>
|
||||||
{%- if isOld -%}
|
{%- if isOld -%}
|
||||||
<section>
|
<section>
|
||||||
<h1>Rename {%s hyphaName %}</h1>
|
<h1>Rename {%s hyphaName %}</h1>
|
||||||
|
@ -26,12 +26,12 @@ var (
|
|||||||
func StreamRenameAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
func StreamRenameAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
||||||
//line templates/rename.qtpl:3
|
//line templates/rename.qtpl:3
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
<main>
|
|
||||||
`)
|
`)
|
||||||
//line templates/rename.qtpl:5
|
//line templates/rename.qtpl:4
|
||||||
streamnavHTML(qw422016, rq, hyphaName, "rename-ask")
|
streamnavHTML(qw422016, rq, hyphaName, "rename-ask")
|
||||||
//line templates/rename.qtpl:5
|
//line templates/rename.qtpl:4
|
||||||
qw422016.N().S(`
|
qw422016.N().S(`
|
||||||
|
<main>
|
||||||
`)
|
`)
|
||||||
//line templates/rename.qtpl:6
|
//line templates/rename.qtpl:6
|
||||||
if isOld {
|
if isOld {
|
||||||
|
24
templates/unattach.qtpl
Normal file
24
templates/unattach.qtpl
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{% 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 %}
|
148
templates/unattach.qtpl.go
Normal file
148
templates/unattach.qtpl.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// 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
|
||||||
|
}
|
22
tree/tree.go
22
tree/tree.go
@ -5,22 +5,27 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// If Name == "", the tree is empty.
|
// If Name == "", the tree is empty.
|
||||||
type tree struct {
|
type tree struct {
|
||||||
name string
|
name string
|
||||||
|
exists bool
|
||||||
|
prevSibling string
|
||||||
|
nextSibling string
|
||||||
siblings []string
|
siblings []string
|
||||||
descendants []*tree
|
descendants []*tree
|
||||||
root bool
|
root bool
|
||||||
hyphaIterator func(func(string))
|
hyphaIterator func(func(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TreeAsHtml generates a tree for `hyphaName`. `hyphaStorage` has this type because package `tree` has no access to `HyphaData` data type. One day it shall have it, I guess.
|
// Tree generates a tree for `hyphaName` as html and returns next and previous hyphae if any.
|
||||||
func TreeAsHtml(hyphaName string, hyphaIterator func(func(string))) string {
|
func Tree(hyphaName string, hyphaIterator func(func(string))) (html, prev, next string) {
|
||||||
t := &tree{name: hyphaName, root: true, hyphaIterator: hyphaIterator}
|
t := &tree{name: hyphaName, root: true, hyphaIterator: hyphaIterator}
|
||||||
t.fill()
|
t.fill()
|
||||||
return t.asHtml()
|
return t.asHtml(), util.BeautifulName(t.prevSibling), util.BeautifulName(t.nextSibling)
|
||||||
}
|
}
|
||||||
|
|
||||||
// subtree adds a descendant tree to `t` and returns that tree.
|
// subtree adds a descendant tree to `t` and returns that tree.
|
||||||
@ -34,11 +39,22 @@ func (t *tree) fork(descendantName string) *tree {
|
|||||||
return subt
|
return subt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compare current prev next hyphae and decide if any of them should be set to `name2`.
|
||||||
|
func (t *tree) prevNextDetermine(name2 string) {
|
||||||
|
if name2 < t.name && (name2 > t.prevSibling || t.prevSibling == "") {
|
||||||
|
t.prevSibling = name2
|
||||||
|
} else if name2 > t.name && (name2 < t.nextSibling || t.nextSibling == "") {
|
||||||
|
t.nextSibling = name2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Compares names and does something with them, may generate a subtree.
|
// Compares names and does something with them, may generate a subtree.
|
||||||
func (t *tree) compareNamesAndAppend(name2 string) {
|
func (t *tree) compareNamesAndAppend(name2 string) {
|
||||||
switch {
|
switch {
|
||||||
case t.name == name2:
|
case t.name == name2:
|
||||||
|
t.exists = true
|
||||||
case t.root && path.Dir(t.name) == path.Dir(name2):
|
case t.root && path.Dir(t.name) == path.Dir(name2):
|
||||||
|
t.prevNextDetermine(name2)
|
||||||
t.siblings = append(t.siblings, name2)
|
t.siblings = append(t.siblings, name2)
|
||||||
case t.name == path.Dir(name2):
|
case t.name == path.Dir(name2):
|
||||||
t.fork(name2).fill()
|
t.fork(name2).fill()
|
||||||
|
@ -10,53 +10,55 @@ import (
|
|||||||
"github.com/bouncepaw/mycorrhiza/util"
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PopulateFixedUserStorage() {
|
// ReadUsersFromFilesystem reads all user information from filesystem and stores it internally. Call it during initialization.
|
||||||
|
func ReadUsersFromFilesystem() {
|
||||||
|
rememberUsers(usersFromFixedCredentials())
|
||||||
|
readTokensToUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
func usersFromFixedCredentials() []*User {
|
||||||
|
var users []*User
|
||||||
contents, err := ioutil.ReadFile(util.FixedCredentialsPath)
|
contents, err := ioutil.ReadFile(util.FixedCredentialsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(contents, &UserStorage.Users)
|
err = json.Unmarshal(contents, &users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, user := range UserStorage.Users {
|
log.Println("Found", len(users), "fixed users")
|
||||||
user.Group = groupFromString(user.GroupString)
|
return users
|
||||||
}
|
}
|
||||||
log.Println("Found", len(UserStorage.Users), "fixed users")
|
|
||||||
|
|
||||||
contents, err = ioutil.ReadFile(tokenStoragePath())
|
func rememberUsers(uu []*User) {
|
||||||
|
// uu is used to not shadow the `users` in `users.go`.
|
||||||
|
for _, user := range uu {
|
||||||
|
users.Store(user.Name, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readTokensToUsers() {
|
||||||
|
contents, err := ioutil.ReadFile(tokenStoragePath())
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp map[string]string
|
var tmp map[string]string
|
||||||
err = json.Unmarshal(contents, &tmp)
|
err = json.Unmarshal(contents, &tmp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for token, username := range tmp {
|
for token, username := range tmp {
|
||||||
user := UserStorage.userByName(username)
|
commenceSession(username, token)
|
||||||
UserStorage.Tokens[token] = user
|
|
||||||
}
|
}
|
||||||
log.Println("Found", len(tmp), "active sessions")
|
log.Println("Found", len(tmp), "active sessions")
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpTokens() {
|
// Return path to tokens.json. Creates folders if needed.
|
||||||
tmp := make(map[string]string)
|
|
||||||
for token, user := range UserStorage.Tokens {
|
|
||||||
tmp[token] = user.Name
|
|
||||||
}
|
|
||||||
blob, err := json.Marshal(tmp)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
} else {
|
|
||||||
ioutil.WriteFile(tokenStoragePath(), blob, 0644)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return path to tokens.json.
|
|
||||||
func tokenStoragePath() string {
|
func tokenStoragePath() string {
|
||||||
dir, err := xdg.DataFile("mycorrhiza/tokens.json")
|
dir, err := xdg.DataFile("mycorrhiza/tokens.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -67,3 +69,21 @@ func tokenStoragePath() string {
|
|||||||
}
|
}
|
||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dumpTokens() {
|
||||||
|
tmp := make(map[string]string)
|
||||||
|
|
||||||
|
tokens.Range(func(k, v interface{}) bool {
|
||||||
|
token := k.(string)
|
||||||
|
username := v.(string)
|
||||||
|
tmp[token] = username
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
blob, err := json.Marshal(tmp)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
} else {
|
||||||
|
ioutil.WriteFile(tokenStoragePath(), blob, 0644)
|
||||||
|
}
|
||||||
|
}
|
@ -1,70 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func groupFromString(s string) UserGroup {
|
|
||||||
switch s {
|
|
||||||
case "admin":
|
|
||||||
return UserAdmin
|
|
||||||
case "moderator":
|
|
||||||
return UserModerator
|
|
||||||
case "trusted":
|
|
||||||
return UserTrusted
|
|
||||||
case "editor":
|
|
||||||
return UserEditor
|
|
||||||
default:
|
|
||||||
log.Fatal("Unknown user group", s)
|
|
||||||
return UserAnon
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserGroup represents a group that a user is part of.
|
|
||||||
type UserGroup int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UserAnon is the default user group which all unauthorized visitors have.
|
|
||||||
UserAnon UserGroup = iota
|
|
||||||
// UserEditor is a user who can edit and upload stuff.
|
|
||||||
UserEditor
|
|
||||||
// UserTrusted is a trusted editor who can also rename stuff.
|
|
||||||
UserTrusted
|
|
||||||
// UserModerator is a moderator who can also delete stuff.
|
|
||||||
UserModerator
|
|
||||||
// UserAdmin can do everything.
|
|
||||||
UserAdmin
|
|
||||||
)
|
|
||||||
|
|
||||||
var minimalRights = map[string]UserGroup{
|
|
||||||
"edit": UserEditor,
|
|
||||||
"upload-binary": UserEditor,
|
|
||||||
"upload-text": UserEditor,
|
|
||||||
"rename-ask": UserTrusted,
|
|
||||||
"rename-confirm": UserTrusted,
|
|
||||||
"delete-ask": UserModerator,
|
|
||||||
"delete-confirm": UserModerator,
|
|
||||||
"reindex": UserAdmin,
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ug UserGroup) CanAccessRoute(route string) bool {
|
|
||||||
if !AuthUsed {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if minimalRight, ok := minimalRights[route]; ok {
|
|
||||||
if ug >= minimalRight {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func CanProceed(rq *http.Request, route string) bool {
|
|
||||||
return FromRequest(rq).OrAnon().CanProceed(route)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) CanProceed(route string) bool {
|
|
||||||
return u.Group.CanAccessRoute(route)
|
|
||||||
}
|
|
75
user/net.go
Normal file
75
user/net.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bouncepaw/mycorrhiza/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CanProceed returns `true` if the user in `rq` has enough rights to access `route`.
|
||||||
|
func CanProceed(rq *http.Request, route string) bool {
|
||||||
|
return FromRequest(rq).CanProceed(route)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromRequest returns user from `rq`. If there is no user, an anon user is returned instead.
|
||||||
|
func FromRequest(rq *http.Request) *User {
|
||||||
|
cookie, err := rq.Cookie("mycorrhiza_token")
|
||||||
|
if err != nil {
|
||||||
|
return EmptyUser()
|
||||||
|
}
|
||||||
|
return userByToken(cookie.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogoutFromRequest logs the user in `rq` out and rewrites the cookie in `w`.
|
||||||
|
func LogoutFromRequest(w http.ResponseWriter, rq *http.Request) {
|
||||||
|
cookieFromUser, err := rq.Cookie("mycorrhiza_token")
|
||||||
|
if err == nil {
|
||||||
|
http.SetCookie(w, cookie("token", "", time.Unix(0, 0)))
|
||||||
|
terminateSession(cookieFromUser.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginDataHTTP logs such user in and returns string representation of an error if there is any.
|
||||||
|
func LoginDataHTTP(w http.ResponseWriter, rq *http.Request, username, password string) string {
|
||||||
|
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||||
|
if !HasUsername(username) {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
log.Println("Unknown username", username, "was entered")
|
||||||
|
return "unknown username"
|
||||||
|
}
|
||||||
|
if !CredentialsOK(username, password) {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
log.Println("A wrong password was entered for username", username)
|
||||||
|
return "wrong password"
|
||||||
|
}
|
||||||
|
token, err := AddSession(username)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
http.SetCookie(w, cookie("token", token, time.Now().Add(365*24*time.Hour)))
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSession saves a session for `username` and returns a token to use.
|
||||||
|
func AddSession(username string) (string, error) {
|
||||||
|
token, err := util.RandomString(16)
|
||||||
|
if err == nil {
|
||||||
|
commenceSession(username, token)
|
||||||
|
log.Println("New token for", username, "is", token)
|
||||||
|
}
|
||||||
|
return token, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// A handy cookie constructor
|
||||||
|
func cookie(name_suffix, val string, t time.Time) *http.Cookie {
|
||||||
|
return &http.Cookie{
|
||||||
|
Name: "mycorrhiza_" + name_suffix,
|
||||||
|
Value: val,
|
||||||
|
Expires: t,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
}
|
179
user/user.go
179
user/user.go
@ -1,138 +1,69 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"sync"
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/bouncepaw/mycorrhiza/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (u *User) OrAnon() *User {
|
|
||||||
if u == nil {
|
|
||||||
return &User{}
|
|
||||||
}
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func LogoutFromRequest(w http.ResponseWriter, rq *http.Request) {
|
|
||||||
cookieFromUser, err := rq.Cookie("mycorrhiza_token")
|
|
||||||
if err == nil {
|
|
||||||
http.SetCookie(w, cookie("token", "", time.Unix(0, 0)))
|
|
||||||
terminateSession(cookieFromUser.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *FixedUserStorage) userByToken(token string) *User {
|
|
||||||
if user, ok := us.Tokens[token]; ok {
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *FixedUserStorage) userByName(username string) *User {
|
|
||||||
for _, user := range us.Users {
|
|
||||||
if user.Name == username {
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromRequest(rq *http.Request) *User {
|
|
||||||
cookie, err := rq.Cookie("mycorrhiza_token")
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return UserStorage.userByToken(cookie.Value).OrAnon()
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoginDataHTTP(w http.ResponseWriter, rq *http.Request, username, password string) string {
|
|
||||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
|
||||||
if !HasUsername(username) {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
log.Println("Unknown username", username, "was entered")
|
|
||||||
return "unknown username"
|
|
||||||
}
|
|
||||||
if !CredentialsOK(username, password) {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
log.Println("A wrong password was entered for username", username)
|
|
||||||
return "wrong password"
|
|
||||||
}
|
|
||||||
token, err := AddSession(username)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return err.Error()
|
|
||||||
}
|
|
||||||
http.SetCookie(w, cookie("token", token, time.Now().Add(14*24*time.Hour)))
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddSession saves a session for `username` and returns a token to use.
|
|
||||||
func AddSession(username string) (string, error) {
|
|
||||||
token, err := util.RandomString(16)
|
|
||||||
if err == nil {
|
|
||||||
for _, user := range UserStorage.Users {
|
|
||||||
if user.Name == username {
|
|
||||||
UserStorage.Tokens[token] = user
|
|
||||||
go dumpTokens()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Println("New token for", username, "is", token)
|
|
||||||
}
|
|
||||||
return token, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func terminateSession(token string) {
|
|
||||||
delete(UserStorage.Tokens, token)
|
|
||||||
go dumpTokens()
|
|
||||||
}
|
|
||||||
|
|
||||||
func HasUsername(username string) bool {
|
|
||||||
for _, user := range UserStorage.Users {
|
|
||||||
if user.Name == username {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func CredentialsOK(username, password string) bool {
|
|
||||||
for _, user := range UserStorage.Users {
|
|
||||||
if user.Name == username && user.Password == password {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type FixedUserStorage struct {
|
|
||||||
Users []*User
|
|
||||||
Tokens map[string]*User
|
|
||||||
}
|
|
||||||
|
|
||||||
var UserStorage = FixedUserStorage{Tokens: make(map[string]*User)}
|
|
||||||
|
|
||||||
// AuthUsed shows if a method of authentication is used. You should set it by yourself.
|
|
||||||
var AuthUsed bool
|
|
||||||
|
|
||||||
// User is a user.
|
// User is a user.
|
||||||
type User struct {
|
type User struct {
|
||||||
// Name is a username. It must follow hypha naming rules.
|
// Name is a username. It must follow hypha naming rules.
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
// Group the user is part of.
|
Group string `json:"group"`
|
||||||
Group UserGroup `json:"-"`
|
|
||||||
GroupString string `json:"group"`
|
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// A handy cookie constructor
|
// Route — Right (more is more right)
|
||||||
func cookie(name_suffix, val string, t time.Time) *http.Cookie {
|
var minimalRights = map[string]int{
|
||||||
return &http.Cookie{
|
"edit": 1,
|
||||||
Name: "mycorrhiza_" + name_suffix,
|
"upload-binary": 1,
|
||||||
Value: val,
|
"upload-text": 1,
|
||||||
Expires: t,
|
"rename-ask": 2,
|
||||||
Path: "/",
|
"rename-confirm": 2,
|
||||||
|
"unattach-ask": 2,
|
||||||
|
"unattach-confirm": 2,
|
||||||
|
"update-header-links": 3,
|
||||||
|
"delete-ask": 3,
|
||||||
|
"delete-confirm": 3,
|
||||||
|
"reindex": 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group — Right
|
||||||
|
var groupRight = map[string]int{
|
||||||
|
"anon": 0,
|
||||||
|
"editor": 1,
|
||||||
|
"trusted": 2,
|
||||||
|
"moderator": 3,
|
||||||
|
"admin": 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmptyUser() *User {
|
||||||
|
return &User{
|
||||||
|
Name: "anon",
|
||||||
|
Group: "anon",
|
||||||
|
Password: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (user *User) CanProceed(route string) bool {
|
||||||
|
if !AuthUsed {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
user.RLock()
|
||||||
|
defer user.RUnlock()
|
||||||
|
|
||||||
|
right, _ := groupRight[user.Group]
|
||||||
|
minimalRight, _ := minimalRights[route]
|
||||||
|
if right >= minimalRight {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *User) isCorrectPassword(password string) bool {
|
||||||
|
user.RLock()
|
||||||
|
defer user.RUnlock()
|
||||||
|
|
||||||
|
return password == user.Password
|
||||||
|
}
|
||||||
|
66
user/users.go
Normal file
66
user/users.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AuthUsed bool
|
||||||
|
var users sync.Map
|
||||||
|
var tokens sync.Map
|
||||||
|
|
||||||
|
func ListUsersWithGroup(group string) []string {
|
||||||
|
usersWithTheGroup := []string{}
|
||||||
|
users.Range(func(_, v interface{}) bool {
|
||||||
|
userobj := v.(*User)
|
||||||
|
|
||||||
|
if userobj.Group == group {
|
||||||
|
usersWithTheGroup = append(usersWithTheGroup, userobj.Name)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return usersWithTheGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func Count() int {
|
||||||
|
i := 0
|
||||||
|
users.Range(func(k, v interface{}) bool {
|
||||||
|
i++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasUsername(username string) bool {
|
||||||
|
_, has := users.Load(username)
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
func CredentialsOK(username, password string) bool {
|
||||||
|
return userByName(username).isCorrectPassword(password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func userByToken(token string) *User {
|
||||||
|
if usernameUntyped, ok := tokens.Load(token); ok {
|
||||||
|
username := usernameUntyped.(string)
|
||||||
|
return userByName(username)
|
||||||
|
}
|
||||||
|
return EmptyUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
func userByName(username string) *User {
|
||||||
|
if userUntyped, ok := users.Load(username); ok {
|
||||||
|
user := userUntyped.(*User)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
return EmptyUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
func commenceSession(username, token string) {
|
||||||
|
tokens.Store(token, username)
|
||||||
|
go dumpTokens()
|
||||||
|
}
|
||||||
|
|
||||||
|
func terminateSession(token string) {
|
||||||
|
tokens.Delete(token)
|
||||||
|
go dumpTokens()
|
||||||
|
}
|
35
util/header_links.go
Normal file
35
util/header_links.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetDefaultHeaderLinks() {
|
||||||
|
HeaderLinks = []HeaderLink{
|
||||||
|
{"/", SiteName},
|
||||||
|
{"/recent-changes", "Recent changes"},
|
||||||
|
{"/list", "All hyphae"},
|
||||||
|
{"/random", "Random"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rocketlinkλ is markup.Rocketlink. You have to pass it like that to avoid cyclical dependency.
|
||||||
|
func ParseHeaderLinks(text string, rocketlinkλ func(string, string) (string, string, string)) {
|
||||||
|
HeaderLinks = []HeaderLink{}
|
||||||
|
for _, line := range strings.Split(text, "\n") {
|
||||||
|
if strings.HasPrefix(line, "=>") {
|
||||||
|
href, text, _ := rocketlinkλ(line, HeaderLinksHypha)
|
||||||
|
HeaderLinks = append(HeaderLinks, HeaderLink{
|
||||||
|
Href: href,
|
||||||
|
Display: text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HeaderLink struct {
|
||||||
|
Href string
|
||||||
|
Display string
|
||||||
|
}
|
||||||
|
|
||||||
|
var HeaderLinks []HeaderLink
|
15
util/util.go
15
util/util.go
@ -8,11 +8,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
URL string
|
||||||
ServerPort string
|
ServerPort string
|
||||||
HomePage string
|
HomePage string
|
||||||
SiteTitle string
|
SiteNavIcon string
|
||||||
|
SiteName string
|
||||||
WikiDir string
|
WikiDir string
|
||||||
UserTree string
|
UserHypha string
|
||||||
|
HeaderLinksHypha string
|
||||||
AuthMethod string
|
AuthMethod string
|
||||||
FixedCredentialsPath string
|
FixedCredentialsPath string
|
||||||
)
|
)
|
||||||
@ -54,3 +57,11 @@ func RandomString(n int) (string, error) {
|
|||||||
}
|
}
|
||||||
return hex.EncodeToString(bytes), nil
|
return hex.EncodeToString(bytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strip hypha name from all ancestor names, replace _ with spaces, title case
|
||||||
|
func BeautifulName(uglyName string) string {
|
||||||
|
if uglyName == "" {
|
||||||
|
return uglyName
|
||||||
|
}
|
||||||
|
return strings.Title(strings.ReplaceAll(uglyName, "_", " "))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user