root/html-wrapper.scm

(define-module (html-wrapper)
  #:use-module (srfi srfi-1) ;; map/fold
  #:use-module (sxml simple)
  #:use-module (utils)
  #:use-module (ice-9 optargs)
  #:export (html-wrapper write-html))

(define (minimize-css css)
  (define replacements
    '(("  " . " ")
      ("\n\n" . "\n")
      (": " . ":")
      (", " . ",")
      ("{\n" . "{ ")
      (" { " . "{")
      ("; " . ";")
      (";\n" . ";")
      (";}" . "}")))

  (fold (lambda (replacement str)
          (string-substitute
           str
           (car replacement)
           (cdr replacement)
           #t))
        css replacements))

;; (define dark-3 "#3D3846")
;; (define light-3 "#DEDDDA")
;; (define border (light-3 . dark-3))
;; (define light-4 "#C0BFBC")
;; (define dark-4 "#241F31")
;; (define dim (dark-4 . light-4))

(define adwcss (minimize-css
                "body { color: #F6F5F4; background: #241F31; font-family:Cantarell, sans-serif;}
a { color: #99C1F1; text-decoration: none; }
a:hover { color: #C061CB; text-decoration: underline; }
a:visted { color: #DC8ADD }
.raw-link { font-size: 0.6em; margin-left: 2em; }
.small { font-size:0.6em; }
.icon { margin-right: 0.5em; }
td { padding-left: 1em;}
.date-row { padding-left: 1em; color: #C0BFBC; }
.file-view .contents { white-space: nowrap;}
.file-view.image .contents img { background-image: linear-gradient(gray, darkgray); }
.line-numbers { width: 2em; text-align:end; border-top: solid #3D3846 1px; border-bottom: solid #3D3846 1px; padding-top:2px; padding-bottom:2px; padding-right: 1em;}
.file-content { padding-left:1em; padding-right:2em; border-left: solid #3D3846 1px; border-top: solid #3D3846 1px; border-bottom: solid #3D3846 1px; padding-top:2px; padding-bottom:2px;}
.file-view pre {
display: inline-block;
color: #DEDDDA;
background: #241F31;
background-image: -webkit-linear-gradient(#140F21 50%, #191522 50%);
background-image:    -moz-linear-gradient(#140F21 50%, #191522 50%);
background-image:     -ms-linear-gradient(#140F21 50%, #191522 50%);
background-image:      -o-linear-gradient(#140F21 50%, #191522 50%);
background-image:         linear-gradient(#140F21 50%, #191522 50%);
background-position: 2px 2px;
background-repeat: repeat;
background-size: 2.5em 2.5em;
line-height: 1.25em;
font-size: 1em;}
h4 { margin-bottom:0px;}
.flatpak { display: flex; gap: 20px; margin-top: 20px; }
.flatpak .platform { border: solid 1px #3D3846; text-align:center; padding: 10px; }
.flatpak .icon { font-size: 64px; margin:0px; }
.flatpak a:hover { text-decoration: none;}
.flatpak .small { padding-left: 10px; }
.rendered-markup.org .figure { display: inline-block; }
.mainwrap { display: flex; flex-wrap: wrap; gap: 20px;}
.copypasteButton { background: none; border: none; border-radius: 10px; color: #99C1F1; font-weight: bold; font-size: 1.5em; transition: background 0.3s, color 0.3s;}
.copypasteButton:hover { background: #222226; color: #78e9ab; }
.copypasteButton:active { background: #26a269; color: #ffffff; }
.git-links pre { display: inline-block; }
.git-links .link { display: flex; gap: 10px; }
"))

;;; old no longer used
(define discss
  (minimize-css
                "body{font-family:Mono;}
 .file-view .contents {white-space: nowrap;}
 .file-view.image .contents img { background-image: linear-gradient(gray, darkgray); }
 .line-numbers{ padding-right:4px; border-top: solid gray 1px; border-bottom: solid gray 1px; padding-top:2px; padding-bottom:2px;}
 .file-content{ padding-left:5px; border-left: solid gray 1px; border-top: solid gray 1px; border-bottom: solid gray 1px; padding-top:2px; padding-bottom:2px;}
 .file-view pre {
display: inline-block;
background: #fefefe;
background-image: -webkit-linear-gradient(#fefefe 50%, #f7f7f7 50%);
background-image:    -moz-linear-gradient(#fefefe 50%, #f7f7f7 50%);
background-image:     -ms-linear-gradient(#fefefe 50%, #f7f7f7 50%);
background-image:      -o-linear-gradient(#fefefe 50%, #f7f7f7 50%);
background-image:         linear-gradient(#fefefe 50%, #f7f7f7 50%);
background-position: 2px 2px;
background-repeat: repeat;
background-size: 2.5em 2.5em;
line-height: 1.25em;
font-size: 1em;}
h4 {margin-bottom:0px;}
.rendered-markup.org .figure { display: inline-block; }
.mainwrap { display: flex; flex-wrap: wrap;}
.copypasteButton {background: none; border: none; border-radius: 10px; color: #99C1F1; font-weight: bold; font-size: 1.5em; transition: background 0.3s, color 0.3s;}
.copypasteButton:hover { background: #222226; color: #78e9ab; }
.copypasteButton:active { background: #26a269; color: #ffffff; }
.git-links pre { display: inline-block; }
.git-links .link { display: flex; gap: 10px; }
"))
;; .rendered-markup.org p, .rendered-markup.org h1, .rendered-markup.org h2, .rendered-markup.org h3, .rendered-markup.org h4 {margin: 0px;}

(define mincss adwcss)

(define js-script-block
  "
function localize(val) {
  return val; // placeholder for future implementation
}

// copy paste buttons for git links
(function() {
  const linksCollection = document.getElementsByClassName(\"git-links\");
  const links = Array.prototype.slice.call( linksCollection, 0 );
  links.map(function(container) {
    const innerLinksCollection = container.getElementsByTagName(\"pre\");
    const innerLinks = Array.prototype.slice.call( innerLinksCollection, 0 );
    innerLinks.map(function(innerLink) {
      const copypasteButton = document.createElement(\"button\");
      copypasteButton.innerText = \"⎘\";
      copypasteButton.title = localize(\"Copy clone command\");
      copypasteButton.className = \"copypasteButton\";
      copypasteButton.addEventListener(\"click\", function() {
        const data = { \"text/plain\": innerLink.innerText };
        const item = new ClipboardItem(data);
        navigator.clipboard.write([item]);
        copypasteButton.innerText = localize(\"⎘ Copied!\");
        window.setTimeout(function() { copypasteButton.innerText = localize(\"⎘\") }, 10 * 1000);
      });
      const parent = innerLink.parentElement;
      parent.insertBefore(copypasteButton, innerLink.nextSibling);
     });
  });
})();

// Make dates relative times
(function() {
  const SECONDS_IN_DAY = 86400.0 ;
  const now = new Date()  / 1000.0;
  const unixNow = new Date(now);
  const datesCollection = document.getElementsByClassName(\"date-row\");
  const dates = Array.prototype.slice.call( datesCollection, 0 );
  dates.map(function(date) {
    const text = date.innerText;
    const unixDate = new Date(text).getTime() / 1000.0;
    const days = (unixNow - unixDate) / SECONDS_IN_DAY;
    const months = days / 30.0;
    const years = days / 365.0;
    let newText;
    if (years > 1.0) {
      newText = localize(Math.floor(years) + \" Years ago\");
    } else if (months > 1.0) {
      newText = localize(Math.floor(months) + \" Months ago\");
    } else if (days > 1.0) {
      newText = localize(Math.floor(days) + \" Days ago\");
    } else {
      newText = \"Today\";
    }

    date.innerText = newText;
    date.title = text;
  });
})();
")

;;; TODO get rid of this, just use write-html
(define* (html-wrapper title content #:key (favicon #f))
  `(
    (head
     (title ,title)
     (style ,mincss)
     (meta (@ (http-equiv "content-type") (content "text/html; charset=utf-8")))
     ,(if favicon
          `(link (@ (rel "icon")
                   ;; TODO match mime from extension
                   ;; (type "image/x-icon")
                   (href ,favicon)))
          '()))
    (,content)
    ;; adding js will mess up encoding with &quote;
    ;; (script ,js)
    ))

(define* (write-html html #:optional use-js)
  "displays html to output port including js"
  (display "<!DOCTYPE html>\n")
  (display "<html>")
  (sxml->xml (car html))
  (display "<body>")
  (sxml->xml (car (cdr html)))
  (when use-js
    (display "<script>")
    (display  js-script-block)
    (display "</script>"))
  (display "</body>")

  (display "</html>"))