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

Merge pull request #33 from bouncepaw/0.12

Version 0.12
This commit is contained in:
Timur Ismagilov 2021-01-24 13:41:17 +05:00 committed by GitHub
commit 2a5d7d580c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 2941 additions and 993 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
hypha
mycorrhiza

View File

@ -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
View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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>

View File

@ -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)

View File

@ -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"
}

View File

@ -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
View 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")
}

View File

@ -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)

View File

@ -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))
}

View File

@ -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
View 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
View 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
View File

@ -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))
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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, "./"):

View File

@ -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 (

View File

@ -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)))
}

View File

@ -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
View 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"
}
}

View File

@ -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
View File

@ -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
View 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
View 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
}

View File

@ -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>

View File

@ -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>

View File

@ -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 %}

View File

@ -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
}

View File

@ -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 %}

View File

@ -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
View 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; }
}
}

View File

@ -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>

View File

@ -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 {

View File

@ -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 %}

View File

@ -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
}

View File

@ -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 %}

View File

@ -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
}

View File

@ -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 %}

View File

@ -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
}

View 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

View 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

View 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

View 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

View File

@ -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

View File

@ -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
}

View File

@ -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>

View File

@ -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
View 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
View 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
}

View File

@ -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()

View File

@ -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)
}
}

View File

@ -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
View 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: "/",
}
}

View File

@ -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
View 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
View 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

View File

@ -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, "_", " "))
}