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

Merge pull request #29 from bouncepaw/0.11

0.11
This commit is contained in:
Timur Ismagilov 2020-11-27 23:29:06 +05:00 committed by GitHub
commit c4e0e4879b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1807 additions and 569 deletions

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,6 +1,9 @@
run: build run: build
./mycorrhiza metarrhiza ./mycorrhiza metarrhiza
run_with_fixed_auth: build
./mycorrhiza -auth-method fixed metarrhiza
build: build:
go generate go generate
go build . go build .

View File

@ -1,4 +1,4 @@
# 🍄 MycorrhizaWiki 0.10 # 🍄 MycorrhizaWiki 0.11
A wiki engine. A wiki engine.
## Building ## Building
@ -15,13 +15,21 @@ make
``` ```
mycorrhiza [OPTIONS...] WIKI_PATH mycorrhiza [OPTIONS...] WIKI_PATH
WIKI_PATH must be a path to git repository which you want to be a wiki.
Options: Options:
-auth-method string
What auth method to use. Variants: "none", "fixed" (default "none")
-fixed-credentials-path string
Used when -auth-method=fixed. Path to file with user credentials. (default "mycocredentials.json")
-home string -home string
The home page (default "home") The home page (default "home")
-port string -port string
Port to serve the wiki at (default "1737") Port to serve the wiki at (default "1737")
-title string -title string
How to call your wiki in the navititle (default "🍄") How to call your wiki in the navititle (default "🍄")
-user-tree string
Hypha which is a superhypha of all user pages (default "u")
``` ```
## Features ## Features
@ -39,11 +47,11 @@ Options:
* Hyphae can be deleted (while still preserving history) * Hyphae can be deleted (while still preserving history)
* Hyphae can be renamed (recursive renaming of subhyphae is also supported) * Hyphae can be renamed (recursive renaming of subhyphae is also supported)
* Light on resources: I run a home wiki on this engine 24/7 at an [Orange π Lite](http://www.orangepi.org/orangepilite/). * Light on resources: I run a home wiki on this engine 24/7 at an [Orange π Lite](http://www.orangepi.org/orangepilite/).
* Authorization with pre-set credentials
## Contributing ## Contributing
Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where some development is coordinated. Feel free to open an issue or contact me. Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where some development is coordinated. Feel free to open an issue or contact me.
## Future plans ## Future plans
* Tagging system * Tagging system
* Authorization
* Better history viewing * Better history viewing

17
flag.go
View File

@ -5,6 +5,7 @@ import (
"log" "log"
"path/filepath" "path/filepath"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@ -12,6 +13,9 @@ func init() {
flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at") flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at")
flag.StringVar(&util.HomePage, "home", "home", "The home page") 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.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.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.")
} }
// Do the things related to cli args and die maybe // Do the things related to cli args and die maybe
@ -33,4 +37,17 @@ func parseCliArgs() {
if !isCanonicalName(util.HomePage) { if !isCanonicalName(util.HomePage) {
log.Fatal("Error: you must use a proper name for the homepage") log.Fatal("Error: you must use a proper name for the homepage")
} }
if !isCanonicalName(util.UserTree) {
log.Fatal("Error: you must use a proper name for user tree")
}
switch util.AuthMethod {
case "none":
case "fixed":
user.AuthUsed = true
user.PopulateFixedUserStorage()
default:
log.Fatal("Error: unknown auth method:", util.AuthMethod)
}
} }

5
go.mod
View File

@ -2,4 +2,7 @@ module github.com/bouncepaw/mycorrhiza
go 1.14 go 1.14
require github.com/valyala/quicktemplate v1.6.3 require (
github.com/adrg/xdg v0.2.2
github.com/valyala/quicktemplate v1.6.3
)

8
go.sum
View File

@ -1,6 +1,12 @@
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/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
@ -13,3 +19,5 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@ -76,11 +77,19 @@ func (rev Revision) HyphaeLinks() (html string) {
} }
func (rev Revision) RecentChangesEntry() (html string) { func (rev Revision) RecentChangesEntry() (html string) {
if user.AuthUsed && rev.Username != "anon" {
return fmt.Sprintf(`
<li class="rc-entry__time"><time>%[1]s</time></li>
<li class="rc-entry__hash">%[2]s</li>
<li class="rc-entry__links">%[5]s</li>
<li class="rc-entry__msg">%[6]s <span class="rc-entry__author">by <a href="/page/%[3]s/%[4]s" rel="author">%[4]s</a></span></li>
`, rev.TimeString(), rev.Hash, util.UserTree, rev.Username, rev.HyphaeLinks(), rev.Message)
}
return fmt.Sprintf(` return fmt.Sprintf(`
<li class="rc-entry__time"><time>%s</time></li> <li class="rc-entry__time"><time>%[1]s</time></li>
<li class="rc-entry__hash">%s</li> <li class="rc-entry__hash">%[2]s</li>
<li class="rc-entry__links">%s</li> <li class="rc-entry__links">%[3]s</li>
<li class="rc-entry__msg">%s</li> <li class="rc-entry__msg">%[4]s</li>
`, rev.TimeString(), rev.Hash, rev.HyphaeLinks(), rev.Message) `, rev.TimeString(), rev.Hash, rev.HyphaeLinks(), rev.Message)
} }

View File

@ -15,7 +15,7 @@ func RecentChanges(n int) string {
var ( var (
out, err = gitsh( out, err = gitsh(
"log", "--oneline", "--no-merges", "log", "--oneline", "--no-merges",
"--pretty=format:\"%h\t%ce\t%ct\t%s\"", "--pretty=format:\"%h\t%ae\t%at\t%s\"",
"--max-count="+strconv.Itoa(n), "--max-count="+strconv.Itoa(n),
) )
revs []Revision revs []Revision

View File

@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
"sync" "sync"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@ -29,7 +30,7 @@ const (
type HistoryOp struct { type HistoryOp struct {
// All errors are appended here. // All errors are appended here.
Errs []error Errs []error
opType OpType Type OpType
userMsg string userMsg string
name string name string
email string email string
@ -39,8 +40,10 @@ type HistoryOp struct {
func Operation(opType OpType) *HistoryOp { func Operation(opType OpType) *HistoryOp {
gitMutex.Lock() gitMutex.Lock()
hop := &HistoryOp{ hop := &HistoryOp{
Errs: []error{}, Errs: []error{},
opType: opType, name: "anon",
email: "anon@mycorrhiza",
Type: opType,
} }
return hop return hop
} }
@ -116,9 +119,11 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
return hop return hop
} }
// WithSignature sets a signature for the future commit. You need to pass a username only, the rest is upon us (including email and time). // WithUser sets a user for the commit.
func (hop *HistoryOp) WithSignature(username string) *HistoryOp { func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
hop.name = username if u.Group != user.UserAnon {
hop.email = username + "@mycorrhiza" // A fake email, why not hop.name = u.Name
hop.email = u.Name + "@mycorrhiza"
}
return hop return hop
} }

62
http_auth.go Normal file
View File

@ -0,0 +1,62 @@
package main
import (
"log"
"net/http"
"github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/user"
)
func init() {
http.HandleFunc("/login", handlerLogin)
http.HandleFunc("/login-data", handlerLoginData)
http.HandleFunc("/logout", handlerLogout)
http.HandleFunc("/logout-confirm", handlerLogoutConfirm)
}
func handlerLogout(w http.ResponseWriter, rq *http.Request) {
var (
u = user.FromRequest(rq)
can = u != nil
)
w.Header().Set("Content-Type", "text/html;charset=utf-8")
if can {
log.Println("User", u.Name, "tries to log out")
w.WriteHeader(http.StatusOK)
} else {
log.Println("Unknown user tries to log out")
w.WriteHeader(http.StatusForbidden)
}
w.Write([]byte(base("Logout?", templates.LogoutHTML(can))))
}
func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) {
user.LogoutFromRequest(w, rq)
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
var (
username = CanonicalName(rq.PostFormValue("username"))
password = rq.PostFormValue("password")
err = user.LoginDataHTTP(w, rq, username, password)
)
if err != "" {
w.Write([]byte(base(err, templates.LoginErrorHTML(err))))
} else {
http.Redirect(w, rq, "/", http.StatusSeeOther)
}
}
func handlerLogin(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
w.Header().Set("Content-Type", "text/html;charset=utf-8")
if user.AuthUsed {
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(http.StatusForbidden)
}
w.Write([]byte(base("Login", templates.LoginHTML())))
}

View File

@ -6,16 +6,19 @@ import (
"net/http" "net/http"
"github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
func init() { func init() {
http.HandleFunc("/upload-binary/", handlerUploadBinary) // Those that do not actually mutate anything:
http.HandleFunc("/upload-text/", handlerUploadText)
http.HandleFunc("/edit/", handlerEdit) http.HandleFunc("/edit/", handlerEdit)
http.HandleFunc("/delete-ask/", handlerDeleteAsk) http.HandleFunc("/delete-ask/", handlerDeleteAsk)
http.HandleFunc("/delete-confirm/", handlerDeleteConfirm)
http.HandleFunc("/rename-ask/", handlerRenameAsk) http.HandleFunc("/rename-ask/", handlerRenameAsk)
// 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("/rename-confirm/", handlerRenameConfirm)
} }
@ -25,22 +28,28 @@ func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) {
hyphaName = HyphaNameFromRq(rq, "rename-ask") hyphaName = HyphaNameFromRq(rq, "rename-ask")
_, isOld = HyphaStorage[hyphaName] _, isOld = HyphaStorage[hyphaName]
) )
util.HTTP200Page(w, base("Rename "+hyphaName+"?", templates.RenameAskHTML(hyphaName, isOld))) if ok := user.CanProceed(rq, "rename-confirm"); !ok {
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)))
} }
func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) { func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) log.Println(rq.URL)
var ( var (
hyphaName = HyphaNameFromRq(rq, "rename-confirm") hyphaName = HyphaNameFromRq(rq, "rename-confirm")
hyphaData, isOld = HyphaStorage[hyphaName] _, isOld = HyphaStorage[hyphaName]
newName = CanonicalName(rq.PostFormValue("new-name")) newName = CanonicalName(rq.PostFormValue("new-name"))
_, newNameIsUsed = HyphaStorage[newName] _, newNameIsUsed = HyphaStorage[newName]
recursive bool recursive = rq.PostFormValue("recursive") == "true"
u = user.FromRequest(rq).OrAnon()
) )
if rq.PostFormValue("recursive") == "true" {
recursive = true
}
switch { switch {
case !u.CanProceed("rename-confirm"):
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.")
log.Println("Rejected", rq.URL)
case newNameIsUsed: case newNameIsUsed:
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: hypha exists", HttpErr(w, http.StatusBadRequest, hyphaName, "Error: hypha exists",
fmt.Sprintf("Hypha named <a href='/page/%s'>%s</a> already exists.", hyphaName, hyphaName)) fmt.Sprintf("Hypha named <a href='/page/%s'>%s</a> already exists.", hyphaName, hyphaName))
@ -54,12 +63,12 @@ func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: invalid name", HttpErr(w, http.StatusBadRequest, hyphaName, "Error: invalid name",
"Invalid new name. Names cannot contain characters <code>^?!:#@&gt;&lt;*|\"\\'&amp;%</code>") "Invalid new name. Names cannot contain characters <code>^?!:#@&gt;&lt;*|\"\\'&amp;%</code>")
default: default:
if hop := hyphaData.RenameHypha(hyphaName, newName, recursive); len(hop.Errs) == 0 { if hop := RenameHypha(hyphaName, newName, recursive, u); len(hop.Errs) != 0 {
http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther)
} else {
HttpErr(w, http.StatusInternalServerError, hyphaName, HttpErr(w, http.StatusInternalServerError, hyphaName,
"Error: could not rename hypha", "Error: could not rename hypha",
fmt.Sprintf("Could not rename this hypha due to an internal error. Server errors: <code>%v</code>", hop.Errs)) fmt.Sprintf("Could not rename this hypha due to an internal error. Server errors: <code>%v</code>", hop.Errs))
} else {
http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther)
} }
} }
} }
@ -71,7 +80,12 @@ func handlerDeleteAsk(w http.ResponseWriter, rq *http.Request) {
hyphaName = HyphaNameFromRq(rq, "delete-ask") hyphaName = HyphaNameFromRq(rq, "delete-ask")
_, isOld = HyphaStorage[hyphaName] _, isOld = HyphaStorage[hyphaName]
) )
util.HTTP200Page(w, base("Delete "+hyphaName+"?", templates.DeleteAskHTML(hyphaName, isOld))) if ok := user.CanProceed(rq, "delete-ask"); !ok {
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)))
} }
// handlerDeleteConfirm deletes a hypha for sure // handlerDeleteConfirm deletes a hypha for sure
@ -80,22 +94,27 @@ func handlerDeleteConfirm(w http.ResponseWriter, rq *http.Request) {
var ( var (
hyphaName = HyphaNameFromRq(rq, "delete-confirm") hyphaName = HyphaNameFromRq(rq, "delete-confirm")
hyphaData, isOld = HyphaStorage[hyphaName] hyphaData, isOld = HyphaStorage[hyphaName]
u = user.FromRequest(rq)
) )
if isOld { if !u.CanProceed("delete-confirm") {
// If deleted successfully HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.")
if hop := hyphaData.DeleteHypha(hyphaName); len(hop.Errs) == 0 { log.Println("Rejected", rq.URL)
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) return
} else { }
HttpErr(w, http.StatusInternalServerError, hyphaName, if !isOld {
"Error: could not delete hypha",
fmt.Sprintf("Could not delete this hypha due to an internal error. Server errors: <code>%v</code>", hop.Errs))
}
} else {
// The precondition is to have the hypha in the first place. // The precondition is to have the hypha in the first place.
HttpErr(w, http.StatusPreconditionFailed, hyphaName, HttpErr(w, http.StatusPreconditionFailed, hyphaName,
"Error: no such hypha", "Error: no such hypha",
"Could not delete this hypha because it does not exist.") "Could not delete this hypha because it does not exist.")
return
} }
if hop := hyphaData.DeleteHypha(hyphaName, u); len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName,
"Error: could not delete hypha",
fmt.Sprintf("Could not delete this hypha due to internal errors. Server errors: <code>%v</code>", hop.Errs))
return
}
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
} }
// handlerEdit shows the edit form. It doesn't edit anything actually. // handlerEdit shows the edit form. It doesn't edit anything actually.
@ -108,6 +127,11 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
textAreaFill string textAreaFill string
err error err error
) )
if ok := user.CanProceed(rq, "edit"); !ok {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
log.Println("Rejected", rq.URL)
return
}
if isOld { if isOld {
textAreaFill, err = FetchTextPart(hyphaData) textAreaFill, err = FetchTextPart(hyphaData)
if err != nil { if err != nil {
@ -118,25 +142,27 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
} else { } else {
warning = `<p>You are creating a new hypha.</p>` warning = `<p>You are creating a new hypha.</p>`
} }
util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(hyphaName, textAreaFill, warning))) util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(rq, hyphaName, textAreaFill, warning)))
} }
// handlerUploadText uploads a new text part for the hypha. // handlerUploadText uploads a new text part for the hypha.
func handlerUploadText(w http.ResponseWriter, rq *http.Request) { func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) log.Println(rq.URL)
var ( var (
hyphaName = HyphaNameFromRq(rq, "upload-text") hyphaName = HyphaNameFromRq(rq, "upload-text")
hyphaData, isOld = HyphaStorage[hyphaName] textData = rq.PostFormValue("text")
textData = rq.PostFormValue("text") u = user.FromRequest(rq).OrAnon()
) )
if !isOld { if ok := user.CanProceed(rq, "upload-text"); !ok {
hyphaData = &HyphaData{} HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
log.Println("Rejected", rq.URL)
return
} }
if textData == "" { if textData == "" {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed") HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
return return
} }
if hop := hyphaData.UploadText(hyphaName, textData, isOld); len(hop.Errs) != 0 { if hop := UploadText(hyphaName, textData, u); len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error()) HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
} else { } else {
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther) http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
@ -146,31 +172,37 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
// handlerUploadBinary uploads a new binary part for the hypha. // handlerUploadBinary uploads a new binary part for the hypha.
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) { func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) log.Println(rq.URL)
hyphaName := HyphaNameFromRq(rq, "upload-binary") var (
rq.ParseMultipartForm(10 << 20) hyphaName = HyphaNameFromRq(rq, "upload-binary")
u = user.FromRequest(rq)
)
if !u.CanProceed("upload-binary") {
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to upload attachments.")
log.Println("Rejected", rq.URL)
return
}
rq.ParseMultipartForm(10 << 20) // Set upload limit
file, handler, err := rq.FormFile("binary") file, handler, err := rq.FormFile("binary")
if file != nil { if file != nil {
defer file.Close() defer file.Close()
} }
// If file is not passed: // If file is not passed:
if err != nil { if err != nil {
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed") HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed")
return return
} }
// If file is passed: // If file is passed:
var ( var (
hyphaData, isOld = HyphaStorage[hyphaName] mime = handler.Header.Get("Content-Type")
mime = handler.Header.Get("Content-Type") hop = UploadBinary(hyphaName, mime, file, u)
) )
if !isOld {
hyphaData = &HyphaData{}
}
hop := hyphaData.UploadBinary(hyphaName, mime, file, isOld)
if len(hop.Errs) != 0 { if len(hop.Errs) != 0 {
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error()) HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
} else { return
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
} }
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
} }

View File

@ -40,6 +40,7 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
contents = markup.ToHtml(hyphaName, textContents) contents = markup.ToHtml(hyphaName, textContents)
} }
page := templates.RevisionHTML( page := templates.RevisionHTML(
rq,
hyphaName, hyphaName,
naviTitle(hyphaName), naviTitle(hyphaName),
contents, contents,
@ -67,7 +68,7 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) {
log.Println("Found", len(revs), "revisions for", hyphaName) log.Println("Found", len(revs), "revisions for", hyphaName)
util.HTTP200Page(w, util.HTTP200Page(w,
base(hyphaName, templates.HistoryHTML(hyphaName, tbody))) base(hyphaName, templates.HistoryHTML(rq, hyphaName, tbody)))
} }
// handlerText serves raw source text of the hypha. // handlerText serves raw source text of the hypha.
@ -110,7 +111,7 @@ func handlerPage(w http.ResponseWriter, rq *http.Request) {
contents = binaryHtmlBlock(hyphaName, data) + contents contents = binaryHtmlBlock(hyphaName, data) + contents
} }
} }
util.HTTP200Page(w, base(hyphaName, templates.PageHTML(hyphaName, util.HTTP200Page(w, base(hyphaName, templates.PageHTML(rq, hyphaName,
naviTitle(hyphaName), naviTitle(hyphaName),
contents, contents,
tree.TreeAsHtml(hyphaName, IterateHyphaNamesWith)))) tree.TreeAsHtml(hyphaName, IterateHyphaNamesWith))))

View File

@ -12,6 +12,7 @@ import (
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/markup" "github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@ -31,6 +32,16 @@ func init() {
} }
return return
} }
markup.HyphaIterate = IterateHyphaNamesWith
}
// GetHyphaData finds a hypha addressed by `hyphaName` and returns its `hyphaData`. `hyphaData` is set to a zero value if this hypha does not exist. `isOld` is false if this hypha does not exist.
func GetHyphaData(hyphaName string) (hyphaData *HyphaData, isOld bool) {
hyphaData, isOld = HyphaStorage[hyphaName]
if hyphaData == nil {
hyphaData = &HyphaData{}
}
return
} }
// HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types. // HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types.
@ -40,10 +51,16 @@ type HyphaData struct {
} }
// uploadHelp is a helper function for UploadText and UploadBinary // uploadHelp is a helper function for UploadText and UploadBinary
func (hd *HyphaData) uploadHelp(hop *history.HistoryOp, hyphaName, ext string, originalFullPath *string, isOld bool, data []byte) *history.HistoryOp { func uploadHelp(hop *history.HistoryOp, hyphaName, ext string, data []byte, u *user.User) *history.HistoryOp {
var ( var (
fullPath = filepath.Join(WikiDir, hyphaName+ext) hyphaData, isOld = GetHyphaData(hyphaName)
fullPath = filepath.Join(WikiDir, hyphaName+ext)
originalFullPath = &hyphaData.textPath
) )
if hop.Type == history.TypeEditBinary {
originalFullPath = &hyphaData.binaryPath
}
if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil { if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil {
return hop.WithError(err) return hop.WithError(err)
} }
@ -51,30 +68,34 @@ func (hd *HyphaData) uploadHelp(hop *history.HistoryOp, hyphaName, ext string, o
if err := ioutil.WriteFile(fullPath, data, 0644); err != nil { if err := ioutil.WriteFile(fullPath, data, 0644); err != nil {
return hop.WithError(err) return hop.WithError(err)
} }
if isOld && *originalFullPath != fullPath && *originalFullPath != "" { if isOld && *originalFullPath != fullPath && *originalFullPath != "" {
if err := history.Rename(*originalFullPath, fullPath); err != nil { if err := history.Rename(*originalFullPath, fullPath); err != nil {
return hop.WithError(err) return hop.WithError(err)
} }
log.Println("Move", *originalFullPath, "to", fullPath) log.Println("Move", *originalFullPath, "to", fullPath)
} }
// New hyphae must be added to the hypha storage
if !isOld { if !isOld {
HyphaStorage[hyphaName] = hd HyphaStorage[hyphaName] = hyphaData
} }
*originalFullPath = fullPath *originalFullPath = fullPath
log.Printf("%v\n", *hd)
return hop.WithFiles(fullPath). return hop.WithFiles(fullPath).
WithSignature("anon"). WithUser(u).
Apply() Apply()
} }
// UploadText loads a new text part from `textData` for hypha `hyphaName` with `hd`. It must be marked if the hypha `isOld`. // UploadText loads a new text part from `textData` for hypha `hyphaName`.
func (hd *HyphaData) UploadText(hyphaName, textData string, isOld bool) *history.HistoryOp { func UploadText(hyphaName, textData string, u *user.User) *history.HistoryOp {
hop := history.Operation(history.TypeEditText).WithMsg(fmt.Sprintf("Edit %s", hyphaName)) return uploadHelp(
return hd.uploadHelp(hop, hyphaName, ".myco", &hd.textPath, isOld, []byte(textData)) history.
Operation(history.TypeEditText).
WithMsg(fmt.Sprintf("Edit %s", hyphaName)),
hyphaName, ".myco", []byte(textData), u)
} }
// UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`. // UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`.
func (hd *HyphaData) UploadBinary(hyphaName, mime string, file multipart.File, isOld bool) *history.HistoryOp { func UploadBinary(hyphaName, mime string, file multipart.File, u *user.User) *history.HistoryOp {
var ( var (
hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for %s with type %s", hyphaName, mime)) hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for %s with type %s", hyphaName, mime))
data, err = ioutil.ReadAll(file) data, err = ioutil.ReadAll(file)
@ -82,16 +103,15 @@ func (hd *HyphaData) UploadBinary(hyphaName, mime string, file multipart.File, i
if err != nil { if err != nil {
return hop.WithError(err).Apply() return hop.WithError(err).Apply()
} }
return uploadHelp(hop, hyphaName, MimeToExtension(mime), data, u)
return hd.uploadHelp(hop, hyphaName, MimeToExtension(mime), &hd.binaryPath, isOld, data)
} }
// DeleteHypha deletes hypha and makes a history record about that. // DeleteHypha deletes hypha and makes a history record about that.
func (hd *HyphaData) DeleteHypha(hyphaName string) *history.HistoryOp { func (hd *HyphaData) DeleteHypha(hyphaName string, u *user.User) *history.HistoryOp {
hop := history.Operation(history.TypeDeleteHypha). hop := history.Operation(history.TypeDeleteHypha).
WithFilesRemoved(hd.textPath, hd.binaryPath). WithFilesRemoved(hd.textPath, hd.binaryPath).
WithMsg(fmt.Sprintf("Delete %s", hyphaName)). WithMsg(fmt.Sprintf("Delete %s", hyphaName)).
WithSignature("anon"). WithUser(u).
Apply() Apply()
if len(hop.Errs) == 0 { if len(hop.Errs) == 0 {
delete(HyphaStorage, hyphaName) delete(HyphaStorage, hyphaName)
@ -138,7 +158,7 @@ func relocateHyphaData(hyphaNames []string, replaceName func(string) string) {
} }
// RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way. // RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way.
func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *history.HistoryOp { func RenameHypha(hyphaName, newName string, recursive bool, u *user.User) *history.HistoryOp {
var ( var (
replaceName = func(str string) string { replaceName = func(str string) string {
return strings.Replace(str, hyphaName, newName, 1) return strings.Replace(str, hyphaName, newName, 1)
@ -157,7 +177,7 @@ func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *his
} }
hop.WithFilesRenamed(renameMap). hop.WithFilesRenamed(renameMap).
WithMsg(fmt.Sprintf(renameMsg, hyphaName, newName)). WithMsg(fmt.Sprintf(renameMsg, hyphaName, newName)).
WithSignature("anon"). WithUser(u).
Apply() Apply()
if len(hop.Errs) == 0 { if len(hop.Errs) == 0 {
relocateHyphaData(hyphaNames, replaceName) relocateHyphaData(hyphaNames, replaceName)
@ -171,7 +191,7 @@ func binaryHtmlBlock(hyphaName string, hd *HyphaData) string {
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico": case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
return fmt.Sprintf(` return fmt.Sprintf(`
<div class="binary-container binary-container_with-img"> <div class="binary-container binary-container_with-img">
<a href="/page/%[1]s"><img src="/binary/%[1]s"/></a> <a href="/binary/%[1]s"><img src="/binary/%[1]s"/></a>
</div>`, hyphaName) </div>`, hyphaName)
case ".ogg", ".webm", ".mp4": case ".ogg", ".webm", ".mp4":
return fmt.Sprintf(` return fmt.Sprintf(`

View File

@ -15,6 +15,7 @@ import (
"github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/templates" "github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/user"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@ -22,7 +23,7 @@ import (
var WikiDir string var WikiDir string
// HyphaPattern is a pattern which all hyphae must match. // HyphaPattern is a pattern which all hyphae must match.
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%]+`) var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
// HyphaStorage is a mapping between canonical hypha names and their meta information. // HyphaStorage is a mapping between canonical hypha names and their meta information.
var HyphaStorage = make(map[string]*HyphaData) var HyphaStorage = make(map[string]*HyphaData)
@ -63,6 +64,11 @@ var base = templates.BaseHTML
// Reindex all hyphae by checking the wiki storage directory anew. // Reindex all hyphae by checking the wiki storage directory anew.
func handlerReindex(w http.ResponseWriter, rq *http.Request) { func handlerReindex(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL) log.Println(rq.URL)
if ok := user.CanProceed(rq, "reindex"); !ok {
HttpErr(w, http.StatusForbidden, util.HomePage, "Not enough rights", "You must be an admin to reindex hyphae.")
log.Println("Rejected", rq.URL)
return
}
HyphaStorage = make(map[string]*HyphaData) HyphaStorage = make(map[string]*HyphaData)
log.Println("Wiki storage directory is", WikiDir) log.Println("Wiki storage directory is", WikiDir)
log.Println("Start indexing hyphae...") log.Println("Start indexing hyphae...")
@ -125,6 +131,7 @@ func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static")))) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static"))))
// See http_readers.go for /page/, /text/, /binary/, /history/. // 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_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/.
// See http_auth.go for /login, /login-data, /logout, /logout-confirm
http.HandleFunc("/list", handlerList) http.HandleFunc("/list", handlerList)
http.HandleFunc("/reindex", handlerReindex) http.HandleFunc("/reindex", handlerReindex)
http.HandleFunc("/random", handlerRandom) http.HandleFunc("/random", handlerRandom)

View File

@ -13,53 +13,169 @@ func MatchesImg(line string) bool {
} }
type imgEntry struct { type imgEntry struct {
path string trimmedPath string
sizeH string path strings.Builder
sizeV string sizeW strings.Builder
desc string sizeH strings.Builder
desc strings.Builder
} }
func (entry *imgEntry) descriptionAsHtml(hyphaName string) (html string) {
if entry.desc.Len() == 0 {
return ""
}
lines := strings.Split(entry.desc.String(), "\n")
for _, line := range lines {
if line = strings.TrimSpace(line); line != "" {
if html != "" {
html += `<br>`
}
html += ParagraphToHtml(hyphaName, line)
}
}
return `<figcaption>` + html + `</figcaption>`
}
func (entry *imgEntry) sizeWAsAttr() string {
if entry.sizeW.Len() == 0 {
return ""
}
return ` width="` + entry.sizeW.String() + `"`
}
func (entry *imgEntry) sizeHAsAttr() string {
if entry.sizeH.Len() == 0 {
return ""
}
return ` height="` + entry.sizeH.String() + `"`
}
type imgState int
const (
inRoot imgState = iota
inName
inDimensionsW
inDimensionsH
inDescription
)
type Img struct { type Img struct {
entries []imgEntry entries []imgEntry
inDesc bool currEntry imgEntry
hyphaName string hyphaName string
state imgState
}
func (img *Img) pushEntry() {
if strings.TrimSpace(img.currEntry.path.String()) != "" {
img.entries = append(img.entries, img.currEntry)
img.currEntry = imgEntry{}
img.currEntry.path.Reset()
}
} }
func (img *Img) Process(line string) (shouldGoBackToNormal bool) { func (img *Img) Process(line string) (shouldGoBackToNormal bool) {
if img.inDesc { stateToProcessor := map[imgState]func(rune) bool{
rightBraceIndex := strings.IndexRune(line, '}') inRoot: img.processInRoot,
if cnt := len(img.entries); rightBraceIndex == -1 && cnt != 0 { inName: img.processInName,
img.entries[cnt-1].desc += "\n" + line inDimensionsW: img.processInDimensionsW,
} else if rightBraceIndex != -1 && cnt != 0 { inDimensionsH: img.processInDimensionsH,
img.entries[cnt-1].desc += "\n" + line[:rightBraceIndex] inDescription: img.processInDescription,
img.inDesc = false }
} for _, r := range line {
if strings.Count(line, "}") > 1 { if shouldReturnTrue := stateToProcessor[img.state](r); shouldReturnTrue {
return true return true
} }
} else if s := strings.TrimSpace(line); s != "" {
if s[0] == '}' {
return true
}
img.parseStartOfEntry(line)
} }
return false return false
} }
func ImgFromFirstLine(line, hyphaName string) Img { func (img *Img) processInDescription(r rune) (shouldReturnTrue bool) {
img := Img{ switch r {
case '}':
img.state = inName
default:
img.currEntry.desc.WriteRune(r)
}
return false
}
func (img *Img) processInRoot(r rune) (shouldReturnTrue bool) {
switch r {
case '}':
img.pushEntry()
return true
case '\n', '\r':
img.pushEntry()
case ' ', '\t':
default:
img.state = inName
img.currEntry = imgEntry{}
img.currEntry.path.Reset()
img.currEntry.path.WriteRune(r)
}
return false
}
func (img *Img) processInName(r rune) (shouldReturnTrue bool) {
switch r {
case '}':
img.pushEntry()
return true
case '|':
img.state = inDimensionsW
case '{':
img.state = inDescription
case '\n', '\r':
img.pushEntry()
img.state = inRoot
default:
img.currEntry.path.WriteRune(r)
}
return false
}
func (img *Img) processInDimensionsW(r rune) (shouldReturnTrue bool) {
switch r {
case '}':
img.pushEntry()
return true
case '*':
img.state = inDimensionsH
case ' ', '\t', '\n':
case '{':
img.state = inDescription
default:
img.currEntry.sizeW.WriteRune(r)
}
return false
}
func (img *Img) processInDimensionsH(r rune) (shouldGoBackToNormal bool) {
switch r {
case '}':
img.pushEntry()
return true
case ' ', '\t', '\n':
case '{':
img.state = inDescription
default:
img.currEntry.sizeH.WriteRune(r)
}
return false
}
func ImgFromFirstLine(line, hyphaName string) (img *Img, shouldGoBackToNormal bool) {
img = &Img{
hyphaName: hyphaName, hyphaName: hyphaName,
entries: make([]imgEntry, 0), entries: make([]imgEntry, 0),
} }
line = line[strings.IndexRune(line, '{'):] line = line[strings.IndexRune(line, '{')+1:]
if len(line) == 1 { // if { only return img, img.Process(line)
} else {
line = line[1:] // Drop the {
}
return img
} }
func (img *Img) canonicalPathFor(path string) string { func (img *Img) binaryPathFor(path string) string {
path = strings.TrimSpace(path) path = strings.TrimSpace(path)
if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 { if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
return path return path
@ -68,73 +184,71 @@ func (img *Img) canonicalPathFor(path string) string {
} }
} }
func (img *Img) parseStartOfEntry(line string) (entry imgEntry, followedByDesc bool) { func (img *Img) pagePathFor(path string) string {
pipeIndex := strings.IndexRune(line, '|') path = strings.TrimSpace(path)
if pipeIndex == -1 { // If no : in string if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
entry.path = img.canonicalPathFor(line) return path
} else { } else {
entry.path = img.canonicalPathFor(line[:pipeIndex]) return "/page/" + xclCanonicalName(img.hyphaName, path)
line = strings.TrimPrefix(line, line[:pipeIndex+1])
var (
leftBraceIndex = strings.IndexRune(line, '{')
rightBraceIndex = strings.IndexRune(line, '}')
dimensions string
)
if leftBraceIndex == -1 {
dimensions = line
} else {
dimensions = line[:leftBraceIndex]
}
sizeH, sizeV := parseDimensions(dimensions)
entry.sizeH = sizeH
entry.sizeV = sizeV
if leftBraceIndex != -1 && rightBraceIndex == -1 {
img.inDesc = true
followedByDesc = true
entry.desc = strings.TrimPrefix(line, line[:leftBraceIndex+1])
} else if leftBraceIndex != -1 && rightBraceIndex != -1 {
entry.desc = line[leftBraceIndex+1 : rightBraceIndex]
}
} }
img.entries = append(img.entries, entry)
return
} }
func parseDimensions(dimensions string) (sizeH, sizeV string) { func parseDimensions(dimensions string) (sizeW, sizeH string) {
xIndex := strings.IndexRune(dimensions, '*') xIndex := strings.IndexRune(dimensions, '*')
if xIndex == -1 { // If no x in dimensions if xIndex == -1 { // If no x in dimensions
sizeH = strings.TrimSpace(dimensions) sizeW = strings.TrimSpace(dimensions)
} else { } else {
sizeH = strings.TrimSpace(dimensions[:xIndex]) sizeW = strings.TrimSpace(dimensions[:xIndex])
sizeV = strings.TrimSpace(strings.TrimPrefix(dimensions, dimensions[:xIndex+1])) sizeH = strings.TrimSpace(strings.TrimPrefix(dimensions, dimensions[:xIndex+1]))
} }
return return
} }
func (img Img) ToHtml() (html string) { func (img *Img) checkLinks() map[string]bool {
for _, entry := range img.entries { m := make(map[string]bool)
html += fmt.Sprintf(`<figure> for i, entry := range img.entries {
<img src="%s" width="%s" height="%s"> // Also trim them for later use
`, entry.path, entry.sizeH, entry.sizeV) entry.trimmedPath = strings.TrimSpace(entry.path.String())
if entry.desc != "" { isAbsoluteUrl := strings.ContainsRune(entry.trimmedPath, ':')
html += ` <figcaption>` if !isAbsoluteUrl {
for i, line := range strings.Split(entry.desc, "\n") { entry.trimmedPath = canonicalName(entry.trimmedPath)
if line != "" {
if i > 0 {
html += `<br>`
}
html += ParagraphToHtml(img.hyphaName, line)
}
}
html += `</figcaption>`
} }
img.entries[i] = entry
m[entry.trimmedPath] = isAbsoluteUrl
}
HyphaIterate(func(hyphaName string) {
for _, entry := range img.entries {
if hyphaName == entry.trimmedPath {
m[entry.trimmedPath] = true
}
}
})
return m
}
func (img *Img) ToHtml() (html string) {
linkAvailabilityMap := img.checkLinks()
isOneImageOnly := len(img.entries) == 1 && img.entries[0].desc.Len() == 0
if isOneImageOnly {
html += `<section class="img-gallery img-gallery_one-image">`
} else {
html += `<section class="img-gallery img-gallery_many-images">`
}
for _, entry := range img.entries {
html += `<figure>`
// If is existing hypha or an external path
if linkAvailabilityMap[entry.trimmedPath] {
html += fmt.Sprintf(
`<a href="%s"><img src="%s" %s %s></a>`,
img.pagePathFor(entry.trimmedPath),
img.binaryPathFor(entry.trimmedPath),
entry.sizeWAsAttr(), entry.sizeHAsAttr())
} else { // If is a non-existent hypha
html += fmt.Sprintf(`<a class="wikilink_new" href="%s">Hypha <em>%s</em> does not exist</a>`, img.pagePathFor(entry.trimmedPath), entry.trimmedPath)
}
html += entry.descriptionAsHtml(img.hyphaName)
html += `</figure>` html += `</figure>`
} }
return `<section class="img-gallery"> return html + `</section>`
` + html + `
</section>`
} }

View File

@ -12,6 +12,9 @@ var HyphaExists func(string) bool
// HyphaAccess holds function that accesses a hypha by its name. // HyphaAccess holds function that accesses a hypha by its name.
var HyphaAccess func(string) (rawText, binaryHtml string, err error) var HyphaAccess func(string) (rawText, binaryHtml string, err error)
// HyphaIterate is a function that iterates all hypha names existing.
var HyphaIterate func(func(string))
// GemLexerState is used by markup parser to remember what is going on. // GemLexerState is used by markup parser to remember what is going on.
type GemLexerState struct { type GemLexerState struct {
// Name of hypha being parsed // Name of hypha being parsed
@ -21,7 +24,7 @@ type GemLexerState struct {
id int id int
buf string buf string
// Temporaries // Temporaries
img Img img *Img
} }
type Line struct { type Line struct {
@ -45,13 +48,17 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
*ast = append(*ast, Line{id: state.id, contents: text}) *ast = append(*ast, Line{id: state.id, contents: text})
} }
// Process empty lines depending on the current state
if "" == strings.TrimSpace(line) { if "" == strings.TrimSpace(line) {
if state.where == "list" { switch state.where {
case "list":
state.where = "" state.where = ""
addLine(state.buf + "</ul>") addLine(state.buf + "</ul>")
} else if state.where == "number" { case "number":
state.where = "" state.where = ""
addLine(state.buf + "</ol>") addLine(state.buf + "</ol>")
case "pre":
state.buf += "\n"
} }
return return
} }
@ -59,6 +66,9 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
startsWith := func(token string) bool { startsWith := func(token string) bool {
return strings.HasPrefix(line, token) return strings.HasPrefix(line, token)
} }
addHeading := func(i int) {
addLine(fmt.Sprintf("<h%d id='%d'>%s</h%d>", i, state.id, ParagraphToHtml(state.name, line[i+1:]), i))
}
// Beware! Usage of goto. Some may say it is considered evil but in this case it helped to make a better-structured code. // Beware! Usage of goto. Some may say it is considered evil but in this case it helped to make a better-structured code.
switch state.where { switch state.where {
@ -77,7 +87,7 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
imgState: imgState:
if shouldGoBackToNormal := state.img.Process(line); shouldGoBackToNormal { if shouldGoBackToNormal := state.img.Process(line); shouldGoBackToNormal {
state.where = "" state.where = ""
addLine(state.img) addLine(*state.img)
} }
return return
@ -142,23 +152,17 @@ normalState:
goto numberState goto numberState
case startsWith("###### "): case startsWith("###### "):
addLine(fmt.Sprintf( addHeading(6)
"<h6 id='%d'>%s</h6>", state.id, line[7:]))
case startsWith("##### "): case startsWith("##### "):
addLine(fmt.Sprintf( addHeading(5)
"<h5 id='%d'>%s</h5>", state.id, line[6:]))
case startsWith("#### "): case startsWith("#### "):
addLine(fmt.Sprintf( addHeading(4)
"<h4 id='%d'>%s</h4>", state.id, line[5:]))
case startsWith("### "): case startsWith("### "):
addLine(fmt.Sprintf( addHeading(3)
"<h3 id='%d'>%s</h3>", state.id, line[4:]))
case startsWith("## "): case startsWith("## "):
addLine(fmt.Sprintf( addHeading(2)
"<h2 id='%d'>%s</h2>", state.id, line[3:]))
case startsWith("# "): case startsWith("# "):
addLine(fmt.Sprintf( addHeading(1)
"<h1 id='%d'>%s</h1>", state.id, line[2:]))
case startsWith(">"): case startsWith(">"):
addLine(fmt.Sprintf( addLine(fmt.Sprintf(
@ -173,8 +177,13 @@ normalState:
case line == "----": case line == "----":
*ast = append(*ast, Line{id: -1, contents: "<hr/>"}) *ast = append(*ast, Line{id: -1, contents: "<hr/>"})
case MatchesImg(line): case MatchesImg(line):
state.where = "img" img, shouldGoBackToNormal := ImgFromFirstLine(line, state.name)
state.img = ImgFromFirstLine(line, state.name) if shouldGoBackToNormal {
addLine(*img)
} else {
state.where = "img"
state.img = img
}
default: default:
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, ParagraphToHtml(state.name, line))) addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, ParagraphToHtml(state.name, line)))
} }

View File

@ -41,7 +41,7 @@ func TestLex(t *testing.T) {
</ul>`}, </ul>`},
{6, "<p id='6'>text</p>"}, {6, "<p id='6'>text</p>"},
{7, "<p id='7'>more text</p>"}, {7, "<p id='7'>more text</p>"},
{8, `<p><a id='8' class='rocketlink wikilink_internal' href="/page/Pear">some link</a></p>`}, {8, `<p><a id='8' class='rocketlink wikilink_internal' href="/page/pear">some link</a></p>`},
{9, `<ul id='9'> {9, `<ul id='9'>
<li>lin&#34;+</li> <li>lin&#34;+</li>
</ul>`}, </ul>`},

View File

@ -17,6 +17,7 @@ const (
spanSuper spanSuper
spanSub spanSub
spanMark spanMark
spanStrike
spanLink spanLink
) )
@ -78,7 +79,7 @@ func getTextNode(input *bytes.Buffer) string {
escaping = false escaping = false
} else if b == '\\' { } else if b == '\\' {
escaping = true escaping = true
} else if strings.IndexByte("/*`^,![", b) >= 0 { } else if strings.IndexByte("/*`^,![~", b) >= 0 {
input.UnreadByte() input.UnreadByte()
break break
} else { } else {
@ -127,6 +128,9 @@ func ParagraphToHtml(hyphaName, input string) string {
case startsWith("!!"): case startsWith("!!"):
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!")) ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
p.Next(2) p.Next(2)
case startsWith("~~"):
ret.WriteString(tagFromState(spanMark, tagState, "s", "~~"))
p.Next(2)
case startsWith("[["): case startsWith("[["):
ret.WriteString(getLinkNode(p, hyphaName)) ret.WriteString(getLinkNode(p, hyphaName))
default: default:
@ -149,6 +153,8 @@ func ParagraphToHtml(hyphaName, input string) string {
ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,")) ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,"))
case spanMark: case spanMark:
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!")) ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
case spanStrike:
ret.WriteString(tagFromState(spanMark, tagState, "s", "~~"))
case spanLink: case spanLink:
ret.WriteString(tagFromState(spanLink, tagState, "a", "[[")) ret.WriteString(tagFromState(spanLink, tagState, "a", "[["))
} }

View File

@ -32,6 +32,7 @@ func TestParagraphToHtml(t *testing.T) {
{"Embedded //italic//", "Embedded <em>italic</em>"}, {"Embedded //italic//", "Embedded <em>italic</em>"},
{"double //italian// //text//", "double <em>italian</em> <em>text</em>"}, {"double //italian// //text//", "double <em>italian</em> <em>text</em>"},
{"it has `mono`", "it has <code>mono</code>"}, {"it has `mono`", "it has <code>mono</code>"},
{"it has ~~strike~~", "it has <s>strike</s>"},
{"this is a left **bold", "this is a left <strong>bold</strong>"}, {"this is a left **bold", "this is a left <strong>bold</strong>"},
{"this line has a ,comma, two of them", "this line has a ,comma, two of them"}, {"this line has a ,comma, two of them", "this line has a ,comma, two of them"},
{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."}, {"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},

View File

@ -21,6 +21,8 @@ func Parse(ast []Line, from, to int, state GemParserState) (html string) {
html += v.ToHtml() html += v.ToHtml()
case string: case string:
html += v html += v
default:
html += "Unknown"
} }
} }
} }

@ -1 +1 @@
Subproject commit d78a90718ae9c36f7a114901ddad84b0e23221b3 Subproject commit 7828352598c19afe5f2e13df0219656ac7b44c9c

View File

@ -1,19 +0,0 @@
package main
import (
"testing"
)
func TestMimeData(t *testing.T) {
check := func(ext string, expectedIsText bool, expectedMimeId int) {
isText, mimeId := mimeData(ext)
if isText != expectedIsText || mimeId != expectedMimeId {
t.Error(ext, isText, mimeId)
}
}
check(".txt", true, int(TextPlain))
check(".gmi", true, int(TextGemini))
check(".bin", false, int(BinaryOctet))
check(".jpg", false, int(BinaryJpeg))
check(".bin", false, int(BinaryOctet))
}

24
mycocredentials.json Normal file
View File

@ -0,0 +1,24 @@
[
{
"name": "admin",
"password": "mycorrhiza",
"group": "admin"
},
{
"name": "weird_fish",
"password": "DeepestOcean",
"group": "moderator"
},
{
"name": "king_of_limbs",
"password": "ambush",
"group": "trusted"
},
{
"name": "paranoid_android",
"password": "ok computer",
"group": "editor"
}
]

60
templates/auth.qtpl Normal file
View File

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

218
templates/auth.qtpl.go Normal file
View File

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

View File

@ -1,3 +1,7 @@
{% import "net/http" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% import "github.com/bouncepaw/mycorrhiza/util" %}
This is the <nav> seen on top of many pages. This is the <nav> seen on top of many pages.
{% code {% code
type navEntry struct { type navEntry struct {
@ -10,23 +14,38 @@ var navEntries = []navEntry{
{"text", "Raw text"}, {"text", "Raw text"},
{"history", "History"}, {"history", "History"},
{"revision", "NOT REACHED"}, {"revision", "NOT REACHED"},
{"delete-ask", "Delete"},
{"rename-ask", "Rename"}, {"rename-ask", "Rename"},
{"delete-ask", "Delete"},
} }
%} %}
{% func navHTML(hyphaName, navType string, revisionHash ...string) %} {% func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %}
<nav> {% code
u := user.FromRequest(rq).OrAnon()
%}
<nav class="navlinks">
<ul> <ul>
{%- for _, entry := range navEntries -%} {%- for _, entry := range navEntries -%}
{%- if navType == "revision" && entry.path == "revision" -%} {%- if navType == "revision" && entry.path == "revision" -%}
<li><b>{%s revisionHash[0] %}</b></li> <li><b>{%s revisionHash[0] %}</b></li>
{%- elseif navType == entry.path -%} {%- elseif navType == entry.path -%}
<li><b>{%s entry.title %}</b></li> <li><b>{%s entry.title %}</b></li>
{%- elseif entry.path != "revision"-%} {%- elseif entry.path != "revision" && u.Group.CanAccessRoute(entry.path) -%}
<li><a href="/{%s entry.path %}/{%s hyphaName %}">{%s entry.title %}</a></li> <li><a href="/{%s entry.path %}/{%s hyphaName %}">{%s entry.title %}</a></li>
{%- endif -%} {%- endif -%}
{%- endfor -%} {%- endfor -%}
{%s= userMenuHTML(u) %}
</ul> </ul>
</nav> </nav>
{% endfunc %} {% endfunc %}
{% func userMenuHTML(u *user.User) %}
<li class="navlinks__user">
{% if u.Group == user.UserAnon %}
<a href="/login">Login</a>
{% else %}
<a href="/page/{%s util.UserTree %}/{%s u.Name %}">{%s u.Name %}</a>
{% endif %}
</li>
{% endfunc %}

View File

@ -1,25 +1,34 @@
// Code generated by qtc from "common.qtpl". DO NOT EDIT. // Code generated by qtc from "common.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details. // See https://github.com/valyala/quicktemplate for details.
// This is the <nav> seen on top of many pages. //line templates/common.qtpl:1
//line templates/common.qtpl:2
package templates package templates
//line templates/common.qtpl:1
import "net/http"
//line templates/common.qtpl:2 //line templates/common.qtpl:2
import "github.com/bouncepaw/mycorrhiza/user"
//line templates/common.qtpl:3
import "github.com/bouncepaw/mycorrhiza/util"
// This is the <nav> seen on top of many pages.
//line templates/common.qtpl:6
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line templates/common.qtpl:2 //line templates/common.qtpl:6
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line templates/common.qtpl:3 //line templates/common.qtpl:7
type navEntry struct { type navEntry struct {
path string path string
title string title string
@ -31,87 +40,163 @@ var navEntries = []navEntry{
{"text", "Raw text"}, {"text", "Raw text"},
{"history", "History"}, {"history", "History"},
{"revision", "NOT REACHED"}, {"revision", "NOT REACHED"},
{"delete-ask", "Delete"},
{"rename-ask", "Rename"}, {"rename-ask", "Rename"},
{"delete-ask", "Delete"},
} }
//line templates/common.qtpl:18 //line templates/common.qtpl:22
func streamnavHTML(qw422016 *qt422016.Writer, hyphaName, navType string, revisionHash ...string) { func streamnavHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
//line templates/common.qtpl:18 //line templates/common.qtpl:22
qw422016.N().S(` qw422016.N().S(`
<nav> `)
//line templates/common.qtpl:24
u := user.FromRequest(rq).OrAnon()
//line templates/common.qtpl:25
qw422016.N().S(`
<nav class="navlinks">
<ul> <ul>
`) `)
//line templates/common.qtpl:21 //line templates/common.qtpl:28
for _, entry := range navEntries { for _, entry := range navEntries {
//line templates/common.qtpl:22 //line templates/common.qtpl:29
if navType == "revision" && entry.path == "revision" { if navType == "revision" && entry.path == "revision" {
//line templates/common.qtpl:22 //line templates/common.qtpl:29
qw422016.N().S(` <li><b>`) qw422016.N().S(` <li><b>`)
//line templates/common.qtpl:23 //line templates/common.qtpl:30
qw422016.E().S(revisionHash[0]) qw422016.E().S(revisionHash[0])
//line templates/common.qtpl:23 //line templates/common.qtpl:30
qw422016.N().S(`</b></li> qw422016.N().S(`</b></li>
`) `)
//line templates/common.qtpl:24 //line templates/common.qtpl:31
} else if navType == entry.path { } else if navType == entry.path {
//line templates/common.qtpl:24 //line templates/common.qtpl:31
qw422016.N().S(` <li><b>`) qw422016.N().S(` <li><b>`)
//line templates/common.qtpl:25 //line templates/common.qtpl:32
qw422016.E().S(entry.title) qw422016.E().S(entry.title)
//line templates/common.qtpl:25 //line templates/common.qtpl:32
qw422016.N().S(`</b></li> qw422016.N().S(`</b></li>
`) `)
//line templates/common.qtpl:26 //line templates/common.qtpl:33
} else if entry.path != "revision" { } else if entry.path != "revision" && u.Group.CanAccessRoute(entry.path) {
//line templates/common.qtpl:26 //line templates/common.qtpl:33
qw422016.N().S(` <li><a href="/`) qw422016.N().S(` <li><a href="/`)
//line templates/common.qtpl:27 //line templates/common.qtpl:34
qw422016.E().S(entry.path) qw422016.E().S(entry.path)
//line templates/common.qtpl:27 //line templates/common.qtpl:34
qw422016.N().S(`/`) qw422016.N().S(`/`)
//line templates/common.qtpl:27 //line templates/common.qtpl:34
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line templates/common.qtpl:27 //line templates/common.qtpl:34
qw422016.N().S(`">`) qw422016.N().S(`">`)
//line templates/common.qtpl:27 //line templates/common.qtpl:34
qw422016.E().S(entry.title) qw422016.E().S(entry.title)
//line templates/common.qtpl:27 //line templates/common.qtpl:34
qw422016.N().S(`</a></li> qw422016.N().S(`</a></li>
`) `)
//line templates/common.qtpl:28 //line templates/common.qtpl:35
} }
//line templates/common.qtpl:29 //line templates/common.qtpl:36
} }
//line templates/common.qtpl:29 //line templates/common.qtpl:36
qw422016.N().S(` </ul> qw422016.N().S(` `)
//line templates/common.qtpl:37
qw422016.N().S(userMenuHTML(u))
//line templates/common.qtpl:37
qw422016.N().S(`
</ul>
</nav> </nav>
`) `)
//line templates/common.qtpl:32 //line templates/common.qtpl:40
} }
//line templates/common.qtpl:32 //line templates/common.qtpl:40
func writenavHTML(qq422016 qtio422016.Writer, hyphaName, navType string, revisionHash ...string) { func writenavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
//line templates/common.qtpl:32 //line templates/common.qtpl:40
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/common.qtpl:32 //line templates/common.qtpl:40
streamnavHTML(qw422016, hyphaName, navType, revisionHash...) streamnavHTML(qw422016, rq, hyphaName, navType, revisionHash...)
//line templates/common.qtpl:32 //line templates/common.qtpl:40
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line templates/common.qtpl:32 //line templates/common.qtpl:40
} }
//line templates/common.qtpl:32 //line templates/common.qtpl:40
func navHTML(hyphaName, navType string, revisionHash ...string) string { func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string {
//line templates/common.qtpl:32 //line templates/common.qtpl:40
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line templates/common.qtpl:32 //line templates/common.qtpl:40
writenavHTML(qb422016, hyphaName, navType, revisionHash...) writenavHTML(qb422016, rq, hyphaName, navType, revisionHash...)
//line templates/common.qtpl:32 //line templates/common.qtpl:40
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line templates/common.qtpl:32 //line templates/common.qtpl:40
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line templates/common.qtpl:32 //line templates/common.qtpl:40
return qs422016 return qs422016
//line templates/common.qtpl:32 //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:50
func writeuserMenuHTML(qq422016 qtio422016.Writer, u *user.User) {
//line templates/common.qtpl:50
qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/common.qtpl:50
streamuserMenuHTML(qw422016, u)
//line templates/common.qtpl:50
qt422016.ReleaseWriter(qw422016)
//line templates/common.qtpl:50
}
//line templates/common.qtpl:50
func userMenuHTML(u *user.User) string {
//line templates/common.qtpl:50
qb422016 := qt422016.AcquireByteBuffer()
//line templates/common.qtpl:50
writeuserMenuHTML(qb422016, u)
//line templates/common.qtpl:50
qs422016 := string(qb422016.B)
//line templates/common.qtpl:50
qt422016.ReleaseByteBuffer(qb422016)
//line templates/common.qtpl:50
return qs422016
//line templates/common.qtpl:50
} }

View File

@ -21,10 +21,11 @@ blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;}
.wikilink_new {color:#a55858;} .wikilink_new {color:#a55858;}
.wikilink_new:visited {color:#a55858;} .wikilink_new:visited {color:#a55858;}
.wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;} .wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;}
p code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; } article code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; }
.codeblock {background-color:#eee; padding:.5rem; font-size:16px; white-space: pre-wrap;} 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 code, .transclusion .codeblock {background-color:#ddd;}
.transclusion {background-color:#eee; } .transclusion {background-color:#eee; border-radius: .25rem; }
.transclusion__content > *:not(.binary-container) {margin: 0.5rem; } .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 {display: block; text-align: right; font-style: italic; margin-top: 0.5rem; color: black; text-decoration: none;}
.transclusion__link::before {content: "⇐ ";} .transclusion__link::before {content: "⇐ ";}
@ -33,16 +34,21 @@ p code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font
.binary-container_with-video video, .binary-container_with-video video,
.binary-container_with-audio audio {width: 100%} .binary-container_with-audio audio {width: 100%}
.navi-title a {text-decoration:none;} .navi-title a {text-decoration:none;}
.img-gallery img { max-width: 100%; } .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; } figure { margin: 0; }
figcaption { padding-bottom: .5rem; }
nav ul {display:flex; padding-left:0; flex-wrap:wrap; margin-top:0;} nav ul {display:flex; padding-left:0; flex-wrap:wrap; margin-top:0;}
nav ul li {list-style-type:none;margin-right:1rem;} nav ul li {list-style-type:none;margin-right:1rem;}
#new-name {width:100%;} #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 { display: grid; list-style-type: none; padding: .25rem; background-color: #eee; grid-template-columns: 1fr 1fr; }
.rc-entry__time { font-style: italic; } .rc-entry__time { font-style: italic; }
.rc-entry__hash { font-style: italic; text-align: right; } .rc-entry__hash { font-style: italic; text-align: right; }
.rc-entry__links { grid-column: 1 / span 2; } .rc-entry__links { grid-column: 1 / span 2; }
.rc-entry__author { font-style: italic; }
{% endfunc %} {% endfunc %}

View File

@ -43,10 +43,11 @@ blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;}
.wikilink_new {color:#a55858;} .wikilink_new {color:#a55858;}
.wikilink_new:visited {color:#a55858;} .wikilink_new:visited {color:#a55858;}
.wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;} .wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;}
p code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; } article code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; }
.codeblock {background-color:#eee; padding:.5rem; font-size:16px; white-space: pre-wrap;} 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 code, .transclusion .codeblock {background-color:#ddd;}
.transclusion {background-color:#eee; } .transclusion {background-color:#eee; border-radius: .25rem; }
.transclusion__content > *:not(.binary-container) {margin: 0.5rem; } .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 {display: block; text-align: right; font-style: italic; margin-top: 0.5rem; color: black; text-decoration: none;}
.transclusion__link::before {content: "⇐ ";} .transclusion__link::before {content: "⇐ ";}
@ -55,44 +56,49 @@ p code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font
.binary-container_with-video video, .binary-container_with-video video,
.binary-container_with-audio audio {width: 100%} .binary-container_with-audio audio {width: 100%}
.navi-title a {text-decoration:none;} .navi-title a {text-decoration:none;}
.img-gallery img { max-width: 100%; } .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; } figure { margin: 0; }
figcaption { padding-bottom: .5rem; }
nav ul {display:flex; padding-left:0; flex-wrap:wrap; margin-top:0;} nav ul {display:flex; padding-left:0; flex-wrap:wrap; margin-top:0;}
nav ul li {list-style-type:none;margin-right:1rem;} nav ul li {list-style-type:none;margin-right:1rem;}
#new-name {width:100%;} #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 { display: grid; list-style-type: none; padding: .25rem; background-color: #eee; grid-template-columns: 1fr 1fr; }
.rc-entry__time { font-style: italic; } .rc-entry__time { font-style: italic; }
.rc-entry__hash { font-style: italic; text-align: right; } .rc-entry__hash { font-style: italic; text-align: right; }
.rc-entry__links { grid-column: 1 / span 2; } .rc-entry__links { grid-column: 1 / span 2; }
.rc-entry__author { font-style: italic; }
`) `)
//line templates/css.qtpl:48 //line templates/css.qtpl:54
} }
//line templates/css.qtpl:48 //line templates/css.qtpl:54
func WriteDefaultCSS(qq422016 qtio422016.Writer) { func WriteDefaultCSS(qq422016 qtio422016.Writer) {
//line templates/css.qtpl:48 //line templates/css.qtpl:54
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/css.qtpl:48 //line templates/css.qtpl:54
StreamDefaultCSS(qw422016) StreamDefaultCSS(qw422016)
//line templates/css.qtpl:48 //line templates/css.qtpl:54
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line templates/css.qtpl:48 //line templates/css.qtpl:54
} }
//line templates/css.qtpl:48 //line templates/css.qtpl:54
func DefaultCSS() string { func DefaultCSS() string {
//line templates/css.qtpl:48 //line templates/css.qtpl:54
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line templates/css.qtpl:48 //line templates/css.qtpl:54
WriteDefaultCSS(qb422016) WriteDefaultCSS(qb422016)
//line templates/css.qtpl:48 //line templates/css.qtpl:54
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line templates/css.qtpl:48 //line templates/css.qtpl:54
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line templates/css.qtpl:48 //line templates/css.qtpl:54
return qs422016 return qs422016
//line templates/css.qtpl:48 //line templates/css.qtpl:54
} }

View File

@ -1,7 +1,9 @@
{% import "net/http" %}
This dialog is to be shown to a user when they try to delete a hypha. This dialog is to be shown to a user when they try to delete a hypha.
{% func DeleteAskHTML(hyphaName string, isOld bool) %} {% func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
<main> <main>
{%= navHTML(hyphaName, "delete-ask") %} {%= navHTML(rq, hyphaName, "delete-ask") %}
{% if isOld %} {% if isOld %}
<section> <section>
<h1>Delete {%s hyphaName %}?</h1> <h1>Delete {%s hyphaName %}?</h1>

View File

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

View File

@ -1,13 +1,16 @@
{% func EditHTML(hyphaName, textAreaFill, warning string) %} {% import "net/http" %}
<main class="edit">
<h1>Edit {%s hyphaName %}</h1> {% func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) %}
{%s= warning %} <main class="edit">
<form method="post" class="edit-form" {%s= navHTML(rq, hyphaName, "edit") %}
action="/upload-text/{%s hyphaName %}"> <h1>Edit {%s hyphaName %}</h1>
<textarea name="text">{%s textAreaFill %}</textarea> {%s= warning %}
<br/> <form method="post" class="edit-form"
<input type="submit"/> action="/upload-text/{%s hyphaName %}">
<a href="/page/{%s hyphaName %}">Cancel</a> <textarea name="text">{%s textAreaFill %}</textarea>
</form> <br/>
</main> <input type="submit"/>
<a href="/page/{%s hyphaName %}">Cancel</a>
</form>
</main>
{% endfunc %} {% endfunc %}

View File

@ -5,79 +5,87 @@
package templates package templates
//line templates/http_mutators.qtpl:1 //line templates/http_mutators.qtpl:1
import "net/http"
//line templates/http_mutators.qtpl:3
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line templates/http_mutators.qtpl:1 //line templates/http_mutators.qtpl:3
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line templates/http_mutators.qtpl:1
func StreamEditHTML(qw422016 *qt422016.Writer, hyphaName, textAreaFill, warning string) {
//line templates/http_mutators.qtpl:1
qw422016.N().S(`
<main class="edit">
<h1>Edit `)
//line templates/http_mutators.qtpl:3 //line templates/http_mutators.qtpl:3
qw422016.E().S(hyphaName) func StreamEditHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
//line templates/http_mutators.qtpl:3 //line templates/http_mutators.qtpl:3
qw422016.N().S(`</h1>
`)
//line templates/http_mutators.qtpl:4
qw422016.N().S(warning)
//line templates/http_mutators.qtpl:4
qw422016.N().S(` qw422016.N().S(`
<form method="post" class="edit-form" <main class="edit">
action="/upload-text/`)
//line templates/http_mutators.qtpl:6
qw422016.E().S(hyphaName)
//line templates/http_mutators.qtpl:6
qw422016.N().S(`">
<textarea name="text">`)
//line templates/http_mutators.qtpl:7
qw422016.E().S(textAreaFill)
//line templates/http_mutators.qtpl:7
qw422016.N().S(`</textarea>
<br/>
<input type="submit"/>
<a href="/page/`)
//line templates/http_mutators.qtpl:10
qw422016.E().S(hyphaName)
//line templates/http_mutators.qtpl:10
qw422016.N().S(`">Cancel</a>
</form>
</main>
`) `)
//line templates/http_mutators.qtpl:5
qw422016.N().S(navHTML(rq, hyphaName, "edit"))
//line templates/http_mutators.qtpl:5
qw422016.N().S(`
<h1>Edit `)
//line templates/http_mutators.qtpl:6
qw422016.E().S(hyphaName)
//line templates/http_mutators.qtpl:6
qw422016.N().S(`</h1>
`)
//line templates/http_mutators.qtpl:7
qw422016.N().S(warning)
//line templates/http_mutators.qtpl:7
qw422016.N().S(`
<form method="post" class="edit-form"
action="/upload-text/`)
//line templates/http_mutators.qtpl:9
qw422016.E().S(hyphaName)
//line templates/http_mutators.qtpl:9
qw422016.N().S(`">
<textarea name="text">`)
//line templates/http_mutators.qtpl:10
qw422016.E().S(textAreaFill)
//line templates/http_mutators.qtpl:10
qw422016.N().S(`</textarea>
<br/>
<input type="submit"/>
<a href="/page/`)
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:13
qw422016.E().S(hyphaName)
//line templates/http_mutators.qtpl:13
qw422016.N().S(`">Cancel</a>
</form>
</main>
`)
//line templates/http_mutators.qtpl:16
} }
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
func WriteEditHTML(qq422016 qtio422016.Writer, hyphaName, textAreaFill, warning string) { func WriteEditHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
StreamEditHTML(qw422016, hyphaName, textAreaFill, warning) StreamEditHTML(qw422016, rq, hyphaName, textAreaFill, warning)
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
} }
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
func EditHTML(hyphaName, textAreaFill, warning string) string { func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) string {
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
WriteEditHTML(qb422016, hyphaName, textAreaFill, warning) WriteEditHTML(qb422016, rq, hyphaName, textAreaFill, warning)
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
return qs422016 return qs422016
//line templates/http_mutators.qtpl:13 //line templates/http_mutators.qtpl:16
} }

View File

@ -1,6 +1,9 @@
{% func HistoryHTML(hyphaName, tbody string) %} {% import "net/http" %}
{% import "github.com/bouncepaw/mycorrhiza/user" %}
{% func HistoryHTML(rq *http.Request, hyphaName, tbody string) %}
<main> <main>
{%= navHTML(hyphaName, "history") %} {%= navHTML(rq, hyphaName, "history") %}
<table> <table>
<thead> <thead>
<tr> <tr>
@ -16,9 +19,9 @@
</main> </main>
{% endfunc %} {% endfunc %}
{% func RevisionHTML(hyphaName, naviTitle, contents, tree, revHash string) %} {% func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) %}
<main> <main>
{%= navHTML(hyphaName, "revision", revHash) %} {%= navHTML(rq, hyphaName, "revision", revHash) %}
<article> <article>
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p> <p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
{%s= naviTitle %} {%s= naviTitle %}
@ -32,9 +35,9 @@
{% endfunc %} {% endfunc %}
If `contents` == "", a helpful message is shown instead. If `contents` == "", a helpful message is shown instead.
{% func PageHTML(hyphaName, naviTitle, contents, tree string) %} {% func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree string) %}
<main> <main>
{%= navHTML(hyphaName, "page") %} {%= navHTML(rq, hyphaName, "page") %}
<article> <article>
{%s= naviTitle %} {%s= naviTitle %}
{% if contents == "" %} {% if contents == "" %}
@ -44,14 +47,16 @@ If `contents` == "", a helpful message is shown instead.
{% endif %} {% endif %}
</article> </article>
<hr/> <hr/>
{% if u := user.FromRequest(rq).OrAnon(); !user.AuthUsed || u.Group > user.UserAnon %}
<form action="/upload-binary/{%s hyphaName %}" <form action="/upload-binary/{%s hyphaName %}"
method="post" enctype="multipart/form-data"> method="post" enctype="multipart/form-data">
<label for="upload-binary__input">Upload new binary part</label> <label for="upload-binary__input">Upload a new attachment</label>
<br> <br>
<input type="file" id="upload-binary__input" name="binary"/> <input type="file" id="upload-binary__input" name="binary"/>
<input type="submit"/> <input type="submit"/>
</form> </form>
<hr/> <hr/>
{% endif %}
<aside> <aside>
{%s= tree %} {%s= tree %}
</aside> </aside>

View File

@ -5,27 +5,33 @@
package templates package templates
//line templates/http_readers.qtpl:1 //line templates/http_readers.qtpl:1
import "net/http"
//line templates/http_readers.qtpl:2
import "github.com/bouncepaw/mycorrhiza/user"
//line templates/http_readers.qtpl:4
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line templates/http_readers.qtpl:1 //line templates/http_readers.qtpl:4
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line templates/http_readers.qtpl:1 //line templates/http_readers.qtpl:4
func StreamHistoryHTML(qw422016 *qt422016.Writer, hyphaName, tbody string) { func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, tbody string) {
//line templates/http_readers.qtpl:1 //line templates/http_readers.qtpl:4
qw422016.N().S(` qw422016.N().S(`
<main> <main>
`) `)
//line templates/http_readers.qtpl:3 //line templates/http_readers.qtpl:6
streamnavHTML(qw422016, hyphaName, "history") streamnavHTML(qw422016, rq, hyphaName, "history")
//line templates/http_readers.qtpl:3 //line templates/http_readers.qtpl:6
qw422016.N().S(` qw422016.N().S(`
<table> <table>
<thead> <thead>
@ -37,196 +43,206 @@ func StreamHistoryHTML(qw422016 *qt422016.Writer, hyphaName, tbody string) {
</thead> </thead>
<tbody> <tbody>
`) `)
//line templates/http_readers.qtpl:13 //line templates/http_readers.qtpl:16
qw422016.N().S(tbody) qw422016.N().S(tbody)
//line templates/http_readers.qtpl:13 //line templates/http_readers.qtpl:16
qw422016.N().S(` qw422016.N().S(`
</tbody> </tbody>
</table> </table>
</main> </main>
`) `)
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
} }
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
func WriteHistoryHTML(qq422016 qtio422016.Writer, hyphaName, tbody string) { func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, tbody string) {
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
StreamHistoryHTML(qw422016, hyphaName, tbody) StreamHistoryHTML(qw422016, rq, hyphaName, tbody)
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
} }
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
func HistoryHTML(hyphaName, tbody string) string { func HistoryHTML(rq *http.Request, hyphaName, tbody string) string {
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
WriteHistoryHTML(qb422016, hyphaName, tbody) WriteHistoryHTML(qb422016, rq, hyphaName, tbody)
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
return qs422016 return qs422016
//line templates/http_readers.qtpl:17 //line templates/http_readers.qtpl:20
} }
//line templates/http_readers.qtpl:19 //line templates/http_readers.qtpl:22
func StreamRevisionHTML(qw422016 *qt422016.Writer, hyphaName, naviTitle, contents, tree, revHash string) { func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) {
//line templates/http_readers.qtpl:19 //line templates/http_readers.qtpl:22
qw422016.N().S(` qw422016.N().S(`
<main> <main>
`) `)
//line templates/http_readers.qtpl:21 //line templates/http_readers.qtpl:24
streamnavHTML(qw422016, hyphaName, "revision", revHash) streamnavHTML(qw422016, rq, hyphaName, "revision", revHash)
//line templates/http_readers.qtpl:21 //line templates/http_readers.qtpl:24
qw422016.N().S(` qw422016.N().S(`
<article> <article>
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p> <p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
`) `)
//line templates/http_readers.qtpl:24 //line templates/http_readers.qtpl:27
qw422016.N().S(naviTitle) qw422016.N().S(naviTitle)
//line templates/http_readers.qtpl:24 //line templates/http_readers.qtpl:27
qw422016.N().S(` qw422016.N().S(`
`) `)
//line templates/http_readers.qtpl:25 //line templates/http_readers.qtpl:28
qw422016.N().S(contents) qw422016.N().S(contents)
//line templates/http_readers.qtpl:25 //line templates/http_readers.qtpl:28
qw422016.N().S(` qw422016.N().S(`
</article> </article>
<hr/> <hr/>
<aside> <aside>
`) `)
//line templates/http_readers.qtpl:29 //line templates/http_readers.qtpl:32
qw422016.N().S(tree) qw422016.N().S(tree)
//line templates/http_readers.qtpl:29 //line templates/http_readers.qtpl:32
qw422016.N().S(` qw422016.N().S(`
</aside> </aside>
</main> </main>
`) `)
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
} }
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
func WriteRevisionHTML(qq422016 qtio422016.Writer, hyphaName, naviTitle, contents, tree, revHash string) { func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) {
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
StreamRevisionHTML(qw422016, hyphaName, naviTitle, contents, tree, revHash) StreamRevisionHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, revHash)
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
} }
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
func RevisionHTML(hyphaName, naviTitle, contents, tree, revHash string) string { func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) string {
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
WriteRevisionHTML(qb422016, hyphaName, naviTitle, contents, tree, revHash) WriteRevisionHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, revHash)
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
return qs422016 return qs422016
//line templates/http_readers.qtpl:32 //line templates/http_readers.qtpl:35
} }
// If `contents` == "", a helpful message is shown instead. // If `contents` == "", a helpful message is shown instead.
//line templates/http_readers.qtpl:35 //line templates/http_readers.qtpl:38
func StreamPageHTML(qw422016 *qt422016.Writer, hyphaName, naviTitle, contents, tree string) { func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree string) {
//line templates/http_readers.qtpl:35 //line templates/http_readers.qtpl:38
qw422016.N().S(` qw422016.N().S(`
<main> <main>
`) `)
//line templates/http_readers.qtpl:37 //line templates/http_readers.qtpl:40
streamnavHTML(qw422016, hyphaName, "page") streamnavHTML(qw422016, rq, hyphaName, "page")
//line templates/http_readers.qtpl:37 //line templates/http_readers.qtpl:40
qw422016.N().S(` qw422016.N().S(`
<article> <article>
`) `)
//line templates/http_readers.qtpl:39 //line templates/http_readers.qtpl:42
qw422016.N().S(naviTitle) qw422016.N().S(naviTitle)
//line templates/http_readers.qtpl:39 //line templates/http_readers.qtpl:42
qw422016.N().S(` qw422016.N().S(`
`) `)
//line templates/http_readers.qtpl:40 //line templates/http_readers.qtpl:43
if contents == "" { if contents == "" {
//line templates/http_readers.qtpl:40 //line templates/http_readers.qtpl:43
qw422016.N().S(` qw422016.N().S(`
<p>This hypha has no text. Why not <a href="/edit/`) <p>This hypha has no text. Why not <a href="/edit/`)
//line templates/http_readers.qtpl:41 //line templates/http_readers.qtpl:44
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line templates/http_readers.qtpl:41 //line templates/http_readers.qtpl:44
qw422016.N().S(`">create it</a>?</p> qw422016.N().S(`">create it</a>?</p>
`) `)
//line templates/http_readers.qtpl:42 //line templates/http_readers.qtpl:45
} else { } else {
//line templates/http_readers.qtpl:42 //line templates/http_readers.qtpl:45
qw422016.N().S(` qw422016.N().S(`
`) `)
//line templates/http_readers.qtpl:43 //line templates/http_readers.qtpl:46
qw422016.N().S(contents) qw422016.N().S(contents)
//line templates/http_readers.qtpl:43 //line templates/http_readers.qtpl:46
qw422016.N().S(` qw422016.N().S(`
`) `)
//line templates/http_readers.qtpl:44 //line templates/http_readers.qtpl:47
} }
//line templates/http_readers.qtpl:44 //line templates/http_readers.qtpl:47
qw422016.N().S(` qw422016.N().S(`
</article> </article>
<hr/> <hr/>
`)
//line templates/http_readers.qtpl:50
if u := user.FromRequest(rq).OrAnon(); !user.AuthUsed || u.Group > user.UserAnon {
//line templates/http_readers.qtpl:50
qw422016.N().S(`
<form action="/upload-binary/`) <form action="/upload-binary/`)
//line templates/http_readers.qtpl:47 //line templates/http_readers.qtpl:51
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line templates/http_readers.qtpl:47 //line templates/http_readers.qtpl:51
qw422016.N().S(`" qw422016.N().S(`"
method="post" enctype="multipart/form-data"> method="post" enctype="multipart/form-data">
<label for="upload-binary__input">Upload new binary part</label> <label for="upload-binary__input">Upload a new attachment</label>
<br> <br>
<input type="file" id="upload-binary__input" name="binary"/> <input type="file" id="upload-binary__input" name="binary"/>
<input type="submit"/> <input type="submit"/>
</form> </form>
<hr/> <hr/>
`)
//line templates/http_readers.qtpl:59
}
//line templates/http_readers.qtpl:59
qw422016.N().S(`
<aside> <aside>
`) `)
//line templates/http_readers.qtpl:56 //line templates/http_readers.qtpl:61
qw422016.N().S(tree) qw422016.N().S(tree)
//line templates/http_readers.qtpl:56 //line templates/http_readers.qtpl:61
qw422016.N().S(` qw422016.N().S(`
</aside> </aside>
</main> </main>
`) `)
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
} }
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
func WritePageHTML(qq422016 qtio422016.Writer, hyphaName, naviTitle, contents, tree string) { func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree string) {
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
StreamPageHTML(qw422016, hyphaName, naviTitle, contents, tree) StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree)
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
} }
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
func PageHTML(hyphaName, naviTitle, contents, tree string) string { func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree string) string {
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
WritePageHTML(qb422016, hyphaName, naviTitle, contents, tree) WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree)
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
return qs422016 return qs422016
//line templates/http_readers.qtpl:59 //line templates/http_readers.qtpl:64
} }

View File

@ -1,7 +1,8 @@
{% import "net/http" %}
This dialog is to be shown to a user when they try to rename a hypha. This dialog is to be shown to a user when they try to rename a hypha.
{% func RenameAskHTML(hyphaName string, isOld bool) %} {% func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) %}
<main> <main>
{%= navHTML(hyphaName, "rename-ask") %} {%= navHTML(rq, hyphaName, "rename-ask") %}
{%- if isOld -%} {%- if isOld -%}
<section> <section>
<h1>Rename {%s hyphaName %}</h1> <h1>Rename {%s hyphaName %}</h1>

View File

@ -1,55 +1,58 @@
// Code generated by qtc from "rename.qtpl". DO NOT EDIT. // Code generated by qtc from "rename.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details. // See https://github.com/valyala/quicktemplate for details.
// This dialog is to be shown to a user when they try to rename a hypha. //line templates/rename.qtpl:1
//line templates/rename.qtpl:2
package templates package templates
//line templates/rename.qtpl:2 //line templates/rename.qtpl:1
import "net/http"
// This dialog is to be shown to a user when they try to rename a hypha.
//line templates/rename.qtpl:3
import ( import (
qtio422016 "io" qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate" qt422016 "github.com/valyala/quicktemplate"
) )
//line templates/rename.qtpl:2 //line templates/rename.qtpl:3
var ( var (
_ = qtio422016.Copy _ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer _ = qt422016.AcquireByteBuffer
) )
//line templates/rename.qtpl:2 //line templates/rename.qtpl:3
func StreamRenameAskHTML(qw422016 *qt422016.Writer, hyphaName string, isOld bool) { func StreamRenameAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line templates/rename.qtpl:2 //line templates/rename.qtpl:3
qw422016.N().S(` qw422016.N().S(`
<main> <main>
`) `)
//line templates/rename.qtpl:4 //line templates/rename.qtpl:5
streamnavHTML(qw422016, hyphaName, "rename-ask") streamnavHTML(qw422016, rq, hyphaName, "rename-ask")
//line templates/rename.qtpl:4 //line templates/rename.qtpl:5
qw422016.N().S(` qw422016.N().S(`
`) `)
//line templates/rename.qtpl:5 //line templates/rename.qtpl:6
if isOld { if isOld {
//line templates/rename.qtpl:5 //line templates/rename.qtpl:6
qw422016.N().S(` <section> qw422016.N().S(` <section>
<h1>Rename `) <h1>Rename `)
//line templates/rename.qtpl:7 //line templates/rename.qtpl:8
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line templates/rename.qtpl:7 //line templates/rename.qtpl:8
qw422016.N().S(`</h1> qw422016.N().S(`</h1>
<form action="/rename-confirm/`) <form action="/rename-confirm/`)
//line templates/rename.qtpl:8 //line templates/rename.qtpl:9
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line templates/rename.qtpl:8 //line templates/rename.qtpl:9
qw422016.N().S(`" method="post" enctype="multipart/form-data"> qw422016.N().S(`" method="post" enctype="multipart/form-data">
<fieldset> <fieldset>
<legend>New name</legend> <legend>New name</legend>
<input type="text" value="`) <input type="text" value="`)
//line templates/rename.qtpl:11 //line templates/rename.qtpl:12
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line templates/rename.qtpl:11 //line templates/rename.qtpl:12
qw422016.N().S(`" required autofocus id="new-name" name="new-name"/> qw422016.N().S(`" required autofocus id="new-name" name="new-name"/>
</fieldset> </fieldset>
@ -64,92 +67,92 @@ func StreamRenameAskHTML(qw422016 *qt422016.Writer, hyphaName string, isOld bool
</form> </form>
</section> </section>
`) `)
//line templates/rename.qtpl:24 //line templates/rename.qtpl:25
} else { } else {
//line templates/rename.qtpl:24 //line templates/rename.qtpl:25
qw422016.N().S(` `) qw422016.N().S(` `)
//line templates/rename.qtpl:25 //line templates/rename.qtpl:26
streamcannotRenameDueToNonExistence(qw422016, hyphaName) streamcannotRenameDueToNonExistence(qw422016, hyphaName)
//line templates/rename.qtpl:25 //line templates/rename.qtpl:26
qw422016.N().S(` qw422016.N().S(`
`) `)
//line templates/rename.qtpl:26 //line templates/rename.qtpl:27
} }
//line templates/rename.qtpl:26 //line templates/rename.qtpl:27
qw422016.N().S(`</main> qw422016.N().S(`</main>
`) `)
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
} }
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
func WriteRenameAskHTML(qq422016 qtio422016.Writer, hyphaName string, isOld bool) { func WriteRenameAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
StreamRenameAskHTML(qw422016, hyphaName, isOld) StreamRenameAskHTML(qw422016, rq, hyphaName, isOld)
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
} }
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
func RenameAskHTML(hyphaName string, isOld bool) string { func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) string {
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
WriteRenameAskHTML(qb422016, hyphaName, isOld) WriteRenameAskHTML(qb422016, rq, hyphaName, isOld)
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
return qs422016 return qs422016
//line templates/rename.qtpl:28 //line templates/rename.qtpl:29
} }
//line templates/rename.qtpl:30 //line templates/rename.qtpl:31
func streamcannotRenameDueToNonExistence(qw422016 *qt422016.Writer, hyphaName string) { func streamcannotRenameDueToNonExistence(qw422016 *qt422016.Writer, hyphaName string) {
//line templates/rename.qtpl:30 //line templates/rename.qtpl:31
qw422016.N().S(` qw422016.N().S(`
<section> <section>
<h1>Cannot rename `) <h1>Cannot rename `)
//line templates/rename.qtpl:32 //line templates/rename.qtpl:33
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line templates/rename.qtpl:32 //line templates/rename.qtpl:33
qw422016.N().S(`</h1> qw422016.N().S(`</h1>
<p>This hypha does not exist.</p> <p>This hypha does not exist.</p>
<p><a href="/page/`) <p><a href="/page/`)
//line templates/rename.qtpl:34 //line templates/rename.qtpl:35
qw422016.E().S(hyphaName) qw422016.E().S(hyphaName)
//line templates/rename.qtpl:34 //line templates/rename.qtpl:35
qw422016.N().S(`">Go back</a></p> qw422016.N().S(`">Go back</a></p>
</section> </section>
`) `)
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
} }
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
func writecannotRenameDueToNonExistence(qq422016 qtio422016.Writer, hyphaName string) { func writecannotRenameDueToNonExistence(qq422016 qtio422016.Writer, hyphaName string) {
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
qw422016 := qt422016.AcquireWriter(qq422016) qw422016 := qt422016.AcquireWriter(qq422016)
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
streamcannotRenameDueToNonExistence(qw422016, hyphaName) streamcannotRenameDueToNonExistence(qw422016, hyphaName)
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
qt422016.ReleaseWriter(qw422016) qt422016.ReleaseWriter(qw422016)
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
} }
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
func cannotRenameDueToNonExistence(hyphaName string) string { func cannotRenameDueToNonExistence(hyphaName string) string {
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
qb422016 := qt422016.AcquireByteBuffer() qb422016 := qt422016.AcquireByteBuffer()
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
writecannotRenameDueToNonExistence(qb422016, hyphaName) writecannotRenameDueToNonExistence(qb422016, hyphaName)
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
qs422016 := string(qb422016.B) qs422016 := string(qb422016.B)
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
qt422016.ReleaseByteBuffer(qb422016) qt422016.ReleaseByteBuffer(qb422016)
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
return qs422016 return qs422016
//line templates/rename.qtpl:36 //line templates/rename.qtpl:37
} }

69
user/fs.go Normal file
View File

@ -0,0 +1,69 @@
package user
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"github.com/adrg/xdg"
"github.com/bouncepaw/mycorrhiza/util"
)
func PopulateFixedUserStorage() {
contents, err := ioutil.ReadFile(util.FixedCredentialsPath)
if err != nil {
log.Fatal(err)
}
err = json.Unmarshal(contents, &UserStorage.Users)
if err != nil {
log.Fatal(err)
}
for _, user := range UserStorage.Users {
user.Group = groupFromString(user.GroupString)
}
log.Println("Found", len(UserStorage.Users), "fixed users")
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
}
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.
func tokenStoragePath() string {
dir, err := xdg.DataFile("mycorrhiza/tokens.json")
if err != nil {
// Yes, it is unix-only, but function above rarely fails, so this block is probably never reached.
path := "/home/" + os.Getenv("HOME") + "/.local/share/mycorrhiza/tokens.json"
os.MkdirAll(path, 0777)
return path
}
return dir
}

70
user/group.go Normal file
View File

@ -0,0 +1,70 @@
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)
}

138
user/user.go Normal file
View File

@ -0,0 +1,138 @@
package user
import (
"log"
"net/http"
"time"
"github.com/bouncepaw/mycorrhiza/util"
)
func (u *User) OrAnon() *User {
if u == nil {
return &User{}
}
return u
}
func LogoutFromRequest(w http.ResponseWriter, rq *http.Request) {
cookieFromUser, err := rq.Cookie("mycorrhiza_token")
if err == nil {
http.SetCookie(w, cookie("token", "", time.Unix(0, 0)))
terminateSession(cookieFromUser.Value)
}
}
func (us *FixedUserStorage) userByToken(token string) *User {
if user, ok := us.Tokens[token]; ok {
return user
}
return nil
}
func (us *FixedUserStorage) userByName(username string) *User {
for _, user := range us.Users {
if user.Name == username {
return user
}
}
return nil
}
func FromRequest(rq *http.Request) *User {
cookie, err := rq.Cookie("mycorrhiza_token")
if err != nil {
return nil
}
return UserStorage.userByToken(cookie.Value).OrAnon()
}
func LoginDataHTTP(w http.ResponseWriter, rq *http.Request, username, password string) string {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
if !HasUsername(username) {
w.WriteHeader(http.StatusBadRequest)
log.Println("Unknown username", username, "was entered")
return "unknown username"
}
if !CredentialsOK(username, password) {
w.WriteHeader(http.StatusBadRequest)
log.Println("A wrong password was entered for username", username)
return "wrong password"
}
token, err := AddSession(username)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
return err.Error()
}
http.SetCookie(w, cookie("token", token, time.Now().Add(14*24*time.Hour)))
return ""
}
// AddSession saves a session for `username` and returns a token to use.
func AddSession(username string) (string, error) {
token, err := util.RandomString(16)
if err == nil {
for _, user := range UserStorage.Users {
if user.Name == username {
UserStorage.Tokens[token] = user
go dumpTokens()
}
}
log.Println("New token for", username, "is", token)
}
return token, err
}
func terminateSession(token string) {
delete(UserStorage.Tokens, token)
go dumpTokens()
}
func HasUsername(username string) bool {
for _, user := range UserStorage.Users {
if user.Name == username {
return true
}
}
return false
}
func CredentialsOK(username, password string) bool {
for _, user := range UserStorage.Users {
if user.Name == username && user.Password == password {
return true
}
}
return false
}
type FixedUserStorage struct {
Users []*User
Tokens map[string]*User
}
var UserStorage = FixedUserStorage{Tokens: make(map[string]*User)}
// AuthUsed shows if a method of authentication is used. You should set it by yourself.
var AuthUsed bool
// User is a user.
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"`
Password string `json:"password"`
}
// 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,15 +1,20 @@
package util package util
import ( import (
"crypto/rand"
"encoding/hex"
"net/http" "net/http"
"strings" "strings"
) )
var ( var (
ServerPort string ServerPort string
HomePage string HomePage string
SiteTitle string SiteTitle string
WikiDir string WikiDir string
UserTree string
AuthMethod string
FixedCredentialsPath string
) )
// ShorterPath is used by handlerList to display shorter path to the files. It simply strips WikiDir. // ShorterPath is used by handlerList to display shorter path to the files. It simply strips WikiDir.
@ -41,3 +46,11 @@ func FindSubhyphae(hyphaName string, hyphaIterator func(func(string))) []string
}) })
return subhyphae return subhyphae
} }
func RandomString(n int) (string, error) {
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}