diff --git a/fs/hypha.go b/fs/hypha.go index 3f198f2..7b0abb2 100644 --- a/fs/hypha.go +++ b/fs/hypha.go @@ -7,9 +7,11 @@ import ( "io/ioutil" "log" "net/http" + "os" "path/filepath" "strconv" "strings" + "time" "github.com/bouncepaw/mycorrhiza/cfg" ) @@ -21,6 +23,22 @@ type Hypha struct { Deleted bool `json:"deleted"` Revisions map[string]*Revision `json:"revisions"` actual *Revision `json:"-"` + Invalid bool + Err error +} + +func (h *Hypha) Invalidate(err error) *Hypha { + h.Invalid = true + h.Err = err + return h +} + +func (h *Hypha) MetaJsonPath() string { + return filepath.Join(h.Path(), "meta.json") +} + +func (h *Hypha) Path() string { + return filepath.Join(cfg.WikiDir, h.FullName) } func (h *Hypha) TextPath() string { @@ -53,7 +71,7 @@ func (h *Hypha) TextContent() string { return fmt.Sprintf(cfg.DescribeHyphaHerePattern, h.FullName) } -func (s *Storage) Open(name string) (*Hypha, error) { +func (s *Storage) Open(name string) *Hypha { h := &Hypha{ Exists: true, FullName: name, @@ -67,14 +85,12 @@ func (s *Storage) Open(name string) (*Hypha, error) { } else { metaJsonText, err := ioutil.ReadFile(filepath.Join(path, "meta.json")) if err != nil { - log.Fatal(err) - return nil, err + return h.Invalidate(err) } err = json.Unmarshal(metaJsonText, &h) if err != nil { - log.Fatal(err) - return nil, err + return h.Invalidate(err) } // fill in rooted paths to content files and full names for idStr, rev := range h.Revisions { @@ -86,10 +102,9 @@ func (s *Storage) Open(name string) (*Hypha, error) { rev.TextPath = filepath.Join(path, rev.TextName) } - err = h.OnRevision("0") - return h, err + return h.OnRevision("0") } - return h, nil + return h } func (h *Hypha) parentName() string { @@ -101,9 +116,12 @@ func (h *Hypha) metaJsonPath() string { } // OnRevision tries to change to a revision specified by `id`. -func (h *Hypha) OnRevision(id string) error { +func (h *Hypha) OnRevision(id string) *Hypha { + if h.Invalid || !h.Exists { + return h + } if len(h.Revisions) == 0 { - return errors.New("This hypha has no revisions") + return h.Invalidate(errors.New("This hypha has no revisions")) } if id == "0" { id = h.NewestId() @@ -112,7 +130,7 @@ func (h *Hypha) OnRevision(id string) error { if rev, _ := h.Revisions[id]; true { h.actual = rev } - return nil + return h } // NewestId finds the largest id among all revisions. @@ -214,3 +232,147 @@ func (h *Hypha) ActionView(w http.ResponseWriter, renderExists, renderNotExists } h.PlainLog("Rendering hypha view") } + +// CreateDirIfNeeded creates directory where the hypha must reside if needed. +// It is not needed if the dir already exists. +func (h *Hypha) CreateDirIfNeeded() *Hypha { + if h.Invalid { + return h + } + // os.MkdirAll created dir if it is not there. Basically, checks it for us. + err := os.MkdirAll(filepath.Join(cfg.WikiDir, h.FullName), os.ModePerm) + if err != nil { + h.Invalidate(err) + } + return h +} + +// makeTagsSlice turns strings like `"foo,, bar,kek"` to slice of strings that represent tag names. Whitespace around commas is insignificant. +// Expected output for string above: []string{"foo", "bar", "kek"} +func makeTagsSlice(responseTagsString string) (ret []string) { + for _, tag := range strings.Split(responseTagsString, ",") { + if trimmed := strings.TrimSpace(tag); "" == trimmed { + ret = append(ret, trimmed) + } + } + return ret +} + +// revisionFromHttpData creates a new revison for hypha `h`. All data is fetched from `rq`, except for BinaryMime and BinaryPath which require additional processing. The revision is inserted for you. You'll have to pop it out if there is an error. +func (h *Hypha) AddRevisionFromHttpData(rq *http.Request) *Hypha { + if h.Invalid { + return h + } + id := 1 + if h.Exists { + id = h.actual.Id + 1 + } + log.Printf("Creating revision %d from http data", id) + rev := &Revision{ + Id: id, + FullName: h.FullName, + ShortName: filepath.Base(h.FullName), + Tags: makeTagsSlice(rq.PostFormValue("tags")), + Comment: rq.PostFormValue("comment"), + Author: rq.PostFormValue("author"), + Time: int(time.Now().Unix()), + TextMime: rq.PostFormValue("text_mime"), + // Fields left: BinaryMime, BinaryPath, BinaryName, TextName, TextPath + } + rev.generateTextFilename() // TextName is set now + rev.TextPath = filepath.Join(h.Path(), rev.TextName) + return h.AddRevision(rev) +} + +func (h *Hypha) AddRevision(rev *Revision) *Hypha { + if h.Invalid { + return h + } + h.Revisions[strconv.Itoa(rev.Id)] = rev + h.actual = rev + return h +} + +// WriteTextFileFromHttpData tries to fetch text content from `rq` for revision `rev` and write it to a corresponding text file. It used in `HandlerUpdate`. +func (h *Hypha) WriteTextFileFromHttpData(rq *http.Request) *Hypha { + if h.Invalid { + return h + } + data := []byte(rq.PostFormValue("text")) + err := ioutil.WriteFile(h.TextPath(), data, 0644) + if err != nil { + log.Println("Failed to write", len(data), "bytes to", h.TextPath()) + h.Invalidate(err) + } + return h +} + +// WriteBinaryFileFromHttpData tries to fetch binary content from `rq` for revision `newRev` and write it to a corresponding binary file. If there is no content, it is taken from a previous revision, if there is any. +func (h *Hypha) WriteBinaryFileFromHttpData(rq *http.Request) *Hypha { + if h.Invalid { + return h + } + // 10 MB file size limit + rq.ParseMultipartForm(10 << 20) + // Read file + file, handler, err := rq.FormFile("binary") + if file != nil { + defer file.Close() + } + // If file is not passed: + if err != nil { + // Let's hope there are no other errors 🙏 + // TODO: actually check if there any other errors + log.Println("No binary data passed for", h.FullName) + // It is expected there is at least one revision + if len(h.Revisions) > 1 { + prevRev := h.Revisions[strconv.Itoa(h.actual.Id-1)] + h.actual.BinaryMime = prevRev.BinaryMime + h.actual.BinaryPath = prevRev.BinaryPath + h.actual.BinaryName = prevRev.BinaryName + log.Println("Set previous revision's binary data") + } + return h + } + // If file is passed: + h.actual.BinaryMime = handler.Header.Get("Content-Type") + h.actual.generateBinaryFilename() + h.actual.BinaryPath = filepath.Join(h.Path(), h.actual.BinaryName) + + data, err := ioutil.ReadAll(file) + if err != nil { + return h.Invalidate(err) + } + log.Println("Got", len(data), "of binary data for", h.FullName) + err = ioutil.WriteFile(h.actual.BinaryPath, data, 0644) + if err != nil { + return h.Invalidate(err) + } + log.Println("Written", len(data), "of binary data for", h.FullName) + return h +} + +// SaveJson dumps the hypha's metadata to `meta.json` file. +func (h *Hypha) SaveJson() *Hypha { + if h.Invalid { + return h + } + data, err := json.MarshalIndent(h, "", "\t") + if err != nil { + return h.Invalidate(err) + } + err = ioutil.WriteFile(h.MetaJsonPath(), data, 0644) + if err != nil { + return h.Invalidate(err) + } + log.Println("Saved JSON data of", h.FullName) + return h +} + +// Store adds `h` to the `Hs` if it is not already there +func (h *Hypha) Store() *Hypha { + if !h.Invalid { + Hs.paths[h.FullName] = h.Path() + } + return h +} diff --git a/fs/revision.go b/fs/revision.go index 67f0346..69ae0c6 100644 --- a/fs/revision.go +++ b/fs/revision.go @@ -1,5 +1,10 @@ package fs +import ( + "mime" + "strconv" +) + type Revision struct { Id int `json:"-"` FullName string `json:"-"` @@ -15,3 +20,23 @@ type Revision struct { TextName string `json:"text_name"` BinaryName string `json:"binary_name"` } + +// TODO: https://github.com/bouncepaw/mycorrhiza/issues/4 +// Some filenames are wrong? +func (rev *Revision) generateTextFilename() { + ts, err := mime.ExtensionsByType(rev.TextMime) + if err != nil || ts == nil { + rev.TextName = strconv.Itoa(rev.Id) + ".txt" + } else { + rev.TextName = strconv.Itoa(rev.Id) + ts[0] + } +} + +func (rev *Revision) generateBinaryFilename() { + ts, err := mime.ExtensionsByType(rev.BinaryMime) + if err != nil || ts == nil { + rev.BinaryName = strconv.Itoa(rev.Id) + ".bin" + } else { + rev.BinaryName = strconv.Itoa(rev.Id) + ts[0] + } +} diff --git a/genealogy.gog b/genealogy.gog deleted file mode 100644 index 8b2a0af..0000000 --- a/genealogy.gog +++ /dev/null @@ -1,107 +0,0 @@ -/* Genealogy is all about relationships between hyphae.*/ -package main - -import ( - "fmt" - "log" - "path/filepath" - "sort" - "strings" -) - -// setRelations fills in all children names based on what hyphae call their parents. -func setRelations(hyphae map[string]*Hypha) { - for name, h := range hyphae { - if _, ok := hyphae[h.parentName]; ok && h.parentName != "." { - hyphae[h.parentName].ChildrenNames = append(hyphae[h.parentName].ChildrenNames, name) - } - } -} - -// AddChild adds a name to the list of children names of the hypha. -func (h *Hypha) AddChild(childName string) { - h.ChildrenNames = append(h.ChildrenNames, childName) -} - -// If Name == "", the tree is empty. -type Tree struct { - Name string - Ancestors []string - Siblings []string - Descendants []*Tree - Root bool -} - -// GetTree generates a Tree for the given hypha name. -// It can also generate trees for non-existent hyphae, that's why we use `name string` instead of making it a method on `Hypha`. -// In `root` is `false`, siblings will not be fetched. -// Parameter `limit` is unused now but it is meant to limit how many subhyphae can be shown. -func GetTree(name string, root bool, limit ...int) *Tree { - t := &Tree{Name: name, Root: root} - for hyphaName, _ := range hyphae { - t.compareNamesAndAppend(hyphaName) - } - sort.Slice(t.Ancestors, func(i, j int) bool { - return strings.Count(t.Ancestors[i], "/") < strings.Count(t.Ancestors[j], "/") - }) - sort.Strings(t.Siblings) - sort.Slice(t.Descendants, func(i, j int) bool { - a := t.Descendants[i].Name - b := t.Descendants[j].Name - return len(a) < len(b) - }) - log.Printf("Generate tree for %v: %v %v\n", t.Name, t.Ancestors, t.Siblings) - return t -} - -// Compares names appends name2 to an array of `t`: -func (t *Tree) compareNamesAndAppend(name2 string) { - switch { - case t.Name == name2: - case strings.HasPrefix(t.Name, name2): - t.Ancestors = append(t.Ancestors, name2) - case t.Root && (strings.Count(t.Name, "/") == strings.Count(name2, "/") && - (filepath.Dir(t.Name) == filepath.Dir(name2))): - t.Siblings = append(t.Siblings, name2) - case strings.HasPrefix(name2, t.Name): - t.Descendants = append(t.Descendants, GetTree(name2, false)) - } -} - -// AsHtml returns HTML representation of a tree. -// It recursively itself on the tree's children. -// TODO: redo with templates. I'm not in mood for it now. -func (t *Tree) AsHtml() (html string) { - if t.Name == "" { - return "" - } - html += `` - return html -} - -// navitreeEntry is a small utility function that makes generating html easier. -// Someone please redo it in templates. -func navitreeEntry(name string) string { - return fmt.Sprintf(`
  • - %s -
  • -`, name, filepath.Base(name)) -} diff --git a/handlers.go b/handlers.go index 6f1fe71..1dbe3f0 100644 --- a/handlers.go +++ b/handlers.go @@ -20,14 +20,10 @@ import ( // Boilerplate code present in many handlers. Good to have it. func HandlerBase(w http.ResponseWriter, rq *http.Request) (*fs.Hypha, bool) { vars := mux.Vars(rq) - h, err := fs.Hs.Open(vars["hypha"]) - if err != nil { - log.Println(err) - return nil, false - } - err = h.OnRevision(RevInMap(vars)) - if err != nil { - log.Println(err) + h := fs.Hs.Open(vars["hypha"]).OnRevision(RevInMap(vars)) + if h.Invalid { + log.Println(h.Err) + return h, false } return h, true } @@ -60,153 +56,30 @@ func HandlerView(w http.ResponseWriter, rq *http.Request) { func HandlerEdit(w http.ResponseWriter, rq *http.Request) { vars := mux.Vars(rq) - h, err := fs.Hs.Open(vars["hypha"]) - // How could this happen? - if err != nil { - log.Println(err) - return - } - h.OnRevision("0") + h := fs.Hs.Open(vars["hypha"]).OnRevision("0") w.Header().Set("Content-Type", "text/html;charset=utf-8") w.WriteHeader(http.StatusOK) w.Write([]byte(render.HyphaEdit(h))) } -/* -// makeTagsSlice turns strings like `"foo,, bar,kek"` to slice of strings that represent tag names. Whitespace around commas is insignificant. -// Expected output for string above: []string{"foo", "bar", "kek"} -func makeTagsSlice(responseTagsString string) (ret []string) { - for _, tag := range strings.Split(responseTagsString, ",") { - if trimmed := strings.TrimSpace(tag); "" == trimmed { - ret = append(ret, trimmed) - } - } - return ret -} - -// getHypha returns an existing hypha if it exists in `hyphae` or creates 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(cfg.WikiDir, name), - Revisions: make(map[string]*Revision), - parentName: filepath.Dir(name), - } - return h, true -} - -// revisionFromHttpData creates a new revison for hypha `h`. All data is fetched from `rq`, except for BinaryMime and BinaryPath which require additional processing. You'll have te insert the revision to `h` yourself. -func revisionFromHttpData(h *Hypha, rq *http.Request) *Revision { - idStr := strconv.Itoa(h.NewestRevisionInt() + 1) - log.Printf("Creating revision %s from http data", idStr) - rev := &Revision{ - Id: h.NewestRevisionInt() + 1, - FullName: h.FullName, - ShortName: filepath.Base(h.FullName), - Tags: makeTagsSlice(rq.PostFormValue("tags")), - Comment: rq.PostFormValue("comment"), - Author: rq.PostFormValue("author"), - Time: int(time.Now().Unix()), - TextMime: rq.PostFormValue("text_mime"), - // Fields left: BinaryMime, BinaryPath, BinaryName, TextName, TextPath - } - rev.desiredTextFilename() // TextName is set now - rev.TextPath = filepath.Join(h.Path, rev.TextName) - return rev -} - -// writeTextFileFromHttpData tries to fetch text content from `rq` for revision `rev` and write it to a corresponding text file. It used in `HandlerUpdate`. -func writeTextFileFromHttpData(rev *Revision, rq *http.Request) error { - data := []byte(rq.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 -} - -// writeBinaryFileFromHttpData tries to fetch binary content from `rq` for revision `newRev` and write it to a corresponding binary file. If there is no content, it is taken from `oldRev`. -func writeBinaryFileFromHttpData(h *Hypha, oldRev Revision, newRev *Revision, rq *http.Request) error { - // 10 MB file size limit - rq.ParseMultipartForm(10 << 20) - // Read file - file, handler, err := rq.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 - newRev.BinaryName = oldRev.BinaryName - 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") - newRev.BinaryName = newRev.desiredBinaryFilename() - 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, rq *http.Request) { vars := mux.Vars(rq) - log.Println("Attempt to update hypha", mux.Vars(rq)["hypha"]) - h, isNew := getHypha(vars["hypha"]) - var oldRev Revision - if !isNew { - oldRev = h.GetNewestRevision() - } else { - h = &Hypha{ - FullName: vars["hypha"], - Path: filepath.Join(cfg.WikiDir, vars["hypha"]), - Revisions: make(map[string]*Revision), - parentName: filepath.Dir(vars["hypha"]), - } - h.CreateDir() - oldRev = Revision{} - } - newRev := revisionFromHttpData(h, rq) + log.Println("Attempt to update hypha", vars["hypha"]) + h := fs.Hs. + Open(vars["hypha"]). + CreateDirIfNeeded(). + AddRevisionFromHttpData(rq). + WriteTextFileFromHttpData(rq). + WriteBinaryFileFromHttpData(rq). + SaveJson(). + Store() - err := writeTextFileFromHttpData(newRev, rq) - if err != nil { - log.Println(err) + if h.Invalid { + log.Println(h.Err) return } - err = writeBinaryFileFromHttpData(h, oldRev, newRev, rq) - if err != nil { - log.Println(err) - return - } - - h.Revisions[newRev.IdAsStr()] = newRev - h.SaveJson() - if isNew { - hyphae[h.FullName] = h - } - - log.Println("Current hyphae storage is", hyphae) w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "text/html; charset=utf-8") - d := map[string]string{"Name": h.FullName} - w.Write([]byte(renderFromMap(d, "updateOk.html"))) + w.Write([]byte(render.HyphaUpdateOk(h))) } -*/ diff --git a/hypha.gog b/hypha.gog deleted file mode 100644 index bbdc6ee..0000000 --- a/hypha.gog +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/http" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/bouncepaw/mycorrhiza/cfg" -) - -// AsHtml returns HTML representation of the hypha. -// No layout or navigation are present here. Just the hypha. -func (h *Hypha) AsHtml(id string, w http.ResponseWriter) (string, error) { - if "0" == id { - id = h.NewestRevision() - } - if rev, ok := h.Revisions[id]; ok { - return rev.AsHtml(w) - } - return "", fmt.Errorf("Hypha %v has no such revision: %v", h.FullName, id) -} - -// CreateDir creates directory where the hypha must reside. -// It is meant to be used with new hyphae. -func (h *Hypha) CreateDir() error { - return os.MkdirAll(h.Path, os.ModePerm) -} - -// SaveJson dumps the hypha's metadata to `meta.json` file. -func (h *Hypha) SaveJson() { - data, err := json.MarshalIndent(h, "", "\t") - 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) -} - -// ActionEdit is called with `?acton=edit`. -// It represents the hypha editor. -func ActionEdit(hyphaName string, w http.ResponseWriter) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - var initContents, initTextMime, initTags string - if h, ok := hyphae[hyphaName]; ok { - newestRev := h.GetNewestRevision() - contents, err := ioutil.ReadFile(newestRev.TextPath) - if err != nil { - log.Println("Could not read", newestRev.TextPath) - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(cfg.GenericErrorMsg)) - return - } - initContents = string(contents) - initTextMime = newestRev.TextMime - initTags = strings.Join(newestRev.Tags, ",") - } else { - initContents = "Describe " + hyphaName + "here." - initTextMime = "text/markdown" - } - - w.WriteHeader(http.StatusOK) - w.Write([]byte(EditHyphaPage(hyphaName, initTextMime, initContents, initTags))) -} diff --git a/main.go b/main.go index 09a3682..215646c 100644 --- a/main.go +++ b/main.go @@ -66,10 +66,8 @@ func main() { r.Queries("action", "edit").Path(cfg.HyphaUrl). HandlerFunc(HandlerEdit) - /* - r.Queries("action", "update").Path(hyphaUrl).Methods("POST"). - HandlerFunc(HandlerUpdate) - */ + r.Queries("action", "update").Path(cfg.HyphaUrl).Methods("POST"). + HandlerFunc(HandlerUpdate) r.HandleFunc(cfg.HyphaUrl, HandlerView) diff --git a/render.gog b/render.gog deleted file mode 100644 index dc9d13a..0000000 --- a/render.gog +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "path" - "text/template" - - "github.com/bouncepaw/mycorrhiza/cfg" -) - -// EditHyphaPage returns HTML page of hypha editor. -func EditHyphaPage(name, textMime, content, tags string) string { - keys := map[string]string{ - "Title": fmt.Sprintf(cfg.TitleEditTemplate, name), - "Header": renderFromString(name, "Hypha/edit/header.html"), - "Sidebar": renderFromString("", "Hypha/edit/sidebar.html"), - } - page := map[string]string{ - "Text": content, - "TextMime": textMime, - "Name": name, - "Tags": tags, - } - return renderBase(renderFromMap(page, "Hypha/edit/index.html"), keys) -} - -// Hypha404 returns 404 page for nonexistent page. -func Hypha404(name string) string { - return HyphaGeneric(name, name, "Hypha/view/404.html") -} - -// HyphaPage returns HTML page of hypha viewer. -func HyphaPage(rev Revision, content string) string { - return HyphaGeneric(rev.FullName, content, "Hypha/view/index.html") -} - -// HyphaGeneric is used when building renderers for all types of hypha pages -func HyphaGeneric(name string, content, templatePath string) string { - var sidebar string - if aside := renderFromMap(map[string]string{ - "Tree": GetTree(name, true).AsHtml(), - }, "Hypha/view/sidebar.html"); aside != "" { - sidebar = aside - } - keys := map[string]string{ - "Title": fmt.Sprintf(cfg.TitleTemplate, name), - "Sidebar": sidebar, - } - return renderBase(renderFromString(content, templatePath), keys) -} - -// renderBase collects and renders page from base template -// Args: -// content: string or pre-rendered template -// keys: map with replaced standart fields -func renderBase(content string, keys map[string]string) string { - page := map[string]string{ - "Title": cfg.SiteTitle, - "Main": "", - "SiteTitle": cfg.SiteTitle, - } - for key, val := range keys { - page[key] = val - } - page["Main"] = content - return renderFromMap(page, "base.html") -} - -// renderFromMap applies `data` map to template in `templatePath` and returns the result. -func renderFromMap(data map[string]string, templatePath string) string { - filePath := path.Join(cfg.TemplatesDir, templatePath) - tmpl, err := template.ParseFiles(filePath) - if err != nil { - return err.Error() - } - buf := new(bytes.Buffer) - if err := tmpl.Execute(buf, data); err != nil { - return err.Error() - } - return buf.String() -} - -// renderFromMap applies `data` string to template in `templatePath` and returns the result. -func renderFromString(data string, templatePath string) string { - filePath := path.Join(cfg.TemplatesDir, templatePath) - tmpl, err := template.ParseFiles(filePath) - if err != nil { - return err.Error() - } - buf := new(bytes.Buffer) - if err := tmpl.Execute(buf, data); err != nil { - return err.Error() - } - return buf.String() -} diff --git a/render/render.go b/render/render.go index 9af9434..7ff6a05 100644 --- a/render/render.go +++ b/render/render.go @@ -66,6 +66,13 @@ func HyphaGeneric(name string, content, templatePath string) string { return renderBase(renderFromString(content, templatePath), keys) } +func HyphaUpdateOk(h *fs.Hypha) string { + data := map[string]string{ + "Name": h.FullName, + } + return renderFromMap(data, "updateOk.html") +} + // renderBase collects and renders page from base template // Args: // content: string or pre-rendered template @@ -86,8 +93,7 @@ func renderBase(content string, keys map[string]string) string { // renderFromMap applies `data` map to template in `templatePath` and returns the result. func renderFromMap(data map[string]string, templatePath string) string { hyphPath := path.Join(cfg.TemplatesDir, cfg.Theme, templatePath) - h, _ := fs.Hs.Open(hyphPath) - h.OnRevision("0") + h := fs.Hs.Open(hyphPath).OnRevision("0") tmpl, err := template.ParseFiles(h.TextPath()) if err != nil { return err.Error() @@ -102,8 +108,7 @@ func renderFromMap(data map[string]string, templatePath string) string { // renderFromMap applies `data` string to template in `templatePath` and returns the result. func renderFromString(data string, templatePath string) string { hyphPath := path.Join(cfg.TemplatesDir, cfg.Theme, templatePath) - h, _ := fs.Hs.Open(hyphPath) - h.OnRevision("0") + h := fs.Hs.Open(hyphPath).OnRevision("0") tmpl, err := template.ParseFiles(h.TextPath()) if err != nil { return err.Error() diff --git a/revision.gog b/revision.gog deleted file mode 100644 index 3117b2a..0000000 --- a/revision.gog +++ /dev/null @@ -1,135 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "log" - "mime" - "net/http" - "strconv" - - "github.com/gomarkdown/markdown" -) - -// Revision represents a revision, duh. -// A revision is a version of a hypha at a point in time. -type Revision struct { - Id int `json:"-"` - FullName string `json:"-"` - Tags []string `json:"tags"` - ShortName string `json:"name"` - Comment string `json:"comment"` - Author string `json:"author"` - Time int `json:"time"` - TextMime string `json:"text_mime"` - BinaryMime string `json:"binary_mime"` - TextPath string `json:"-"` - BinaryPath string `json:"-"` - TextName string `json:"text_name"` - BinaryName string `json:"binary_name"` -} - -// IdAsStr returns revision's id as a string. -func (rev *Revision) IdAsStr() string { - return strconv.Itoa(rev.Id) -} - -// hasBinaryData returns true if the revision has any binary data associated. -// During initialisation, it is guaranteed that r.BinaryMime is set to "" if the revision has no binary data. -func (rev *Revision) hasBinaryData() bool { - return rev.BinaryMime != "" -} - -// desiredBinaryFilename returns string that represents filename to use when saving a binary content file of a new revison. -// It also sets the corresponding field in `rev`. -func (rev *Revision) desiredBinaryFilename() string { - ts, err := mime.ExtensionsByType(rev.BinaryMime) - if err != nil || ts == nil { - rev.BinaryName = rev.IdAsStr() + ".bin" - } else { - rev.BinaryName = rev.IdAsStr() + ts[0] - } - return rev.BinaryName -} - -// desiredTextFilename returns string that represents filename to use when saving a text content file of a new revison. -// It also sets the corresponding field in `rev`. -func (rev *Revision) desiredTextFilename() string { - ts, err := mime.ExtensionsByType(rev.TextMime) - if err != nil || ts == nil { - log.Println("No idea how I should name this one:", rev.TextMime) - rev.TextName = rev.IdAsStr() + ".txt" - } else { - log.Println("A good extension would be one of these:", ts) - rev.TextName = rev.IdAsStr() + ts[0] - } - return rev.TextName -} - -// AsHtml returns HTML representation of the revision. -// If there is an error, it will be told about it in `w`. -func (rev *Revision) AsHtml(w http.ResponseWriter) (ret string, err error) { - ret += `
    -

    ` + rev.FullName + `

    -` - // TODO: support things other than images - if rev.hasBinaryData() { - ret += fmt.Sprintf(``, rev.FullName, rev.Id) - } - - contents, err := ioutil.ReadFile(rev.TextPath) - if err != nil { - log.Println("Failed to render", rev.FullName) - w.WriteHeader(http.StatusInternalServerError) - return "", err - } - - // TODO: support more markups. - // TODO: support mycorrhiza extensions like transclusion. - switch rev.TextMime { - case "text/markdown": - html := markdown.ToHTML(contents, nil, nil) - ret += string(html) - default: - ret += fmt.Sprintf(`
    %s
    `, contents) - } - - ret += ` -
    ` - return ret, nil -} - -// ActionGetBinary is used with `?action=getBinary`. -// It writes binary data of the revision. It also sets the MIME-type. -func (rev *Revision) ActionGetBinary(w http.ResponseWriter) { - fileContents, err := ioutil.ReadFile(rev.BinaryPath) - if err != nil { - log.Println("Failed to load binary data of", rev.FullName, rev.Id) - w.WriteHeader(http.StatusNotFound) - return - } - w.Header().Set("Content-Type", rev.BinaryMime) - w.WriteHeader(http.StatusOK) - w.Write(fileContents) - log.Println("Serving binary data of", rev.FullName, rev.Id) -} - -// ActionZen is used with `?action=zen`. -// It renders the revision without any layout or navigation. -func (rev *Revision) ActionZen(w http.ResponseWriter) { - html, err := rev.AsHtml(w) - if err == nil { - fmt.Fprint(w, html) - log.Println("Rendering", rev.FullName, "in zen mode") - } -} - -// ActionView is used with `?action=view` or without any action. -// It renders the revision with layout and navigation. -func (rev *Revision) ActionView(w http.ResponseWriter, layoutFun func(Revision, string) string) { - html, err := rev.AsHtml(w) - if err == nil { - fmt.Fprint(w, layoutFun(*rev, html)) - log.Println("Rendering", rev.FullName) - } -} diff --git a/w/config.json b/w/config.json index 3497a5a..409bcd2 100644 --- a/w/config.json +++ b/w/config.json @@ -1,6 +1,6 @@ { "address": "127.0.0.1:1737", - "theme": "default-dark", + "theme": "default-light", "site-title": "🍄 MycorrhizaWiki", "title-templates": { "edit-hypha": "Edit %s at MycorrhizaWiki", diff --git a/w/m/Templates/1.markdown b/w/m/Templates/1.markdown new file mode 100644 index 0000000..bf3de04 --- /dev/null +++ b/w/m/Templates/1.markdown @@ -0,0 +1 @@ +**TODO:** Reorganize templates. \ No newline at end of file diff --git a/w/m/Templates/default-light/Hypha/edit/index.html/1.html b/w/m/Templates/default-light/Hypha/edit/index.html/1.html index 943cf5f..d12ced6 100644 --- a/w/m/Templates/default-light/Hypha/edit/index.html/1.html +++ b/w/m/Templates/default-light/Hypha/edit/index.html/1.html @@ -2,7 +2,8 @@