diff --git a/config.go b/config.go index c648696..34fc6d6 100644 --- a/config.go +++ b/config.go @@ -3,9 +3,10 @@ package main const ( TitleTemplate = `%s at MycorrhizaWiki` DefaultTitle = "MycorrhizaWiki" - DefaultHeaderText = `MycorrhizaWiki` - DefaultFooterText = "MycorrhizaWiki" + DefaultHeaderText = `MycorrhizaWiki 🍄` + DefaultFooterText = `This website runs MycorrhizaWiki.` DefaultSidebar = "" + DefaultBodyBottom = "" DefaultContent = "It is empty here" DefaultStyles = ` diff --git a/handlers.go b/handlers.go index 4a60771..3951971 100644 --- a/handlers.go +++ b/handlers.go @@ -1,8 +1,13 @@ package main import ( + "io/ioutil" "log" "net/http" + "path/filepath" + "strconv" + "strings" + "time" "github.com/gorilla/mux" ) @@ -63,7 +68,118 @@ func HandlerRename(w http.ResponseWriter, r *http.Request) { log.Println("Attempt to access an unimplemented thing") } -func HandlerUpdate(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) - log.Println("Attempt to access an unimplemented thing") +func makeTagsSlice(responseTagsString string) (ret []string) { + // `responseTagsString` is string like "foo,, bar,kek". Whitespace around commas is insignificant. Expected output: []string{"foo", "bar", "kek"} + for _, tag := range strings.Split(responseTagsString, ",") { + if trimmed := strings.TrimSpace(tag); "" == trimmed { + ret = append(ret, trimmed) + } + } + return ret +} + +// Return an existing hypha it exists in `hyphae` or create a new one. If it `isNew`, you'll have to insert it to `hyphae` yourself. +func getHypha(name string) (*Hypha, bool) { + log.Println("Accessing hypha", name) + if h, ok := hyphae[name]; ok { + log.Println("Got hypha", name) + return h, false + } + log.Println("Create hypha", name) + h := &Hypha{ + FullName: name, + Path: filepath.Join(rootWikiDir, name), + Revisions: make(map[string]*Revision), + parentName: filepath.Dir(name), + } + return h, true +} + +// Create a new revison for hypha `h`. All data is fetched from `r`, except for BinaryMime and BinaryPath which require additional processing. You'll have te insert the revision to `h` yourself. +func revisionFromHttpData(h *Hypha, r *http.Request) *Revision { + idStr := strconv.Itoa(h.NewestRevisionInt() + 1) + log.Println(idStr) + rev := &Revision{ + Id: h.NewestRevisionInt() + 1, + FullName: h.FullName, + Tags: makeTagsSlice(r.PostFormValue("tags")), + Comment: r.PostFormValue("comment"), + Author: r.PostFormValue("author"), + Time: int(time.Now().Unix()), + TextMime: r.PostFormValue("text_mime"), + TextPath: filepath.Join(h.Path, idStr+".txt"), + // Left: BinaryMime, BinaryPath + } + return rev +} + +func writeTextFileFromHttpData(rev *Revision, r *http.Request) error { + data := []byte(r.PostFormValue("text")) + err := ioutil.WriteFile(rev.TextPath, data, 0644) + if err != nil { + log.Println("Failed to write", len(data), "bytes to", rev.TextPath) + } + return err +} + +func writeBinaryFileFromHttpData(h *Hypha, oldRev Revision, newRev *Revision, r *http.Request) error { + // 10 MB file size limit + r.ParseMultipartForm(10 << 20) + // Read file + file, handler, err := r.FormFile("binary") + if file != nil { + defer file.Close() + } + if err != nil { + log.Println("No binary data passed for", newRev.FullName) + newRev.BinaryMime = oldRev.BinaryMime + newRev.BinaryPath = oldRev.BinaryPath + log.Println("Set previous revision's binary data") + return nil + } + newRev.BinaryMime = handler.Header.Get("Content-Type") + newRev.BinaryPath = filepath.Join(h.Path, newRev.IdAsStr()+".bin") + data, err := ioutil.ReadAll(file) + if err != nil { + log.Println(err) + return err + } + log.Println("Got", len(data), "of binary data for", newRev.FullName) + err = ioutil.WriteFile(newRev.BinaryPath, data, 0644) + if err != nil { + log.Println("Failed to write", len(data), "bytes to", newRev.TextPath) + return err + } + log.Println("Written", len(data), "of binary data for", newRev.FullName) + return nil +} + +func HandlerUpdate(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + log.Println("Attempt to update hypha", mux.Vars(r)["hypha"]) + h, isNew := getHypha(vars["hypha"]) + oldRev := h.GetNewestRevision() + newRev := revisionFromHttpData(h, r) + + if isNew { + h.CreateDir() + } + err := writeTextFileFromHttpData(newRev, r) + if err != nil { + log.Println(err) + return + } + err = writeBinaryFileFromHttpData(h, oldRev, newRev, r) + if err != nil { + log.Println(err) + return + } + + h.Revisions[newRev.IdAsStr()] = newRev + h.SaveJson() + + log.Println("Current hyphae storage is", hyphae) + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Saved successfully")) } diff --git a/hypha.go b/hypha.go index b29b602..1189a57 100644 --- a/hypha.go +++ b/hypha.go @@ -1,20 +1,24 @@ package main import ( + "encoding/json" "fmt" "io/ioutil" + "log" "net/http" + "os" + "path/filepath" "strconv" "strings" ) type Hypha struct { - FullName string - Path string + FullName string `json:"-"` + Path string `json:"-"` ViewCount int `json:"views"` Deleted bool `json:"deleted"` Revisions map[string]*Revision `json:"revisions"` - ChildrenNames []string + ChildrenNames []string `json:"-"` parentName string } @@ -39,7 +43,15 @@ func (h *Hypha) Name() string { return h.FullName } +func (h *Hypha) GetNewestRevision() Revision { + return *h.Revisions[h.NewestRevision()] +} + func (h *Hypha) NewestRevision() string { + return strconv.Itoa(h.NewestRevisionInt()) +} + +func (h *Hypha) NewestRevisionInt() int { var largest int for k, _ := range h.Revisions { rev, _ := strconv.Atoi(k) @@ -47,16 +59,38 @@ func (h *Hypha) NewestRevision() string { largest = rev } } - return strconv.Itoa(largest) + return largest +} + +func (h *Hypha) MetaJsonPath() string { + return filepath.Join(h.Path, "meta.json") +} + +func (h *Hypha) CreateDir() error { + return os.MkdirAll(h.Path, 0644) } func (h *Hypha) ParentName() string { return h.parentName } +func (h *Hypha) SaveJson() { + data, err := json.Marshal(h) + if err != nil { + log.Println("Failed to create JSON of hypha.", err) + return + } + err = ioutil.WriteFile(h.MetaJsonPath(), data, 0644) + if err != nil { + log.Println("Failed to save JSON of hypha.", err) + return + } + log.Println("Saved JSON data of", h.FullName) +} + func ActionEdit(hyphaName string, w http.ResponseWriter) { w.Header().Set("Content-Type", "text/html; charset=utf-8") - var initContents, initTextMime, initBinaryMime, initTags string + var initContents, initTextMime, initTags string hypha, ok := hyphae[hyphaName] if !ok { initContents = "Describe " + hyphaName + "here." @@ -71,10 +105,9 @@ func ActionEdit(hyphaName string, w http.ResponseWriter) { } initContents = string(contents) initTextMime = newestRev.TextMime - initBinaryMime = newestRev.BinaryMime initTags = strings.Join(newestRev.Tags, ",") } w.WriteHeader(http.StatusOK) - w.Write([]byte(EditHyphaPage(hyphaName, initTextMime, initBinaryMime, initContents, initTags))) + w.Write([]byte(EditHyphaPage(hyphaName, initTextMime, initContents, initTags))) } diff --git a/main.go b/main.go index be1daa0..d957eeb 100644 --- a/main.go +++ b/main.go @@ -101,7 +101,9 @@ func main() { r.Queries("action", "rename", "to", hyphaPattern).Path(hyphaUrl). HandlerFunc(HandlerRename) - r.Queries("action", "update").Path(hyphaUrl). + r.Queries( + "action", "update", + ).Path(hyphaUrl).Methods("POST"). HandlerFunc(HandlerUpdate) r.HandleFunc(hyphaUrl, HandlerView) diff --git a/render.go b/render.go index ff74fdf..21a8f0b 100644 --- a/render.go +++ b/render.go @@ -3,29 +3,36 @@ package main import ( "bytes" "fmt" + "io/ioutil" "path" "text/template" ) -func EditHyphaPage(name, text_mime, binary_mime, content, tags string) string { +func EditHyphaPage(name, textMime, content, tags string) string { keys := map[string]string{ - "Title": fmt.Sprintf(TitleTemplate, name), + "Title": fmt.Sprintf(TitleTemplate, "Edit "+name), + "Header": renderFromString(name, "Hypha/edit/header.html"), } page := map[string]string{ "Text": content, - "TextMime": text_mime, - "BinMime": binary_mime, + "TextMime": textMime, "Name": name, "Tags": tags, } - return renderBase(renderFromMap(page, "Hypha/edit.html"), keys) + return renderBase(renderFromMap(page, "Hypha/edit/index.html"), keys) } func HyphaPage(hyphae map[string]*Hypha, rev Revision, content string) string { - keys := map[string]string{ - "Title": fmt.Sprintf(TitleTemplate, rev.FullName), + sidebar := DefaultSidebar + bside, err := ioutil.ReadFile("Hypha/view/sidebar.html") + if err == nil { + sidebar = string(bside) } - return renderBase(renderFromString(content, "Hypha/index.html"), keys) + keys := map[string]string{ + "Title": fmt.Sprintf(TitleTemplate, rev.FullName), + "Sidebar": sidebar, + } + return renderBase(renderFromString(content, "Hypha/view/index.html"), keys) } /* @@ -36,11 +43,13 @@ Args: */ func renderBase(content string, keys map[string]string) string { page := map[string]string{ - "Title": DefaultTitle, - "Header": renderFromString(DefaultHeaderText, "header.html"), - "Footer": renderFromString(DefaultFooterText, "footer.html"), - "Sidebar": DefaultSidebar, - "Main": DefaultContent, + "Title": DefaultTitle, + "Head": DefaultStyles, + "Sidebar": DefaultSidebar, + "Main": DefaultContent, + "BodyBottom": DefaultBodyBottom, + "Header": renderFromString(DefaultHeaderText, "header.html"), + "Footer": renderFromString(DefaultFooterText, "footer.html"), } for key, val := range keys { page[key] = val diff --git a/revision.go b/revision.go index 9e7cf51..3e0c519 100644 --- a/revision.go +++ b/revision.go @@ -6,12 +6,15 @@ import ( "log" "net/http" + "strconv" + "github.com/gomarkdown/markdown" ) +// In different places, revision variable is called `r`. But when there is an http.Request as well, the revision becomes `rev`. TODO: name them consistently. type Revision struct { - Id int - FullName string + Id int `json:"-"` + FullName string `json:"-"` Tags []string `json:"tags"` ShortName string `json:"name"` Comment string `json:"comment"` @@ -19,8 +22,12 @@ type Revision struct { Time int `json:"time"` TextMime string `json:"text_mime"` BinaryMime string `json:"binary_mime"` - TextPath string - BinaryPath string + TextPath string `json:"-"` + BinaryPath string `json:"-"` +} + +func (r *Revision) IdAsStr() string { + return strconv.Itoa(r.Id) } // During initialisation, it is guaranteed that r.BinaryMime is set to "" if the revision has no binary data. diff --git a/templates/Hypha/edit/header.html b/templates/Hypha/edit/header.html new file mode 100644 index 0000000..31dfc0f --- /dev/null +++ b/templates/Hypha/edit/header.html @@ -0,0 +1 @@ +