1 ;;; mumi -- Mediocre, uh, mail interface
2 ;;; Copyright © 2016, 2017, 2018 Ricardo Wurmus <rekado@elephly.net>
4 ;;; This program is free software: you can redistribute it and/or
5 ;;; modify it under the terms of the GNU Affero General Public License
6 ;;; as published by the Free Software Foundation, either version 3 of
7 ;;; the License, or (at your option) any later version.
9 ;;; This program is distributed in the hope that it will be useful,
10 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 ;;; Affero General Public License for more details.
14 ;;; You should have received a copy of the GNU Affero General Public
15 ;;; License along with this program. If not, see
16 ;;; <http://www.gnu.org/licenses/>.
18 (define-module (mumi web view html
)
19 #:use-module
(debbugs email
)
20 #:use-module
(debbugs bug
)
21 #:use-module
(mumi config
)
22 #:use-module
(mumi messages
)
23 #:use-module
(mumi web view utils
)
24 #:use-module
(srfi srfi-1
)
25 #:use-module
(srfi srfi-19
)
30 list-of-matching-bugs
))
32 (define (status-tag bug
)
33 "Return a colored tag indicating the BUG status."
34 (let ((status (if (bug-done bug
) "Done" "Open")))
35 `(span (@ (class ,(string-append "status-tag "
36 (string-downcase status
))))
39 (define* (layout #:key
42 (title "Guix issue tracker")
44 `(#:sxml
((doctype "html")
48 (meta (@ (http-equiv "Content-Type") (content "text/html; charset=UTF-8")))
49 (meta (@ (http-equiv "Content-Language") (content "en")))
50 (meta (@ (name "author") (content "Ricardo Wurmus")))
51 (meta (@ (name "viewport")
52 (content "width=device-width, initial-scale=1")))
57 (href "/css/reset.css")))
62 (href "/css/bootstrap.css")))
68 (href "/css/screen.css"))))
70 #:extra-headers
,extra-headers
))
72 (define* (search-form #:key
(standalone?
#f
))
73 `(form (@ (id "search")
79 (@ (class "form-group"))
80 (input (@ (type "text")
83 (placeholder "input search query"))))
86 (class "btn btn-lg btn-primary btn-block")
87 ,@(if standalone?
'() '((style "display:none"))))
90 (define* (header #:key
(search-bar?
#t
))
95 (a (@ (href "/") (class "logo"))
96 (img (@ (src "/img/logo.png")
97 (alt "Guix patch tracker"))))
98 ,@(if search-bar?
(list (search-form)) '()))))
103 '((cache-control .
((max-age .
60))))
105 `(,(header #:search-bar?
#f
)
107 (@ (class "container"))
108 (h1 "Guix patch tracker")
112 (p "This is a web frontend to the Guix patch tracker. Send email to "
113 (a (@ (href ,(string-append "mailto:" (%config
'submission-email-address
))))
114 ,(%config
'submission-email-address
))
115 " to submit your patches."))
116 ,(search-form #:standalone?
#t
)
117 ;; TODO: do this via JS?
118 ,@(let ((bugs (recent-bugs 5)))
121 `((h2 "Recent issues")
122 ,(list-of-bugs bugs
))))))))
128 (div (@ (class "container"))
129 (h1 "Patch not found")
130 (p "There is no submission with id " (strong ,id
))
131 (p (a (@ (href "/")) "Try another one?"))))))
133 (define (error-page message
)
137 (div (@ (class "container"))
139 (p "An error occurred. Sorry about that!")
141 (p (a (@ (href "/")) "Try something else?"))))))
143 (define (issue-page bug
)
144 "Render the conversation for the given BUG."
145 (define id
(bug-num bug
))
146 (define messages
(patch-messages id
))
147 (define parties
(filter (compose (negate bot?
) extract-email
)
148 (participants messages
)))
149 (define (show-message message
)
152 (a (@ (id ,(number->string
(email-msg-num message
)))))
154 (@ (class "avatar col-md-1")
155 (style ,(string-append "background-color:"
156 (avatar-color (sender-email message
)
157 (map extract-email parties
)))))
158 ,(string-upcase (string-take (sender-name message
) 1)))
160 (@ (class "message col-md-11"))
162 (@ (class "panel panel-default"))
164 (@ (class "panel-heading"))
167 (span (@ (class "address")) ,(sender-name message
))
169 (span (@ (class "date"))
170 (a (@ (href ,(string-append "#" (number->string
171 (email-msg-num message
)))))
174 (@ (class "details"))
176 (@ (class "recipients"))
177 (label "Recipients:")
178 ,(map (lambda (address)
179 `(span (@ (class "address")) ,address
))
180 (recipients message
)))
182 (@ (class "message-id"))
183 (label "Message-ID:")
184 ,(message-id message
))))
186 (@ (class "body panel-body"))
187 ,(prettify (email-body message
))))))
188 ,@(if (closing? message id
)
190 (@ (class "row event"))
192 (@ (class "col-md-offset-1 col-md-11 text-center"))
193 (div (@ (class "label label-primary closed")) "Closed"))))
196 #:title
(bug-subject bug
)
200 ;; Tell browser to cache this for 12 hours.
201 '((cache-control .
((max-age .
43200)))))
203 ;; Tell browser to cache this for 1 hour.
204 '((cache-control .
((max-age .
3600)))))
209 (@ (class "container"))
211 (@ (class "row title col-md-12"))
212 (h1 ,(bug-subject bug
))
213 (span (@ (class "details"))
215 ,(string-append "Submitted by "
216 (extract-name (bug-originator bug
))
221 (@ (class "conversation col-md-9"))
222 ,(map show-message
(filter (lambda (msg)
225 ;; internal messages.
226 (and (email-body msg
)
227 (not (internal-message? msg
))))
230 (@ (class "row comment-box"))
231 (a (@ (id "comment")))
233 (@ (class "avatar col-md-1")
234 (style "background-color:#bc80bd")) "?")
236 (@ (class "message col-md-11"))
238 (@ (class "panel panel-default"))
240 (@ (class "panel-heading"))
241 (div (@ (class "from"))
242 (span (@ (class "address")) "Your comment")))
244 (@ (class "body panel-body"))
245 (p "Comments via the web interface are not currently
246 supported. To comment on this conversation "
247 (a (@ (href ,(string-append "mailto:"
248 (number->string id
) "@" (%config
'debbugs-domain
)
249 "?subject=" (bug-subject bug
))))
250 ,(string-append "send email to "
251 (number->string id
) "@" (%config
'debbugs-domain
)))))))))
254 (@ (class "info col-md-3"))
257 ,@(let ((num (length parties
)))
258 `((label ,(if (= num
1)
260 (string-append (number->string num
)
262 (ul ,(map (lambda (name)
263 `(li (span (@ (class "name")))
265 (map extract-name parties
))))))
269 ,(or (and=> (bug-owner bug
) extract-name
) "unassigned"))
273 ,(status-tag bug
))))))))
275 (define (list-of-bugs bugs
)
276 "Return a table of BUGS."
278 `(p "Nothing to see here. "
280 "Look for something else?"))
281 `(table (@ (class "table-condensed"))
285 (th "Date submitted")
289 (let ((id (number->string
(bug-num bug
))))
293 `(a (@ (href ,(string-append "/issue/" id
)))
296 (td ,(date->string
(bug-date bug
)))
297 (td ,(status-tag bug
)))))
300 (define (list-of-matching-bugs query bugs
)
304 (div (@ (class "container"))
306 `((h1 "No issues found")
307 (p "We could not find any issues matching your query "
310 "Try searching for something else?")))
311 `((h1 "Submissions matching " (code ,query
))
312 ,(list-of-bugs bugs
)))))))