mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2025-01-05 17:40:26 +00:00
Start the Great Refactoring
This commit is contained in:
parent
5cafaaa3d8
commit
ee02211b3e
6
flag.go
6
flag.go
@ -42,9 +42,9 @@ func parseCliArgs() {
|
||||
util.URL = "http://0.0.0.0:" + util.ServerPort
|
||||
}
|
||||
|
||||
util.HomePage = CanonicalName(util.HomePage)
|
||||
util.UserHypha = CanonicalName(util.UserHypha)
|
||||
util.HeaderLinksHypha = CanonicalName(util.HeaderLinksHypha)
|
||||
util.HomePage = util.CanonicalName(util.HomePage)
|
||||
util.UserHypha = util.CanonicalName(util.UserHypha)
|
||||
util.HeaderLinksHypha = util.CanonicalName(util.HeaderLinksHypha)
|
||||
|
||||
switch util.AuthMethod {
|
||||
case "none":
|
||||
|
13
gemini.go
13
gemini.go
@ -11,6 +11,7 @@ import (
|
||||
"git.sr.ht/~adnano/go-gemini"
|
||||
"git.sr.ht/~adnano/go-gemini/certificate"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
@ -28,13 +29,13 @@ Visit home hypha:
|
||||
func geminiHypha(w *gemini.ResponseWriter, rq *gemini.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = geminiHyphaNameFromRq(rq, "page", "hypha")
|
||||
data, hyphaExists = HyphaStorage[hyphaName]
|
||||
hasAmnt = hyphaExists && data.BinaryPath != ""
|
||||
contents string
|
||||
hyphaName = geminiHyphaNameFromRq(rq, "page", "hypha")
|
||||
h = hyphae.ByName(hyphaName)
|
||||
hasAmnt = h.Exists && h.BinaryPath != ""
|
||||
contents string
|
||||
)
|
||||
if hyphaExists {
|
||||
fileContentsT, errT := ioutil.ReadFile(data.TextPath)
|
||||
if h.Exists {
|
||||
fileContentsT, errT := ioutil.ReadFile(h.TextPath)
|
||||
if errT == nil {
|
||||
md := markup.Doc(hyphaName, string(fileContentsT))
|
||||
contents = md.AsGemtext()
|
||||
|
@ -134,3 +134,11 @@ func (hop *HistoryOp) WithUser(u *user.User) *HistoryOp {
|
||||
}
|
||||
return hop
|
||||
}
|
||||
|
||||
func (hop *HistoryOp) HasErrors() bool {
|
||||
return len(hop.Errs) > 0
|
||||
}
|
||||
|
||||
func (hop *HistoryOp) FirstErrorText() string {
|
||||
return hop.Errs[0].Error()
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/templates"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -39,7 +40,7 @@ func handlerLogoutConfirm(w http.ResponseWriter, rq *http.Request) {
|
||||
func handlerLoginData(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
username = CanonicalName(rq.PostFormValue("username"))
|
||||
username = util.CanonicalName(rq.PostFormValue("username"))
|
||||
password = rq.PostFormValue("password")
|
||||
err = user.LoginDataHTTP(w, rq, username, password)
|
||||
)
|
||||
|
349
http_mutators.go
349
http_mutators.go
@ -5,6 +5,8 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/templates"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
@ -25,182 +27,146 @@ func init() {
|
||||
http.HandleFunc("/unattach-confirm/", handlerUnattachConfirm)
|
||||
}
|
||||
|
||||
func handlerUnattachAsk(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "unattach-ask")
|
||||
hd, isOld = HyphaStorage[hyphaName]
|
||||
hasAmnt = hd != nil && hd.BinaryPath != ""
|
||||
)
|
||||
if !hasAmnt {
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach")
|
||||
log.Println("Rejected (no amnt):", rq.URL)
|
||||
return
|
||||
} else if ok := user.CanProceed(rq, "unattach-confirm"); !ok {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments")
|
||||
log.Println("Rejected (no rights):", rq.URL)
|
||||
return
|
||||
}
|
||||
util.HTTP200Page(w, base("Unattach "+hyphaName+"?", templates.UnattachAskHTML(rq, hyphaName, isOld), user.FromRequest(rq)))
|
||||
}
|
||||
|
||||
func handlerUnattachConfirm(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "unattach-confirm")
|
||||
hyphaData, isOld = HyphaStorage[hyphaName]
|
||||
hasAmnt = hyphaData != nil && hyphaData.BinaryPath != ""
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if !u.CanProceed("unattach-confirm") {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to unattach attachments")
|
||||
log.Println("Rejected (no rights):", rq.URL)
|
||||
return
|
||||
}
|
||||
if !hasAmnt {
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Cannot unattach", "No attachment attached yet, therefore you cannot unattach")
|
||||
log.Println("Rejected (no amnt):", rq.URL)
|
||||
return
|
||||
} else if !isOld {
|
||||
// The precondition is to have the hypha in the first place.
|
||||
HttpErr(w, http.StatusPreconditionFailed, hyphaName,
|
||||
"Error: no such hypha",
|
||||
"Could not unattach this hypha because it does not exist")
|
||||
return
|
||||
}
|
||||
if hop := hyphaData.UnattachHypha(hyphaName, u); len(hop.Errs) != 0 {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
"Error: could not unattach hypha",
|
||||
fmt.Sprintf("Could not unattach this hypha due to internal errors. Server errors: <code>%v</code>", hop.Errs))
|
||||
return
|
||||
}
|
||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func handlerRenameAsk(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "rename-ask")
|
||||
_, isOld = HyphaStorage[hyphaName]
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if !u.CanProceed("rename-confirm") {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a trusted editor to rename pages.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
return
|
||||
}
|
||||
util.HTTP200Page(w, base("Rename "+hyphaName+"?", templates.RenameAskHTML(rq, hyphaName, isOld), u))
|
||||
}
|
||||
|
||||
func handlerRenameConfirm(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "rename-confirm")
|
||||
_, isOld = HyphaStorage[hyphaName]
|
||||
newName = CanonicalName(rq.PostFormValue("new-name"))
|
||||
_, newNameIsUsed = HyphaStorage[newName]
|
||||
recursive = rq.PostFormValue("recursive") == "true"
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
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))
|
||||
case newName == "":
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: no name",
|
||||
"No new name is given.")
|
||||
case !isOld:
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: no such hypha",
|
||||
"Cannot rename a hypha that does not exist yet.")
|
||||
case !HyphaPattern.MatchString(newName):
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error: invalid name",
|
||||
"Invalid new name. Names cannot contain characters <code>^?!:#@><*|\"\\'&%</code>")
|
||||
default:
|
||||
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)
|
||||
func factoryHandlerAsker(
|
||||
actionPath string,
|
||||
asker func(*hyphae.Hypha, *user.User) (error, string),
|
||||
succTitleTemplate string,
|
||||
succPageTemplate func(*http.Request, string, bool) string,
|
||||
) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, actionPath)
|
||||
h = hyphae.ByName(hyphaName)
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if err, errtitle := asker(h, u); err != nil {
|
||||
HttpErr(
|
||||
w,
|
||||
http.StatusInternalServerError,
|
||||
hyphaName,
|
||||
errtitle,
|
||||
err.Error())
|
||||
return
|
||||
}
|
||||
util.HTTP200Page(
|
||||
w,
|
||||
base(
|
||||
fmt.Sprintf(succTitleTemplate, hyphaName),
|
||||
succPageTemplate(rq, hyphaName, h.Exists),
|
||||
u))
|
||||
}
|
||||
}
|
||||
|
||||
// handlerDeleteAsk shows a delete dialog.
|
||||
func handlerDeleteAsk(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "delete-ask")
|
||||
_, isOld = HyphaStorage[hyphaName]
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if !u.CanProceed("delete-ask") {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be a moderator to delete pages.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
return
|
||||
var handlerUnattachAsk = factoryHandlerAsker(
|
||||
"unattach-ask",
|
||||
func(h *hyphae.Hypha, u *user.User) (error, string) {
|
||||
return h.CanUnattach(u)
|
||||
},
|
||||
"Unattach %s?",
|
||||
templates.UnattachAskHTML,
|
||||
)
|
||||
|
||||
var handlerDeleteAsk = factoryHandlerAsker(
|
||||
"delete-ask",
|
||||
func(h *hyphae.Hypha, u *user.User) (error, string) {
|
||||
return h.CanDelete(u)
|
||||
},
|
||||
"Delete %s?",
|
||||
templates.DeleteAskHTML,
|
||||
)
|
||||
|
||||
var handlerRenameAsk = factoryHandlerAsker(
|
||||
"rename-ask",
|
||||
func(h *hyphae.Hypha, u *user.User) (error, string) {
|
||||
return h.CanRename(u)
|
||||
},
|
||||
"Rename %s?",
|
||||
templates.RenameAskHTML,
|
||||
)
|
||||
|
||||
func factoryHandlerConfirmer(
|
||||
actionPath string,
|
||||
confirmer func(*hyphae.Hypha, *user.User, *http.Request) (*history.HistoryOp, string),
|
||||
) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "unattach-confirm")
|
||||
h = hyphae.ByName(hyphaName)
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if hop, errtitle := confirmer(h, u, rq); hop.HasErrors() {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
errtitle,
|
||||
hop.FirstErrorText())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
|
||||
}
|
||||
util.HTTP200Page(w, base("Delete "+hyphaName+"?", templates.DeleteAskHTML(rq, hyphaName, isOld), u))
|
||||
}
|
||||
|
||||
// handlerDeleteConfirm deletes a hypha for sure
|
||||
func handlerDeleteConfirm(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "delete-confirm")
|
||||
hyphaData, isOld = HyphaStorage[hyphaName]
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
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)
|
||||
}
|
||||
var handlerUnattachConfirm = factoryHandlerConfirmer(
|
||||
"unattach-confirm",
|
||||
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) {
|
||||
return h.UnattachHypha(u)
|
||||
},
|
||||
)
|
||||
|
||||
var handlerDeleteConfirm = factoryHandlerConfirmer(
|
||||
"delete-confirm",
|
||||
func(h *hyphae.Hypha, u *user.User, _ *http.Request) (*history.HistoryOp, string) {
|
||||
return h.DeleteHypha(u)
|
||||
},
|
||||
)
|
||||
|
||||
var handlerRenameConfirm = factoryHandlerConfirmer(
|
||||
"rename-confirm",
|
||||
func(oldHypha *hyphae.Hypha, u *user.User, rq *http.Request) (*history.HistoryOp, string) {
|
||||
var (
|
||||
newName = util.CanonicalName(rq.PostFormValue("new-name"))
|
||||
recursive = rq.PostFormValue("recursive") == "true"
|
||||
newHypha = hyphae.ByName(newName)
|
||||
)
|
||||
return oldHypha.RenameHypha(newHypha, recursive, u)
|
||||
},
|
||||
)
|
||||
|
||||
// handlerEdit shows the edit form. It doesn't edit anything actually.
|
||||
func handlerEdit(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "edit")
|
||||
hyphaData, isOld = HyphaStorage[hyphaName]
|
||||
warning string
|
||||
textAreaFill string
|
||||
err error
|
||||
u = user.FromRequest(rq)
|
||||
hyphaName = HyphaNameFromRq(rq, "edit")
|
||||
h = hyphae.ByName(hyphaName)
|
||||
warning string
|
||||
textAreaFill string
|
||||
err error
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if !u.CanProceed("edit") {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
if err, errtitle := h.CanEdit(u); err != nil {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
errtitle,
|
||||
err.Error())
|
||||
return
|
||||
}
|
||||
if isOld {
|
||||
textAreaFill, err = FetchTextPart(hyphaData)
|
||||
if h.Exists {
|
||||
textAreaFill, err = h.FetchTextPart()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", "Could not fetch text data")
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
"Error",
|
||||
"Could not fetch text data")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
warning = `<p>You are creating a new hypha.</p>`
|
||||
warning = `<p class="warning warning_new-hypha">You are creating a new hypha.</p>`
|
||||
}
|
||||
util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(rq, hyphaName, textAreaFill, warning), u))
|
||||
util.HTTP200Page(
|
||||
w,
|
||||
base(
|
||||
"Edit "+hyphaName,
|
||||
templates.EditHTML(rq, hyphaName, textAreaFill, warning),
|
||||
u))
|
||||
}
|
||||
|
||||
// handlerUploadText uploads a new text part for the hypha.
|
||||
@ -208,62 +174,75 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "upload-text")
|
||||
h = hyphae.ByName(hyphaName)
|
||||
textData = rq.PostFormValue("text")
|
||||
action = rq.PostFormValue("action")
|
||||
u = user.FromRequest(rq)
|
||||
hop *history.HistoryOp
|
||||
errtitle string
|
||||
)
|
||||
if !u.CanProceed("upload-text") {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName, "Not enough rights", "You must be an editor to edit pages.")
|
||||
log.Println("Rejected", rq.URL)
|
||||
return
|
||||
}
|
||||
if textData == "" {
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
|
||||
|
||||
if action != "Preview" {
|
||||
hop, errtitle = h.UploadText([]byte(textData), u)
|
||||
}
|
||||
|
||||
if hop.HasErrors() {
|
||||
HttpErr(w, http.StatusForbidden, hyphaName,
|
||||
errtitle,
|
||||
hop.FirstErrorText())
|
||||
return
|
||||
}
|
||||
|
||||
if action == "Preview" {
|
||||
util.HTTP200Page(w, base("Preview "+hyphaName, templates.PreviewHTML(rq, hyphaName, textData, "", markup.Doc(hyphaName, textData).AsHTML()), u))
|
||||
} else if hop := UploadText(hyphaName, textData, u); len(hop.Errs) != 0 {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
|
||||
util.HTTP200Page(
|
||||
w,
|
||||
base(
|
||||
"Preview "+hyphaName,
|
||||
templates.PreviewHTML(
|
||||
rq,
|
||||
hyphaName,
|
||||
textData,
|
||||
"",
|
||||
markup.Doc(hyphaName, textData).AsHTML()),
|
||||
u))
|
||||
} else {
|
||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
// handlerUploadBinary uploads a new binary part for the hypha.
|
||||
func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
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()
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "upload-binary")
|
||||
h = hyphae.ByName(hyphaName)
|
||||
u = user.FromRequest(rq)
|
||||
file, handler, err = rq.FormFile("binary")
|
||||
)
|
||||
if err, errtitle := h.CanAttach(err, u); err != nil {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName,
|
||||
errtitle,
|
||||
err.Error())
|
||||
}
|
||||
|
||||
// If file is not passed:
|
||||
if err != nil {
|
||||
HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed")
|
||||
return
|
||||
}
|
||||
|
||||
// If file is passed:
|
||||
if file != nil {
|
||||
defer file.Close()
|
||||
}
|
||||
var (
|
||||
mime = handler.Header.Get("Content-Type")
|
||||
hop = UploadBinary(hyphaName, mime, file, u)
|
||||
mime = handler.Header.Get("Content-Type")
|
||||
hop, errtitle = h.UploadBinary(mime, file, u)
|
||||
)
|
||||
|
||||
if len(hop.Errs) != 0 {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
|
||||
if hop.HasErrors() {
|
||||
HttpErr(w, http.StatusInternalServerError, hyphaName, errtitle, hop.FirstErrorText())
|
||||
return
|
||||
}
|
||||
http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
|
||||
http.Redirect(w, rq, "/hypha/"+hyphaName, http.StatusSeeOther)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/mimetype"
|
||||
"github.com/bouncepaw/mycorrhiza/templates"
|
||||
@ -33,7 +34,7 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
|
||||
shorterUrl = strings.TrimPrefix(rq.URL.Path, "/rev/")
|
||||
firstSlashIndex = strings.IndexRune(shorterUrl, '/')
|
||||
revHash = shorterUrl[:firstSlashIndex]
|
||||
hyphaName = CanonicalName(shorterUrl[firstSlashIndex+1:])
|
||||
hyphaName = util.CanonicalName(shorterUrl[firstSlashIndex+1:])
|
||||
contents = fmt.Sprintf(`<p>This hypha had no text at this revision.</p>`)
|
||||
TextPath = hyphaName + ".myco"
|
||||
textContents, err = history.FileAtRevision(TextPath, revHash)
|
||||
@ -42,7 +43,7 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
|
||||
if err == nil {
|
||||
contents = markup.Doc(hyphaName, textContents).AsHTML()
|
||||
}
|
||||
treeHTML, _, _ := tree.Tree(hyphaName, IterateHyphaNamesWith)
|
||||
treeHTML, _, _ := tree.Tree(hyphaName)
|
||||
page := templates.RevisionHTML(
|
||||
rq,
|
||||
hyphaName,
|
||||
@ -60,10 +61,10 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
|
||||
func handlerText(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
hyphaName := HyphaNameFromRq(rq, "text")
|
||||
if data, ok := HyphaStorage[hyphaName]; ok {
|
||||
log.Println("Serving", data.TextPath)
|
||||
if h := hyphae.ByName(hyphaName); h.Exists {
|
||||
log.Println("Serving", h.TextPath)
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
http.ServeFile(w, rq, data.TextPath)
|
||||
http.ServeFile(w, rq, h.TextPath)
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,10 +72,10 @@ func handlerText(w http.ResponseWriter, rq *http.Request) {
|
||||
func handlerBinary(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
hyphaName := HyphaNameFromRq(rq, "binary")
|
||||
if data, ok := HyphaStorage[hyphaName]; ok {
|
||||
log.Println("Serving", data.BinaryPath)
|
||||
w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(data.BinaryPath)))
|
||||
http.ServeFile(w, rq, data.BinaryPath)
|
||||
if h := hyphae.ByName(hyphaName); h.Exists {
|
||||
log.Println("Serving", h.BinaryPath)
|
||||
w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.BinaryPath)))
|
||||
http.ServeFile(w, rq, h.BinaryPath)
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,26 +83,26 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) {
|
||||
func handlerHypha(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var (
|
||||
hyphaName = HyphaNameFromRq(rq, "page", "hypha")
|
||||
data, hyphaExists = HyphaStorage[hyphaName]
|
||||
hasAmnt = hyphaExists && data.BinaryPath != ""
|
||||
contents string
|
||||
openGraph string
|
||||
u = user.FromRequest(rq)
|
||||
hyphaName = HyphaNameFromRq(rq, "page", "hypha")
|
||||
h = hyphae.ByName(hyphaName)
|
||||
hasAmnt = h.Exists && h.BinaryPath != ""
|
||||
contents string
|
||||
openGraph string
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
if hyphaExists {
|
||||
fileContentsT, errT := ioutil.ReadFile(data.TextPath)
|
||||
_, errB := os.Stat(data.BinaryPath)
|
||||
if h.Exists {
|
||||
fileContentsT, errT := ioutil.ReadFile(h.TextPath)
|
||||
_, errB := os.Stat(h.BinaryPath)
|
||||
if errT == nil {
|
||||
md := markup.Doc(hyphaName, string(fileContentsT))
|
||||
contents = md.AsHTML()
|
||||
openGraph = md.OpenGraphHTML()
|
||||
}
|
||||
if !os.IsNotExist(errB) {
|
||||
contents = binaryHtmlBlock(hyphaName, data) + contents
|
||||
contents = h.BinaryHtmlBlock() + contents
|
||||
}
|
||||
}
|
||||
treeHTML, prevHypha, nextHypha := tree.Tree(hyphaName, IterateHyphaNamesWith)
|
||||
treeHTML, prevHypha, nextHypha := tree.Tree(hyphaName)
|
||||
util.HTTP200Page(w,
|
||||
templates.BaseHTML(
|
||||
util.BeautifulName(hyphaName),
|
||||
|
326
hypha.go
326
hypha.go
@ -1,326 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/mimetype"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
markup.HyphaExists = func(hyphaName string) bool {
|
||||
_, hyphaExists := HyphaStorage[hyphaName]
|
||||
return hyphaExists
|
||||
}
|
||||
markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) {
|
||||
if hyphaData, ok := HyphaStorage[hyphaName]; ok {
|
||||
rawText, err = FetchTextPart(hyphaData)
|
||||
if hyphaData.BinaryPath != "" {
|
||||
binaryBlock = binaryHtmlBlock(hyphaName, hyphaData)
|
||||
}
|
||||
} else {
|
||||
err = errors.New("Hypha " + hyphaName + " does not exist")
|
||||
}
|
||||
return
|
||||
}
|
||||
markup.HyphaIterate = IterateHyphaNamesWith
|
||||
markup.HyphaImageForOG = func(hyphaName string) string {
|
||||
if hd, isOld := GetHyphaData(hyphaName); isOld && hd.BinaryPath != "" {
|
||||
return util.URL + "/binary/" + hyphaName
|
||||
}
|
||||
return util.URL + "/favicon.ico"
|
||||
}
|
||||
}
|
||||
|
||||
// GetHyphaData finds a hypha addressed by `hyphaName` and returns its `hyphaData`. `hyphaData` is set to a zero value if this hypha does not exist. `isOld` is false if this hypha does not exist.
|
||||
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.
|
||||
type HyphaData hyphae.Hypha
|
||||
|
||||
// uploadHelp is a helper function for UploadText and UploadBinary
|
||||
func uploadHelp(hop *history.HistoryOp, hyphaName, ext string, data []byte, u *user.User) *history.HistoryOp {
|
||||
var (
|
||||
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)
|
||||
}
|
||||
|
||||
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] = hyphaData
|
||||
hyphae.IncrementCount()
|
||||
}
|
||||
*originalFullPath = fullPath
|
||||
if isOld && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) {
|
||||
return hop.Abort()
|
||||
}
|
||||
return hop.WithFiles(fullPath).
|
||||
WithUser(u).
|
||||
Apply()
|
||||
}
|
||||
|
||||
// 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 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)
|
||||
)
|
||||
if err != nil {
|
||||
return hop.WithError(err).Apply()
|
||||
}
|
||||
return uploadHelp(hop, hyphaName, mimetype.ToExtension(mime), data, u)
|
||||
}
|
||||
|
||||
// DeleteHypha deletes hypha and makes a history record about that.
|
||||
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)).
|
||||
WithUser(u).
|
||||
Apply()
|
||||
if len(hop.Errs) == 0 {
|
||||
delete(HyphaStorage, hyphaName)
|
||||
hyphae.DecrementCount()
|
||||
}
|
||||
return hop
|
||||
}
|
||||
|
||||
// UnattachHypha unattaches hypha and makes a history record about that.
|
||||
func (hd *HyphaData) UnattachHypha(hyphaName string, u *user.User) *history.HistoryOp {
|
||||
hop := history.Operation(history.TypeUnattachHypha).
|
||||
WithFilesRemoved(hd.BinaryPath).
|
||||
WithMsg(fmt.Sprintf("Unattach ‘%s’", hyphaName)).
|
||||
WithUser(u).
|
||||
Apply()
|
||||
if len(hop.Errs) == 0 {
|
||||
hd, ok := HyphaStorage[hyphaName]
|
||||
if ok {
|
||||
if hd.BinaryPath != "" {
|
||||
hd.BinaryPath = ""
|
||||
}
|
||||
// If nothing is left of the hypha
|
||||
if hd.TextPath == "" {
|
||||
delete(HyphaStorage, hyphaName)
|
||||
hyphae.DecrementCount()
|
||||
}
|
||||
}
|
||||
}
|
||||
return hop
|
||||
}
|
||||
|
||||
func findHyphaeToRename(hyphaName string, recursive bool) []string {
|
||||
hyphae := []string{hyphaName}
|
||||
if recursive {
|
||||
hyphae = append(hyphae, util.FindSubhyphae(hyphaName, IterateHyphaNamesWith)...)
|
||||
}
|
||||
return hyphae
|
||||
}
|
||||
|
||||
func renamingPairs(hyphaNames []string, replaceName func(string) string) (map[string]string, error) {
|
||||
renameMap := make(map[string]string)
|
||||
for _, hn := range hyphaNames {
|
||||
if hd, ok := HyphaStorage[hn]; ok {
|
||||
if _, nameUsed := HyphaStorage[replaceName(hn)]; nameUsed {
|
||||
return nil, errors.New("Hypha " + replaceName(hn) + " already exists")
|
||||
}
|
||||
if hd.TextPath != "" {
|
||||
renameMap[hd.TextPath] = replaceName(hd.TextPath)
|
||||
}
|
||||
if hd.BinaryPath != "" {
|
||||
renameMap[hd.BinaryPath] = replaceName(hd.BinaryPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
return renameMap, nil
|
||||
}
|
||||
|
||||
// word Data is plural here
|
||||
func relocateHyphaData(hyphaNames []string, replaceName func(string) string) {
|
||||
for _, hyphaName := range hyphaNames {
|
||||
if hd, ok := HyphaStorage[hyphaName]; ok {
|
||||
hd.TextPath = replaceName(hd.TextPath)
|
||||
hd.BinaryPath = replaceName(hd.BinaryPath)
|
||||
HyphaStorage[replaceName(hyphaName)] = hd
|
||||
delete(HyphaStorage, hyphaName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RenameHypha renames hypha from old name `hyphaName` to `newName` and makes a history record about that. If `recursive` is `true`, its subhyphae will be renamed the same way.
|
||||
func RenameHypha(hyphaName, newName string, recursive bool, u *user.User) *history.HistoryOp {
|
||||
var (
|
||||
re = regexp.MustCompile(`(?i)` + hyphaName)
|
||||
replaceName = func(str string) string {
|
||||
return re.ReplaceAllString(CanonicalName(str), newName)
|
||||
}
|
||||
hyphaNames = findHyphaeToRename(hyphaName, recursive)
|
||||
renameMap, err = renamingPairs(hyphaNames, replaceName)
|
||||
renameMsg = "Rename ‘%s’ to ‘%s’"
|
||||
hop = history.Operation(history.TypeRenameHypha)
|
||||
)
|
||||
if err != nil {
|
||||
hop.Errs = append(hop.Errs, err)
|
||||
return hop
|
||||
}
|
||||
if recursive {
|
||||
renameMsg += " recursively"
|
||||
}
|
||||
hop.WithFilesRenamed(renameMap).
|
||||
WithMsg(fmt.Sprintf(renameMsg, hyphaName, newName)).
|
||||
WithUser(u).
|
||||
Apply()
|
||||
if len(hop.Errs) == 0 {
|
||||
relocateHyphaData(hyphaNames, replaceName)
|
||||
}
|
||||
return hop
|
||||
}
|
||||
|
||||
// binaryHtmlBlock creates an html block for binary part of the hypha.
|
||||
func binaryHtmlBlock(hyphaName string, hd *HyphaData) string {
|
||||
switch filepath.Ext(hd.BinaryPath) {
|
||||
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
|
||||
return fmt.Sprintf(`
|
||||
<div class="binary-container binary-container_with-img">
|
||||
<a href="/binary/%[1]s"><img src="/binary/%[1]s"/></a>
|
||||
</div>`, hyphaName)
|
||||
case ".ogg", ".webm", ".mp4":
|
||||
return fmt.Sprintf(`
|
||||
<div class="binary-container binary-container_with-video">
|
||||
<video>
|
||||
<source src="/binary/%[1]s"/>
|
||||
<p>Your browser does not support video. See video's <a href="/binary/%[1]s">direct url</a></p>
|
||||
</video>
|
||||
`, hyphaName)
|
||||
case ".mp3":
|
||||
return fmt.Sprintf(`
|
||||
<div class="binary-container binary-container_with-audio">
|
||||
<audio>
|
||||
<source src="/binary/%[1]s"/>
|
||||
<p>Your browser does not support audio. See audio's <a href="/binary/%[1]s">direct url</a></p>
|
||||
</audio>
|
||||
`, hyphaName)
|
||||
default:
|
||||
return fmt.Sprintf(`
|
||||
<div class="binary-container binary-container_with-nothing">
|
||||
<p>This hypha's media cannot be rendered. <a href="/binary/%s">Download it</a></p>
|
||||
</div>
|
||||
`, hyphaName)
|
||||
}
|
||||
}
|
||||
|
||||
// FetchTextPart tries to read text file in the `d`. If there is no file, empty string is returned.
|
||||
func FetchTextPart(d *HyphaData) (string, error) {
|
||||
if d.TextPath == "" {
|
||||
return "", nil
|
||||
}
|
||||
_, err := os.Stat(d.TextPath)
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
text, err := ioutil.ReadFile(d.TextPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(text), nil
|
||||
}
|
||||
|
||||
func setHeaderLinks() {
|
||||
if userLinksHypha, ok := GetHyphaData(util.HeaderLinksHypha); !ok {
|
||||
util.SetDefaultHeaderLinks()
|
||||
} else {
|
||||
contents, err := ioutil.ReadFile(userLinksHypha.TextPath)
|
||||
if err != nil || len(contents) == 0 {
|
||||
util.SetDefaultHeaderLinks()
|
||||
} else {
|
||||
text := string(contents)
|
||||
util.ParseHeaderLinks(text, markup.Rocketlink)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HyphaToTemporaryWorkaround(h *hyphae.Hypha) *HyphaData {
|
||||
return &HyphaData{
|
||||
Name: h.Name,
|
||||
TextPath: h.TextPath,
|
||||
BinaryPath: h.BinaryPath,
|
||||
}
|
||||
}
|
||||
|
||||
// MergeIn merges in content file paths from a different hypha object. Prints warnings sometimes.
|
||||
func (h *HyphaData) MergeIn(oh *hyphae.Hypha) {
|
||||
if h.TextPath == "" && oh.TextPath != "" {
|
||||
h.TextPath = oh.TextPath
|
||||
}
|
||||
if oh.BinaryPath != "" {
|
||||
if h.BinaryPath != "" {
|
||||
log.Println("There is a file collision for binary part of a hypha:", h.BinaryPath, "and", oh.BinaryPath, "-- going on with the latter")
|
||||
}
|
||||
h.BinaryPath = oh.BinaryPath
|
||||
}
|
||||
}
|
||||
|
||||
// Index finds all hypha files in the full `path` and saves them to HyphaStorage. This function is recursive.
|
||||
func Index(path string) {
|
||||
ch := make(chan *hyphae.Hypha, 5)
|
||||
|
||||
go func() {
|
||||
hyphae.Index(path, 0, ch)
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
for h := range ch {
|
||||
if oldHypha, ok := HyphaStorage[h.Name]; ok {
|
||||
oldHypha.MergeIn(h)
|
||||
} else {
|
||||
HyphaStorage[h.Name] = HyphaToTemporaryWorkaround(h)
|
||||
hyphae.IncrementCount()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
52
hyphae/delete.go
Normal file
52
hyphae/delete.go
Normal file
@ -0,0 +1,52 @@
|
||||
package hyphae
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
)
|
||||
|
||||
func rejectDeleteLog(h *Hypha, u *user.User, errmsg string) {
|
||||
log.Printf("Reject delete ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg)
|
||||
}
|
||||
|
||||
// CanDelete checks if given user can delete given hypha.
|
||||
func (h *Hypha) CanDelete(u *user.User) (err error, errtitle string) {
|
||||
// First, check if can unattach at all
|
||||
if !u.CanProceed("delete-confirm") {
|
||||
rejectDeleteLog(h, u, "no rights")
|
||||
return errors.New("Not enough rights to delete, you must be a moderator"), "Not enough rights"
|
||||
}
|
||||
|
||||
if !h.Exists {
|
||||
rejectDeleteLog(h, u, "does not exist")
|
||||
return errors.New("Cannot delete this hypha because it does not exist"), "Does not exist"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// DeleteHypha deletes hypha and makes a history record about that.
|
||||
func (h *Hypha) DeleteHypha(u *user.User) (hop *history.HistoryOp, errtitle string) {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
hop = history.Operation(history.TypeDeleteHypha)
|
||||
|
||||
if err, errtitle := h.CanDelete(u); errtitle != "" {
|
||||
hop.WithError(err)
|
||||
return hop, errtitle
|
||||
}
|
||||
|
||||
hop.
|
||||
WithFilesRemoved(h.TextPath, h.BinaryPath).
|
||||
WithMsg(fmt.Sprintf("Delete ‘%s’", h.Name)).
|
||||
WithUser(u).
|
||||
Apply()
|
||||
if len(hop.Errs) == 0 {
|
||||
h.delete()
|
||||
}
|
||||
return hop, ""
|
||||
}
|
@ -9,8 +9,28 @@ import (
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
// Index finds all hypha files in the full `path` and sends them to the channel. Handling of duplicate entries and attachment and counting them is up to the caller.
|
||||
func Index(path string, nestLevel uint, ch chan *Hypha) {
|
||||
// Index finds all hypha files in the full `path` and saves them to the hypha storage.
|
||||
func Index(path string) {
|
||||
ch := make(chan *Hypha, 5)
|
||||
|
||||
go func(ch chan *Hypha) {
|
||||
indexHelper(path, 0, ch)
|
||||
close(ch)
|
||||
}(ch)
|
||||
|
||||
for h := range ch {
|
||||
// At this time it is safe to ignore the mutex, because there is only one worker.
|
||||
if oldHypha, ok := byNames[h.Name]; ok {
|
||||
oldHypha.MergeIn(h)
|
||||
} else {
|
||||
byNames[h.Name] = h
|
||||
IncrementCount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// indexHelper finds all hypha files in the full `path` and sends them to the channel. Handling of duplicate entries and attachment and counting them is up to the caller.
|
||||
func indexHelper(path string, nestLevel uint, ch chan *Hypha) {
|
||||
nodes, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@ -22,14 +42,14 @@ func Index(path string, nestLevel uint, ch chan *Hypha) {
|
||||
util.IsCanonicalName(node.Name()) &&
|
||||
node.Name() != ".git" &&
|
||||
!(nestLevel == 0 && node.Name() == "static") {
|
||||
Index(filepath.Join(path, node.Name()), nestLevel+1, ch)
|
||||
indexHelper(filepath.Join(path, node.Name()), nestLevel+1, ch)
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
hyphaPartPath = filepath.Join(path, node.Name())
|
||||
hyphaName, isText, skip = mimetype.DataFromFilename(hyphaPartPath)
|
||||
hypha = &Hypha{Name: hyphaName}
|
||||
hypha = &Hypha{Name: hyphaName, Exists: true}
|
||||
)
|
||||
if !skip {
|
||||
if isText {
|
||||
@ -39,6 +59,5 @@ func Index(path string, nestLevel uint, ch chan *Hypha) {
|
||||
}
|
||||
ch <- hypha
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
166
hyphae/hyphae.go
166
hyphae/hyphae.go
@ -1,9 +1,47 @@
|
||||
package hyphae
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
markup.HyphaExists = func(hyphaName string) bool {
|
||||
return ByName(hyphaName).Exists
|
||||
}
|
||||
markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) {
|
||||
if h := ByName(hyphaName); h.Exists {
|
||||
rawText, err = h.FetchTextPart()
|
||||
if h.BinaryPath != "" {
|
||||
binaryBlock = h.BinaryHtmlBlock()
|
||||
}
|
||||
} else {
|
||||
err = errors.New("Hypha " + hyphaName + " does not exist")
|
||||
}
|
||||
return
|
||||
}
|
||||
markup.HyphaIterate = func(λ func(string)) {
|
||||
for h := range YieldExistingHyphae() {
|
||||
λ(h.Name)
|
||||
}
|
||||
}
|
||||
markup.HyphaImageForOG = func(hyphaName string) string {
|
||||
if h := ByName(hyphaName); h.Exists && h.BinaryPath != "" {
|
||||
return util.URL + "/binary/" + hyphaName
|
||||
}
|
||||
return util.URL + "/favicon.ico"
|
||||
}
|
||||
}
|
||||
|
||||
// HyphaPattern is a pattern which all hyphae must match.
|
||||
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
|
||||
|
||||
type Hypha struct {
|
||||
sync.RWMutex
|
||||
|
||||
@ -11,33 +49,125 @@ type Hypha struct {
|
||||
Exists bool
|
||||
TextPath string
|
||||
BinaryPath string
|
||||
OutLinks []*Hypha
|
||||
BackLinks []*Hypha
|
||||
OutLinks []*Hypha // not used yet
|
||||
BackLinks []*Hypha // not used yet
|
||||
}
|
||||
|
||||
/*
|
||||
// Insert inserts the hypha into the mycelium. It overwrites the previous record, if there was any, and returns false. If the was no previous record, return true.
|
||||
var byNames = make(map[string]*Hypha)
|
||||
var byNamesMutex = sync.Mutex{}
|
||||
|
||||
// YieldExistingHyphae iterates over all hyphae and yields all existing ones.
|
||||
func YieldExistingHyphae() chan *Hypha {
|
||||
ch := make(chan *Hypha)
|
||||
go func(ch chan *Hypha) {
|
||||
for _, h := range byNames {
|
||||
if h.Exists {
|
||||
ch <- h
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
}(ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
// Subhyphae returns slice of subhyphae.
|
||||
func (h *Hypha) Subhyphae() []*Hypha {
|
||||
hyphae := []*Hypha{}
|
||||
for subh := range YieldExistingHyphae() {
|
||||
if strings.HasPrefix(subh.Name, h.Name+"/") {
|
||||
hyphae = append(hyphae, subh)
|
||||
}
|
||||
}
|
||||
return hyphae
|
||||
}
|
||||
|
||||
// AreFreeNames checks if all given `hyphaNames` are not taken.
|
||||
func AreFreeNames(hyphaNames ...string) (firstFailure string, ok bool) {
|
||||
for h := range YieldExistingHyphae() {
|
||||
for _, hn := range hyphaNames {
|
||||
if hn == h.Name {
|
||||
return hn, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
||||
// EmptyHypha returns an empty hypha struct with given name.
|
||||
func EmptyHypha(hyphaName string) *Hypha {
|
||||
return &Hypha{
|
||||
Name: hyphaName,
|
||||
Exists: false,
|
||||
TextPath: "",
|
||||
BinaryPath: "",
|
||||
OutLinks: make([]*Hypha, 0),
|
||||
BackLinks: make([]*Hypha, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// ByName returns a hypha by name. If h.Exists, the returned hypha pointer is known to be part of the hypha index (byNames map).
|
||||
func ByName(hyphaName string) (h *Hypha) {
|
||||
byNamesMutex.Lock()
|
||||
defer byNamesMutex.Unlock()
|
||||
|
||||
h, exists := byNames[hyphaName]
|
||||
if exists {
|
||||
return h
|
||||
}
|
||||
return EmptyHypha(hyphaName)
|
||||
}
|
||||
|
||||
// Insert inserts the hypha into the storage. It overwrites the previous record, if there was any, and returns false. If the was no previous record, return true.
|
||||
func (h *Hypha) Insert() (justCreated bool) {
|
||||
var hp *Hypha
|
||||
hp, justCreated = ByName(h.Name)
|
||||
hp = ByName(h.Name)
|
||||
|
||||
mycm.Lock()
|
||||
defer mycm.Unlock()
|
||||
if justCreated {
|
||||
mycm.byNames[hp.Name] = h
|
||||
} else {
|
||||
byNamesMutex.Lock()
|
||||
defer byNamesMutex.Unlock()
|
||||
if hp.Exists {
|
||||
hp = h
|
||||
} else {
|
||||
byNames[hp.Name] = h
|
||||
}
|
||||
|
||||
return justCreated
|
||||
}*/
|
||||
return !hp.Exists
|
||||
}
|
||||
|
||||
// PhaseOut marks the hypha as non-existent. This is an idempotent operation.
|
||||
func (h *Hypha) PhaseOut() {
|
||||
func (h *Hypha) InsertIfNew() (justCreated bool) {
|
||||
if h.Exists {
|
||||
return h.Insert()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *Hypha) delete() {
|
||||
byNamesMutex.Lock()
|
||||
h.Lock()
|
||||
h.Exists = false
|
||||
h.OutLinks = make([]*Hypha, 0)
|
||||
h.TextPath = ""
|
||||
h.BinaryPath = ""
|
||||
delete(byNames, h.Name)
|
||||
DecrementCount()
|
||||
byNamesMutex.Unlock()
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
func (h *Hypha) renameTo(newName string) {
|
||||
byNamesMutex.Lock()
|
||||
h.Lock()
|
||||
delete(byNames, h.Name)
|
||||
h.Name = newName
|
||||
byNames[h.Name] = h
|
||||
byNamesMutex.Unlock()
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
// MergeIn merges in content file paths from a different hypha object. Prints warnings sometimes.
|
||||
func (h *Hypha) MergeIn(oh *Hypha) {
|
||||
if h.TextPath == "" && oh.TextPath != "" {
|
||||
h.TextPath = oh.TextPath
|
||||
}
|
||||
if oh.BinaryPath != "" {
|
||||
if h.BinaryPath != "" {
|
||||
log.Println("There is a file collision for binary part of a hypha:", h.BinaryPath, "and", oh.BinaryPath, "-- going on with the latter")
|
||||
}
|
||||
h.BinaryPath = oh.BinaryPath
|
||||
}
|
||||
}
|
||||
|
122
hyphae/rename.go
Normal file
122
hyphae/rename.go
Normal file
@ -0,0 +1,122 @@
|
||||
package hyphae
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
func rejectRenameLog(h *Hypha, u *user.User, errmsg string) {
|
||||
log.Printf("Reject rename ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg)
|
||||
}
|
||||
|
||||
func (h *Hypha) CanRename(u *user.User) (err error, errtitle string) {
|
||||
if !u.CanProceed("rename-confirm") {
|
||||
rejectRenameLog(h, u, "no rights")
|
||||
return errors.New("Not enough rights to rename, you must be a trusted editor"), "Not enough rights"
|
||||
}
|
||||
|
||||
if !h.Exists {
|
||||
rejectRenameLog(h, u, "does not exist")
|
||||
return errors.New("Cannot rename this hypha because it does not exist"), "Does not exist"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func canRenameThisToThat(oh *Hypha, nh *Hypha, u *user.User) (err error, errtitle string) {
|
||||
if nh.Exists {
|
||||
rejectRenameLog(oh, u, fmt.Sprintf("name ‘%s’ taken already", nh.Name))
|
||||
return errors.New(fmt.Sprintf("Hypha named <a href='/hypha/%[1]s'>%[1]s</a> already exists, cannot rename", nh.Name)), "Name taken"
|
||||
}
|
||||
|
||||
if nh.Name == "" {
|
||||
rejectRenameLog(oh, u, "no new name given")
|
||||
return errors.New("No new name is given"), "No name given"
|
||||
}
|
||||
|
||||
if !HyphaPattern.MatchString(nh.Name) {
|
||||
rejectRenameLog(oh, u, fmt.Sprintf("new name ‘%s’ invalid", nh.Name))
|
||||
return errors.New("Invalid new name. Names cannot contain characters <code>^?!:#@><*|\"\\'&%</code>"), "Invalid name"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// 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 (h *Hypha) RenameHypha(newHypha *Hypha, recursive bool, u *user.User) (hop *history.HistoryOp, errtitle string) {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
newHypha.Lock()
|
||||
defer newHypha.Unlock()
|
||||
hop = history.Operation(history.TypeRenameHypha)
|
||||
|
||||
if err, errtitle := h.CanRename(u); errtitle != "" {
|
||||
hop.WithError(err)
|
||||
return hop, errtitle
|
||||
}
|
||||
if err, errtitle := canRenameThisToThat(h, newHypha, u); errtitle != "" {
|
||||
hop.WithError(err)
|
||||
return hop, errtitle
|
||||
}
|
||||
|
||||
var (
|
||||
re = regexp.MustCompile(`(?i)` + h.Name)
|
||||
replaceName = func(str string) string {
|
||||
return re.ReplaceAllString(util.CanonicalName(str), newHypha.Name)
|
||||
}
|
||||
hyphaeToRename = findHyphaeToRename(h, recursive)
|
||||
renameMap, err = renamingPairs(hyphaeToRename, replaceName)
|
||||
renameMsg = "Rename ‘%s’ to ‘%s’"
|
||||
)
|
||||
if err != nil {
|
||||
hop.Errs = append(hop.Errs, err)
|
||||
return hop, hop.FirstErrorText()
|
||||
}
|
||||
if recursive && len(hyphaeToRename) > 0 {
|
||||
renameMsg += " recursively"
|
||||
}
|
||||
hop.WithFilesRenamed(renameMap).
|
||||
WithMsg(fmt.Sprintf(renameMsg, h.Name, newHypha.Name)).
|
||||
WithUser(u).
|
||||
Apply()
|
||||
if len(hop.Errs) == 0 {
|
||||
for _, h := range hyphaeToRename {
|
||||
h.renameTo(replaceName(h.Name))
|
||||
}
|
||||
}
|
||||
return hop, ""
|
||||
}
|
||||
|
||||
func findHyphaeToRename(superhypha *Hypha, recursive bool) []*Hypha {
|
||||
hyphae := []*Hypha{superhypha}
|
||||
if recursive {
|
||||
hyphae = append(hyphae, superhypha.Subhyphae()...)
|
||||
}
|
||||
return hyphae
|
||||
}
|
||||
|
||||
func renamingPairs(hyphaeToRename []*Hypha, replaceName func(string) string) (map[string]string, error) {
|
||||
renameMap := make(map[string]string)
|
||||
newNames := make([]string, len(hyphaeToRename))
|
||||
for _, h := range hyphaeToRename {
|
||||
h.RLock()
|
||||
newNames = append(newNames, replaceName(h.Name))
|
||||
if h.TextPath != "" {
|
||||
renameMap[h.TextPath] = replaceName(h.TextPath)
|
||||
}
|
||||
if h.BinaryPath != "" {
|
||||
renameMap[h.BinaryPath] = replaceName(h.BinaryPath)
|
||||
}
|
||||
h.RUnlock()
|
||||
}
|
||||
if firstFailure, ok := AreFreeNames(newNames...); !ok {
|
||||
return nil, errors.New("Hypha " + firstFailure + " already exists")
|
||||
}
|
||||
return renameMap, nil
|
||||
}
|
66
hyphae/unattach.go
Normal file
66
hyphae/unattach.go
Normal file
@ -0,0 +1,66 @@
|
||||
package hyphae
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
)
|
||||
|
||||
func rejectUnattachLog(h *Hypha, u *user.User, errmsg string) {
|
||||
log.Printf("Reject unattach ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg)
|
||||
}
|
||||
|
||||
// CanUnattach checks if given user can unattach given hypha. If they can, `errtitle` is an empty string and `err` is nil. If they cannot, `errtitle` is not an empty string, and `err` is an error.
|
||||
func (h *Hypha) CanUnattach(u *user.User) (err error, errtitle string) {
|
||||
if !u.CanProceed("unattach-confirm") {
|
||||
rejectUnattachLog(h, u, "no rights")
|
||||
return errors.New("Not enough rights to unattach, you must be a trusted editor"), "Not enough rights"
|
||||
}
|
||||
|
||||
if !h.Exists {
|
||||
rejectUnattachLog(h, u, "does not exist")
|
||||
return errors.New("Cannot unattach this hypha because it does not exist"), "Does not exist"
|
||||
}
|
||||
|
||||
if h.BinaryPath == "" {
|
||||
rejectUnattachLog(h, u, "no amnt")
|
||||
return errors.New("Cannot unattach this hypha because it has no attachment"), "No attachment"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// UnattachHypha unattaches hypha and makes a history record about that.
|
||||
func (h *Hypha) UnattachHypha(u *user.User) (hop *history.HistoryOp, errtitle string) {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
hop = history.Operation(history.TypeUnattachHypha)
|
||||
|
||||
if err, errtitle := h.CanUnattach(u); errtitle != "" {
|
||||
hop.WithError(err)
|
||||
return hop, errtitle
|
||||
}
|
||||
|
||||
hop.
|
||||
WithFilesRemoved(h.BinaryPath).
|
||||
WithMsg(fmt.Sprintf("Unattach ‘%s’", h.Name)).
|
||||
WithUser(u).
|
||||
Apply()
|
||||
|
||||
if len(hop.Errs) > 0 {
|
||||
rejectUnattachLog(h, u, "fail")
|
||||
return hop.WithError(errors.New(fmt.Sprintf("Could not unattach this hypha due to internal server errors: <code>%v</code>", hop.Errs))), "Error"
|
||||
}
|
||||
|
||||
if h.BinaryPath != "" {
|
||||
h.BinaryPath = ""
|
||||
}
|
||||
// If nothing is left of the hypha
|
||||
if h.TextPath == "" {
|
||||
h.delete()
|
||||
}
|
||||
return hop, ""
|
||||
}
|
122
hyphae/upload.go
Normal file
122
hyphae/upload.go
Normal file
@ -0,0 +1,122 @@
|
||||
package hyphae
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
"github.com/bouncepaw/mycorrhiza/mimetype"
|
||||
"github.com/bouncepaw/mycorrhiza/user"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
func rejectEditLog(h *Hypha, u *user.User, errmsg string) {
|
||||
log.Printf("Reject edit ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg)
|
||||
}
|
||||
|
||||
func rejectAttachLog(h *Hypha, u *user.User, errmsg string) {
|
||||
log.Printf("Reject attach ‘%s’ by @%s: %s\n", h.Name, u.Name, errmsg)
|
||||
}
|
||||
|
||||
func (h *Hypha) CanEdit(u *user.User) (err error, errtitle string) {
|
||||
if !u.CanProceed("edit") {
|
||||
rejectEditLog(h, u, "no rights")
|
||||
return errors.New("You must be an editor to edit pages."), "Not enough rights"
|
||||
}
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (h *Hypha) CanUploadThat(data []byte, u *user.User) (err error, errtitle string) {
|
||||
if len(data) == 0 {
|
||||
return errors.New("No text data passed"), "Empty"
|
||||
}
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (h *Hypha) UploadText(textData []byte, u *user.User) (hop *history.HistoryOp, errtitle string) {
|
||||
hop = history.Operation(history.TypeEditText)
|
||||
if h.Exists {
|
||||
hop.WithMsg(fmt.Sprintf("Edit ‘%s’", h.Name))
|
||||
} else {
|
||||
hop.WithMsg(fmt.Sprintf("Create ‘%s’", h.Name))
|
||||
}
|
||||
|
||||
if err, errtitle := h.CanEdit(u); err != nil {
|
||||
return hop.WithError(err), errtitle
|
||||
}
|
||||
if err, errtitle := h.CanUploadThat(textData, u); err != nil {
|
||||
return hop.WithError(err), errtitle
|
||||
}
|
||||
|
||||
return h.uploadHelp(hop, ".myco", textData, u)
|
||||
}
|
||||
|
||||
func (h *Hypha) CanAttach(err error, u *user.User) (error, string) {
|
||||
if !u.CanProceed("upload-binary") {
|
||||
rejectAttachLog(h, u, "no rights")
|
||||
return errors.New("You must be an editor to upload attachments."), "Not enough rights"
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
rejectAttachLog(h, u, err.Error())
|
||||
return errors.New("No binary data passed"), err.Error()
|
||||
}
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (h *Hypha) UploadBinary(mime string, file multipart.File, u *user.User) (*history.HistoryOp, string) {
|
||||
var (
|
||||
hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", h.Name, mime))
|
||||
data, err = ioutil.ReadAll(file)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return hop.WithError(err), err.Error()
|
||||
}
|
||||
if err, errtitle := h.CanEdit(u); err != nil {
|
||||
return hop.WithError(err), errtitle
|
||||
}
|
||||
if err, errtitle := h.CanUploadThat(data, u); err != nil {
|
||||
return hop.WithError(err), errtitle
|
||||
}
|
||||
|
||||
return h.uploadHelp(hop, mimetype.ToExtension(mime), data, u)
|
||||
}
|
||||
|
||||
// uploadHelp is a helper function for UploadText and UploadBinary
|
||||
func (h *Hypha) uploadHelp(hop *history.HistoryOp, ext string, data []byte, u *user.User) (*history.HistoryOp, string) {
|
||||
var (
|
||||
fullPath = filepath.Join(util.WikiDir, h.Name+ext)
|
||||
originalFullPath = &h.TextPath
|
||||
)
|
||||
if hop.Type == history.TypeEditBinary {
|
||||
originalFullPath = &h.BinaryPath
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil {
|
||||
return hop.WithError(err), err.Error()
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(fullPath, data, 0644); err != nil {
|
||||
return hop.WithError(err), err.Error()
|
||||
}
|
||||
|
||||
if h.Exists && *originalFullPath != fullPath && *originalFullPath != "" {
|
||||
if err := history.Rename(*originalFullPath, fullPath); err != nil {
|
||||
return hop.WithError(err), err.Error()
|
||||
}
|
||||
log.Println("Move", *originalFullPath, "to", fullPath)
|
||||
}
|
||||
|
||||
h.InsertIfNew()
|
||||
*originalFullPath = fullPath
|
||||
if h.Exists && hop.Type == history.TypeEditText && !history.FileChanged(fullPath) {
|
||||
return hop.Abort(), "No changes"
|
||||
}
|
||||
return hop.WithFiles(fullPath).WithUser(u).Apply(), ""
|
||||
}
|
72
hyphae/view.go
Normal file
72
hyphae/view.go
Normal file
@ -0,0 +1,72 @@
|
||||
package hyphae
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/markup"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
// FetchTextPart tries to read text file of the given hypha. If there is no file, empty string is returned.
|
||||
func (h *Hypha) FetchTextPart() (string, error) {
|
||||
if h.TextPath == "" {
|
||||
return "", nil
|
||||
}
|
||||
text, err := ioutil.ReadFile(h.TextPath)
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(text), nil
|
||||
}
|
||||
|
||||
// binaryHtmlBlock creates an html block for binary part of the hypha.
|
||||
func (h *Hypha) BinaryHtmlBlock() string {
|
||||
switch filepath.Ext(h.BinaryPath) {
|
||||
case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
|
||||
return fmt.Sprintf(`
|
||||
<div class="binary-container binary-container_with-img">
|
||||
<a href="/binary/%[1]s"><img src="/binary/%[1]s"/></a>
|
||||
</div>`, h.Name)
|
||||
case ".ogg", ".webm", ".mp4":
|
||||
return fmt.Sprintf(`
|
||||
<div class="binary-container binary-container_with-video">
|
||||
<video>
|
||||
<source src="/binary/%[1]s"/>
|
||||
<p>Your browser does not support video. <a href="/binary/%[1]s">Download video</a></p>
|
||||
</video>
|
||||
`, h.Name)
|
||||
case ".mp3":
|
||||
return fmt.Sprintf(`
|
||||
<div class="binary-container binary-container_with-audio">
|
||||
<audio>
|
||||
<source src="/binary/%[1]s"/>
|
||||
<p>Your browser does not support audio. <a href="/binary/%[1]s">Download audio</a></p>
|
||||
</audio>
|
||||
`, h.Name)
|
||||
default:
|
||||
return fmt.Sprintf(`
|
||||
<div class="binary-container binary-container_with-nothing">
|
||||
<p><a href="/binary/%s">Download media</a></p>
|
||||
</div>
|
||||
`, h.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func SetHeaderLinks() {
|
||||
if userLinksHypha := ByName(util.HeaderLinksHypha); !userLinksHypha.Exists {
|
||||
util.SetDefaultHeaderLinks()
|
||||
} else {
|
||||
contents, err := ioutil.ReadFile(userLinksHypha.TextPath)
|
||||
if err != nil || len(contents) == 0 {
|
||||
util.SetDefaultHeaderLinks()
|
||||
} else {
|
||||
text := string(contents)
|
||||
util.ParseHeaderLinks(text, markup.Rocketlink)
|
||||
}
|
||||
}
|
||||
}
|
36
main.go
36
main.go
@ -10,7 +10,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/history"
|
||||
@ -24,19 +23,6 @@ import (
|
||||
// WikiDir is a rooted path to the wiki storage directory.
|
||||
var WikiDir string
|
||||
|
||||
// HyphaPattern is a pattern which all hyphae must match.
|
||||
var HyphaPattern = regexp.MustCompile(`[^?!:#@><*|"\'&%{}]+`)
|
||||
|
||||
// HyphaStorage is a mapping between canonical hypha names and their meta information.
|
||||
var HyphaStorage = make(map[string]*HyphaData)
|
||||
|
||||
// IterateHyphaNamesWith is a closure to be passed to subpackages to let them iterate all hypha names read-only.
|
||||
func IterateHyphaNamesWith(f func(string)) {
|
||||
for hyphaName := range HyphaStorage {
|
||||
f(hyphaName)
|
||||
}
|
||||
}
|
||||
|
||||
// HttpErr is used by many handlers to signal errors in a compact way.
|
||||
func HttpErr(w http.ResponseWriter, status int, name, title, errMsg string) {
|
||||
log.Println(errMsg, "for", name)
|
||||
@ -64,8 +50,8 @@ func handlerList(w http.ResponseWriter, rq *http.Request) {
|
||||
pageCount = hyphae.Count()
|
||||
u = user.FromRequest(rq)
|
||||
)
|
||||
for hyphaName, data := range HyphaStorage {
|
||||
tbody += templates.HyphaListRowHTML(hyphaName, mimetype.FromExtension(filepath.Ext(data.BinaryPath)), data.BinaryPath != "")
|
||||
for h := range hyphae.YieldExistingHyphae() {
|
||||
tbody += templates.HyphaListRowHTML(h.Name, mimetype.FromExtension(filepath.Ext(h.BinaryPath)), h.BinaryPath != "")
|
||||
}
|
||||
util.HTTP200Page(w, base("List of pages", templates.HyphaListHTML(tbody, pageCount), u))
|
||||
}
|
||||
@ -82,10 +68,9 @@ func handlerReindex(w http.ResponseWriter, rq *http.Request) {
|
||||
return
|
||||
}
|
||||
hyphae.ResetCount()
|
||||
HyphaStorage = make(map[string]*HyphaData)
|
||||
log.Println("Wiki storage directory is", WikiDir)
|
||||
log.Println("Start indexing hyphae...")
|
||||
Index(WikiDir)
|
||||
hyphae.Index(WikiDir)
|
||||
log.Println("Indexed", hyphae.Count(), "hyphae")
|
||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||
}
|
||||
@ -98,7 +83,7 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println("Rejected", rq.URL)
|
||||
return
|
||||
}
|
||||
setHeaderLinks()
|
||||
hyphae.SetHeaderLinks()
|
||||
http.Redirect(w, rq, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
@ -107,14 +92,13 @@ func handlerRandom(w http.ResponseWriter, rq *http.Request) {
|
||||
log.Println(rq.URL)
|
||||
var randomHyphaName string
|
||||
i := rand.Intn(hyphae.Count())
|
||||
for hyphaName := range HyphaStorage {
|
||||
for h := range hyphae.YieldExistingHyphae() {
|
||||
if i == 0 {
|
||||
randomHyphaName = hyphaName
|
||||
break
|
||||
randomHyphaName = h.Name
|
||||
}
|
||||
i--
|
||||
}
|
||||
http.Redirect(w, rq, "/page/"+randomHyphaName, http.StatusSeeOther)
|
||||
http.Redirect(w, rq, "/hypha/"+randomHyphaName, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func handlerStyle(w http.ResponseWriter, rq *http.Request) {
|
||||
@ -187,11 +171,11 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Wiki storage directory is", WikiDir)
|
||||
Index(WikiDir)
|
||||
hyphae.Index(WikiDir)
|
||||
log.Println("Indexed", hyphae.Count(), "hyphae")
|
||||
|
||||
history.Start(WikiDir)
|
||||
setHeaderLinks()
|
||||
hyphae.SetHeaderLinks()
|
||||
|
||||
go handleGemini()
|
||||
|
||||
@ -212,7 +196,7 @@ func main() {
|
||||
http.HandleFunc("/static/common.css", handlerStyle)
|
||||
http.HandleFunc("/static/icon/", handlerIcon)
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) {
|
||||
http.Redirect(w, rq, "/page/"+util.HomePage, http.StatusSeeOther)
|
||||
http.Redirect(w, rq, "/hypha/"+util.HomePage, http.StatusSeeOther)
|
||||
})
|
||||
http.HandleFunc("/robots.txt", handlerRobotsTxt)
|
||||
log.Fatal(http.ListenAndServe("0.0.0.0:"+util.ServerPort, nil))
|
||||
|
14
name.go
14
name.go
@ -11,23 +11,13 @@ import (
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
// isCanonicalName checks if the `name` is canonical.
|
||||
func isCanonicalName(name string) bool {
|
||||
return HyphaPattern.MatchString(name)
|
||||
}
|
||||
|
||||
// CanonicalName makes sure the `name` is canonical. A name is canonical if it is lowercase and all spaces are replaced with underscores.
|
||||
func CanonicalName(name string) string {
|
||||
return strings.ToLower(strings.ReplaceAll(name, " ", "_"))
|
||||
}
|
||||
|
||||
// naviTitle turns `canonicalName` into html string with each hypha path parts higlighted as links.
|
||||
// TODO: rework as a template
|
||||
func naviTitle(canonicalName string) string {
|
||||
var (
|
||||
html = fmt.Sprintf(`<h1 class="navi-title" id="navi-title">
|
||||
<a href="/page/%s">%s</a><span aria-hidden="true" class="navi-title__colon">:</span>`, util.HomePage, util.SiteNavIcon)
|
||||
prevAcc = `/page/`
|
||||
<a href="/hypha/%s">%s</a><span aria-hidden="true" class="navi-title__colon">:</span>`, util.HomePage, util.SiteNavIcon)
|
||||
prevAcc = `/hypha/`
|
||||
parts = strings.Split(canonicalName, "/")
|
||||
rel = "up"
|
||||
)
|
||||
|
25
tree/tree.go
25
tree/tree.go
@ -6,6 +6,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/hyphae"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
@ -81,21 +82,21 @@ func mainFamilyFromPool(hyphaName string, subhyphaePool map[string]bool) *mainFa
|
||||
}
|
||||
|
||||
// Tree generates a tree for `hyphaName` as html and returns next and previous hyphae if any.
|
||||
func Tree(hyphaName string, hyphaIterator func(func(string))) (html, prev, next string) {
|
||||
func Tree(hyphaName string) (html, prev, next string) {
|
||||
var (
|
||||
// One of the siblings is the hypha with name `hyphaName`
|
||||
siblings = findSiblings(hyphaName, hyphaIterator)
|
||||
siblings = findSiblings(hyphaName)
|
||||
subhyphaePool = make(map[string]bool)
|
||||
I int
|
||||
)
|
||||
hyphaIterator(func(otherHyphaName string) {
|
||||
for h := range hyphae.YieldExistingHyphae() {
|
||||
for _, s := range siblings {
|
||||
s.checkThisChild(otherHyphaName)
|
||||
s.checkThisChild(h.Name)
|
||||
}
|
||||
if strings.HasPrefix(otherHyphaName, hyphaName+"/") {
|
||||
subhyphaePool[otherHyphaName] = true
|
||||
if strings.HasPrefix(h.Name, hyphaName+"/") {
|
||||
subhyphaePool[h.Name] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
for i, s := range siblings {
|
||||
if s.name == hyphaName {
|
||||
I = i
|
||||
@ -116,13 +117,13 @@ func Tree(hyphaName string, hyphaIterator func(func(string))) (html, prev, next
|
||||
return fmt.Sprintf(`<ul class="navitree">%s</ul>`, html), prev, next
|
||||
}
|
||||
|
||||
func findSiblings(hyphaName string, hyphaIterator func(func(string))) []*sibling {
|
||||
func findSiblings(hyphaName string) []*sibling {
|
||||
siblings := []*sibling{&sibling{name: hyphaName, hasChildren: true}}
|
||||
hyphaIterator(func(otherHyphaName string) {
|
||||
if path.Dir(hyphaName) == path.Dir(otherHyphaName) && hyphaName != otherHyphaName {
|
||||
siblings = append(siblings, &sibling{name: otherHyphaName, hasChildren: false})
|
||||
for h := range hyphae.YieldExistingHyphae() {
|
||||
if path.Dir(hyphaName) == path.Dir(h.Name) && hyphaName != h.Name {
|
||||
siblings = append(siblings, &sibling{name: h.Name, hasChildren: false})
|
||||
}
|
||||
})
|
||||
}
|
||||
sort.Slice(siblings, func(i, j int) bool {
|
||||
return siblings[i].name < siblings[j].name
|
||||
})
|
||||
|
11
util/util.go
11
util/util.go
@ -41,17 +41,6 @@ func HTTP200Page(w http.ResponseWriter, page string) {
|
||||
w.Write([]byte(page))
|
||||
}
|
||||
|
||||
// FindSubhyphae finds names of existing hyphae given the `hyphaIterator`.
|
||||
func FindSubhyphae(hyphaName string, hyphaIterator func(func(string))) []string {
|
||||
subhyphae := make([]string, 0)
|
||||
hyphaIterator(func(otherHyphaName string) {
|
||||
if strings.HasPrefix(otherHyphaName, hyphaName+"/") {
|
||||
subhyphae = append(subhyphae, otherHyphaName)
|
||||
}
|
||||
})
|
||||
return subhyphae
|
||||
}
|
||||
|
||||
func RandomString(n int) (string, error) {
|
||||
bytes := make([]byte, n)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user