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
|
||||
|
35
README.md
35
README.md
@ -1,7 +1,10 @@
|
||||
# 🍄 MycorrhizaWiki 0.11
|
||||
# 🍄 MycorrhizaWiki 0.12
|
||||
A wiki engine.
|
||||
|
||||
[Main wiki](https://mycorrhiza.lesarbr.es)
|
||||
|
||||
## Building
|
||||
Also see [detailed instructions](https://mycorrhiza.lesarbr.es/page/deploy) on wiki.
|
||||
```sh
|
||||
git clone --recurse-submodules https://github.com/bouncepaw/mycorrhiza
|
||||
cd mycorrhiza
|
||||
@ -22,36 +25,40 @@ Options:
|
||||
What auth method to use. Variants: "none", "fixed" (default "none")
|
||||
-fixed-credentials-path string
|
||||
Used when -auth-method=fixed. Path to file with user credentials. (default "mycocredentials.json")
|
||||
-header-links-hypha string
|
||||
Optional hypha that overrides the header links
|
||||
-home string
|
||||
The home page (default "home")
|
||||
-icon string
|
||||
What to show in the navititle in the beginning, before the colon (default "🍄")
|
||||
-name string
|
||||
What is the name of your wiki (default "wiki")
|
||||
-port string
|
||||
Port to serve the wiki at (default "1737")
|
||||
-title string
|
||||
How to call your wiki in the navititle (default "🍄")
|
||||
-user-tree string
|
||||
-url string
|
||||
URL at which your wiki can be found. Used to generate feeds (default "http://0.0.0.0:$port")
|
||||
-user-hypha string
|
||||
Hypha which is a superhypha of all user pages (default "u")
|
||||
```
|
||||
|
||||
## Features
|
||||
* Edit pages through html forms
|
||||
* Responsive design
|
||||
* Edit pages through html forms, graphical preview
|
||||
* Responsive design, dark theme (synced with system theme)
|
||||
* Works in text browsers
|
||||
* Wiki pages (called hyphae) are written in mycomarkup
|
||||
* Everything is stored as simple files, no database required. You can run a wiki on almost any directory and get something to work with.
|
||||
* Page trees
|
||||
* Everything is stored as simple files, no database required. You can run a wiki on almost any directory and get something to work with
|
||||
* Page trees; links to previous and next pages
|
||||
* Changes are saved to git
|
||||
* List of hyphae page
|
||||
* History page
|
||||
* Random page
|
||||
* Recent changes page
|
||||
* Recent changes page; RSS, Atom and JSON feeds available
|
||||
* Hyphae can be deleted (while still preserving history)
|
||||
* Hyphae can be renamed (recursive renaming of subhyphae is also supported)
|
||||
* Light on resources: I run a home wiki on this engine 24/7 at an [Orange π Lite](http://www.orangepi.org/orangepilite/).
|
||||
* Light on resources
|
||||
* Authorization with pre-set credentials
|
||||
|
||||
## Contributing
|
||||
Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where some development is coordinated. Feel free to open an issue or contact me.
|
||||
Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where some development is coordinated. You can also sponsor on [boosty](https://boosty.to/bouncepaw). Feel free to open an issue or contact directly.
|
||||
|
||||
## Future plans
|
||||
* Tagging system
|
||||
* Better history viewing
|
||||
You can view list of all planned features on [our kanban board](https://github.com/bouncepaw/mycorrhiza/projects/1).
|
||||
|
23
flag.go
23
flag.go
@ -10,12 +10,15 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at")
|
||||
flag.StringVar(&util.HomePage, "home", "home", "The home page")
|
||||
flag.StringVar(&util.SiteTitle, "title", "🍄", "How to call your wiki in the navititle")
|
||||
flag.StringVar(&util.UserTree, "user-tree", "u", "Hypha which is a superhypha of all user pages")
|
||||
flag.StringVar(&util.URL, "url", "http://0.0.0.0:$port", "URL at which your wiki can be found. Used to generate feeds and social media previews")
|
||||
flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at using HTTP")
|
||||
flag.StringVar(&util.HomePage, "home", "home", "The home page name")
|
||||
flag.StringVar(&util.SiteNavIcon, "icon", "🍄", "What to show in the navititle in the beginning, before the colon")
|
||||
flag.StringVar(&util.SiteName, "name", "wiki", "What is the name of your wiki")
|
||||
flag.StringVar(&util.UserHypha, "user-hypha", "u", "Hypha which is a superhypha of all user pages")
|
||||
flag.StringVar(&util.AuthMethod, "auth-method", "none", "What auth method to use. Variants: \"none\", \"fixed\"")
|
||||
flag.StringVar(&util.FixedCredentialsPath, "fixed-credentials-path", "mycocredentials.json", "Used when -auth-method=fixed. Path to file with user credentials.")
|
||||
flag.StringVar(&util.HeaderLinksHypha, "header-links-hypha", "", "Optional hypha that overrides the header links")
|
||||
}
|
||||
|
||||
// Do the things related to cli args and die maybe
|
||||
@ -34,19 +37,19 @@ func parseCliArgs() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !isCanonicalName(util.HomePage) {
|
||||
log.Fatal("Error: you must use a proper name for the homepage")
|
||||
if util.URL == "http://0.0.0.0:$port" {
|
||||
util.URL = "http://0.0.0.0:" + util.ServerPort
|
||||
}
|
||||
|
||||
if !isCanonicalName(util.UserTree) {
|
||||
log.Fatal("Error: you must use a proper name for user tree")
|
||||
}
|
||||
util.HomePage = CanonicalName(util.HomePage)
|
||||
util.UserHypha = CanonicalName(util.UserHypha)
|
||||
util.HeaderLinksHypha = CanonicalName(util.HeaderLinksHypha)
|
||||
|
||||
switch util.AuthMethod {
|
||||
case "none":
|
||||
case "fixed":
|
||||
user.AuthUsed = true
|
||||
user.PopulateFixedUserStorage()
|
||||
user.ReadUsersFromFilesystem()
|
||||
default:
|
||||
log.Fatal("Error: unknown auth method:", util.AuthMethod)
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -4,5 +4,7 @@ go 1.14
|
||||
|
||||
require (
|
||||
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
|
||||
)
|
||||
|
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/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
|
||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
||||
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
@ -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-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -13,6 +14,8 @@ import (
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
var renameMsgPattern = regexp.MustCompile(`^Rename ‘(.*)’ to ‘.*’`)
|
||||
|
||||
// Start initializes git credentials.
|
||||
func Start(wikiDir string) {
|
||||
_, err := gitsh("config", "user.name", "wikimind")
|
||||
@ -31,6 +34,42 @@ type Revision struct {
|
||||
Username string
|
||||
Time time.Time
|
||||
Message string
|
||||
hyphaeAffectedBuf []string
|
||||
}
|
||||
|
||||
// determine what hyphae were affected by this revision
|
||||
func (rev *Revision) hyphaeAffected() (hyphae []string) {
|
||||
if nil != rev.hyphaeAffectedBuf {
|
||||
return rev.hyphaeAffectedBuf
|
||||
}
|
||||
hyphae = make([]string, 0)
|
||||
var (
|
||||
// List of files affected by this revision, one per line.
|
||||
out, err = gitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash)
|
||||
// set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most currently).
|
||||
set = make(map[string]bool)
|
||||
isNewName = func(hyphaName string) bool {
|
||||
if _, present := set[hyphaName]; present {
|
||||
return false
|
||||
}
|
||||
set[hyphaName] = true
|
||||
return true
|
||||
}
|
||||
)
|
||||
if err != nil {
|
||||
return hyphae
|
||||
}
|
||||
for _, filename := range strings.Split(out.String(), "\n") {
|
||||
if strings.IndexRune(filename, '.') >= 0 {
|
||||
dotPos := strings.LastIndexByte(filename, '.')
|
||||
hyphaName := string([]byte(filename)[0:dotPos]) // is it safe?
|
||||
if isNewName(hyphaName) {
|
||||
hyphae = append(hyphae, hyphaName)
|
||||
}
|
||||
}
|
||||
}
|
||||
rev.hyphaeAffectedBuf = hyphae
|
||||
return hyphae
|
||||
}
|
||||
|
||||
// TimeString returns a human readable time representation.
|
||||
@ -40,42 +79,38 @@ func (rev Revision) TimeString() string {
|
||||
|
||||
// HyphaeLinks returns a comma-separated list of hyphae that were affected by this revision as HTML string.
|
||||
func (rev Revision) HyphaeLinks() (html string) {
|
||||
// diff-tree --no-commit-id --name-only -r
|
||||
var (
|
||||
// List of files affected by this revision, one per line.
|
||||
out, err = gitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash)
|
||||
// set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most).
|
||||
set = make(map[string]bool)
|
||||
isNewName = func(hyphaName string) bool {
|
||||
if _, present := set[hyphaName]; present {
|
||||
return false
|
||||
} else {
|
||||
set[hyphaName] = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, filename := range strings.Split(out.String(), "\n") {
|
||||
// If filename has an ampersand:
|
||||
if strings.IndexRune(filename, '.') >= 0 {
|
||||
// Remove ampersanded suffix from filename:
|
||||
ampersandPos := strings.LastIndexByte(filename, '.')
|
||||
hyphaName := string([]byte(filename)[0:ampersandPos]) // is it safe?
|
||||
if isNewName(hyphaName) {
|
||||
// Entries are separated by commas
|
||||
if len(set) > 1 {
|
||||
hyphae := rev.hyphaeAffected()
|
||||
for i, hyphaName := range hyphae {
|
||||
if i > 0 {
|
||||
html += `<span aria-hidden="true">, </span>`
|
||||
}
|
||||
html += fmt.Sprintf(`<a href="/page/%[1]s">%[1]s</a>`, hyphaName)
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if user.AuthUsed && rev.Username != "anon" {
|
||||
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__links">%[5]s</li>
|
||||
<li class="rc-entry__msg">%[6]s <span class="rc-entry__author">by <a href="/page/%[3]s/%[4]s" rel="author">%[4]s</a></span></li>
|
||||
`, rev.TimeString(), rev.Hash, util.UserTree, rev.Username, rev.HyphaeLinks(), rev.Message)
|
||||
`, rev.TimeString(), rev.Hash, util.UserHypha, rev.Username, rev.HyphaeLinks(), rev.Message)
|
||||
}
|
||||
return fmt.Sprintf(`
|
||||
<li class="rc-entry__time"><time>%[1]s</time></li>
|
||||
|
@ -7,10 +7,60 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/templates"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
"github.com/gorilla/feeds"
|
||||
)
|
||||
|
||||
func recentChangesFeed() *feeds.Feed {
|
||||
feed := &feeds.Feed{
|
||||
Title: "Recent changes",
|
||||
Link: &feeds.Link{Href: util.URL},
|
||||
Description: "List of 30 recent changes on the wiki",
|
||||
Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"},
|
||||
Updated: time.Now(),
|
||||
}
|
||||
var (
|
||||
out, err = gitsh(
|
||||
"log", "--oneline", "--no-merges",
|
||||
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
|
||||
"--max-count=30",
|
||||
)
|
||||
revs []Revision
|
||||
)
|
||||
if err == nil {
|
||||
for _, line := range strings.Split(out.String(), "\n") {
|
||||
revs = append(revs, parseRevisionLine(line))
|
||||
}
|
||||
}
|
||||
for _, rev := range revs {
|
||||
feed.Add(&feeds.Item{
|
||||
Title: rev.Message,
|
||||
Author: &feeds.Author{Name: rev.Username},
|
||||
Id: rev.Hash,
|
||||
Description: rev.descriptionForFeed(),
|
||||
Created: rev.Time,
|
||||
Updated: rev.Time,
|
||||
Link: &feeds.Link{Href: util.URL + rev.bestLink()},
|
||||
})
|
||||
}
|
||||
return feed
|
||||
}
|
||||
|
||||
func RecentChangesRSS() (string, error) {
|
||||
return recentChangesFeed().ToRss()
|
||||
}
|
||||
|
||||
func RecentChangesAtom() (string, error) {
|
||||
return recentChangesFeed().ToAtom()
|
||||
}
|
||||
|
||||
func RecentChangesJSON() (string, error) {
|
||||
return recentChangesFeed().ToJSON()
|
||||
}
|
||||
|
||||
func RecentChanges(n int) string {
|
||||
var (
|
||||
out, err = gitsh(
|
||||
@ -32,13 +82,19 @@ func RecentChanges(n int) string {
|
||||
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.
|
||||
func Revisions(hyphaName string) ([]Revision, error) {
|
||||
var (
|
||||
out, err = gitsh(
|
||||
"log", "--oneline", "--no-merges",
|
||||
// Hash, Commiter email, Commiter time, Commit msg separated by tab
|
||||
"--pretty=format:\"%h\t%ce\t%ct\t%s\"",
|
||||
// Hash, author email, author time, commit msg separated by tab
|
||||
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
|
||||
"--", hyphaName+".*",
|
||||
)
|
||||
revs []Revision
|
||||
@ -53,6 +109,59 @@ func Revisions(hyphaName string) ([]Revision, error) {
|
||||
return revs, err
|
||||
}
|
||||
|
||||
// HistoryWithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
|
||||
func HistoryWithRevisions(hyphaName string, revs []Revision) (html string) {
|
||||
var (
|
||||
currentYear int
|
||||
currentMonth time.Month
|
||||
)
|
||||
for i, rev := range revs {
|
||||
if rev.Time.Month() != currentMonth || rev.Time.Year() != currentYear {
|
||||
currentYear = rev.Time.Year()
|
||||
currentMonth = rev.Time.Month()
|
||||
if i != 0 {
|
||||
html += `
|
||||
</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.
|
||||
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`.
|
||||
func FileAtRevision(filepath, hash string) (string, error) {
|
||||
out, err := gitsh("show", hash+":"+filepath)
|
||||
|
@ -24,6 +24,7 @@ const (
|
||||
TypeEditBinary
|
||||
TypeDeleteHypha
|
||||
TypeRenameHypha
|
||||
TypeUnattachHypha
|
||||
)
|
||||
|
||||
// HistoryOp is an object representing a history operation.
|
||||
@ -108,6 +109,12 @@ func (hop *HistoryOp) Apply() *HistoryOp {
|
||||
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.
|
||||
func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
|
||||
for _, ch := range userMsg {
|
||||
@ -121,7 +128,7 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
|
||||
|
||||
// WithUser sets a user for the commit.
|
||||
func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
|
||||
if u.Group != user.UserAnon {
|
||||
if u.Group != "anon" {
|
||||
hop.name = u.Name
|
||||
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")
|
||||
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) {
|
||||
@ -44,7 +44,7 @@ func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
|
||||
err = user.LoginDataHTTP(w, rq, username, password)
|
||||
)
|
||||
if err != "" {
|
||||
w.Write([]byte(base(err, templates.LoginErrorHTML(err))))
|
||||
w.Write([]byte(base(err, templates.LoginErrorHTML(err), user.EmptyUser())))
|
||||
} else {
|
||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||
}
|
||||
@ -58,5 +58,5 @@ func handlerLogin(w http.ResponseWriter, rq *http.Request) {
|
||||
} else {
|
||||
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"
|
||||
"net/http"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/templates"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
@ -15,11 +16,65 @@ func init() {
|
||||
http.HandleFunc("/edit/", handlerEdit)
|
||||
http.HandleFunc("/delete-ask/", handlerDeleteAsk)
|
||||
http.HandleFunc("/rename-ask/", handlerRenameAsk)
|
||||
http.HandleFunc("/unattach-ask/", handlerUnattachAsk)
|
||||
// And those that do mutate something:
|
||||
http.HandleFunc("/upload-binary/", handlerUploadBinary)
|
||||
http.HandleFunc("/upload-text/", handlerUploadText)
|
||||
http.HandleFunc("/delete-confirm/", handlerDeleteConfirm)
|
||||
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) {
|
||||
@ -27,13 +82,14 @@ func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) {
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "rename-ask")
|
||||
_, 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.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
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) {
|
||||
@ -44,7 +100,7 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
|
||||
newName = CanonicalName(rq.PostFormValue("new-name"))
|
||||
_, newNameIsUsed = HyphaStorage[newName]
|
||||
recursive = rq.PostFormValue("recursive") == "true"
|
||||
u = user.FromRequest(rq).OrAnon()
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
switch {
|
||||
case !u.CanProceed("rename-confirm"):
|
||||
@ -79,13 +135,14 @@ func handlerDeleteAsk(w http.ResponseWriter, rq *http.Request) {
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "delete-ask")
|
||||
_, 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.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
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
|
||||
@ -126,8 +183,9 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
||||
warning string
|
||||
textAreaFill string
|
||||
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.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
return
|
||||
@ -142,7 +200,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
||||
} else {
|
||||
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.
|
||||
@ -151,9 +209,10 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "upload-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.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
return
|
||||
@ -162,7 +221,9 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
|
||||
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())
|
||||
} else {
|
||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/templates"
|
||||
"github.com/bouncepaw/mycorrhiza/tree"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
@ -20,7 +21,6 @@ func init() {
|
||||
http.HandleFunc("/page/", handlerPage)
|
||||
http.HandleFunc("/text/", handlerText)
|
||||
http.HandleFunc("/binary/", handlerBinary)
|
||||
http.HandleFunc("/history/", handlerHistory)
|
||||
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>`)
|
||||
textPath = hyphaName + ".myco"
|
||||
textContents, err = history.FileAtRevision(textPath, revHash)
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if err == nil {
|
||||
contents = markup.ToHtml(hyphaName, textContents)
|
||||
contents = markup.Doc(hyphaName, textContents).AsHTML()
|
||||
}
|
||||
treeHTML, _, _ := tree.Tree(hyphaName, IterateHyphaNamesWith)
|
||||
page := templates.RevisionHTML(
|
||||
rq,
|
||||
hyphaName,
|
||||
naviTitle(hyphaName),
|
||||
contents,
|
||||
tree.TreeAsHtml(hyphaName, IterateHyphaNamesWith),
|
||||
treeHTML,
|
||||
revHash,
|
||||
)
|
||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(base(hyphaName, page)))
|
||||
}
|
||||
|
||||
// handlerHistory lists all revisions of a hypha
|
||||
func handlerHistory(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
hyphaName := HyphaNameFromRq(rq, "history")
|
||||
var 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)))
|
||||
w.Write([]byte(base(hyphaName, page, u)))
|
||||
}
|
||||
|
||||
// handlerText serves raw source text of the hypha.
|
||||
@ -99,20 +82,32 @@ func handlerPage(w http.ResponseWriter, rq *http.Request) {
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "page")
|
||||
data, hyphaExists = HyphaStorage[hyphaName]
|
||||
hasAmnt = hyphaExists && data.binaryPath != ""
|
||||
contents string
|
||||
openGraph string
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if hyphaExists {
|
||||
fileContentsT, errT := ioutil.ReadFile(data.textPath)
|
||||
_, errB := os.Stat(data.binaryPath)
|
||||
if errT == nil {
|
||||
contents = markup.ToHtml(hyphaName, string(fileContentsT))
|
||||
md := markup.Doc(hyphaName, string(fileContentsT))
|
||||
contents = md.AsHTML()
|
||||
openGraph = md.OpenGraphHTML()
|
||||
}
|
||||
if !os.IsNotExist(errB) {
|
||||
contents = binaryHtmlBlock(hyphaName, data) + contents
|
||||
}
|
||||
}
|
||||
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),
|
||||
contents,
|
||||
tree.TreeAsHtml(hyphaName, IterateHyphaNamesWith))))
|
||||
treeHTML, prevHypha, nextHypha,
|
||||
hasAmnt),
|
||||
u,
|
||||
openGraph))
|
||||
}
|
||||
|
57
hypha.go
57
hypha.go
@ -8,9 +8,10 @@ import (
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"regexp"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
@ -33,6 +34,12 @@ func init() {
|
||||
return
|
||||
}
|
||||
markup.HyphaIterate = IterateHyphaNamesWith
|
||||
markup.HyphaImageForOG = func(hyphaName string) string {
|
||||
if hd, isOld := GetHyphaData(hyphaName); isOld && hd.binaryPath != "" {
|
||||
return util.URL + "/binary/" + hyphaName
|
||||
}
|
||||
return util.URL + "/favicon.ico"
|
||||
}
|
||||
}
|
||||
|
||||
// GetHyphaData finds a hypha addressed by `hyphaName` and returns its `hyphaData`. `hyphaData` is set to a zero value if this hypha does not exist. `isOld` is false if this hypha does not exist.
|
||||
@ -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
|
||||
if !isOld {
|
||||
HyphaStorage[hyphaName] = hyphaData
|
||||
hyphae.IncrementCount()
|
||||
}
|
||||
*originalFullPath = fullPath
|
||||
if isOld && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) {
|
||||
return hop.Abort()
|
||||
}
|
||||
return hop.WithFiles(fullPath).
|
||||
WithUser(u).
|
||||
Apply()
|
||||
@ -115,6 +126,30 @@ func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.Histor
|
||||
Apply()
|
||||
if len(hop.Errs) == 0 {
|
||||
delete(HyphaStorage, hyphaName)
|
||||
hyphae.DecrementCount()
|
||||
}
|
||||
return hop
|
||||
}
|
||||
|
||||
// UnattachHypha unattaches hypha and makes a history record about that.
|
||||
func (hd *HyphaData) UnattachHypha(hyphaName string, u *user.User) *history.HistoryOp {
|
||||
hop := history.Operation(history.TypeUnattachHypha).
|
||||
WithFilesRemoved(hd.binaryPath).
|
||||
WithMsg(fmt.Sprintf("Unattach ‘%s’", hyphaName)).
|
||||
WithUser(u).
|
||||
Apply()
|
||||
if len(hop.Errs) == 0 {
|
||||
hd, ok := HyphaStorage[hyphaName]
|
||||
if ok {
|
||||
if hd.binaryPath != "" {
|
||||
hd.binaryPath = ""
|
||||
}
|
||||
// If nothing is left of the hypha
|
||||
if hd.textPath == "" {
|
||||
delete(HyphaStorage, hyphaName)
|
||||
hyphae.DecrementCount()
|
||||
}
|
||||
}
|
||||
}
|
||||
return hop
|
||||
}
|
||||
@ -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.
|
||||
func RenameHypha(hyphaName, newName string, recursive bool, u *user.User) *history.HistoryOp {
|
||||
var (
|
||||
re = regexp.MustCompile(`(?i)` + hyphaName)
|
||||
replaceName = func(str string) string {
|
||||
return strings.Replace(str, hyphaName, newName, 1)
|
||||
return re.ReplaceAllString(CanonicalName(str), newName)
|
||||
}
|
||||
hyphaNames = findHyphaeToRename(hyphaName, recursive)
|
||||
renameMap, err = renamingPairs(hyphaNames, replaceName)
|
||||
@ -212,7 +248,7 @@ func binaryHtmlBlock(hyphaName string, hd *HyphaData) string {
|
||||
default:
|
||||
return fmt.Sprintf(`
|
||||
<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>
|
||||
`, hyphaName)
|
||||
}
|
||||
@ -244,6 +280,7 @@ func Index(path string) {
|
||||
} else {
|
||||
hyphaData = &HyphaData{}
|
||||
HyphaStorage[hyphaName] = hyphaData
|
||||
hyphae.IncrementCount()
|
||||
}
|
||||
if isText {
|
||||
hyphaData.textPath = hyphaPartPath
|
||||
@ -276,3 +313,17 @@ func FetchTextPart(d *HyphaData) (string, error) {
|
||||
}
|
||||
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 (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||
"github.com/bouncepaw/mycorrhiza/templates"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
@ -40,9 +41,18 @@ func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) {
|
||||
log.Println(errMsg, "for", name)
|
||||
w.Header().Set("Content-Type", "text/html;charset=utf-8")
|
||||
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>`,
|
||||
errMsg, name)))
|
||||
errMsg,
|
||||
name,
|
||||
),
|
||||
user.EmptyUser(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Show all hyphae
|
||||
@ -50,12 +60,13 @@ func handlerList(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
tbody string
|
||||
pageCount = len(HyphaStorage)
|
||||
pageCount = hyphae.Count()
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
for hyphaName, data := range HyphaStorage {
|
||||
tbody += templates.HyphaListRowHTML(hyphaName, ExtensionToMime(filepath.Ext(data.binaryPath)), data.binaryPath != "")
|
||||
}
|
||||
util.HTTP200Page(w, base("List of pages", templates.HyphaListHTML(tbody, pageCount)))
|
||||
util.HTTP200Page(w, base("List of pages", templates.HyphaListHTML(tbody, pageCount), u))
|
||||
}
|
||||
|
||||
// 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)
|
||||
return
|
||||
}
|
||||
hyphae.ResetCount()
|
||||
HyphaStorage = make(map[string]*HyphaData)
|
||||
log.Println("Wiki storage directory is", WikiDir)
|
||||
log.Println("Start indexing hyphae...")
|
||||
Index(WikiDir)
|
||||
log.Println("Indexed", len(HyphaStorage), "hyphae")
|
||||
log.Println("Indexed", hyphae.Count(), "hyphae")
|
||||
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.
|
||||
func handlerRandom(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var randomHyphaName string
|
||||
i := rand.Intn(len(HyphaStorage))
|
||||
i := rand.Intn(hyphae.Count())
|
||||
for hyphaName := range HyphaStorage {
|
||||
if i == 0 {
|
||||
randomHyphaName = hyphaName
|
||||
@ -91,20 +116,6 @@ func handlerRandom(w http.ResponseWriter, rq *http.Request) {
|
||||
http.Redirect(w, rq, "/page/"+randomHyphaName, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// Recent changes
|
||||
func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
noPrefix = strings.TrimPrefix(rq.URL.String(), "/recent-changes/")
|
||||
n, err = strconv.Atoi(noPrefix)
|
||||
)
|
||||
if err == nil && n < 101 {
|
||||
util.HTTP200Page(w, base(strconv.Itoa(n)+" recent changes", history.RecentChanges(n)))
|
||||
} else {
|
||||
http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
if _, err := os.Stat(WikiDir + "/static/common.css"); err == nil {
|
||||
@ -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() {
|
||||
log.Println("Running MycorrhizaWiki β")
|
||||
parseCliArgs()
|
||||
@ -122,26 +177,30 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Wiki storage directory is", WikiDir)
|
||||
log.Println("Start indexing hyphae...")
|
||||
Index(WikiDir)
|
||||
log.Println("Indexed", len(HyphaStorage), "hyphae")
|
||||
log.Println("Indexed", hyphae.Count(), "hyphae")
|
||||
|
||||
history.Start(WikiDir)
|
||||
setHeaderLinks()
|
||||
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static"))))
|
||||
// See http_readers.go for /page/, /text/, /binary/, /history/.
|
||||
// See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/.
|
||||
// See http_readers.go for /page/, /text/, /binary/
|
||||
// See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/, /unattach-ask/, /unattach-confirm/
|
||||
// See http_auth.go for /login, /login-data, /logout, /logout-confirm
|
||||
// See http_history.go for /history/, /recent-changes
|
||||
http.HandleFunc("/list", handlerList)
|
||||
http.HandleFunc("/reindex", handlerReindex)
|
||||
http.HandleFunc("/update-header-links", handlerUpdateHeaderLinks)
|
||||
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.ServeFile(w, rq, WikiDir+"/static/favicon.ico")
|
||||
})
|
||||
http.HandleFunc("/static/common.css", handlerStyle)
|
||||
http.HandleFunc("/static/icon/", handlerIcon)
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
|
||||
http.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther)
|
||||
})
|
||||
http.HandleFunc("/robots.txt", handlerRobotsTxt)
|
||||
log.Fatal(http.ListenAndServe("0.0.0.0:"+util.ServerPort, nil))
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
var imgRe = regexp.MustCompile(`^img\s+{`)
|
||||
@ -184,6 +186,14 @@ func (img *Img) binaryPathFor(path string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (img *Img) ogBinaryPathFor(path string) string {
|
||||
path = img.binaryPathFor(path)
|
||||
if strings.HasPrefix(path, "/binary/") {
|
||||
return util.URL + path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (img *Img) pagePathFor(path string) string {
|
||||
path = strings.TrimSpace(path)
|
||||
if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
|
||||
@ -218,7 +228,7 @@ func (img *Img) checkLinks() map[string]bool {
|
||||
}
|
||||
HyphaIterate(func(hyphaName string) {
|
||||
for _, entry := range img.entries {
|
||||
if hyphaName == entry.trimmedPath {
|
||||
if hyphaName == xclCanonicalName(img.hyphaName, entry.trimmedPath) {
|
||||
m[entry.trimmedPath] = true
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,9 @@ import (
|
||||
// HyphaExists holds function that checks that a hypha is present.
|
||||
var HyphaExists func(string) bool
|
||||
|
||||
//
|
||||
var HyphaImageForOG func(string) string
|
||||
|
||||
// HyphaAccess holds function that accesses a hypha by its name.
|
||||
var HyphaAccess func(string) (rawText, binaryHtml string, err error)
|
||||
|
||||
@ -25,28 +28,36 @@ type GemLexerState struct {
|
||||
buf string
|
||||
// Temporaries
|
||||
img *Img
|
||||
table *Table
|
||||
}
|
||||
|
||||
type Line struct {
|
||||
id int
|
||||
// interface{} may be bad. What I need is a sum of string and Transclusion
|
||||
// interface{} may be bad. TODO: a proper type
|
||||
contents interface{}
|
||||
}
|
||||
|
||||
func lex(name, content string) (ast []Line) {
|
||||
var state = GemLexerState{name: name}
|
||||
func (md *MycoDoc) lex() (ast []Line) {
|
||||
var state = GemLexerState{name: md.hyphaName}
|
||||
|
||||
for _, line := range append(strings.Split(content, "\n"), "") {
|
||||
geminiLineToAST(line, &state, &ast)
|
||||
for _, line := range append(strings.Split(md.contents, "\n"), "") {
|
||||
lineToAST(line, &state, &ast)
|
||||
}
|
||||
return ast
|
||||
}
|
||||
|
||||
// Lex `line` in markup and save it to `ast` using `state`.
|
||||
func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
|
||||
func lineToAST(line string, state *GemLexerState, ast *[]Line) {
|
||||
addLine := func(text interface{}) {
|
||||
*ast = append(*ast, Line{id: state.id, contents: text})
|
||||
}
|
||||
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
|
||||
if "" == strings.TrimSpace(line) {
|
||||
@ -59,6 +70,11 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
|
||||
addLine(state.buf + "</ol>")
|
||||
case "pre":
|
||||
state.buf += "\n"
|
||||
case "launchpad":
|
||||
state.where = ""
|
||||
addLine(state.buf + "</ul>")
|
||||
case "p":
|
||||
addParagraphIfNeeded()
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -74,13 +90,17 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
|
||||
switch state.where {
|
||||
case "img":
|
||||
goto imgState
|
||||
case "table":
|
||||
goto tableState
|
||||
case "pre":
|
||||
goto preformattedState
|
||||
case "list":
|
||||
goto listState
|
||||
case "number":
|
||||
goto numberState
|
||||
default:
|
||||
case "launchpad":
|
||||
goto launchpadState
|
||||
default: // "p" or ""
|
||||
goto normalState
|
||||
}
|
||||
|
||||
@ -91,6 +111,13 @@ imgState:
|
||||
}
|
||||
return
|
||||
|
||||
tableState:
|
||||
if shouldGoBackToNormal := state.table.Process(line); shouldGoBackToNormal {
|
||||
state.where = ""
|
||||
addLine(*state.table)
|
||||
}
|
||||
return
|
||||
|
||||
preformattedState:
|
||||
switch {
|
||||
case startsWith("```"):
|
||||
@ -135,48 +162,84 @@ numberState:
|
||||
}
|
||||
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:
|
||||
state.id++
|
||||
switch {
|
||||
|
||||
case startsWith("```"):
|
||||
addParagraphIfNeeded()
|
||||
state.where = "pre"
|
||||
state.buf = fmt.Sprintf("<pre id='%d' alt='%s' class='codeblock'><code>", state.id, strings.TrimPrefix(line, "```"))
|
||||
case startsWith("* "):
|
||||
addParagraphIfNeeded()
|
||||
state.where = "list"
|
||||
state.buf = fmt.Sprintf("<ul id='%d'>\n", state.id)
|
||||
goto listState
|
||||
case startsWith("*. "):
|
||||
addParagraphIfNeeded()
|
||||
state.where = "number"
|
||||
state.buf = fmt.Sprintf("<ol id='%d'>\n", state.id)
|
||||
goto numberState
|
||||
|
||||
case startsWith("###### "):
|
||||
addParagraphIfNeeded()
|
||||
addHeading(6)
|
||||
case startsWith("##### "):
|
||||
addParagraphIfNeeded()
|
||||
addHeading(5)
|
||||
case startsWith("#### "):
|
||||
addParagraphIfNeeded()
|
||||
addHeading(4)
|
||||
case startsWith("### "):
|
||||
addParagraphIfNeeded()
|
||||
addHeading(3)
|
||||
case startsWith("## "):
|
||||
addParagraphIfNeeded()
|
||||
addHeading(2)
|
||||
case startsWith("# "):
|
||||
addParagraphIfNeeded()
|
||||
addHeading(1)
|
||||
|
||||
case startsWith(">"):
|
||||
addLine(fmt.Sprintf(
|
||||
"<blockquote id='%d'>%s</blockquote>", state.id, remover(">")(line)))
|
||||
addParagraphIfNeeded()
|
||||
addLine(
|
||||
fmt.Sprintf(
|
||||
"<blockquote id='%d'>%s</blockquote>",
|
||||
state.id,
|
||||
ParagraphToHtml(state.name, remover(">")(line)),
|
||||
),
|
||||
)
|
||||
case startsWith("=>"):
|
||||
href, text, class := Rocketlink(line, state.name)
|
||||
addLine(fmt.Sprintf(
|
||||
`<p><a id='%d' class='rocketlink %s' href="%s">%s</a></p>`, state.id, class, href, text))
|
||||
addParagraphIfNeeded()
|
||||
state.where = "launchpad"
|
||||
state.buf = fmt.Sprintf("<ul class='launchpad' id='%d'>\n", state.id)
|
||||
goto launchpadState
|
||||
|
||||
case startsWith("<="):
|
||||
addParagraphIfNeeded()
|
||||
addLine(parseTransclusion(line, state.name))
|
||||
case line == "----":
|
||||
addParagraphIfNeeded()
|
||||
*ast = append(*ast, Line{id: -1, contents: "<hr/>"})
|
||||
case MatchesImg(line):
|
||||
addParagraphIfNeeded()
|
||||
img, shouldGoBackToNormal := ImgFromFirstLine(line, state.name)
|
||||
if shouldGoBackToNormal {
|
||||
addLine(*img)
|
||||
@ -184,7 +247,15 @@ normalState:
|
||||
state.where = "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:
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
@ -15,11 +16,19 @@ func LinkParts(addr, display, hyphaName string) (href, text, class string) {
|
||||
} else {
|
||||
text = strings.TrimSpace(display)
|
||||
}
|
||||
class = "wikilink_internal"
|
||||
class = "wikilink wikilink_internal"
|
||||
|
||||
switch {
|
||||
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, "/"):
|
||||
return addr, text, class
|
||||
case strings.HasPrefix(addr, "./"):
|
||||
|
@ -2,8 +2,12 @@
|
||||
package markup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
// A Mycomarkup-formatted document
|
||||
@ -11,26 +15,79 @@ type MycoDoc struct {
|
||||
// data
|
||||
hyphaName string
|
||||
contents string
|
||||
|
||||
// state
|
||||
recursionDepth int
|
||||
|
||||
// indicators
|
||||
parsedAlready bool
|
||||
// results
|
||||
ast []Line
|
||||
html string
|
||||
firstImageURL string
|
||||
description string
|
||||
}
|
||||
|
||||
// Constructor
|
||||
func Doc(hyphaName, contents string) *MycoDoc {
|
||||
return &MycoDoc{
|
||||
md := &MycoDoc{
|
||||
hyphaName: hyphaName,
|
||||
contents: contents,
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
func (md *MycoDoc) Lex(recursionLevel int) *MycoDoc {
|
||||
if !md.parsedAlready {
|
||||
md.ast = md.lex()
|
||||
}
|
||||
md.parsedAlready = true
|
||||
return md
|
||||
}
|
||||
|
||||
// AsHtml returns an html representation of the document
|
||||
func (md *MycoDoc) AsHtml() string {
|
||||
return ""
|
||||
func (md *MycoDoc) AsHTML() string {
|
||||
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
|
||||
|
||||
const (
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"html"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type spanTokenType int
|
||||
@ -34,8 +35,10 @@ func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, o
|
||||
}
|
||||
}
|
||||
|
||||
func getLinkNode(input *bytes.Buffer, hyphaName string) string {
|
||||
input.Next(2)
|
||||
func getLinkNode(input *bytes.Buffer, hyphaName string, isBracketedLink bool) string {
|
||||
if isBracketedLink {
|
||||
input.Next(2) // drop those [[
|
||||
}
|
||||
var (
|
||||
escaping = false
|
||||
addrBuf = bytes.Buffer{}
|
||||
@ -47,11 +50,13 @@ func getLinkNode(input *bytes.Buffer, hyphaName string) string {
|
||||
if escaping {
|
||||
currBuf.WriteByte(b)
|
||||
escaping = false
|
||||
} else if b == '|' && currBuf == &addrBuf {
|
||||
} else if isBracketedLink && b == '|' && currBuf == &addrBuf {
|
||||
currBuf = &displayBuf
|
||||
} else if b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) {
|
||||
} else if isBracketedLink && b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) {
|
||||
input.Next(1)
|
||||
break
|
||||
} else if !isBracketedLink && unicode.IsSpace(rune(b)) {
|
||||
break
|
||||
} else {
|
||||
currBuf.WriteByte(b)
|
||||
}
|
||||
@ -65,6 +70,12 @@ func getTextNode(input *bytes.Buffer) string {
|
||||
var (
|
||||
textNodeBuffer = bytes.Buffer{}
|
||||
escaping = false
|
||||
startsWith = func(t string) bool {
|
||||
return bytes.HasPrefix(input.Bytes(), []byte(t))
|
||||
}
|
||||
couldBeLinkStart = func() bool {
|
||||
return startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://")
|
||||
}
|
||||
)
|
||||
// Always read the first byte in advance to avoid endless loops that kill computers (sad experience)
|
||||
if input.Len() != 0 {
|
||||
@ -82,6 +93,9 @@ func getTextNode(input *bytes.Buffer) string {
|
||||
} else if strings.IndexByte("/*`^,![~", b) >= 0 {
|
||||
input.UnreadByte()
|
||||
break
|
||||
} else if couldBeLinkStart() {
|
||||
textNodeBuffer.WriteByte(b)
|
||||
break
|
||||
} else {
|
||||
textNodeBuffer.WriteByte(b)
|
||||
}
|
||||
@ -106,6 +120,9 @@ func ParagraphToHtml(hyphaName, input string) string {
|
||||
startsWith = func(t string) bool {
|
||||
return bytes.HasPrefix(p.Bytes(), []byte(t))
|
||||
}
|
||||
noTagsActive = func() bool {
|
||||
return !(tagState[spanItalic] || tagState[spanBold] || tagState[spanMono] || tagState[spanSuper] || tagState[spanSub] || tagState[spanMark] || tagState[spanLink])
|
||||
}
|
||||
)
|
||||
|
||||
for p.Len() != 0 {
|
||||
@ -132,7 +149,9 @@ func ParagraphToHtml(hyphaName, input string) string {
|
||||
ret.WriteString(tagFromState(spanMark, tagState, "s", "~~"))
|
||||
p.Next(2)
|
||||
case startsWith("[["):
|
||||
ret.WriteString(getLinkNode(p, hyphaName))
|
||||
ret.WriteString(getLinkNode(p, hyphaName, true))
|
||||
case (startsWith("https://") || startsWith("http://") || startsWith("gemini://") || startsWith("gopher://") || startsWith("ftp://")) && noTagsActive():
|
||||
ret.WriteString(getLinkNode(p, hyphaName, false))
|
||||
default:
|
||||
ret.WriteString(html.EscapeString(getTextNode(p)))
|
||||
}
|
||||
|
@ -1,35 +1,26 @@
|
||||
package markup
|
||||
|
||||
import ()
|
||||
|
||||
const maxRecursionLevel = 3
|
||||
|
||||
type GemParserState struct {
|
||||
recursionLevel int
|
||||
}
|
||||
|
||||
func Parse(ast []Line, from, to int, state GemParserState) (html string) {
|
||||
if state.recursionLevel > maxRecursionLevel {
|
||||
func Parse(ast []Line, from, to int, recursionLevel int) (html string) {
|
||||
if recursionLevel > maxRecursionLevel {
|
||||
return "Transclusion depth limit"
|
||||
}
|
||||
for _, line := range ast {
|
||||
if line.id >= from && (line.id <= to || to == 0) || line.id == -1 {
|
||||
switch v := line.contents.(type) {
|
||||
case Transclusion:
|
||||
html += Transclude(v, state)
|
||||
html += Transclude(v, recursionLevel)
|
||||
case Img:
|
||||
html += v.ToHtml()
|
||||
case Table:
|
||||
html += v.asHtml()
|
||||
case string:
|
||||
html += v
|
||||
default:
|
||||
html += "Unknown"
|
||||
html += "<b class='error'>Unknown element.</b>"
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
func Transclude(xcl Transclusion, state GemParserState) (html string) {
|
||||
state.recursionLevel++
|
||||
func Transclude(xcl Transclusion, recursionLevel int) (html string) {
|
||||
recursionLevel++
|
||||
tmptOk := `<section class="transclusion transclusion_ok">
|
||||
<a class="transclusion__link" href="/page/%s">%s</a>
|
||||
<div class="transclusion__content">%s</div>
|
||||
</section>`
|
||||
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>`
|
||||
if xcl.from == xclError || xcl.to == xclError || xcl.from > xcl.to {
|
||||
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
|
||||
@ -34,7 +34,8 @@ func Transclude(xcl Transclusion, state GemParserState) (html string) {
|
||||
if err != nil {
|
||||
return fmt.Sprintf(tmptFailed, xcl.name, xcl.name)
|
||||
}
|
||||
xclText := Parse(lex(xcl.name, rawText), xcl.from, xcl.to, state)
|
||||
md := Doc(xcl.name, rawText)
|
||||
xclText := Parse(md.lex(), xcl.from, xcl.to, recursionLevel)
|
||||
return fmt.Sprintf(tmptOk, xcl.name, xcl.name, binaryHtml+xclText)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
var (
|
||||
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/`
|
||||
parts = strings.Split(canonicalName, "/")
|
||||
rel = "up"
|
||||
)
|
||||
for _, part := range parts {
|
||||
html += fmt.Sprintf(`
|
||||
<span aria-hidden="true">/</span>
|
||||
<a href="%s">%s</a>`,
|
||||
for i, part := range parts {
|
||||
if i > 0 {
|
||||
html += `<span aria-hidden="true" class="navi-title__separator">/</span>`
|
||||
}
|
||||
if i == len(parts)-1 {
|
||||
rel = "bookmark"
|
||||
}
|
||||
html += fmt.Sprintf(
|
||||
`<a href="%s" rel="%s">%s</a>`,
|
||||
prevAcc+part,
|
||||
strings.Title(part))
|
||||
rel,
|
||||
util.BeautifulName(part),
|
||||
)
|
||||
prevAcc += part + "/"
|
||||
}
|
||||
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 %}
|
||||
<h1>Login</h1>
|
||||
<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>
|
||||
<legend>Username</legend>
|
||||
<input type="text" required autofocus name="username" autocomplete="on">
|
||||
@ -15,7 +15,7 @@
|
||||
<legend>Password</legend>
|
||||
<input type="password" required name="password" autocomplete="on">
|
||||
</fieldset>
|
||||
<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you.</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">
|
||||
<a href="/">Cancel</a>
|
||||
</form>
|
||||
|
@ -33,7 +33,7 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) {
|
||||
qw422016.N().S(`
|
||||
<h1>Login</h1>
|
||||
<form method="post" action="/login-data" id="login-form" enctype="multipart/form-data">
|
||||
<p>Use the data you were given by the administrator.</p>
|
||||
<p>Use the data you were given by an administrator.</p>
|
||||
<fieldset>
|
||||
<legend>Username</legend>
|
||||
<input type="text" required autofocus name="username" autocomplete="on">
|
||||
@ -42,7 +42,7 @@ func StreamLoginHTML(qw422016 *qt422016.Writer) {
|
||||
<legend>Password</legend>
|
||||
<input type="password" required name="password" autocomplete="on">
|
||||
</fieldset>
|
||||
<p>By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you.</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">
|
||||
<a href="/">Cancel</a>
|
||||
</form>
|
||||
|
@ -21,31 +21,39 @@ var navEntries = []navEntry{
|
||||
|
||||
{% func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %}
|
||||
{% 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 -%}
|
||||
{%- 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 -%}
|
||||
<li><b>{%s entry.title %}</b></li>
|
||||
{%- elseif entry.path != "revision" && u.Group.CanAccessRoute(entry.path) -%}
|
||||
<li><a href="/{%s entry.path %}/{%s hyphaName %}">{%s entry.title %}</a></li>
|
||||
<li class="hypha-tabs__tab hypha-tabs__tab_active">
|
||||
{%s entry.title %}
|
||||
</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 -%}
|
||||
{%- endfor -%}
|
||||
{%s= userMenuHTML(u) %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endfunc %}
|
||||
|
||||
{% func userMenuHTML(u *user.User) %}
|
||||
<li class="navlinks__user">
|
||||
{% if u.Group == user.UserAnon %}
|
||||
<a href="/login">Login</a>
|
||||
{% if user.AuthUsed %}
|
||||
<li class="header-links__entry header-links__entry_user">
|
||||
{% if u.Group == "anon" %}
|
||||
<a href="/login" class="header-links__link">Login</a>
|
||||
{% 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 %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfunc %}
|
||||
|
||||
|
@ -50,153 +50,165 @@ func streamnavHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navTy
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/common.qtpl:24
|
||||
u := user.FromRequest(rq).OrAnon()
|
||||
u := user.FromRequest(rq)
|
||||
|
||||
//line templates/common.qtpl:25
|
||||
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 {
|
||||
//line templates/common.qtpl:29
|
||||
//line templates/common.qtpl:30
|
||||
if navType == "revision" && entry.path == "revision" {
|
||||
//line templates/common.qtpl:29
|
||||
qw422016.N().S(` <li><b>`)
|
||||
//line templates/common.qtpl:30
|
||||
qw422016.N().S(` <li class="hypha-tabs__tab hypha-tabs__tab_active">
|
||||
`)
|
||||
//line templates/common.qtpl:32
|
||||
qw422016.E().S(revisionHash[0])
|
||||
//line templates/common.qtpl: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
|
||||
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(`
|
||||
</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
|
||||
func writeuserMenuHTML(qq422016 qtio422016.Writer, u *user.User) {
|
||||
//line templates/common.qtpl:50
|
||||
//line templates/common.qtpl:46
|
||||
func writenavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
|
||||
//line templates/common.qtpl:46
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/common.qtpl:50
|
||||
streamuserMenuHTML(qw422016, u)
|
||||
//line templates/common.qtpl:50
|
||||
//line templates/common.qtpl:46
|
||||
streamnavHTML(qw422016, rq, hyphaName, navType, revisionHash...)
|
||||
//line templates/common.qtpl:46
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/common.qtpl:50
|
||||
//line templates/common.qtpl:46
|
||||
}
|
||||
|
||||
//line templates/common.qtpl:50
|
||||
func userMenuHTML(u *user.User) string {
|
||||
//line templates/common.qtpl:50
|
||||
//line templates/common.qtpl:46
|
||||
func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string {
|
||||
//line templates/common.qtpl:46
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/common.qtpl:50
|
||||
writeuserMenuHTML(qb422016, u)
|
||||
//line templates/common.qtpl:50
|
||||
//line templates/common.qtpl:46
|
||||
writenavHTML(qb422016, rq, hyphaName, navType, revisionHash...)
|
||||
//line templates/common.qtpl:46
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/common.qtpl:50
|
||||
//line templates/common.qtpl:46
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/common.qtpl:50
|
||||
//line templates/common.qtpl:46
|
||||
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.
|
||||
{% func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
|
||||
<main>
|
||||
{%= navHTML(rq, hyphaName, "delete-ask") %}
|
||||
<main>
|
||||
{% if isOld %}
|
||||
<section>
|
||||
<h1>Delete {%s hyphaName %}?</h1>
|
||||
|
@ -26,12 +26,12 @@ var (
|
||||
func StreamDeleteAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
||||
//line templates/delete.qtpl:4
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/delete.qtpl:6
|
||||
//line templates/delete.qtpl:5
|
||||
streamnavHTML(qw422016, rq, hyphaName, "delete-ask")
|
||||
//line templates/delete.qtpl:6
|
||||
//line templates/delete.qtpl:5
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/delete.qtpl:7
|
||||
if isOld {
|
||||
|
@ -1,16 +1,35 @@
|
||||
{% import "net/http" %}
|
||||
|
||||
{% func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) %}
|
||||
<main class="edit">
|
||||
{%s= navHTML(rq, hyphaName, "edit") %}
|
||||
<main class="edit edit_no-preview">
|
||||
<h1>Edit {%s hyphaName %}</h1>
|
||||
{%s= warning %}
|
||||
<form method="post" class="edit-form"
|
||||
action="/upload-text/{%s hyphaName %}">
|
||||
<textarea name="text">{%s textAreaFill %}</textarea>
|
||||
<br/>
|
||||
<input type="submit"/>
|
||||
<a href="/page/{%s hyphaName %}">Cancel</a>
|
||||
<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>
|
||||
</main>
|
||||
{% 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) {
|
||||
//line templates/http_mutators.qtpl:3
|
||||
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"))
|
||||
//line templates/http_mutators.qtpl:5
|
||||
//line templates/http_mutators.qtpl:4
|
||||
qw422016.N().S(`
|
||||
<main class="edit edit_no-preview">
|
||||
<h1>Edit `)
|
||||
//line templates/http_mutators.qtpl:6
|
||||
qw422016.E().S(hyphaName)
|
||||
@ -52,40 +52,118 @@ func StreamEditHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, text
|
||||
//line templates/http_mutators.qtpl:10
|
||||
qw422016.N().S(`</textarea>
|
||||
<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/`)
|
||||
//line templates/http_mutators.qtpl:13
|
||||
//line templates/http_mutators.qtpl:14
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/http_mutators.qtpl:13
|
||||
qw422016.N().S(`">Cancel</a>
|
||||
//line templates/http_mutators.qtpl:14
|
||||
qw422016.N().S(`" class="edit-form__cancel">Cancel</a>
|
||||
</form>
|
||||
</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) {
|
||||
//line templates/http_mutators.qtpl:16
|
||||
//line templates/http_mutators.qtpl:17
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_mutators.qtpl:16
|
||||
//line templates/http_mutators.qtpl:17
|
||||
StreamEditHTML(qw422016, rq, hyphaName, textAreaFill, warning)
|
||||
//line templates/http_mutators.qtpl:16
|
||||
//line templates/http_mutators.qtpl:17
|
||||
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 {
|
||||
//line templates/http_mutators.qtpl:16
|
||||
//line templates/http_mutators.qtpl:17
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_mutators.qtpl:16
|
||||
//line templates/http_mutators.qtpl:17
|
||||
WriteEditHTML(qb422016, rq, hyphaName, textAreaFill, warning)
|
||||
//line templates/http_mutators.qtpl:16
|
||||
//line templates/http_mutators.qtpl:17
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_mutators.qtpl:16
|
||||
//line templates/http_mutators.qtpl:17
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_mutators.qtpl:16
|
||||
//line templates/http_mutators.qtpl:17
|
||||
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 "path" %}
|
||||
{% import "github.com/bouncepaw/mycorrhiza/user" %}
|
||||
|
||||
{% func HistoryHTML(rq *http.Request, hyphaName, tbody string) %}
|
||||
<main>
|
||||
{% func HistoryHTML(rq *http.Request, hyphaName, list string) %}
|
||||
{%= navHTML(rq, hyphaName, "history") %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Hash</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%s= tbody %}
|
||||
</tbody>
|
||||
</table>
|
||||
<main>
|
||||
<article class="history">
|
||||
<h1>History of {%s hyphaName %}</h1>
|
||||
{%s= list %}
|
||||
</article>
|
||||
</main>
|
||||
{% endfunc %}
|
||||
|
||||
{% func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) %}
|
||||
<main>
|
||||
{%= navHTML(rq, hyphaName, "revision", revHash) %}
|
||||
<main>
|
||||
<article>
|
||||
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
|
||||
{%s= naviTitle %}
|
||||
@ -35,9 +28,9 @@
|
||||
{% endfunc %}
|
||||
|
||||
If `contents` == "", a helpful message is shown instead.
|
||||
{% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree string) %}
|
||||
<main>
|
||||
{% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) %}
|
||||
{%= navHTML(rq, hyphaName, "page") %}
|
||||
<main>
|
||||
<article>
|
||||
{%s= naviTitle %}
|
||||
{% if contents == "" %}
|
||||
@ -46,16 +39,26 @@ If `contents` == "", a helpful message is shown instead.
|
||||
{%s= contents %}
|
||||
{% endif %}
|
||||
</article>
|
||||
<hr/>
|
||||
{% if u := user.FromRequest(rq).OrAnon(); !user.AuthUsed || u.Group > user.UserAnon %}
|
||||
<section class="prevnext">
|
||||
{% if prevHyphaName != "" %}
|
||||
<a class="prevnext__el prevnext__prev" href="/page/{%s prevHyphaName %}" rel="prev">← {%s path.Base(prevHyphaName) %}</a>
|
||||
{% endif %}
|
||||
{% if nextHyphaName != "" %}
|
||||
<a class="prevnext__el prevnext__next" href="/page/{%s nextHyphaName %}" rel="next">{%s path.Base(nextHyphaName) %} →</a>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% if u := user.FromRequest(rq); !user.AuthUsed || u.Group != "anon" %}
|
||||
<form action="/upload-binary/{%s hyphaName %}"
|
||||
method="post" enctype="multipart/form-data">
|
||||
method="post" enctype="multipart/form-data"
|
||||
class="upload-amnt">
|
||||
{% if hasAmnt %}
|
||||
<a class="upload-amnt__unattach" href="/unattach-ask/{%s hyphaName %}">Unattach current attachment?</a>
|
||||
{% endif %}
|
||||
<label for="upload-binary__input">Upload a new attachment</label>
|
||||
<br>
|
||||
<input type="file" id="upload-binary__input" name="binary"/>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
<hr/>
|
||||
{% endif %}
|
||||
<aside>
|
||||
{%s= tree %}
|
||||
|
@ -8,188 +8,226 @@ package templates
|
||||
import "net/http"
|
||||
|
||||
//line templates/http_readers.qtpl:2
|
||||
import "path"
|
||||
|
||||
//line templates/http_readers.qtpl:3
|
||||
import "github.com/bouncepaw/mycorrhiza/user"
|
||||
|
||||
//line templates/http_readers.qtpl:4
|
||||
//line templates/http_readers.qtpl:5
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line templates/http_readers.qtpl:4
|
||||
//line templates/http_readers.qtpl:5
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line templates/http_readers.qtpl:4
|
||||
func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, tbody string) {
|
||||
//line templates/http_readers.qtpl:4
|
||||
//line templates/http_readers.qtpl:5
|
||||
func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, list string) {
|
||||
//line templates/http_readers.qtpl:5
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:6
|
||||
streamnavHTML(qw422016, rq, hyphaName, "history")
|
||||
//line templates/http_readers.qtpl:6
|
||||
qw422016.N().S(`
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Hash</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<main>
|
||||
<article class="history">
|
||||
<h1>History of `)
|
||||
//line templates/http_readers.qtpl:9
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/http_readers.qtpl:9
|
||||
qw422016.N().S(`</h1>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:16
|
||||
qw422016.N().S(tbody)
|
||||
//line templates/http_readers.qtpl:16
|
||||
//line templates/http_readers.qtpl:10
|
||||
qw422016.N().S(list)
|
||||
//line templates/http_readers.qtpl:10
|
||||
qw422016.N().S(`
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:20
|
||||
//line templates/http_readers.qtpl:13
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:20
|
||||
func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, tbody string) {
|
||||
//line templates/http_readers.qtpl:20
|
||||
//line templates/http_readers.qtpl:13
|
||||
func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, list string) {
|
||||
//line templates/http_readers.qtpl:13
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_readers.qtpl:20
|
||||
StreamHistoryHTML(qw422016, rq, hyphaName, tbody)
|
||||
//line templates/http_readers.qtpl:20
|
||||
//line templates/http_readers.qtpl:13
|
||||
StreamHistoryHTML(qw422016, rq, hyphaName, list)
|
||||
//line templates/http_readers.qtpl:13
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/http_readers.qtpl:20
|
||||
//line templates/http_readers.qtpl:13
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:20
|
||||
func HistoryHTML(rq *http.Request, hyphaName, tbody string) string {
|
||||
//line templates/http_readers.qtpl:20
|
||||
//line templates/http_readers.qtpl:13
|
||||
func HistoryHTML(rq *http.Request, hyphaName, list string) string {
|
||||
//line templates/http_readers.qtpl:13
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_readers.qtpl:20
|
||||
WriteHistoryHTML(qb422016, rq, hyphaName, tbody)
|
||||
//line templates/http_readers.qtpl:20
|
||||
//line templates/http_readers.qtpl:13
|
||||
WriteHistoryHTML(qb422016, rq, hyphaName, list)
|
||||
//line templates/http_readers.qtpl:13
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_readers.qtpl:20
|
||||
//line templates/http_readers.qtpl:13
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_readers.qtpl:20
|
||||
//line templates/http_readers.qtpl:13
|
||||
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) {
|
||||
//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(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:24
|
||||
streamnavHTML(qw422016, rq, hyphaName, "revision", revHash)
|
||||
//line templates/http_readers.qtpl:24
|
||||
qw422016.N().S(`
|
||||
<article>
|
||||
<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)
|
||||
//line templates/http_readers.qtpl:27
|
||||
//line templates/http_readers.qtpl:20
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/http_readers.qtpl:28
|
||||
//line templates/http_readers.qtpl:21
|
||||
qw422016.N().S(contents)
|
||||
//line templates/http_readers.qtpl:28
|
||||
//line templates/http_readers.qtpl:21
|
||||
qw422016.N().S(`
|
||||
</article>
|
||||
<hr/>
|
||||
<aside>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:32
|
||||
//line templates/http_readers.qtpl:25
|
||||
qw422016.N().S(tree)
|
||||
//line templates/http_readers.qtpl:32
|
||||
//line templates/http_readers.qtpl:25
|
||||
qw422016.N().S(`
|
||||
</aside>
|
||||
</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) {
|
||||
//line templates/http_readers.qtpl:35
|
||||
//line templates/http_readers.qtpl:28
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_readers.qtpl:35
|
||||
//line templates/http_readers.qtpl:28
|
||||
StreamRevisionHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, revHash)
|
||||
//line templates/http_readers.qtpl:35
|
||||
//line templates/http_readers.qtpl:28
|
||||
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 {
|
||||
//line templates/http_readers.qtpl:35
|
||||
//line templates/http_readers.qtpl:28
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_readers.qtpl:35
|
||||
//line templates/http_readers.qtpl:28
|
||||
WriteRevisionHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, revHash)
|
||||
//line templates/http_readers.qtpl:35
|
||||
//line templates/http_readers.qtpl:28
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_readers.qtpl:35
|
||||
//line templates/http_readers.qtpl:28
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_readers.qtpl:35
|
||||
//line templates/http_readers.qtpl:28
|
||||
return qs422016
|
||||
//line templates/http_readers.qtpl:35
|
||||
//line templates/http_readers.qtpl:28
|
||||
}
|
||||
|
||||
// If `contents` == "", a helpful message is shown instead.
|
||||
|
||||
//line templates/http_readers.qtpl:38
|
||||
func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree string) {
|
||||
//line templates/http_readers.qtpl:38
|
||||
//line templates/http_readers.qtpl:31
|
||||
func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) {
|
||||
//line templates/http_readers.qtpl:31
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/http_readers.qtpl:32
|
||||
streamnavHTML(qw422016, rq, hyphaName, "page")
|
||||
//line templates/http_readers.qtpl:32
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:40
|
||||
streamnavHTML(qw422016, rq, hyphaName, "page")
|
||||
//line templates/http_readers.qtpl:40
|
||||
qw422016.N().S(`
|
||||
<article>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:42
|
||||
//line templates/http_readers.qtpl:35
|
||||
qw422016.N().S(naviTitle)
|
||||
//line templates/http_readers.qtpl:42
|
||||
//line templates/http_readers.qtpl:35
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/http_readers.qtpl:43
|
||||
//line templates/http_readers.qtpl:36
|
||||
if contents == "" {
|
||||
//line templates/http_readers.qtpl:43
|
||||
//line templates/http_readers.qtpl:36
|
||||
qw422016.N().S(`
|
||||
<p>This hypha has no text. Why not <a href="/edit/`)
|
||||
//line templates/http_readers.qtpl:44
|
||||
//line templates/http_readers.qtpl:37
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/http_readers.qtpl:44
|
||||
//line templates/http_readers.qtpl:37
|
||||
qw422016.N().S(`">create it</a>?</p>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:45
|
||||
//line templates/http_readers.qtpl:38
|
||||
} else {
|
||||
//line templates/http_readers.qtpl:45
|
||||
//line templates/http_readers.qtpl:38
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/http_readers.qtpl:46
|
||||
//line templates/http_readers.qtpl:39
|
||||
qw422016.N().S(contents)
|
||||
//line templates/http_readers.qtpl:46
|
||||
//line templates/http_readers.qtpl:39
|
||||
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(`
|
||||
</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
|
||||
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
|
||||
qw422016.N().S(`
|
||||
<form action="/upload-binary/`)
|
||||
@ -197,52 +235,67 @@ func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navi
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/http_readers.qtpl:51
|
||||
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>
|
||||
<br>
|
||||
<input type="file" id="upload-binary__input" name="binary"/>
|
||||
<input type="submit"/>
|
||||
</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(`
|
||||
<aside>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:61
|
||||
//line templates/http_readers.qtpl:64
|
||||
qw422016.N().S(tree)
|
||||
//line templates/http_readers.qtpl:61
|
||||
//line templates/http_readers.qtpl:64
|
||||
qw422016.N().S(`
|
||||
</aside>
|
||||
</main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:64
|
||||
func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree string) {
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) {
|
||||
//line templates/http_readers.qtpl:67
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_readers.qtpl:64
|
||||
StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt)
|
||||
//line templates/http_readers.qtpl:67
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:64
|
||||
func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree string) string {
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName string, hasAmnt bool) string {
|
||||
//line templates/http_readers.qtpl:67
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_readers.qtpl:64
|
||||
WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, prevHyphaName, nextHyphaName, hasAmnt)
|
||||
//line templates/http_readers.qtpl:67
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_readers.qtpl:64
|
||||
//line templates/http_readers.qtpl:67
|
||||
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>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="/static/common.css">
|
||||
<title>{%s title %}</title>
|
||||
{% for _, el := range headElements %}{%s= el %}{% endfor %}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="header-links">
|
||||
<ul class="header-links__list">
|
||||
{%- for _, link := range util.HeaderLinks -%}
|
||||
<li class="header-links__entry"><a class="header-links__link" href="{%s link.Href %}">{%s link.Display %}</a></li>
|
||||
{%- endfor -%}
|
||||
{%s= userMenuHTML(u) %}
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
{%s= body %}
|
||||
</body>
|
||||
</html>
|
||||
@ -40,3 +54,25 @@
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfunc %}
|
||||
|
||||
{% func AboutHTML() %}
|
||||
<main>
|
||||
<section>
|
||||
<h1>About {%s util.SiteName %}</h1>
|
||||
<ul>
|
||||
<li><b><a href="https://mycorrhiza.lesarbr.es">MycorrhizaWiki</a> version:</b> β 0.12 indev</li>
|
||||
{%- if user.AuthUsed -%}
|
||||
<li><b>User count:</b> {%d user.Count() %}</li>
|
||||
<li><b>Home page:</b> <a href="/">{%s util.HomePage %}</a></li>
|
||||
<li><b>Administrators:</b> {%- for i, username := range user.ListUsersWithGroup("admin") -%}
|
||||
{%- if i > 0 -%}<span aria-hidden="true">, </span>
|
||||
{%- endif -%}
|
||||
<a href="/page/{%s util.UserHypha %}/{%s username %}">{%s username %}</a>{%- endfor -%}</li>
|
||||
{%- else -%}
|
||||
<li>This wiki does not use authorization</li>
|
||||
{%- endif -%}
|
||||
</ul>
|
||||
<p>See <a href="/list">/list</a> for information about hyphae on this wiki.</p>
|
||||
</section>
|
||||
</main>
|
||||
{% endfunc %}
|
||||
|
@ -5,21 +5,27 @@
|
||||
package templates
|
||||
|
||||
//line templates/http_stuff.qtpl:1
|
||||
import "github.com/bouncepaw/mycorrhiza/util"
|
||||
|
||||
//line templates/http_stuff.qtpl:2
|
||||
import "github.com/bouncepaw/mycorrhiza/user"
|
||||
|
||||
//line templates/http_stuff.qtpl:4
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line templates/http_stuff.qtpl:1
|
||||
//line templates/http_stuff.qtpl:4
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line templates/http_stuff.qtpl:1
|
||||
func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string) {
|
||||
//line templates/http_stuff.qtpl:1
|
||||
//line templates/http_stuff.qtpl:4
|
||||
func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string, u *user.User, headElements ...string) {
|
||||
//line templates/http_stuff.qtpl:4
|
||||
qw422016.N().S(`
|
||||
<!doctype html>
|
||||
<html>
|
||||
@ -27,59 +33,96 @@ func StreamBaseHTML(qw422016 *qt422016.Writer, title, body string) {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="/static/common.css">
|
||||
<title>`)
|
||||
//line templates/http_stuff.qtpl:7
|
||||
//line templates/http_stuff.qtpl:10
|
||||
qw422016.E().S(title)
|
||||
//line templates/http_stuff.qtpl:7
|
||||
//line templates/http_stuff.qtpl:10
|
||||
qw422016.N().S(`</title>
|
||||
`)
|
||||
//line templates/http_stuff.qtpl:11
|
||||
for _, el := range headElements {
|
||||
//line templates/http_stuff.qtpl:11
|
||||
qw422016.N().S(el)
|
||||
//line templates/http_stuff.qtpl:11
|
||||
}
|
||||
//line templates/http_stuff.qtpl:11
|
||||
qw422016.N().S(`
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="header-links">
|
||||
<ul class="header-links__list">
|
||||
`)
|
||||
//line templates/http_stuff.qtpl:10
|
||||
//line templates/http_stuff.qtpl:17
|
||||
for _, link := range util.HeaderLinks {
|
||||
//line templates/http_stuff.qtpl:17
|
||||
qw422016.N().S(` <li class="header-links__entry"><a class="header-links__link" href="`)
|
||||
//line templates/http_stuff.qtpl:18
|
||||
qw422016.E().S(link.Href)
|
||||
//line templates/http_stuff.qtpl:18
|
||||
qw422016.N().S(`">`)
|
||||
//line templates/http_stuff.qtpl:18
|
||||
qw422016.E().S(link.Display)
|
||||
//line templates/http_stuff.qtpl:18
|
||||
qw422016.N().S(`</a></li>
|
||||
`)
|
||||
//line templates/http_stuff.qtpl:19
|
||||
}
|
||||
//line templates/http_stuff.qtpl:19
|
||||
qw422016.N().S(` `)
|
||||
//line templates/http_stuff.qtpl:20
|
||||
qw422016.N().S(userMenuHTML(u))
|
||||
//line templates/http_stuff.qtpl:20
|
||||
qw422016.N().S(`
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
`)
|
||||
//line templates/http_stuff.qtpl:24
|
||||
qw422016.N().S(body)
|
||||
//line templates/http_stuff.qtpl:10
|
||||
//line templates/http_stuff.qtpl:24
|
||||
qw422016.N().S(`
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
//line templates/http_stuff.qtpl:13
|
||||
//line templates/http_stuff.qtpl:27
|
||||
}
|
||||
|
||||
//line templates/http_stuff.qtpl:13
|
||||
func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string) {
|
||||
//line templates/http_stuff.qtpl:13
|
||||
//line templates/http_stuff.qtpl:27
|
||||
func WriteBaseHTML(qq422016 qtio422016.Writer, title, body string, u *user.User, headElements ...string) {
|
||||
//line templates/http_stuff.qtpl:27
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_stuff.qtpl:13
|
||||
StreamBaseHTML(qw422016, title, body)
|
||||
//line templates/http_stuff.qtpl:13
|
||||
//line templates/http_stuff.qtpl:27
|
||||
StreamBaseHTML(qw422016, title, body, u, headElements...)
|
||||
//line templates/http_stuff.qtpl:27
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/http_stuff.qtpl:13
|
||||
//line templates/http_stuff.qtpl:27
|
||||
}
|
||||
|
||||
//line templates/http_stuff.qtpl:13
|
||||
func BaseHTML(title, body string) string {
|
||||
//line templates/http_stuff.qtpl:13
|
||||
//line templates/http_stuff.qtpl:27
|
||||
func BaseHTML(title, body string, u *user.User, headElements ...string) string {
|
||||
//line templates/http_stuff.qtpl:27
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_stuff.qtpl:13
|
||||
WriteBaseHTML(qb422016, title, body)
|
||||
//line templates/http_stuff.qtpl:13
|
||||
//line templates/http_stuff.qtpl:27
|
||||
WriteBaseHTML(qb422016, title, body, u, headElements...)
|
||||
//line templates/http_stuff.qtpl:27
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_stuff.qtpl:13
|
||||
//line templates/http_stuff.qtpl:27
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_stuff.qtpl:13
|
||||
//line templates/http_stuff.qtpl:27
|
||||
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) {
|
||||
//line templates/http_stuff.qtpl:15
|
||||
//line templates/http_stuff.qtpl:29
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
<h1>List of hyphae</h1>
|
||||
<p>This wiki has `)
|
||||
//line templates/http_stuff.qtpl:18
|
||||
//line templates/http_stuff.qtpl:32
|
||||
qw422016.N().D(pageCount)
|
||||
//line templates/http_stuff.qtpl:18
|
||||
//line templates/http_stuff.qtpl:32
|
||||
qw422016.N().S(` hyphae.</p>
|
||||
<table>
|
||||
<thead>
|
||||
@ -90,105 +133,203 @@ func StreamHyphaListHTML(qw422016 *qt422016.Writer, tbody string, pageCount int)
|
||||
</thead>
|
||||
<tbody>
|
||||
`)
|
||||
//line templates/http_stuff.qtpl:27
|
||||
//line templates/http_stuff.qtpl:41
|
||||
qw422016.N().S(tbody)
|
||||
//line templates/http_stuff.qtpl:27
|
||||
//line templates/http_stuff.qtpl:41
|
||||
qw422016.N().S(`
|
||||
</tbody>
|
||||
</table>
|
||||
</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) {
|
||||
//line templates/http_stuff.qtpl:31
|
||||
//line templates/http_stuff.qtpl:45
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_stuff.qtpl:31
|
||||
//line templates/http_stuff.qtpl:45
|
||||
StreamHyphaListHTML(qw422016, tbody, pageCount)
|
||||
//line templates/http_stuff.qtpl:31
|
||||
//line templates/http_stuff.qtpl:45
|
||||
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 {
|
||||
//line templates/http_stuff.qtpl:31
|
||||
//line templates/http_stuff.qtpl:45
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_stuff.qtpl:31
|
||||
//line templates/http_stuff.qtpl:45
|
||||
WriteHyphaListHTML(qb422016, tbody, pageCount)
|
||||
//line templates/http_stuff.qtpl:31
|
||||
//line templates/http_stuff.qtpl:45
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_stuff.qtpl:31
|
||||
//line templates/http_stuff.qtpl:45
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_stuff.qtpl:31
|
||||
//line templates/http_stuff.qtpl:45
|
||||
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) {
|
||||
//line templates/http_stuff.qtpl:33
|
||||
//line templates/http_stuff.qtpl:47
|
||||
qw422016.N().S(`
|
||||
<tr>
|
||||
<td><a href="/page/`)
|
||||
//line templates/http_stuff.qtpl:35
|
||||
//line templates/http_stuff.qtpl:49
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/http_stuff.qtpl:35
|
||||
//line templates/http_stuff.qtpl:49
|
||||
qw422016.N().S(`">`)
|
||||
//line templates/http_stuff.qtpl:35
|
||||
//line templates/http_stuff.qtpl:49
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/http_stuff.qtpl:35
|
||||
//line templates/http_stuff.qtpl:49
|
||||
qw422016.N().S(`</a></td>
|
||||
`)
|
||||
//line templates/http_stuff.qtpl:36
|
||||
//line templates/http_stuff.qtpl:50
|
||||
if binaryPresent {
|
||||
//line templates/http_stuff.qtpl:36
|
||||
//line templates/http_stuff.qtpl:50
|
||||
qw422016.N().S(`
|
||||
<td>`)
|
||||
//line templates/http_stuff.qtpl:37
|
||||
//line templates/http_stuff.qtpl:51
|
||||
qw422016.E().S(binaryMime)
|
||||
//line templates/http_stuff.qtpl:37
|
||||
//line templates/http_stuff.qtpl:51
|
||||
qw422016.N().S(`</td>
|
||||
`)
|
||||
//line templates/http_stuff.qtpl:38
|
||||
//line templates/http_stuff.qtpl:52
|
||||
} else {
|
||||
//line templates/http_stuff.qtpl:38
|
||||
//line templates/http_stuff.qtpl:52
|
||||
qw422016.N().S(`
|
||||
<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(`
|
||||
</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) {
|
||||
//line templates/http_stuff.qtpl:42
|
||||
//line templates/http_stuff.qtpl:56
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_stuff.qtpl:42
|
||||
//line templates/http_stuff.qtpl:56
|
||||
StreamHyphaListRowHTML(qw422016, hyphaName, binaryMime, binaryPresent)
|
||||
//line templates/http_stuff.qtpl:42
|
||||
//line templates/http_stuff.qtpl:56
|
||||
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 {
|
||||
//line templates/http_stuff.qtpl:42
|
||||
//line templates/http_stuff.qtpl:56
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_stuff.qtpl:42
|
||||
//line templates/http_stuff.qtpl:56
|
||||
WriteHyphaListRowHTML(qb422016, hyphaName, binaryMime, binaryPresent)
|
||||
//line templates/http_stuff.qtpl:42
|
||||
//line templates/http_stuff.qtpl:56
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_stuff.qtpl:42
|
||||
//line templates/http_stuff.qtpl:56
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_stuff.qtpl:42
|
||||
//line templates/http_stuff.qtpl:56
|
||||
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
|
||||
</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 %}
|
||||
Here I am, willing to add some accesibility using ARIA. Turns out,
|
||||
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
|
||||
</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(`
|
||||
|
||||
<section class="recent-changes__list" role="feed">
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:29
|
||||
//line templates/recent_changes.qtpl:31
|
||||
if len(changes) == 0 {
|
||||
//line templates/recent_changes.qtpl:29
|
||||
//line templates/recent_changes.qtpl:31
|
||||
qw422016.N().S(`
|
||||
<p>Could not find any recent changes.</p>
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:31
|
||||
//line templates/recent_changes.qtpl:33
|
||||
} else {
|
||||
//line templates/recent_changes.qtpl:31
|
||||
//line templates/recent_changes.qtpl:33
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:32
|
||||
//line templates/recent_changes.qtpl:34
|
||||
for i, entry := range changes {
|
||||
//line templates/recent_changes.qtpl:32
|
||||
//line templates/recent_changes.qtpl:34
|
||||
qw422016.N().S(`
|
||||
<ul class="recent-changes__entry rc-entry" role="article"
|
||||
aria-setsize="`)
|
||||
//line templates/recent_changes.qtpl:34
|
||||
//line templates/recent_changes.qtpl:36
|
||||
qw422016.N().D(n)
|
||||
//line templates/recent_changes.qtpl:34
|
||||
//line templates/recent_changes.qtpl:36
|
||||
qw422016.N().S(`" aria-posinset="`)
|
||||
//line templates/recent_changes.qtpl:34
|
||||
//line templates/recent_changes.qtpl:36
|
||||
qw422016.N().D(i)
|
||||
//line templates/recent_changes.qtpl:34
|
||||
//line templates/recent_changes.qtpl:36
|
||||
qw422016.N().S(`">
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:35
|
||||
//line templates/recent_changes.qtpl:37
|
||||
qw422016.N().S(entry)
|
||||
//line templates/recent_changes.qtpl:35
|
||||
//line templates/recent_changes.qtpl:37
|
||||
qw422016.N().S(`
|
||||
</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(`
|
||||
`)
|
||||
//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(`
|
||||
</section>
|
||||
</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) {
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
StreamRecentChangesHTML(qw422016, changes, n)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
}
|
||||
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
func RecentChangesHTML(changes []string, n int) string {
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
WriteRecentChangesHTML(qb422016, changes, n)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
return qs422016
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
{% import "net/http" %}
|
||||
This dialog is to be shown to a user when they try to rename a hypha.
|
||||
{% func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
|
||||
<main>
|
||||
{%= navHTML(rq, hyphaName, "rename-ask") %}
|
||||
<main>
|
||||
{%- if isOld -%}
|
||||
<section>
|
||||
<h1>Rename {%s hyphaName %}</h1>
|
||||
|
@ -26,12 +26,12 @@ var (
|
||||
func StreamRenameAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
||||
//line templates/rename.qtpl:3
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/rename.qtpl:5
|
||||
//line templates/rename.qtpl:4
|
||||
streamnavHTML(qw422016, rq, hyphaName, "rename-ask")
|
||||
//line templates/rename.qtpl:5
|
||||
//line templates/rename.qtpl:4
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/rename.qtpl:6
|
||||
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"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
// If Name == "", the tree is empty.
|
||||
type tree struct {
|
||||
name string
|
||||
exists bool
|
||||
prevSibling string
|
||||
nextSibling string
|
||||
siblings []string
|
||||
descendants []*tree
|
||||
root bool
|
||||
hyphaIterator func(func(string))
|
||||
}
|
||||
|
||||
// TreeAsHtml generates a tree for `hyphaName`. `hyphaStorage` has this type because package `tree` has no access to `HyphaData` data type. One day it shall have it, I guess.
|
||||
func TreeAsHtml(hyphaName string, hyphaIterator func(func(string))) string {
|
||||
// Tree generates a tree for `hyphaName` as html and returns next and previous hyphae if any.
|
||||
func Tree(hyphaName string, hyphaIterator func(func(string))) (html, prev, next string) {
|
||||
t := &tree{name: hyphaName, root: true, hyphaIterator: hyphaIterator}
|
||||
t.fill()
|
||||
return t.asHtml()
|
||||
return t.asHtml(), util.BeautifulName(t.prevSibling), util.BeautifulName(t.nextSibling)
|
||||
}
|
||||
|
||||
// subtree adds a descendant tree to `t` and returns that tree.
|
||||
@ -34,11 +39,22 @@ func (t *tree) fork(descendantName string) *tree {
|
||||
return subt
|
||||
}
|
||||
|
||||
// Compare current prev next hyphae and decide if any of them should be set to `name2`.
|
||||
func (t *tree) prevNextDetermine(name2 string) {
|
||||
if name2 < t.name && (name2 > t.prevSibling || t.prevSibling == "") {
|
||||
t.prevSibling = name2
|
||||
} else if name2 > t.name && (name2 < t.nextSibling || t.nextSibling == "") {
|
||||
t.nextSibling = name2
|
||||
}
|
||||
}
|
||||
|
||||
// Compares names and does something with them, may generate a subtree.
|
||||
func (t *tree) compareNamesAndAppend(name2 string) {
|
||||
switch {
|
||||
case t.name == name2:
|
||||
t.exists = true
|
||||
case t.root && path.Dir(t.name) == path.Dir(name2):
|
||||
t.prevNextDetermine(name2)
|
||||
t.siblings = append(t.siblings, name2)
|
||||
case t.name == path.Dir(name2):
|
||||
t.fork(name2).fill()
|
||||
|
@ -10,53 +10,55 @@ import (
|
||||
"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)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = json.Unmarshal(contents, &UserStorage.Users)
|
||||
err = json.Unmarshal(contents, &users)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, user := range UserStorage.Users {
|
||||
user.Group = groupFromString(user.GroupString)
|
||||
log.Println("Found", len(users), "fixed users")
|
||||
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) {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var tmp map[string]string
|
||||
err = json.Unmarshal(contents, &tmp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for token, username := range tmp {
|
||||
user := UserStorage.userByName(username)
|
||||
UserStorage.Tokens[token] = user
|
||||
commenceSession(username, token)
|
||||
}
|
||||
log.Println("Found", len(tmp), "active sessions")
|
||||
}
|
||||
|
||||
func dumpTokens() {
|
||||
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.
|
||||
// Return path to tokens.json. Creates folders if needed.
|
||||
func tokenStoragePath() string {
|
||||
dir, err := xdg.DataFile("mycorrhiza/tokens.json")
|
||||
if err != nil {
|
||||
@ -67,3 +69,21 @@ func tokenStoragePath() string {
|
||||
}
|
||||
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
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
"sync"
|
||||
)
|
||||
|
||||
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.
|
||||
type User struct {
|
||||
// Name is a username. It must follow hypha naming rules.
|
||||
Name string `json:"name"`
|
||||
// Group the user is part of.
|
||||
Group UserGroup `json:"-"`
|
||||
GroupString string `json:"group"`
|
||||
Group string `json:"group"`
|
||||
Password string `json:"password"`
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// 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: "/",
|
||||
// Route — Right (more is more right)
|
||||
var minimalRights = map[string]int{
|
||||
"edit": 1,
|
||||
"upload-binary": 1,
|
||||
"upload-text": 1,
|
||||
"rename-ask": 2,
|
||||
"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 (
|
||||
URL string
|
||||
ServerPort string
|
||||
HomePage string
|
||||
SiteTitle string
|
||||
SiteNavIcon string
|
||||
SiteName string
|
||||
WikiDir string
|
||||
UserTree string
|
||||
UserHypha string
|
||||
HeaderLinksHypha string
|
||||
AuthMethod string
|
||||
FixedCredentialsPath string
|
||||
)
|
||||
@ -54,3 +57,11 @@ func RandomString(n int) (string, error) {
|
||||
}
|
||||
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