mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2024-12-12 05:20:26 +00:00
commit
c4e0e4879b
201
LICENSE
Normal file
201
LICENSE
Normal 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.
|
3
Makefile
3
Makefile
@ -1,6 +1,9 @@
|
||||
run: build
|
||||
./mycorrhiza metarrhiza
|
||||
|
||||
run_with_fixed_auth: build
|
||||
./mycorrhiza -auth-method fixed metarrhiza
|
||||
|
||||
build:
|
||||
go generate
|
||||
go build .
|
||||
|
12
README.md
12
README.md
@ -1,4 +1,4 @@
|
||||
# 🍄 MycorrhizaWiki 0.10
|
||||
# 🍄 MycorrhizaWiki 0.11
|
||||
A wiki engine.
|
||||
|
||||
## Building
|
||||
@ -15,13 +15,21 @@ make
|
||||
```
|
||||
mycorrhiza [OPTIONS...] WIKI_PATH
|
||||
|
||||
WIKI_PATH must be a path to git repository which you want to be a wiki.
|
||||
|
||||
Options:
|
||||
-auth-method string
|
||||
What auth method to use. Variants: "none", "fixed" (default "none")
|
||||
-fixed-credentials-path string
|
||||
Used when -auth-method=fixed. Path to file with user credentials. (default "mycocredentials.json")
|
||||
-home string
|
||||
The home page (default "home")
|
||||
-port string
|
||||
Port to serve the wiki at (default "1737")
|
||||
-title string
|
||||
How to call your wiki in the navititle (default "🍄")
|
||||
-user-tree string
|
||||
Hypha which is a superhypha of all user pages (default "u")
|
||||
```
|
||||
|
||||
## Features
|
||||
@ -39,11 +47,11 @@ Options:
|
||||
* Hyphae can be deleted (while still preserving history)
|
||||
* Hyphae can be renamed (recursive renaming of subhyphae is also supported)
|
||||
* Light on resources: I run a home wiki on this engine 24/7 at an [Orange π Lite](http://www.orangepi.org/orangepilite/).
|
||||
* Authorization with pre-set credentials
|
||||
|
||||
## Contributing
|
||||
Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where some development is coordinated. Feel free to open an issue or contact me.
|
||||
|
||||
## Future plans
|
||||
* Tagging system
|
||||
* Authorization
|
||||
* Better history viewing
|
||||
|
17
flag.go
17
flag.go
@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"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.HomePage, "home", "home", "The home page")
|
||||
flag.StringVar(&util.SiteTitle, "title", "🍄", "How to call your wiki in the navititle")
|
||||
flag.StringVar(&util.UserTree, "user-tree", "u", "Hypha which is a superhypha of all user pages")
|
||||
flag.StringVar(&util.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
|
||||
@ -33,4 +37,17 @@ func parseCliArgs() {
|
||||
if !isCanonicalName(util.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
5
go.mod
@ -2,4 +2,7 @@ module github.com/bouncepaw/mycorrhiza
|
||||
|
||||
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
8
go.sum
@ -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/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.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/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
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-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
@ -76,11 +77,19 @@ func (rev Revision) HyphaeLinks() (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(`
|
||||
<li class="rc-entry__time"><time>%s</time></li>
|
||||
<li class="rc-entry__hash">%s</li>
|
||||
<li class="rc-entry__links">%s</li>
|
||||
<li class="rc-entry__msg">%s</li>
|
||||
<li class="rc-entry__time"><time>%[1]s</time></li>
|
||||
<li class="rc-entry__hash">%[2]s</li>
|
||||
<li class="rc-entry__links">%[3]s</li>
|
||||
<li class="rc-entry__msg">%[4]s</li>
|
||||
`, rev.TimeString(), rev.Hash, rev.HyphaeLinks(), rev.Message)
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ func RecentChanges(n int) string {
|
||||
var (
|
||||
out, err = gitsh(
|
||||
"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),
|
||||
)
|
||||
revs []Revision
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
@ -29,7 +30,7 @@ const (
|
||||
type HistoryOp struct {
|
||||
// All errors are appended here.
|
||||
Errs []error
|
||||
opType OpType
|
||||
Type OpType
|
||||
userMsg string
|
||||
name string
|
||||
email string
|
||||
@ -39,8 +40,10 @@ type HistoryOp struct {
|
||||
func Operation(opType OpType) *HistoryOp {
|
||||
gitMutex.Lock()
|
||||
hop := &HistoryOp{
|
||||
Errs: []error{},
|
||||
opType: opType,
|
||||
Errs: []error{},
|
||||
name: "anon",
|
||||
email: "anon@mycorrhiza",
|
||||
Type: opType,
|
||||
}
|
||||
return hop
|
||||
}
|
||||
@ -116,9 +119,11 @@ func (hop *HistoryOp) WithMsg(userMsg string) *HistoryOp {
|
||||
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).
|
||||
func (hop *HistoryOp) WithSignature(username string) *HistoryOp {
|
||||
hop.name = username
|
||||
hop.email = username + "@mycorrhiza" // A fake email, why not
|
||||
// WithUser sets a user for the commit.
|
||||
func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
|
||||
if u.Group != user.UserAnon {
|
||||
hop.name = u.Name
|
||||
hop.email = u.Name + "@mycorrhiza"
|
||||
}
|
||||
return hop
|
||||
}
|
||||
|
62
http_auth.go
Normal file
62
http_auth.go
Normal 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())))
|
||||
}
|
112
http_mutators.go
112
http_mutators.go
@ -6,16 +6,19 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/templates"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
http.HandleFunc("/upload-binary/", handlerUploadBinary)
|
||||
http.HandleFunc("/upload-text/", handlerUploadText)
|
||||
// Those that do not actually mutate anything:
|
||||
http.HandleFunc("/edit/", handlerEdit)
|
||||
http.HandleFunc("/delete-ask/", handlerDeleteAsk)
|
||||
http.HandleFunc("/delete-confirm/", handlerDeleteConfirm)
|
||||
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)
|
||||
}
|
||||
|
||||
@ -25,22 +28,28 @@ func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) {
|
||||
hyphaName = HyphaNameFromRq(rq, "rename-ask")
|
||||
_, 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) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "rename-confirm")
|
||||
hyphaData, isOld = HyphaStorage[hyphaName]
|
||||
_, isOld = HyphaStorage[hyphaName]
|
||||
newName = CanonicalName(rq.PostFormValue("new-name"))
|
||||
_, newNameIsUsed = HyphaStorage[newName]
|
||||
recursive bool
|
||||
recursive = rq.PostFormValue("recursive") == "true"
|
||||
u = user.FromRequest(rq).OrAnon()
|
||||
)
|
||||
if rq.PostFormValue("recursive") == "true" {
|
||||
recursive = true
|
||||
}
|
||||
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:
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: hypha exists",
|
||||
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",
|
||||
"Invalid new name. Names cannot contain characters <code>^?!:#@><*|\"\\'&%</code>")
|
||||
default:
|
||||
if hop := hyphaData.RenameHypha(hyphaName, newName, recursive); len(hop.Errs) == 0 {
|
||||
http.Redirect(w, rq, "/page/"+newName, http.StatusSeeOther)
|
||||
} else {
|
||||
if hop := RenameHypha(hyphaName, newName, recursive, u); len(hop.Errs) != 0 {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
"Error: could not rename hypha",
|
||||
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")
|
||||
_, 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
|
||||
@ -80,22 +94,27 @@ func handlerDeleteConfirm(w http.ResponseWriter, rq *http.Request) {
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "delete-confirm")
|
||||
hyphaData, isOld = HyphaStorage[hyphaName]
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if isOld {
|
||||
// If deleted successfully
|
||||
if hop := hyphaData.DeleteHypha(hyphaName); len(hop.Errs) == 0 {
|
||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||
} else {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
"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 {
|
||||
if !u.CanProceed("delete-confirm") {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
return
|
||||
}
|
||||
if !isOld {
|
||||
// The precondition is to have the hypha in the first place.
|
||||
HttpErr(w, http.StatusPreconditionFailed, hyphaName,
|
||||
"Error: no such hypha",
|
||||
"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.
|
||||
@ -108,6 +127,11 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
||||
textAreaFill string
|
||||
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 {
|
||||
textAreaFill, err = FetchTextPart(hyphaData)
|
||||
if err != nil {
|
||||
@ -118,25 +142,27 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
||||
} else {
|
||||
warning = `<p>You are creating a new hypha.</p>`
|
||||
}
|
||||
util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(hyphaName, textAreaFill, warning)))
|
||||
util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(rq, hyphaName, textAreaFill, warning)))
|
||||
}
|
||||
|
||||
// handlerUploadText uploads a new text part for the hypha.
|
||||
func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "upload-text")
|
||||
hyphaData, isOld = HyphaStorage[hyphaName]
|
||||
textData = rq.PostFormValue("text")
|
||||
hyphaName = HyphaNameFromRq(rq, "upload-text")
|
||||
textData = rq.PostFormValue("text")
|
||||
u = user.FromRequest(rq).OrAnon()
|
||||
)
|
||||
if !isOld {
|
||||
hyphaData = &HyphaData{}
|
||||
if ok := user.CanProceed(rq, "upload-text"); !ok {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
return
|
||||
}
|
||||
if textData == "" {
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
|
||||
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())
|
||||
} else {
|
||||
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.
|
||||
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
hyphaName := HyphaNameFromRq(rq, "upload-binary")
|
||||
rq.ParseMultipartForm(10 << 20)
|
||||
var (
|
||||
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")
|
||||
if file != nil {
|
||||
defer file.Close()
|
||||
}
|
||||
|
||||
// If file is not passed:
|
||||
if err != nil {
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed")
|
||||
return
|
||||
}
|
||||
|
||||
// If file is passed:
|
||||
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 {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
|
||||
} else {
|
||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
|
||||
contents = markup.ToHtml(hyphaName, textContents)
|
||||
}
|
||||
page := templates.RevisionHTML(
|
||||
rq,
|
||||
hyphaName,
|
||||
naviTitle(hyphaName),
|
||||
contents,
|
||||
@ -67,7 +68,7 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println("Found", len(revs), "revisions for", hyphaName)
|
||||
|
||||
util.HTTP200Page(w,
|
||||
base(hyphaName, templates.HistoryHTML(hyphaName, tbody)))
|
||||
base(hyphaName, templates.HistoryHTML(rq, hyphaName, tbody)))
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
util.HTTP200Page(w, base(hyphaName, templates.PageHTML(hyphaName,
|
||||
util.HTTP200Page(w, base(hyphaName, templates.PageHTML(rq, hyphaName,
|
||||
naviTitle(hyphaName),
|
||||
contents,
|
||||
tree.TreeAsHtml(hyphaName, IterateHyphaNamesWith))))
|
||||
|
54
hypha.go
54
hypha.go
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
@ -31,6 +32,16 @@ func init() {
|
||||
}
|
||||
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.
|
||||
@ -40,10 +51,16 @@ type HyphaData struct {
|
||||
}
|
||||
|
||||
// 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 (
|
||||
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 {
|
||||
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 {
|
||||
return hop.WithError(err)
|
||||
}
|
||||
|
||||
if isOld && *originalFullPath != fullPath && *originalFullPath != "" {
|
||||
if err := history.Rename(*originalFullPath, fullPath); err != nil {
|
||||
return hop.WithError(err)
|
||||
}
|
||||
log.Println("Move", *originalFullPath, "to", fullPath)
|
||||
}
|
||||
// New hyphae must be added to the hypha storage
|
||||
if !isOld {
|
||||
HyphaStorage[hyphaName] = hd
|
||||
HyphaStorage[hyphaName] = hyphaData
|
||||
}
|
||||
*originalFullPath = fullPath
|
||||
log.Printf("%v\n", *hd)
|
||||
return hop.WithFiles(fullPath).
|
||||
WithSignature("anon").
|
||||
WithUser(u).
|
||||
Apply()
|
||||
}
|
||||
|
||||
// UploadText loads a new text part from `textData` for hypha `hyphaName` with `hd`. It must be marked if the hypha `isOld`.
|
||||
func (hd *HyphaData) UploadText(hyphaName, textData string, isOld bool) *history.HistoryOp {
|
||||
hop := history.Operation(history.TypeEditText).WithMsg(fmt.Sprintf("Edit ‘%s’", hyphaName))
|
||||
return hd.uploadHelp(hop, hyphaName, ".myco", &hd.textPath, isOld, []byte(textData))
|
||||
// UploadText loads a new text part from `textData` for hypha `hyphaName`.
|
||||
func UploadText(hyphaName, textData string, u *user.User) *history.HistoryOp {
|
||||
return uploadHelp(
|
||||
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`.
|
||||
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 (
|
||||
hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", hyphaName, mime))
|
||||
data, err = ioutil.ReadAll(file)
|
||||
@ -82,16 +103,15 @@ func (hd *HyphaData) UploadBinary(hyphaName, mime string, file multipart.File, i
|
||||
if err != nil {
|
||||
return hop.WithError(err).Apply()
|
||||
}
|
||||
|
||||
return hd.uploadHelp(hop, hyphaName, MimeToExtension(mime), &hd.binaryPath, isOld, data)
|
||||
return uploadHelp(hop, hyphaName, MimeToExtension(mime), data, u)
|
||||
}
|
||||
|
||||
// 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).
|
||||
WithFilesRemoved(hd.textPath, hd.binaryPath).
|
||||
WithMsg(fmt.Sprintf("Delete ‘%s’", hyphaName)).
|
||||
WithSignature("anon").
|
||||
WithUser(u).
|
||||
Apply()
|
||||
if len(hop.Errs) == 0 {
|
||||
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.
|
||||
func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *history.HistoryOp {
|
||||
func RenameHypha(hyphaName, newName string, recursive bool, u *user.User) *history.HistoryOp {
|
||||
var (
|
||||
replaceName = func(str string) string {
|
||||
return strings.Replace(str, hyphaName, newName, 1)
|
||||
@ -157,7 +177,7 @@ func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *his
|
||||
}
|
||||
hop.WithFilesRenamed(renameMap).
|
||||
WithMsg(fmt.Sprintf(renameMsg, hyphaName, newName)).
|
||||
WithSignature("anon").
|
||||
WithUser(u).
|
||||
Apply()
|
||||
if len(hop.Errs) == 0 {
|
||||
relocateHyphaData(hyphaNames, replaceName)
|
||||
@ -171,7 +191,7 @@ func binaryHtmlBlock(hyphaName string, hd *HyphaData) string {
|
||||
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
|
||||
return fmt.Sprintf(`
|
||||
<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)
|
||||
case ".ogg", ".webm", ".mp4":
|
||||
return fmt.Sprintf(`
|
||||
|
9
main.go
9
main.go
@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/templates"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
@ -22,7 +23,7 @@ import (
|
||||
var WikiDir string
|
||||
|
||||
// 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.
|
||||
var HyphaStorage = make(map[string]*HyphaData)
|
||||
@ -63,6 +64,11 @@ var base = templates.BaseHTML
|
||||
// Reindex all hyphae by checking the wiki storage directory anew.
|
||||
func handlerReindex(w http.ResponseWriter, rq *http.Request) {
|
||||
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)
|
||||
log.Println("Wiki storage directory is", WikiDir)
|
||||
log.Println("Start indexing hyphae...")
|
||||
@ -125,6 +131,7 @@ func main() {
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(WikiDir+"/static"))))
|
||||
// See http_readers.go for /page/, /text/, /binary/, /history/.
|
||||
// See http_mutators.go for /upload-binary/, /upload-text/, /edit/, /delete-ask/, /delete-confirm/, /rename-ask/, /rename-confirm/.
|
||||
// See http_auth.go for /login, /login-data, /logout, /logout-confirm
|
||||
http.HandleFunc("/list", handlerList)
|
||||
http.HandleFunc("/reindex", handlerReindex)
|
||||
http.HandleFunc("/random", handlerRandom)
|
||||
|
280
markup/img.go
280
markup/img.go
@ -13,53 +13,169 @@ func MatchesImg(line string) bool {
|
||||
}
|
||||
|
||||
type imgEntry struct {
|
||||
path string
|
||||
sizeH string
|
||||
sizeV string
|
||||
desc string
|
||||
trimmedPath string
|
||||
path strings.Builder
|
||||
sizeW strings.Builder
|
||||
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 {
|
||||
entries []imgEntry
|
||||
inDesc bool
|
||||
currEntry imgEntry
|
||||
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) {
|
||||
if img.inDesc {
|
||||
rightBraceIndex := strings.IndexRune(line, '}')
|
||||
if cnt := len(img.entries); rightBraceIndex == -1 && cnt != 0 {
|
||||
img.entries[cnt-1].desc += "\n" + line
|
||||
} else if rightBraceIndex != -1 && cnt != 0 {
|
||||
img.entries[cnt-1].desc += "\n" + line[:rightBraceIndex]
|
||||
img.inDesc = false
|
||||
}
|
||||
if strings.Count(line, "}") > 1 {
|
||||
stateToProcessor := map[imgState]func(rune) bool{
|
||||
inRoot: img.processInRoot,
|
||||
inName: img.processInName,
|
||||
inDimensionsW: img.processInDimensionsW,
|
||||
inDimensionsH: img.processInDimensionsH,
|
||||
inDescription: img.processInDescription,
|
||||
}
|
||||
for _, r := range line {
|
||||
if shouldReturnTrue := stateToProcessor[img.state](r); shouldReturnTrue {
|
||||
return true
|
||||
}
|
||||
} else if s := strings.TrimSpace(line); s != "" {
|
||||
if s[0] == '}' {
|
||||
return true
|
||||
}
|
||||
img.parseStartOfEntry(line)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ImgFromFirstLine(line, hyphaName string) Img {
|
||||
img := Img{
|
||||
func (img *Img) processInDescription(r rune) (shouldReturnTrue bool) {
|
||||
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,
|
||||
entries: make([]imgEntry, 0),
|
||||
}
|
||||
line = line[strings.IndexRune(line, '{'):]
|
||||
if len(line) == 1 { // if { only
|
||||
} else {
|
||||
line = line[1:] // Drop the {
|
||||
}
|
||||
return img
|
||||
line = line[strings.IndexRune(line, '{')+1:]
|
||||
return img, img.Process(line)
|
||||
}
|
||||
|
||||
func (img *Img) canonicalPathFor(path string) string {
|
||||
func (img *Img) binaryPathFor(path string) string {
|
||||
path = strings.TrimSpace(path)
|
||||
if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
|
||||
return path
|
||||
@ -68,73 +184,71 @@ func (img *Img) canonicalPathFor(path string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (img *Img) parseStartOfEntry(line string) (entry imgEntry, followedByDesc bool) {
|
||||
pipeIndex := strings.IndexRune(line, '|')
|
||||
if pipeIndex == -1 { // If no : in string
|
||||
entry.path = img.canonicalPathFor(line)
|
||||
func (img *Img) pagePathFor(path string) string {
|
||||
path = strings.TrimSpace(path)
|
||||
if strings.IndexRune(path, ':') != -1 || strings.IndexRune(path, '/') == 0 {
|
||||
return path
|
||||
} else {
|
||||
entry.path = img.canonicalPathFor(line[:pipeIndex])
|
||||
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]
|
||||
}
|
||||
return "/page/" + xclCanonicalName(img.hyphaName, path)
|
||||
}
|
||||
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, '*')
|
||||
if xIndex == -1 { // If no x in dimensions
|
||||
sizeH = strings.TrimSpace(dimensions)
|
||||
sizeW = strings.TrimSpace(dimensions)
|
||||
} else {
|
||||
sizeH = strings.TrimSpace(dimensions[:xIndex])
|
||||
sizeV = strings.TrimSpace(strings.TrimPrefix(dimensions, dimensions[:xIndex+1]))
|
||||
sizeW = strings.TrimSpace(dimensions[:xIndex])
|
||||
sizeH = strings.TrimSpace(strings.TrimPrefix(dimensions, dimensions[:xIndex+1]))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (img Img) ToHtml() (html string) {
|
||||
for _, entry := range img.entries {
|
||||
html += fmt.Sprintf(`<figure>
|
||||
<img src="%s" width="%s" height="%s">
|
||||
`, entry.path, entry.sizeH, entry.sizeV)
|
||||
if entry.desc != "" {
|
||||
html += ` <figcaption>`
|
||||
for i, line := range strings.Split(entry.desc, "\n") {
|
||||
if line != "" {
|
||||
if i > 0 {
|
||||
html += `<br>`
|
||||
}
|
||||
html += ParagraphToHtml(img.hyphaName, line)
|
||||
}
|
||||
}
|
||||
html += `</figcaption>`
|
||||
func (img *Img) checkLinks() map[string]bool {
|
||||
m := make(map[string]bool)
|
||||
for i, entry := range img.entries {
|
||||
// Also trim them for later use
|
||||
entry.trimmedPath = strings.TrimSpace(entry.path.String())
|
||||
isAbsoluteUrl := strings.ContainsRune(entry.trimmedPath, ':')
|
||||
if !isAbsoluteUrl {
|
||||
entry.trimmedPath = canonicalName(entry.trimmedPath)
|
||||
}
|
||||
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>`
|
||||
}
|
||||
return `<section class="img-gallery">
|
||||
` + html + `
|
||||
</section>`
|
||||
return html + `</section>`
|
||||
}
|
||||
|
@ -12,6 +12,9 @@ var HyphaExists func(string) bool
|
||||
// HyphaAccess holds function that accesses a hypha by its name.
|
||||
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.
|
||||
type GemLexerState struct {
|
||||
// Name of hypha being parsed
|
||||
@ -21,7 +24,7 @@ type GemLexerState struct {
|
||||
id int
|
||||
buf string
|
||||
// Temporaries
|
||||
img Img
|
||||
img *Img
|
||||
}
|
||||
|
||||
type Line struct {
|
||||
@ -45,13 +48,17 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
|
||||
*ast = append(*ast, Line{id: state.id, contents: text})
|
||||
}
|
||||
|
||||
// Process empty lines depending on the current state
|
||||
if "" == strings.TrimSpace(line) {
|
||||
if state.where == "list" {
|
||||
switch state.where {
|
||||
case "list":
|
||||
state.where = ""
|
||||
addLine(state.buf + "</ul>")
|
||||
} else if state.where == "number" {
|
||||
case "number":
|
||||
state.where = ""
|
||||
addLine(state.buf + "</ol>")
|
||||
case "pre":
|
||||
state.buf += "\n"
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -59,6 +66,9 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
|
||||
startsWith := func(token string) bool {
|
||||
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.
|
||||
switch state.where {
|
||||
@ -77,7 +87,7 @@ func geminiLineToAST(line string, state *GemLexerState, ast *[]Line) {
|
||||
imgState:
|
||||
if shouldGoBackToNormal := state.img.Process(line); shouldGoBackToNormal {
|
||||
state.where = ""
|
||||
addLine(state.img)
|
||||
addLine(*state.img)
|
||||
}
|
||||
return
|
||||
|
||||
@ -142,23 +152,17 @@ normalState:
|
||||
goto numberState
|
||||
|
||||
case startsWith("###### "):
|
||||
addLine(fmt.Sprintf(
|
||||
"<h6 id='%d'>%s</h6>", state.id, line[7:]))
|
||||
addHeading(6)
|
||||
case startsWith("##### "):
|
||||
addLine(fmt.Sprintf(
|
||||
"<h5 id='%d'>%s</h5>", state.id, line[6:]))
|
||||
addHeading(5)
|
||||
case startsWith("#### "):
|
||||
addLine(fmt.Sprintf(
|
||||
"<h4 id='%d'>%s</h4>", state.id, line[5:]))
|
||||
addHeading(4)
|
||||
case startsWith("### "):
|
||||
addLine(fmt.Sprintf(
|
||||
"<h3 id='%d'>%s</h3>", state.id, line[4:]))
|
||||
addHeading(3)
|
||||
case startsWith("## "):
|
||||
addLine(fmt.Sprintf(
|
||||
"<h2 id='%d'>%s</h2>", state.id, line[3:]))
|
||||
addHeading(2)
|
||||
case startsWith("# "):
|
||||
addLine(fmt.Sprintf(
|
||||
"<h1 id='%d'>%s</h1>", state.id, line[2:]))
|
||||
addHeading(1)
|
||||
|
||||
case startsWith(">"):
|
||||
addLine(fmt.Sprintf(
|
||||
@ -173,8 +177,13 @@ normalState:
|
||||
case line == "----":
|
||||
*ast = append(*ast, Line{id: -1, contents: "<hr/>"})
|
||||
case MatchesImg(line):
|
||||
state.where = "img"
|
||||
state.img = ImgFromFirstLine(line, state.name)
|
||||
img, shouldGoBackToNormal := ImgFromFirstLine(line, state.name)
|
||||
if shouldGoBackToNormal {
|
||||
addLine(*img)
|
||||
} else {
|
||||
state.where = "img"
|
||||
state.img = img
|
||||
}
|
||||
default:
|
||||
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, ParagraphToHtml(state.name, line)))
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ func TestLex(t *testing.T) {
|
||||
</ul>`},
|
||||
{6, "<p id='6'>text</p>"},
|
||||
{7, "<p id='7'>more text</p>"},
|
||||
{8, `<p><a id='8' class='rocketlink wikilink_internal' href="/page/Pear">some link</a></p>`},
|
||||
{8, `<p><a id='8' class='rocketlink wikilink_internal' href="/page/pear">some link</a></p>`},
|
||||
{9, `<ul id='9'>
|
||||
<li>lin"+</li>
|
||||
</ul>`},
|
||||
|
@ -17,6 +17,7 @@ const (
|
||||
spanSuper
|
||||
spanSub
|
||||
spanMark
|
||||
spanStrike
|
||||
spanLink
|
||||
)
|
||||
|
||||
@ -78,7 +79,7 @@ func getTextNode(input *bytes.Buffer) string {
|
||||
escaping = false
|
||||
} else if b == '\\' {
|
||||
escaping = true
|
||||
} else if strings.IndexByte("/*`^,![", b) >= 0 {
|
||||
} else if strings.IndexByte("/*`^,![~", b) >= 0 {
|
||||
input.UnreadByte()
|
||||
break
|
||||
} else {
|
||||
@ -127,6 +128,9 @@ func ParagraphToHtml(hyphaName, input string) string {
|
||||
case startsWith("!!"):
|
||||
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
|
||||
p.Next(2)
|
||||
case startsWith("~~"):
|
||||
ret.WriteString(tagFromState(spanMark, tagState, "s", "~~"))
|
||||
p.Next(2)
|
||||
case startsWith("[["):
|
||||
ret.WriteString(getLinkNode(p, hyphaName))
|
||||
default:
|
||||
@ -149,6 +153,8 @@ func ParagraphToHtml(hyphaName, input string) string {
|
||||
ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,"))
|
||||
case spanMark:
|
||||
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
|
||||
case spanStrike:
|
||||
ret.WriteString(tagFromState(spanMark, tagState, "s", "~~"))
|
||||
case spanLink:
|
||||
ret.WriteString(tagFromState(spanLink, tagState, "a", "[["))
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ func TestParagraphToHtml(t *testing.T) {
|
||||
{"Embedded //italic//", "Embedded <em>italic</em>"},
|
||||
{"double //italian// //text//", "double <em>italian</em> <em>text</em>"},
|
||||
{"it has `mono`", "it has <code>mono</code>"},
|
||||
{"it has ~~strike~~", "it has <s>strike</s>"},
|
||||
{"this is a left **bold", "this is a left <strong>bold</strong>"},
|
||||
{"this line has a ,comma, two of them", "this line has a ,comma, two of them"},
|
||||
{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},
|
||||
|
@ -21,6 +21,8 @@ func Parse(ast []Line, from, to int, state GemParserState) (html string) {
|
||||
html += v.ToHtml()
|
||||
case string:
|
||||
html += v
|
||||
default:
|
||||
html += "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit d78a90718ae9c36f7a114901ddad84b0e23221b3
|
||||
Subproject commit 7828352598c19afe5f2e13df0219656ac7b44c9c
|
19
mime_test.go
19
mime_test.go
@ -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
24
mycocredentials.json
Normal 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
60
templates/auth.qtpl
Normal 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
218
templates/auth.qtpl.go
Normal 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
|
||||
}
|
@ -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.
|
||||
{% code
|
||||
type navEntry struct {
|
||||
@ -10,23 +14,38 @@ var navEntries = []navEntry{
|
||||
{"text", "Raw text"},
|
||||
{"history", "History"},
|
||||
{"revision", "NOT REACHED"},
|
||||
{"delete-ask", "Delete"},
|
||||
{"rename-ask", "Rename"},
|
||||
{"delete-ask", "Delete"},
|
||||
}
|
||||
%}
|
||||
|
||||
{% func navHTML(hyphaName, navType string, revisionHash ...string) %}
|
||||
<nav>
|
||||
{% func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) %}
|
||||
{% code
|
||||
u := user.FromRequest(rq).OrAnon()
|
||||
%}
|
||||
<nav class="navlinks">
|
||||
<ul>
|
||||
{%- for _, entry := range navEntries -%}
|
||||
{%- if navType == "revision" && entry.path == "revision" -%}
|
||||
<li><b>{%s revisionHash[0] %}</b></li>
|
||||
{%- elseif navType == entry.path -%}
|
||||
<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>
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%s= userMenuHTML(u) %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endfunc %}
|
||||
|
||||
{% func userMenuHTML(u *user.User) %}
|
||||
<li class="navlinks__user">
|
||||
{% if u.Group == user.UserAnon %}
|
||||
<a href="/login">Login</a>
|
||||
{% else %}
|
||||
<a href="/page/{%s util.UserTree %}/{%s u.Name %}">{%s u.Name %}</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfunc %}
|
||||
|
||||
|
@ -1,25 +1,34 @@
|
||||
// Code generated by qtc from "common.qtpl". DO NOT EDIT.
|
||||
// See https://github.com/valyala/quicktemplate for details.
|
||||
|
||||
// This is the <nav> seen on top of many pages.
|
||||
|
||||
//line templates/common.qtpl:2
|
||||
//line templates/common.qtpl:1
|
||||
package templates
|
||||
|
||||
//line templates/common.qtpl:1
|
||||
import "net/http"
|
||||
|
||||
//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 (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line templates/common.qtpl:2
|
||||
//line templates/common.qtpl:6
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line templates/common.qtpl:3
|
||||
//line templates/common.qtpl:7
|
||||
type navEntry struct {
|
||||
path string
|
||||
title string
|
||||
@ -31,87 +40,163 @@ var navEntries = []navEntry{
|
||||
{"text", "Raw text"},
|
||||
{"history", "History"},
|
||||
{"revision", "NOT REACHED"},
|
||||
{"delete-ask", "Delete"},
|
||||
{"rename-ask", "Rename"},
|
||||
{"delete-ask", "Delete"},
|
||||
}
|
||||
|
||||
//line templates/common.qtpl:18
|
||||
func streamnavHTML(qw422016 *qt422016.Writer, hyphaName, navType string, revisionHash ...string) {
|
||||
//line templates/common.qtpl:18
|
||||
//line templates/common.qtpl:22
|
||||
func streamnavHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
|
||||
//line templates/common.qtpl:22
|
||||
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>
|
||||
`)
|
||||
//line templates/common.qtpl:21
|
||||
//line templates/common.qtpl:28
|
||||
for _, entry := range navEntries {
|
||||
//line templates/common.qtpl:22
|
||||
//line templates/common.qtpl:29
|
||||
if navType == "revision" && entry.path == "revision" {
|
||||
//line templates/common.qtpl:22
|
||||
//line templates/common.qtpl:29
|
||||
qw422016.N().S(` <li><b>`)
|
||||
//line templates/common.qtpl:23
|
||||
//line templates/common.qtpl:30
|
||||
qw422016.E().S(revisionHash[0])
|
||||
//line templates/common.qtpl:23
|
||||
//line templates/common.qtpl:30
|
||||
qw422016.N().S(`</b></li>
|
||||
`)
|
||||
//line templates/common.qtpl:24
|
||||
//line templates/common.qtpl:31
|
||||
} else if navType == entry.path {
|
||||
//line templates/common.qtpl:24
|
||||
//line templates/common.qtpl:31
|
||||
qw422016.N().S(` <li><b>`)
|
||||
//line templates/common.qtpl:25
|
||||
//line templates/common.qtpl:32
|
||||
qw422016.E().S(entry.title)
|
||||
//line templates/common.qtpl:25
|
||||
//line templates/common.qtpl:32
|
||||
qw422016.N().S(`</b></li>
|
||||
`)
|
||||
//line templates/common.qtpl:26
|
||||
} else if entry.path != "revision" {
|
||||
//line templates/common.qtpl:26
|
||||
//line templates/common.qtpl:33
|
||||
} else if entry.path != "revision" && u.Group.CanAccessRoute(entry.path) {
|
||||
//line templates/common.qtpl:33
|
||||
qw422016.N().S(` <li><a href="/`)
|
||||
//line templates/common.qtpl:27
|
||||
//line templates/common.qtpl:34
|
||||
qw422016.E().S(entry.path)
|
||||
//line templates/common.qtpl:27
|
||||
//line templates/common.qtpl:34
|
||||
qw422016.N().S(`/`)
|
||||
//line templates/common.qtpl:27
|
||||
//line templates/common.qtpl:34
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/common.qtpl:27
|
||||
//line templates/common.qtpl:34
|
||||
qw422016.N().S(`">`)
|
||||
//line templates/common.qtpl:27
|
||||
//line templates/common.qtpl:34
|
||||
qw422016.E().S(entry.title)
|
||||
//line templates/common.qtpl:27
|
||||
//line templates/common.qtpl:34
|
||||
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
|
||||
qw422016.N().S(` </ul>
|
||||
//line templates/common.qtpl:36
|
||||
qw422016.N().S(` `)
|
||||
//line templates/common.qtpl:37
|
||||
qw422016.N().S(userMenuHTML(u))
|
||||
//line templates/common.qtpl:37
|
||||
qw422016.N().S(`
|
||||
</ul>
|
||||
</nav>
|
||||
`)
|
||||
//line templates/common.qtpl:32
|
||||
//line templates/common.qtpl:40
|
||||
}
|
||||
|
||||
//line templates/common.qtpl:32
|
||||
func writenavHTML(qq422016 qtio422016.Writer, hyphaName, navType string, revisionHash ...string) {
|
||||
//line templates/common.qtpl:32
|
||||
//line templates/common.qtpl:40
|
||||
func writenavHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, navType string, revisionHash ...string) {
|
||||
//line templates/common.qtpl:40
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/common.qtpl:32
|
||||
streamnavHTML(qw422016, hyphaName, navType, revisionHash...)
|
||||
//line templates/common.qtpl:32
|
||||
//line templates/common.qtpl:40
|
||||
streamnavHTML(qw422016, rq, hyphaName, navType, revisionHash...)
|
||||
//line templates/common.qtpl:40
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/common.qtpl:32
|
||||
//line templates/common.qtpl:40
|
||||
}
|
||||
|
||||
//line templates/common.qtpl:32
|
||||
func navHTML(hyphaName, navType string, revisionHash ...string) string {
|
||||
//line templates/common.qtpl:32
|
||||
//line templates/common.qtpl:40
|
||||
func navHTML(rq *http.Request, hyphaName, navType string, revisionHash ...string) string {
|
||||
//line templates/common.qtpl:40
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/common.qtpl:32
|
||||
writenavHTML(qb422016, hyphaName, navType, revisionHash...)
|
||||
//line templates/common.qtpl:32
|
||||
//line templates/common.qtpl:40
|
||||
writenavHTML(qb422016, rq, hyphaName, navType, revisionHash...)
|
||||
//line templates/common.qtpl:40
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/common.qtpl:32
|
||||
//line templates/common.qtpl:40
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/common.qtpl:32
|
||||
//line templates/common.qtpl:40
|
||||
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
|
||||
}
|
||||
|
@ -21,10 +21,11 @@ blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;}
|
||||
.wikilink_new {color:#a55858;}
|
||||
.wikilink_new:visited {color:#a55858;}
|
||||
.wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;}
|
||||
p 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 code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; }
|
||||
article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wrap; border-radius: .25rem;}
|
||||
.codeblock code {padding:0; font-size:15px;}
|
||||
.transclusion code, .transclusion .codeblock {background-color:#ddd;}
|
||||
.transclusion {background-color:#eee; }
|
||||
.transclusion {background-color:#eee; border-radius: .25rem; }
|
||||
.transclusion__content > *:not(.binary-container) {margin: 0.5rem; }
|
||||
.transclusion__link {display: block; text-align: right; font-style: italic; margin-top: 0.5rem; color: black; text-decoration: none;}
|
||||
.transclusion__link::before {content: "⇐ ";}
|
||||
@ -33,16 +34,21 @@ p code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font
|
||||
.binary-container_with-video video,
|
||||
.binary-container_with-audio audio {width: 100%}
|
||||
.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; }
|
||||
figcaption { padding-bottom: .5rem; }
|
||||
|
||||
nav ul {display:flex; padding-left:0; flex-wrap:wrap; margin-top:0;}
|
||||
nav ul li {list-style-type:none;margin-right:1rem;}
|
||||
|
||||
#new-name {width:100%;}
|
||||
.navlinks__user {font-style:italic;}
|
||||
|
||||
.rc-entry { display: grid; list-style-type: none; padding: .25rem; background-color: #eee; grid-template-columns: 1fr 1fr; }
|
||||
.rc-entry__time { font-style: italic; }
|
||||
.rc-entry__hash { font-style: italic; text-align: right; }
|
||||
.rc-entry__links { grid-column: 1 / span 2; }
|
||||
.rc-entry__author { font-style: italic; }
|
||||
{% endfunc %}
|
||||
|
@ -43,10 +43,11 @@ blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;}
|
||||
.wikilink_new {color:#a55858;}
|
||||
.wikilink_new:visited {color:#a55858;}
|
||||
.wikilink_external::after {content:"🌐"; margin-left: .5rem; font-size: small; text-decoration: none; align: bottom;}
|
||||
p 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 code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; }
|
||||
article pre.codeblock {background-color:#eee; padding:.5rem; white-space: pre-wrap; border-radius: .25rem;}
|
||||
.codeblock code {padding:0; font-size:15px;}
|
||||
.transclusion code, .transclusion .codeblock {background-color:#ddd;}
|
||||
.transclusion {background-color:#eee; }
|
||||
.transclusion {background-color:#eee; border-radius: .25rem; }
|
||||
.transclusion__content > *:not(.binary-container) {margin: 0.5rem; }
|
||||
.transclusion__link {display: block; text-align: right; font-style: italic; margin-top: 0.5rem; color: black; text-decoration: none;}
|
||||
.transclusion__link::before {content: "⇐ ";}
|
||||
@ -55,44 +56,49 @@ p code {background-color:#eee; padding: .1rem .3rem; border-radius: .25rem; font
|
||||
.binary-container_with-video video,
|
||||
.binary-container_with-audio audio {width: 100%}
|
||||
.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; }
|
||||
figcaption { padding-bottom: .5rem; }
|
||||
|
||||
nav ul {display:flex; padding-left:0; flex-wrap:wrap; margin-top:0;}
|
||||
nav ul li {list-style-type:none;margin-right:1rem;}
|
||||
|
||||
#new-name {width:100%;}
|
||||
.navlinks__user {font-style:italic;}
|
||||
|
||||
.rc-entry { display: grid; list-style-type: none; padding: .25rem; background-color: #eee; grid-template-columns: 1fr 1fr; }
|
||||
.rc-entry__time { font-style: italic; }
|
||||
.rc-entry__hash { font-style: italic; text-align: right; }
|
||||
.rc-entry__links { grid-column: 1 / span 2; }
|
||||
.rc-entry__author { font-style: italic; }
|
||||
`)
|
||||
//line templates/css.qtpl:48
|
||||
//line templates/css.qtpl:54
|
||||
}
|
||||
|
||||
//line templates/css.qtpl:48
|
||||
//line templates/css.qtpl:54
|
||||
func WriteDefaultCSS(qq422016 qtio422016.Writer) {
|
||||
//line templates/css.qtpl:48
|
||||
//line templates/css.qtpl:54
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/css.qtpl:48
|
||||
//line templates/css.qtpl:54
|
||||
StreamDefaultCSS(qw422016)
|
||||
//line templates/css.qtpl:48
|
||||
//line templates/css.qtpl:54
|
||||
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 {
|
||||
//line templates/css.qtpl:48
|
||||
//line templates/css.qtpl:54
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/css.qtpl:48
|
||||
//line templates/css.qtpl:54
|
||||
WriteDefaultCSS(qb422016)
|
||||
//line templates/css.qtpl:48
|
||||
//line templates/css.qtpl:54
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/css.qtpl:48
|
||||
//line templates/css.qtpl:54
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/css.qtpl:48
|
||||
//line templates/css.qtpl:54
|
||||
return qs422016
|
||||
//line templates/css.qtpl:48
|
||||
//line templates/css.qtpl:54
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
{% import "net/http" %}
|
||||
|
||||
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>
|
||||
{%= navHTML(hyphaName, "delete-ask") %}
|
||||
{%= navHTML(rq, hyphaName, "delete-ask") %}
|
||||
{% if isOld %}
|
||||
<section>
|
||||
<h1>Delete {%s hyphaName %}?</h1>
|
||||
|
@ -1,151 +1,154 @@
|
||||
// Code generated by qtc from "delete.qtpl". DO NOT EDIT.
|
||||
// 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:2
|
||||
//line templates/delete.qtpl:1
|
||||
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 (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line templates/delete.qtpl:2
|
||||
//line templates/delete.qtpl:4
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line templates/delete.qtpl:2
|
||||
func StreamDeleteAskHTML(qw422016 *qt422016.Writer, hyphaName string, isOld bool) {
|
||||
//line templates/delete.qtpl:2
|
||||
//line templates/delete.qtpl:4
|
||||
func StreamDeleteAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
||||
//line templates/delete.qtpl:4
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/delete.qtpl:4
|
||||
streamnavHTML(qw422016, hyphaName, "delete-ask")
|
||||
//line templates/delete.qtpl:4
|
||||
//line templates/delete.qtpl:6
|
||||
streamnavHTML(qw422016, rq, hyphaName, "delete-ask")
|
||||
//line templates/delete.qtpl:6
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/delete.qtpl:5
|
||||
//line templates/delete.qtpl:7
|
||||
if isOld {
|
||||
//line templates/delete.qtpl:5
|
||||
//line templates/delete.qtpl:7
|
||||
qw422016.N().S(`
|
||||
<section>
|
||||
<h1>Delete `)
|
||||
//line templates/delete.qtpl:7
|
||||
//line templates/delete.qtpl:9
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/delete.qtpl:7
|
||||
//line templates/delete.qtpl:9
|
||||
qw422016.N().S(`?</h1>
|
||||
<p>Do you really want to delete hypha <em>`)
|
||||
//line templates/delete.qtpl:8
|
||||
//line templates/delete.qtpl:10
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/delete.qtpl:8
|
||||
//line templates/delete.qtpl:10
|
||||
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><a href="/delete-confirm/`)
|
||||
//line templates/delete.qtpl:10
|
||||
//line templates/delete.qtpl:12
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/delete.qtpl:10
|
||||
//line templates/delete.qtpl:12
|
||||
qw422016.N().S(`"><strong>Confirm</strong></a></p>
|
||||
<p><a href="/page/`)
|
||||
//line templates/delete.qtpl:11
|
||||
//line templates/delete.qtpl:13
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/delete.qtpl:11
|
||||
//line templates/delete.qtpl:13
|
||||
qw422016.N().S(`">Cancel</a></p>
|
||||
</section>
|
||||
`)
|
||||
//line templates/delete.qtpl:13
|
||||
//line templates/delete.qtpl:15
|
||||
} else {
|
||||
//line templates/delete.qtpl:13
|
||||
//line templates/delete.qtpl:15
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/delete.qtpl:14
|
||||
//line templates/delete.qtpl:16
|
||||
streamcannotDeleteDueToNonExistence(qw422016, hyphaName)
|
||||
//line templates/delete.qtpl:14
|
||||
//line templates/delete.qtpl:16
|
||||
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(`
|
||||
</main>
|
||||
`)
|
||||
//line templates/delete.qtpl:17
|
||||
//line templates/delete.qtpl:19
|
||||
}
|
||||
|
||||
//line templates/delete.qtpl:17
|
||||
func WriteDeleteAskHTML(qq422016 qtio422016.Writer, hyphaName string, isOld bool) {
|
||||
//line templates/delete.qtpl:17
|
||||
//line templates/delete.qtpl:19
|
||||
func WriteDeleteAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
||||
//line templates/delete.qtpl:19
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/delete.qtpl:17
|
||||
StreamDeleteAskHTML(qw422016, hyphaName, isOld)
|
||||
//line templates/delete.qtpl:17
|
||||
//line templates/delete.qtpl:19
|
||||
StreamDeleteAskHTML(qw422016, rq, hyphaName, isOld)
|
||||
//line templates/delete.qtpl:19
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/delete.qtpl:17
|
||||
//line templates/delete.qtpl:19
|
||||
}
|
||||
|
||||
//line templates/delete.qtpl:17
|
||||
func DeleteAskHTML(hyphaName string, isOld bool) string {
|
||||
//line templates/delete.qtpl:17
|
||||
//line templates/delete.qtpl:19
|
||||
func DeleteAskHTML(rq *http.Request, hyphaName string, isOld bool) string {
|
||||
//line templates/delete.qtpl:19
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/delete.qtpl:17
|
||||
WriteDeleteAskHTML(qb422016, hyphaName, isOld)
|
||||
//line templates/delete.qtpl:17
|
||||
//line templates/delete.qtpl:19
|
||||
WriteDeleteAskHTML(qb422016, rq, hyphaName, isOld)
|
||||
//line templates/delete.qtpl:19
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/delete.qtpl:17
|
||||
//line templates/delete.qtpl:19
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/delete.qtpl:17
|
||||
//line templates/delete.qtpl:19
|
||||
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) {
|
||||
//line templates/delete.qtpl:19
|
||||
//line templates/delete.qtpl:21
|
||||
qw422016.N().S(`
|
||||
<section>
|
||||
<h1>Cannot delete `)
|
||||
//line templates/delete.qtpl:21
|
||||
//line templates/delete.qtpl:23
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/delete.qtpl:21
|
||||
//line templates/delete.qtpl:23
|
||||
qw422016.N().S(`</h1>
|
||||
<p>This hypha does not exist.</p>
|
||||
<p><a href="/page/`)
|
||||
//line templates/delete.qtpl:23
|
||||
//line templates/delete.qtpl:25
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/delete.qtpl:23
|
||||
//line templates/delete.qtpl:25
|
||||
qw422016.N().S(`">Go back</a></p>
|
||||
</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) {
|
||||
//line templates/delete.qtpl:25
|
||||
//line templates/delete.qtpl:27
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/delete.qtpl:25
|
||||
//line templates/delete.qtpl:27
|
||||
streamcannotDeleteDueToNonExistence(qw422016, hyphaName)
|
||||
//line templates/delete.qtpl:25
|
||||
//line templates/delete.qtpl:27
|
||||
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 {
|
||||
//line templates/delete.qtpl:25
|
||||
//line templates/delete.qtpl:27
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/delete.qtpl:25
|
||||
//line templates/delete.qtpl:27
|
||||
writecannotDeleteDueToNonExistence(qb422016, hyphaName)
|
||||
//line templates/delete.qtpl:25
|
||||
//line templates/delete.qtpl:27
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/delete.qtpl:25
|
||||
//line templates/delete.qtpl:27
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/delete.qtpl:25
|
||||
//line templates/delete.qtpl:27
|
||||
return qs422016
|
||||
//line templates/delete.qtpl:25
|
||||
//line templates/delete.qtpl:27
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
{% func EditHTML(hyphaName, textAreaFill, warning string) %}
|
||||
<main class="edit">
|
||||
<h1>Edit {%s hyphaName %}</h1>
|
||||
{%s= warning %}
|
||||
<form method="post" class="edit-form"
|
||||
action="/upload-text/{%s hyphaName %}">
|
||||
<textarea name="text">{%s textAreaFill %}</textarea>
|
||||
<br/>
|
||||
<input type="submit"/>
|
||||
<a href="/page/{%s hyphaName %}">Cancel</a>
|
||||
</form>
|
||||
</main>
|
||||
{% import "net/http" %}
|
||||
|
||||
{% func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) %}
|
||||
<main class="edit">
|
||||
{%s= navHTML(rq, hyphaName, "edit") %}
|
||||
<h1>Edit {%s hyphaName %}</h1>
|
||||
{%s= warning %}
|
||||
<form method="post" class="edit-form"
|
||||
action="/upload-text/{%s hyphaName %}">
|
||||
<textarea name="text">{%s textAreaFill %}</textarea>
|
||||
<br/>
|
||||
<input type="submit"/>
|
||||
<a href="/page/{%s hyphaName %}">Cancel</a>
|
||||
</form>
|
||||
</main>
|
||||
{% endfunc %}
|
||||
|
@ -5,79 +5,87 @@
|
||||
package templates
|
||||
|
||||
//line templates/http_mutators.qtpl:1
|
||||
import "net/http"
|
||||
|
||||
//line templates/http_mutators.qtpl:3
|
||||
import (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line templates/http_mutators.qtpl:1
|
||||
//line templates/http_mutators.qtpl:3
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = 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
|
||||
qw422016.E().S(hyphaName)
|
||||
func StreamEditHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
|
||||
//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(`
|
||||
<form method="post" class="edit-form"
|
||||
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>
|
||||
<main class="edit">
|
||||
`)
|
||||
//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
|
||||
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
|
||||
func WriteEditHTML(qq422016 qtio422016.Writer, hyphaName, textAreaFill, warning string) {
|
||||
//line templates/http_mutators.qtpl:13
|
||||
//line templates/http_mutators.qtpl:16
|
||||
func WriteEditHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, textAreaFill, warning string) {
|
||||
//line templates/http_mutators.qtpl:16
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_mutators.qtpl:13
|
||||
StreamEditHTML(qw422016, hyphaName, textAreaFill, warning)
|
||||
//line templates/http_mutators.qtpl:13
|
||||
//line templates/http_mutators.qtpl:16
|
||||
StreamEditHTML(qw422016, rq, hyphaName, textAreaFill, warning)
|
||||
//line templates/http_mutators.qtpl:16
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/http_mutators.qtpl:13
|
||||
//line templates/http_mutators.qtpl:16
|
||||
}
|
||||
|
||||
//line templates/http_mutators.qtpl:13
|
||||
func EditHTML(hyphaName, textAreaFill, warning string) string {
|
||||
//line templates/http_mutators.qtpl:13
|
||||
//line templates/http_mutators.qtpl:16
|
||||
func EditHTML(rq *http.Request, hyphaName, textAreaFill, warning string) string {
|
||||
//line templates/http_mutators.qtpl:16
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_mutators.qtpl:13
|
||||
WriteEditHTML(qb422016, hyphaName, textAreaFill, warning)
|
||||
//line templates/http_mutators.qtpl:13
|
||||
//line templates/http_mutators.qtpl:16
|
||||
WriteEditHTML(qb422016, rq, hyphaName, textAreaFill, warning)
|
||||
//line templates/http_mutators.qtpl:16
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_mutators.qtpl:13
|
||||
//line templates/http_mutators.qtpl:16
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_mutators.qtpl:13
|
||||
//line templates/http_mutators.qtpl:16
|
||||
return qs422016
|
||||
//line templates/http_mutators.qtpl:13
|
||||
//line templates/http_mutators.qtpl:16
|
||||
}
|
||||
|
@ -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>
|
||||
{%= navHTML(hyphaName, "history") %}
|
||||
{%= navHTML(rq, hyphaName, "history") %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@ -16,9 +19,9 @@
|
||||
</main>
|
||||
{% endfunc %}
|
||||
|
||||
{% func RevisionHTML(hyphaName, naviTitle, contents, tree, revHash string) %}
|
||||
{% func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) %}
|
||||
<main>
|
||||
{%= navHTML(hyphaName, "revision", revHash) %}
|
||||
{%= navHTML(rq, hyphaName, "revision", revHash) %}
|
||||
<article>
|
||||
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
|
||||
{%s= naviTitle %}
|
||||
@ -32,9 +35,9 @@
|
||||
{% endfunc %}
|
||||
|
||||
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>
|
||||
{%= navHTML(hyphaName, "page") %}
|
||||
{%= navHTML(rq, hyphaName, "page") %}
|
||||
<article>
|
||||
{%s= naviTitle %}
|
||||
{% if contents == "" %}
|
||||
@ -44,14 +47,16 @@ If `contents` == "", a helpful message is shown instead.
|
||||
{% endif %}
|
||||
</article>
|
||||
<hr/>
|
||||
{% if u := user.FromRequest(rq).OrAnon(); !user.AuthUsed || u.Group > user.UserAnon %}
|
||||
<form action="/upload-binary/{%s hyphaName %}"
|
||||
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>
|
||||
<input type="file" id="upload-binary__input" name="binary"/>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
<hr/>
|
||||
{% endif %}
|
||||
<aside>
|
||||
{%s= tree %}
|
||||
</aside>
|
||||
|
@ -5,27 +5,33 @@
|
||||
package templates
|
||||
|
||||
//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 (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line templates/http_readers.qtpl:1
|
||||
//line templates/http_readers.qtpl:4
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line templates/http_readers.qtpl:1
|
||||
func StreamHistoryHTML(qw422016 *qt422016.Writer, hyphaName, tbody string) {
|
||||
//line templates/http_readers.qtpl:1
|
||||
//line templates/http_readers.qtpl:4
|
||||
func StreamHistoryHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, tbody string) {
|
||||
//line templates/http_readers.qtpl:4
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:3
|
||||
streamnavHTML(qw422016, hyphaName, "history")
|
||||
//line templates/http_readers.qtpl:3
|
||||
//line templates/http_readers.qtpl:6
|
||||
streamnavHTML(qw422016, rq, hyphaName, "history")
|
||||
//line templates/http_readers.qtpl:6
|
||||
qw422016.N().S(`
|
||||
<table>
|
||||
<thead>
|
||||
@ -37,196 +43,206 @@ func StreamHistoryHTML(qw422016 *qt422016.Writer, hyphaName, tbody string) {
|
||||
</thead>
|
||||
<tbody>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:13
|
||||
//line templates/http_readers.qtpl:16
|
||||
qw422016.N().S(tbody)
|
||||
//line templates/http_readers.qtpl:13
|
||||
//line templates/http_readers.qtpl:16
|
||||
qw422016.N().S(`
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:17
|
||||
//line templates/http_readers.qtpl:20
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:17
|
||||
func WriteHistoryHTML(qq422016 qtio422016.Writer, hyphaName, tbody string) {
|
||||
//line templates/http_readers.qtpl:17
|
||||
//line templates/http_readers.qtpl:20
|
||||
func WriteHistoryHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, tbody string) {
|
||||
//line templates/http_readers.qtpl:20
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_readers.qtpl:17
|
||||
StreamHistoryHTML(qw422016, hyphaName, tbody)
|
||||
//line templates/http_readers.qtpl:17
|
||||
//line templates/http_readers.qtpl:20
|
||||
StreamHistoryHTML(qw422016, rq, hyphaName, tbody)
|
||||
//line templates/http_readers.qtpl:20
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/http_readers.qtpl:17
|
||||
//line templates/http_readers.qtpl:20
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:17
|
||||
func HistoryHTML(hyphaName, tbody string) string {
|
||||
//line templates/http_readers.qtpl:17
|
||||
//line templates/http_readers.qtpl:20
|
||||
func HistoryHTML(rq *http.Request, hyphaName, tbody string) string {
|
||||
//line templates/http_readers.qtpl:20
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_readers.qtpl:17
|
||||
WriteHistoryHTML(qb422016, hyphaName, tbody)
|
||||
//line templates/http_readers.qtpl:17
|
||||
//line templates/http_readers.qtpl:20
|
||||
WriteHistoryHTML(qb422016, rq, hyphaName, tbody)
|
||||
//line templates/http_readers.qtpl:20
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_readers.qtpl:17
|
||||
//line templates/http_readers.qtpl:20
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_readers.qtpl:17
|
||||
//line templates/http_readers.qtpl:20
|
||||
return qs422016
|
||||
//line templates/http_readers.qtpl:17
|
||||
//line templates/http_readers.qtpl:20
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:19
|
||||
func StreamRevisionHTML(qw422016 *qt422016.Writer, hyphaName, naviTitle, contents, tree, revHash string) {
|
||||
//line templates/http_readers.qtpl:19
|
||||
//line templates/http_readers.qtpl:22
|
||||
func StreamRevisionHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) {
|
||||
//line templates/http_readers.qtpl:22
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:21
|
||||
streamnavHTML(qw422016, hyphaName, "revision", revHash)
|
||||
//line templates/http_readers.qtpl:21
|
||||
//line templates/http_readers.qtpl:24
|
||||
streamnavHTML(qw422016, rq, hyphaName, "revision", revHash)
|
||||
//line templates/http_readers.qtpl:24
|
||||
qw422016.N().S(`
|
||||
<article>
|
||||
<p>Please note that viewing binary parts of hyphae is not supported in history for now.</p>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:24
|
||||
//line templates/http_readers.qtpl:27
|
||||
qw422016.N().S(naviTitle)
|
||||
//line templates/http_readers.qtpl:24
|
||||
//line templates/http_readers.qtpl:27
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/http_readers.qtpl:25
|
||||
//line templates/http_readers.qtpl:28
|
||||
qw422016.N().S(contents)
|
||||
//line templates/http_readers.qtpl:25
|
||||
//line templates/http_readers.qtpl:28
|
||||
qw422016.N().S(`
|
||||
</article>
|
||||
<hr/>
|
||||
<aside>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:29
|
||||
//line templates/http_readers.qtpl:32
|
||||
qw422016.N().S(tree)
|
||||
//line templates/http_readers.qtpl:29
|
||||
//line templates/http_readers.qtpl:32
|
||||
qw422016.N().S(`
|
||||
</aside>
|
||||
</main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:32
|
||||
//line templates/http_readers.qtpl:35
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:32
|
||||
func WriteRevisionHTML(qq422016 qtio422016.Writer, hyphaName, naviTitle, contents, tree, revHash string) {
|
||||
//line templates/http_readers.qtpl:32
|
||||
//line templates/http_readers.qtpl:35
|
||||
func WriteRevisionHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) {
|
||||
//line templates/http_readers.qtpl:35
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_readers.qtpl:32
|
||||
StreamRevisionHTML(qw422016, hyphaName, naviTitle, contents, tree, revHash)
|
||||
//line templates/http_readers.qtpl:32
|
||||
//line templates/http_readers.qtpl:35
|
||||
StreamRevisionHTML(qw422016, rq, hyphaName, naviTitle, contents, tree, revHash)
|
||||
//line templates/http_readers.qtpl:35
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/http_readers.qtpl:32
|
||||
//line templates/http_readers.qtpl:35
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:32
|
||||
func RevisionHTML(hyphaName, naviTitle, contents, tree, revHash string) string {
|
||||
//line templates/http_readers.qtpl:32
|
||||
//line templates/http_readers.qtpl:35
|
||||
func RevisionHTML(rq *http.Request, hyphaName, naviTitle, contents, tree, revHash string) string {
|
||||
//line templates/http_readers.qtpl:35
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_readers.qtpl:32
|
||||
WriteRevisionHTML(qb422016, hyphaName, naviTitle, contents, tree, revHash)
|
||||
//line templates/http_readers.qtpl:32
|
||||
//line templates/http_readers.qtpl:35
|
||||
WriteRevisionHTML(qb422016, rq, hyphaName, naviTitle, contents, tree, revHash)
|
||||
//line templates/http_readers.qtpl:35
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_readers.qtpl:32
|
||||
//line templates/http_readers.qtpl:35
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_readers.qtpl:32
|
||||
//line templates/http_readers.qtpl:35
|
||||
return qs422016
|
||||
//line templates/http_readers.qtpl:32
|
||||
//line templates/http_readers.qtpl:35
|
||||
}
|
||||
|
||||
// If `contents` == "", a helpful message is shown instead.
|
||||
|
||||
//line templates/http_readers.qtpl:35
|
||||
func StreamPageHTML(qw422016 *qt422016.Writer, hyphaName, naviTitle, contents, tree string) {
|
||||
//line templates/http_readers.qtpl:35
|
||||
//line templates/http_readers.qtpl:38
|
||||
func StreamPageHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree string) {
|
||||
//line templates/http_readers.qtpl:38
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:37
|
||||
streamnavHTML(qw422016, hyphaName, "page")
|
||||
//line templates/http_readers.qtpl:37
|
||||
//line templates/http_readers.qtpl:40
|
||||
streamnavHTML(qw422016, rq, hyphaName, "page")
|
||||
//line templates/http_readers.qtpl:40
|
||||
qw422016.N().S(`
|
||||
<article>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:39
|
||||
//line templates/http_readers.qtpl:42
|
||||
qw422016.N().S(naviTitle)
|
||||
//line templates/http_readers.qtpl:39
|
||||
//line templates/http_readers.qtpl:42
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/http_readers.qtpl:40
|
||||
//line templates/http_readers.qtpl:43
|
||||
if contents == "" {
|
||||
//line templates/http_readers.qtpl:40
|
||||
//line templates/http_readers.qtpl:43
|
||||
qw422016.N().S(`
|
||||
<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)
|
||||
//line templates/http_readers.qtpl:41
|
||||
//line templates/http_readers.qtpl:44
|
||||
qw422016.N().S(`">create it</a>?</p>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:42
|
||||
//line templates/http_readers.qtpl:45
|
||||
} else {
|
||||
//line templates/http_readers.qtpl:42
|
||||
//line templates/http_readers.qtpl:45
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/http_readers.qtpl:43
|
||||
//line templates/http_readers.qtpl:46
|
||||
qw422016.N().S(contents)
|
||||
//line templates/http_readers.qtpl:43
|
||||
//line templates/http_readers.qtpl:46
|
||||
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(`
|
||||
</article>
|
||||
<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/`)
|
||||
//line templates/http_readers.qtpl:47
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/http_readers.qtpl:47
|
||||
qw422016.N().S(`"
|
||||
//line templates/http_readers.qtpl:51
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/http_readers.qtpl:51
|
||||
qw422016.N().S(`"
|
||||
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>
|
||||
<input type="file" id="upload-binary__input" name="binary"/>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
<hr/>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:59
|
||||
}
|
||||
//line templates/http_readers.qtpl:59
|
||||
qw422016.N().S(`
|
||||
<aside>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:56
|
||||
//line templates/http_readers.qtpl:61
|
||||
qw422016.N().S(tree)
|
||||
//line templates/http_readers.qtpl:56
|
||||
//line templates/http_readers.qtpl:61
|
||||
qw422016.N().S(`
|
||||
</aside>
|
||||
</main>
|
||||
`)
|
||||
//line templates/http_readers.qtpl:59
|
||||
//line templates/http_readers.qtpl:64
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:59
|
||||
func WritePageHTML(qq422016 qtio422016.Writer, hyphaName, naviTitle, contents, tree string) {
|
||||
//line templates/http_readers.qtpl:59
|
||||
//line templates/http_readers.qtpl:64
|
||||
func WritePageHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName, naviTitle, contents, tree string) {
|
||||
//line templates/http_readers.qtpl:64
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/http_readers.qtpl:59
|
||||
StreamPageHTML(qw422016, hyphaName, naviTitle, contents, tree)
|
||||
//line templates/http_readers.qtpl:59
|
||||
//line templates/http_readers.qtpl:64
|
||||
StreamPageHTML(qw422016, rq, hyphaName, naviTitle, contents, tree)
|
||||
//line templates/http_readers.qtpl:64
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/http_readers.qtpl:59
|
||||
//line templates/http_readers.qtpl:64
|
||||
}
|
||||
|
||||
//line templates/http_readers.qtpl:59
|
||||
func PageHTML(hyphaName, naviTitle, contents, tree string) string {
|
||||
//line templates/http_readers.qtpl:59
|
||||
//line templates/http_readers.qtpl:64
|
||||
func PageHTML(rq *http.Request, hyphaName, naviTitle, contents, tree string) string {
|
||||
//line templates/http_readers.qtpl:64
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/http_readers.qtpl:59
|
||||
WritePageHTML(qb422016, hyphaName, naviTitle, contents, tree)
|
||||
//line templates/http_readers.qtpl:59
|
||||
//line templates/http_readers.qtpl:64
|
||||
WritePageHTML(qb422016, rq, hyphaName, naviTitle, contents, tree)
|
||||
//line templates/http_readers.qtpl:64
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/http_readers.qtpl:59
|
||||
//line templates/http_readers.qtpl:64
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/http_readers.qtpl:59
|
||||
//line templates/http_readers.qtpl:64
|
||||
return qs422016
|
||||
//line templates/http_readers.qtpl:59
|
||||
//line templates/http_readers.qtpl:64
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
{% import "net/http" %}
|
||||
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>
|
||||
{%= navHTML(hyphaName, "rename-ask") %}
|
||||
{%= navHTML(rq, hyphaName, "rename-ask") %}
|
||||
{%- if isOld -%}
|
||||
<section>
|
||||
<h1>Rename {%s hyphaName %}</h1>
|
||||
|
@ -1,55 +1,58 @@
|
||||
// Code generated by qtc from "rename.qtpl". DO NOT EDIT.
|
||||
// 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:2
|
||||
//line templates/rename.qtpl:1
|
||||
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 (
|
||||
qtio422016 "io"
|
||||
|
||||
qt422016 "github.com/valyala/quicktemplate"
|
||||
)
|
||||
|
||||
//line templates/rename.qtpl:2
|
||||
//line templates/rename.qtpl:3
|
||||
var (
|
||||
_ = qtio422016.Copy
|
||||
_ = qt422016.AcquireByteBuffer
|
||||
)
|
||||
|
||||
//line templates/rename.qtpl:2
|
||||
func StreamRenameAskHTML(qw422016 *qt422016.Writer, hyphaName string, isOld bool) {
|
||||
//line templates/rename.qtpl:2
|
||||
//line templates/rename.qtpl:3
|
||||
func StreamRenameAskHTML(qw422016 *qt422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
||||
//line templates/rename.qtpl:3
|
||||
qw422016.N().S(`
|
||||
<main>
|
||||
`)
|
||||
//line templates/rename.qtpl:4
|
||||
streamnavHTML(qw422016, hyphaName, "rename-ask")
|
||||
//line templates/rename.qtpl:4
|
||||
//line templates/rename.qtpl:5
|
||||
streamnavHTML(qw422016, rq, hyphaName, "rename-ask")
|
||||
//line templates/rename.qtpl:5
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/rename.qtpl:5
|
||||
//line templates/rename.qtpl:6
|
||||
if isOld {
|
||||
//line templates/rename.qtpl:5
|
||||
//line templates/rename.qtpl:6
|
||||
qw422016.N().S(` <section>
|
||||
<h1>Rename `)
|
||||
//line templates/rename.qtpl:7
|
||||
//line templates/rename.qtpl:8
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/rename.qtpl:7
|
||||
//line templates/rename.qtpl:8
|
||||
qw422016.N().S(`</h1>
|
||||
<form action="/rename-confirm/`)
|
||||
//line templates/rename.qtpl:8
|
||||
//line templates/rename.qtpl:9
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/rename.qtpl:8
|
||||
//line templates/rename.qtpl:9
|
||||
qw422016.N().S(`" method="post" enctype="multipart/form-data">
|
||||
<fieldset>
|
||||
<legend>New name</legend>
|
||||
<input type="text" value="`)
|
||||
//line templates/rename.qtpl:11
|
||||
//line templates/rename.qtpl:12
|
||||
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"/>
|
||||
</fieldset>
|
||||
|
||||
@ -64,92 +67,92 @@ func StreamRenameAskHTML(qw422016 *qt422016.Writer, hyphaName string, isOld bool
|
||||
</form>
|
||||
</section>
|
||||
`)
|
||||
//line templates/rename.qtpl:24
|
||||
//line templates/rename.qtpl:25
|
||||
} else {
|
||||
//line templates/rename.qtpl:24
|
||||
//line templates/rename.qtpl:25
|
||||
qw422016.N().S(` `)
|
||||
//line templates/rename.qtpl:25
|
||||
//line templates/rename.qtpl:26
|
||||
streamcannotRenameDueToNonExistence(qw422016, hyphaName)
|
||||
//line templates/rename.qtpl:25
|
||||
//line templates/rename.qtpl:26
|
||||
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>
|
||||
`)
|
||||
//line templates/rename.qtpl:28
|
||||
//line templates/rename.qtpl:29
|
||||
}
|
||||
|
||||
//line templates/rename.qtpl:28
|
||||
func WriteRenameAskHTML(qq422016 qtio422016.Writer, hyphaName string, isOld bool) {
|
||||
//line templates/rename.qtpl:28
|
||||
//line templates/rename.qtpl:29
|
||||
func WriteRenameAskHTML(qq422016 qtio422016.Writer, rq *http.Request, hyphaName string, isOld bool) {
|
||||
//line templates/rename.qtpl:29
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/rename.qtpl:28
|
||||
StreamRenameAskHTML(qw422016, hyphaName, isOld)
|
||||
//line templates/rename.qtpl:28
|
||||
//line templates/rename.qtpl:29
|
||||
StreamRenameAskHTML(qw422016, rq, hyphaName, isOld)
|
||||
//line templates/rename.qtpl:29
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/rename.qtpl:28
|
||||
//line templates/rename.qtpl:29
|
||||
}
|
||||
|
||||
//line templates/rename.qtpl:28
|
||||
func RenameAskHTML(hyphaName string, isOld bool) string {
|
||||
//line templates/rename.qtpl:28
|
||||
//line templates/rename.qtpl:29
|
||||
func RenameAskHTML(rq *http.Request, hyphaName string, isOld bool) string {
|
||||
//line templates/rename.qtpl:29
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/rename.qtpl:28
|
||||
WriteRenameAskHTML(qb422016, hyphaName, isOld)
|
||||
//line templates/rename.qtpl:28
|
||||
//line templates/rename.qtpl:29
|
||||
WriteRenameAskHTML(qb422016, rq, hyphaName, isOld)
|
||||
//line templates/rename.qtpl:29
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/rename.qtpl:28
|
||||
//line templates/rename.qtpl:29
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/rename.qtpl:28
|
||||
//line templates/rename.qtpl:29
|
||||
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) {
|
||||
//line templates/rename.qtpl:30
|
||||
//line templates/rename.qtpl:31
|
||||
qw422016.N().S(`
|
||||
<section>
|
||||
<h1>Cannot rename `)
|
||||
//line templates/rename.qtpl:32
|
||||
//line templates/rename.qtpl:33
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/rename.qtpl:32
|
||||
//line templates/rename.qtpl:33
|
||||
qw422016.N().S(`</h1>
|
||||
<p>This hypha does not exist.</p>
|
||||
<p><a href="/page/`)
|
||||
//line templates/rename.qtpl:34
|
||||
//line templates/rename.qtpl:35
|
||||
qw422016.E().S(hyphaName)
|
||||
//line templates/rename.qtpl:34
|
||||
//line templates/rename.qtpl:35
|
||||
qw422016.N().S(`">Go back</a></p>
|
||||
</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) {
|
||||
//line templates/rename.qtpl:36
|
||||
//line templates/rename.qtpl:37
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/rename.qtpl:36
|
||||
//line templates/rename.qtpl:37
|
||||
streamcannotRenameDueToNonExistence(qw422016, hyphaName)
|
||||
//line templates/rename.qtpl:36
|
||||
//line templates/rename.qtpl:37
|
||||
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 {
|
||||
//line templates/rename.qtpl:36
|
||||
//line templates/rename.qtpl:37
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/rename.qtpl:36
|
||||
//line templates/rename.qtpl:37
|
||||
writecannotRenameDueToNonExistence(qb422016, hyphaName)
|
||||
//line templates/rename.qtpl:36
|
||||
//line templates/rename.qtpl:37
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/rename.qtpl:36
|
||||
//line templates/rename.qtpl:37
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/rename.qtpl:36
|
||||
//line templates/rename.qtpl:37
|
||||
return qs422016
|
||||
//line templates/rename.qtpl:36
|
||||
//line templates/rename.qtpl:37
|
||||
}
|
||||
|
69
user/fs.go
Normal file
69
user/fs.go
Normal 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
70
user/group.go
Normal 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
138
user/user.go
Normal 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: "/",
|
||||
}
|
||||
}
|
21
util/util.go
21
util/util.go
@ -1,15 +1,20 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ServerPort string
|
||||
HomePage string
|
||||
SiteTitle string
|
||||
WikiDir string
|
||||
ServerPort string
|
||||
HomePage string
|
||||
SiteTitle 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.
|
||||
@ -41,3 +46,11 @@ func FindSubhyphae(hyphaName string, hyphaIterator func(func(string))) []string
|
||||
})
|
||||
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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user