package httpd import ( "html/template" "net/http" "net/url" "path" "strings" "gitbay.org/gitbay/internal/store" ) // A markup form's Preview button posts to the form's own action with // preview set. The handler renders the body and hands back the page it // came from instead of writing anything, so what appears above the // textarea is the rendering the thread will show. There is no // JavaScript on this site (script-src 'none'), so a preview is a round // trip, the way the blob page's rendered/source toggle is (#235). // // Form names the textarea the draft belongs to, because a page can // carry more than one: an issue has both an edit form and a compose // box, and only the one that was submitted gets the preview. The other // fields of that form ride along in vals so the page can put them back. type draft struct { Form string Body string Format string HTML template.HTML vals url.Values } // Is reports whether this draft belongs to the named form. Templates // call it to place the preview and open the right box; a nil draft // answers false, so a page renders unchanged when nothing was // previewed. func (d *draft) Is(form string) bool { return d != nil && d.Form == form } // Or is what a field of the named form should show: what was typed when // this draft is that form's, and the stored value otherwise. It is how // a previewed form comes back with everything still in it. func (d *draft) Or(form, field, fallback string) string { if !d.Is(form) { return fallback } return d.vals.Get(field) } // wantsPreview reports whether the submission asked to see the markup // rather than save it. func wantsPreview(r *http.Request) bool { return r.FormValue("preview") != "" } // draftFor renders a repository-scoped body the way the thread renders // it, autolinks and all. The caller supplies the format rather than the // helper reading the picker, because only the create forms have one: an // edit keeps the item's stored format, and a comment is markdown, which // is what the command does with no --format. func (s *Server) draftFor(r *http.Request, repo store.Repo, form, field, format string) *draft { return s.draftWith(r, form, format, r.FormValue(field), s.ugcFor(r, repo)) } // draftWith is draftFor for text with no repository behind it: a // profile's about, or a file in the editor. The renderer is the one // that surface uses, so the preview matches where the text will land. func (s *Server) draftWith(r *http.Request, form, format, body string, render ugcRenderer) *draft { r.ParseForm() // Or reads r.Form; a handler may not have touched it yet. return &draft{Form: form, Body: body, Format: format, HTML: render(strings.TrimSpace(body), format), vals: r.Form} } // markupFile reports whether a path is one the forge renders: the blob // page's rendered view and the editor's preview take the same set. func markupFile(filePath string) bool { switch path.Ext(strings.ToLower(filePath)) { case ".md", ".markdown", ".org": return true } return false } // bodyFormat reads the format picker, defaulting to markdown the way // every submit handler does. func bodyFormat(r *http.Request) string { if r.FormValue("format") == "org" { return "org" } return "md" }