internal/httpd/preview.go

v1.32.0
gitbay/internal/httpd/preview.go history · blame · raw

87 lines · 3247 bytes

 1package httpd
 2
 3import (
 4	"html/template"
 5	"net/http"
 6	"net/url"
 7	"path"
 8	"strings"
 9
10	"gitbay.org/gitbay/internal/store"
11)
12
13// A markup form's Preview button posts to the form's own action with
14// preview set. The handler renders the body and hands back the page it
15// came from instead of writing anything, so what appears above the
16// textarea is the rendering the thread will show. There is no
17// JavaScript on this site (script-src 'none'), so a preview is a round
18// trip, the way the blob page's rendered/source toggle is (#235).
19//
20// Form names the textarea the draft belongs to, because a page can
21// carry more than one: an issue has both an edit form and a compose
22// box, and only the one that was submitted gets the preview. The other
23// fields of that form ride along in vals so the page can put them back.
24type draft struct {
25	Form   string
26	Body   string
27	Format string
28	HTML   template.HTML
29	vals   url.Values
30}
31
32// Is reports whether this draft belongs to the named form. Templates
33// call it to place the preview and open the right box; a nil draft
34// answers false, so a page renders unchanged when nothing was
35// previewed.
36func (d *draft) Is(form string) bool { return d != nil && d.Form == form }
37
38// Or is what a field of the named form should show: what was typed when
39// this draft is that form's, and the stored value otherwise. It is how
40// a previewed form comes back with everything still in it.
41func (d *draft) Or(form, field, fallback string) string {
42	if !d.Is(form) {
43		return fallback
44	}
45	return d.vals.Get(field)
46}
47
48// wantsPreview reports whether the submission asked to see the markup
49// rather than save it.
50func wantsPreview(r *http.Request) bool { return r.FormValue("preview") != "" }
51
52// draftFor renders a repository-scoped body the way the thread renders
53// it, autolinks and all. The caller supplies the format rather than the
54// helper reading the picker, because only the create forms have one: an
55// edit keeps the item's stored format, and a comment is markdown, which
56// is what the command does with no --format.
57func (s *Server) draftFor(r *http.Request, repo store.Repo, form, field, format string) *draft {
58	return s.draftWith(r, form, format, r.FormValue(field), s.ugcFor(r, repo))
59}
60
61// draftWith is draftFor for text with no repository behind it: a
62// profile's about, or a file in the editor. The renderer is the one
63// that surface uses, so the preview matches where the text will land.
64func (s *Server) draftWith(r *http.Request, form, format, body string, render ugcRenderer) *draft {
65	r.ParseForm() // Or reads r.Form; a handler may not have touched it yet.
66	return &draft{Form: form, Body: body, Format: format,
67		HTML: render(strings.TrimSpace(body), format), vals: r.Form}
68}
69
70// markupFile reports whether a path is one the forge renders: the blob
71// page's rendered view and the editor's preview take the same set.
72func markupFile(filePath string) bool {
73	switch path.Ext(strings.ToLower(filePath)) {
74	case ".md", ".markdown", ".org":
75		return true
76	}
77	return false
78}
79
80// bodyFormat reads the format picker, defaulting to markdown the way
81// every submit handler does.
82func bodyFormat(r *http.Request) string {
83	if r.FormValue("format") == "org" {
84		return "org"
85	}
86	return "md"
87}