Subject: [PATCH mumi 3/3] html: Add a button to copy a Message-ID to the clipboard.

From: Maxim Cournoyer <[email-masked]>
Date: Tue, 23 Jan 2024 21:20:56 -0500

* mumi/web/view/html.scm (issue-page)
<copy-message-id-button>: New HTML element.
* mumi/web/view/utils.scm (download-icon): Update source.  Use 'rem'
as size unit.
(copy-icon): New variable.
(display-message-body) <download-part>: Add IDs to buttons.  Add to
"message-button" class.
* assets/mumi.scss: Refactor spacing of message header buttons via a
flex display.  Add a shrinking animation to message buttons on hover.
* assets/js/mumi.js (mumi): Register an event for it that copies the
Message-ID to the clipboard.  Add js-indent-level prop line.

Signed-off-by: Maxim Cournoyer <[email-masked]>
---

 assets/js/mumi.js       | 31 ++++++++++++++++++++++++++++-
 assets/mumi.scss        | 23 ++++++++++++++++++---
 mumi/web/view/html.scm  | 20 +++++++++++++++----
 mumi/web/view/utils.scm | 44 +++++++++++++++++++++++++++--------------
 4 files changed, 95 insertions(+), 23 deletions(-)

diff --git a/assets/js/mumi.js b/assets/js/mumi.js
index ab29f08..0d25c5a 100644
--- a/assets/js/mumi.js
+++ b/assets/js/mumi.js
@@ -1,4 +1,4 @@
-// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3.0-or-later
+// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3.0-or-later  -*- js-indent-level: 2; -*-
 var mumi = (function () {
   const possibleTokens = [
     { text: 'is:open' },
@@ -129,12 +129,41 @@ var mumi = (function () {
   var init = function () {
     initTokenInput ();
   };
+
+  // Copy a message Message-ID to the clipboard.
+  var setupMessageIdButtonHandler = function () {
+
+    let messageIdClickHandler = (evt) => {
+      messageIdButton = evt.currentTarget;
+      originalTooltip = messageIdButton.dataset.tooltip;
+      var messageId = messageIdButton.dataset.messageId;
+      if (!window.isSecureContext) {
+	console.log("not in a secure context -- " +
+		    "cannot copy message-id to clipboard\n" +
+		    "tip: for testing, open via http://localhost:1234");
+	return;
+      }
+      // Update button's tooltip for the next 3 s.
+      messageIdButton.dataset.tooltip = "Copied!";
+      setTimeout(() => { messageIdButton.dataset.tooltip = originalTooltip; },
+		 3000);
+      navigator.clipboard.writeText(messageId);
+      console.log("copied Message-ID " + messageId + " to clipboard");
+    };
+
+    var messageIdButton = document.getElementById("copy-message-id-button");
+    if (messageIdButton === null) { return; }
+    messageIdButton.addEventListener("click", messageIdClickHandler);
+  };
+
   return({
     'init': init,
     'lines': setupLineHandler,
+    'messageIdButtonHandler': setupMessageIdButtonHandler,
   });
 })();
 
 window.addEventListener ("load", mumi.init);
 window.addEventListener ("DOMContentLoaded", mumi.lines);
+window.addEventListener ("DOMContentLoaded", mumi.messageIdButtonHandler);
 // @license-end
diff --git a/assets/mumi.scss b/assets/mumi.scss
index 822f110..60dabb1 100644
--- a/assets/mumi.scss
+++ b/assets/mumi.scss
@@ -506,11 +506,28 @@ details {
     margin-right: 0.2em;
 }
 
-.download-message,
 .download-part {
     float: right;
-    font-size: 0.8em;
-    font-style: italic;
+}
+
+.header-buttons {
+    display: flex;
+    flex-direction: row;
+    float: right;
+    justify-content: flex-end;
+ }
+
+.header-buttons > * {
+    margin: 0 0 0 0.5rem;
+    // Custom buttons: undo the Pico CSS default style.
+    background: revert;
+    border: revert;
+    color: revert;
+    padding: revert;
+}
+
+.message-button:hover {
+    transform: scale(0.95);
 }
 
 @media (min-width: 768px) {
diff --git a/mumi/web/view/html.scm b/mumi/web/view/html.scm
index 8f06a36..3709134 100644
--- a/mumi/web/view/html.scm
+++ b/mumi/web/view/html.scm
@@ -1,6 +1,7 @@
 ;;; mumi -- Mediocre, uh, mail interface
 ;;; Copyright © 2016, 2017, 2018, 2019, 2020, 2021, 2022 Ricardo Wurmus <[email-masked]>
 ;;; Copyright © 2018, 2019, 2023 Arun Isaac <[email-masked]>
+;;; Copyright © 2024 Maxim Cournoyer <[email-masked]>
 ;;;
 ;;; This program is free software: you can redistribute it and/or
 ;;; modify it under the terms of the GNU Affero General Public License
@@ -612,6 +613,7 @@ currently disabled."))
                 (not (bug-archived bug)))
            mailer-form
            disabled-mailer)))
+
   (define (show-message message-number message previous-subject)
     `((div
        (a (@ (class "message-anchor")
@@ -642,10 +644,20 @@ currently disabled."))
                                                  message-number)))
                       (title ,(date->string (date message))))
                    ,(time->string (date message)))))
-         (div (@ (class "download-message"))
-              (a (@ (href ,(format #f "/issue/~a/raw/~a"
-                                   id message-number)))
-                 ,download-icon))
+         (div (@ (class "header-buttons"))
+              (div (@ (id "copy-message-id-button")
+                      (class "message-button")
+                      (role "button")   ;specific to Pico CSS
+                      (data-tooltip "Copy Message-ID")
+                      (data-message-id ,(message-id message)))
+                   ,copy-icon)
+              (div (@ (id "download-raw-message-button")
+                      (class "message-button")
+                      (role "button")
+                      (data-tooltip "Download raw message"))
+                   (a (@ (href ,(format #f "/issue/~a/raw/~a"
+                                        id message-number)))
+                      ,download-icon)))
          ,@(if (string-suffix? previous-subject (subject message))
                '()
                `((div (@ (class "subject")) ,(subject message))))
diff --git a/mumi/web/view/utils.scm b/mumi/web/view/utils.scm
index 1ce7b64..ed83d15 100644
--- a/mumi/web/view/utils.scm
+++ b/mumi/web/view/utils.scm
@@ -2,6 +2,7 @@
 ;;; Copyright © 2017, 2018, 2019, 2020 Ricardo Wurmus <[email-masked]>
 ;;; Copyright © 2018, 2019 Arun Isaac <[email-masked]>
 ;;; Copyright © 2018 Ludovic Courtès <[email-masked]>
+;;; Copyright © 2024 Maxim Cournoyer <[email-masked]>
 ;;;
 ;;; This program is free software: you can redistribute it and/or
 ;;; modify it under the terms of the GNU Affero General Public License
@@ -35,6 +36,7 @@
   #:use-module (web uri)
   #:export (prettify
             avatar-color
+            copy-icon
             download-icon
             display-message-body
             time->string))
@@ -215,23 +217,31 @@ numbers with the given MESSAGE-NUMBER."
 ;; https://icons.getbootstrap.com/icons/download/
 (define download-icon
   '(svg (@ (class "bi bi-download")
-           (width "1em")
-           (height "1em")
+           (width "1rem")
+           (height "1rem")
+           (viewBox "0 0 16 16")
+           (fill "currentColor")
+           (xmlns "http://www.w3.org/2000/svg"))
+        (path (@ (d "M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 \
+1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 \
+1-2-2v-2.5a.5.5 0 0 1 .5-.5")))
+        (path (@ (d "M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 \
+0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 \
+1 0-.708.708z")))))
+
+;; https://icons.getbootstrap.com/icons/copy/
+(define copy-icon
+  '(svg (@ (class "bi bi-copy")
+           (width "1rem")
+           (height "1rem")
            (viewBox "0 0 16 16")
            (fill "currentColor")
            (xmlns "http://www.w3.org/2000/svg"))
-        (title "Download")
-        (path (@ (fill-rule "evenodd")
-                 (clip-rule "evenodd")
-                 (d "M.5 8a.5.5 0 01.5.5V12a1 1 0 001 1h12a1 1 0 001-1\
-V8.5a.5.5 0 011 0V12a2 2 0 01-2 2H2a2 2 0 01-2-2V8.5A.5.5 0 01.5 8z")) "")
-        (path (@ (fill-rule "evenodd")
-                 (clip-rule "evenodd")
-                 (d "M5 7.5a.5.5 0 01.707 0L8 9.793 10.293 7.5a.5.5 0 \
-11.707.707l-2.646 2.647a.5.5 0 01-.708 0L5 8.207A.5.5 0 015 7.5z")) "")
         (path (@ (fill-rule "evenodd")
-                 (clip-rule "evenodd")
-                 (d "M8 1a.5.5 0 01.5.5v8a.5.5 0 01-1 0v-8A.5.5 0 018 1z")) "")))
+                 (d "M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 \
+2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 \
+0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 \
+2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z")))))
 
 (define* (display-message-body bug-num message-number message #:optional plain?)
   "Convenience procedure to render MESSAGE (part of bug with number
@@ -285,7 +295,9 @@ lines when PLAIN? is #T."
                    "")))
        ((string-suffix? ".scm" attachment-name)
         `(div (@ (class "multipart scheme"))
-              (div (@ (class "download-part"))
+              (div (@ (id "download-scheme-part-button")
+                      (class "download-part message-button")
+                      (data-tooltip "Download Scheme file"))
                    (a (@ (href ,(attachment-url)))
                       ,download-icon))
               ,(highlights->sxml (highlight lex-scheme body))))
@@ -294,7 +306,9 @@ lines when PLAIN? is #T."
                           (list "multipart" (or (and content-type
                                                      (content-type->css-class content-type))
                                                 "")))))
-              (div (@ (class "download-part"))
+              (div (@ (id "download-part-button")
+                      (class "download-part message-button")
+                      (data-tooltip "Download MIME part"))
                    (a (@ (href ,(attachment-url)))
                       ,download-icon))
               ,(cond
-- 
2.41.0