issues_form.templ RawHistoryFolder
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5// IssueForm is the public form that proposes an issue to the owner. It needs
6// JavaScript: the script (issue-form.js) solves a proof of work in a worker,
7// so the form stays hidden until it runs, and the page says why without it.
8templ IssueForm(l Layout, p IssueFormPage) {
9 @Shell(l, issueFormRail()) {
10 <header class="show mr-h">
11 <h1>New issue</h1>
12 </header>
13 <noscript>
14 <p class="lede">This form needs JavaScript: it runs a short proof of work so that bots cannot flood the tracker.</p>
15 </noscript>
16 <form
17 class="cform create"
18 method="post"
19 action={ templ.SafeURL(p.Action) }
20 hidden
21 data-issue-form
22 data-nonce={ p.Nonce }
23 data-difficulty={ itoa(p.Difficulty) }
24 data-min-submit-at={ i64(p.MinSubmitAt) }
25 data-worker={ p.Worker }
26 autocomplete="off"
27 >
28 <input type="hidden" name="token" value={ p.Token }/>
29 <input type="hidden" name="form_token" value={ p.FormToken }/>
30 <input type="hidden" name="solution" value=""/>
31 <label for="issue-title">title</label>
32 <input
33 id="issue-title"
34 aria-describedby={ issueFieldDescription("title", p.ErrorField) }
35 if p.ErrorField == "title" {
36 aria-invalid="true"
37 }
38 name="title"
39 type="text"
40 required
41 autocomplete="off"
42 value={ p.Title }
43 />
44 <output id="issue-title-count" aria-live="off" data-for="issue-title" data-max={ itoa(p.MaxTitle) } for="issue-title">{ itoa(runeCount(p.Title)) } / { itoa(p.MaxTitle) }</output>
45 if p.ErrorField == "title" {
46 <p id="issue-title-error" class="f-error" role="alert">{ p.Error }</p>
47 }
48 <label for="issue-body">description</label>
49 <textarea
50 id="issue-body"
51 aria-describedby={ issueFieldDescription("body", p.ErrorField) }
52 if p.ErrorField == "body" {
53 aria-invalid="true"
54 }
55 name="body"
56 rows="8"
57 placeholder="Markdown, optional"
58 >{ p.Body }</textarea>
59 <output id="issue-body-count" aria-live="off" data-for="issue-body" data-max={ itoa(p.MaxBody) } for="issue-body">{ itoa(runeCount(p.Body)) } / { itoa(p.MaxBody) }</output>
60 if p.ErrorField == "body" {
61 <p id="issue-body-error" class="f-error" role="alert">{ p.Error }</p>
62 }
63 <label for="issue-author">name (optional, not verified)</label>
64 <input
65 id="issue-author"
66 aria-describedby={ issueFieldDescription("author", p.ErrorField) }
67 if p.ErrorField == "author" {
68 aria-invalid="true"
69 }
70 name="author"
71 type="text"
72 autocomplete="off"
73 value={ p.Author }
74 />
75 <output id="issue-author-count" aria-live="off" data-for="issue-author" data-max={ itoa(p.MaxAuthor) } for="issue-author">{ itoa(runeCount(p.Author)) } / { itoa(p.MaxAuthor) }</output>
76 if p.ErrorField == "author" {
77 <p id="issue-author-error" class="f-error" role="alert">{ p.Error }</p>
78 }
79 <div class="hp" aria-hidden="true">
80 <label for="issue-website">Leave this field empty</label>
81 <input id="issue-website" name="website" type="text" tabindex="-1" autocomplete="off" value=""/>
82 </div>
83 <p class="dim" data-status role="status" aria-live="polite">Checking your browser… (this takes a few seconds)</p>
84 <div class="f-btns">
85 <button type="submit" class="btn primary" disabled>Submit for moderation</button>
86 <a class="btn" href={ templ.SafeURL(p.Cancel) }>Cancel</a>
87 </div>
88 </form>
89 }
90}
91
92templ issueFormRail() {
93 <section aria-labelledby="r-about">
94 <h2 id="r-about">Before you write</h2>
95 <p class="dim">The owner reads every issue before it appears. Plain text and Markdown only; no address of yours is kept.</p>
96 </section>
97}
98
99func issueFieldDescription(field, invalid string) string {
100 ids := "issue-" + field + "-count"
101 if field == invalid {
102 ids += " issue-" + field + "-error"
103 }
104 return ids
105}