repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

3.8 KB · 111 lines · HTML Raw History
  1<!doctype html>
  2<html lang="en">
  3<head>
  4<meta charset="utf-8">
  5<meta name="viewport" content="width=device-width, initial-scale=1">
  6<title>History &middot; search</title>
  7<link rel="stylesheet" href="{{ asset "static/app.css" }}">
  8</head>
  9<body>
 10<div class="crt"></div>
 11<div class="vignette"></div>
 12
 13<header class="bar">
 14  <div class="barin">
 15    <a class="brand" href="/">
 16      <span class="key"><span class="cursor"></span></span>
 17      <span>search</span>
 18    </a>
 19    <span class="where">History</span>
 20    <span class="spacer"></span>
 21    <span class="counts"><b>{{ num .Total }}</b> kept &middot; <b>{{ num .Rated }}</b> rated</span>
 22    <a class="ghost" href="/search">ask</a>
 23  </div>
 24</header>
 25
 26<main>
 27  <p class="introline">Every question that was not asked in incognito, with what it
 28  answered and how it was rated. This is the only thing here that holds a question,
 29  and it is the only page that can delete one.</p>
 30
 31  <div class="filters">
 32    <a href="/history" class="{{ if eq .Only "" }}on{{ end }}">all</a>
 33    <a href="/history?only=down" class="{{ if eq .Only "down" }}on{{ end }}">thumbed down</a>
 34    <a href="/history?only=up" class="{{ if eq .Only "up" }}on{{ end }}">thumbed up</a>
 35    <a href="/history?only=unrated" class="{{ if eq .Only "unrated" }}on{{ end }}">unrated</a>
 36    <span class="spacer"></span>
 37    <button class="danger" id="forgetall">delete everything</button>
 38  </div>
 39
 40  {{ if not .Entries }}
 41    <p class="faint" style="margin-top:1.4rem">Nothing here yet.</p>
 42  {{ end }}
 43
 44  <ul class="hist">
 45    {{ range .Entries }}
 46    <li class="hrow" data-id="{{ .ID }}">
 47      <details>
 48        <summary>
 49          <span class="hq">{{ .Question }}</span>
 50          {{ if .Good }}<span class="verdict up">good</span>{{ end }}
 51          {{ if .Bad }}<span class="verdict down">{{ if .Reason }}{{ reason .Reason }}{{ else }}bad{{ end }}</span>{{ end }}
 52          <span class="label">{{ if .Skill }}{{ .Skill }}{{ else }}{{ .Shape }}{{ end }}</span>
 53          <span class="hwhen">{{ .Asked.Format "2 Jan, 3:04 PM" }}</span>
 54        </summary>
 55        <div class="hbody">
 56          <div class="answer prose">{{ .Body }}</div>
 57
 58          {{ if .Sources }}
 59          <div class="hmeta">
 60            <span class="label">sources</span>
 61            {{ range .Sources }}<a class="chip" href="{{ .URL }}" rel="noreferrer">{{ hostname .URL }}</a>{{ end }}
 62          </div>
 63          {{ end }}
 64
 65          {{ if .Queries }}
 66          <div class="hmeta">
 67            <span class="label">searched</span>
 68            {{ range .Queries }}<span class="chip">{{ . }}</span>{{ end }}
 69          </div>
 70          {{ end }}
 71
 72          <div class="hmeta">
 73            <span class="stamp">{{ .Elapsed }} &middot; {{ .Checked }} checked &middot; {{ .Model }} &middot; prompts {{ .Prompts }}</span>
 74            <span class="spacer"></span>
 75            <button class="danger forget">delete</button>
 76          </div>
 77        </div>
 78      </details>
 79    </li>
 80    {{ end }}
 81  </ul>
 82</main>
 83
 84<script>
 85// Deleting is the point of the page, so it asks once and then actually removes
 86// the row rather than leaving it there looking deleted.
 87async function forget(body) {
 88  const res = await fetch("/forget", {
 89    method: "POST",
 90    headers: { "Content-Type": "application/json" },
 91    body: JSON.stringify(body),
 92  });
 93  return res.ok;
 94}
 95
 96document.querySelectorAll(".forget").forEach((b) => {
 97  b.addEventListener("click", async () => {
 98    const row = b.closest(".hrow");
 99    if (!confirm("Delete this question and its answer?")) return;
100    if (await forget({ id: Number(row.dataset.id) })) row.remove();
101  });
102});
103
104document.getElementById("forgetall").addEventListener("click", async () => {
105  if (!confirm("Delete every question and answer kept here? This cannot be undone.")) return;
106  if (await forget({ all: true })) location.reload();
107});
108</script>
109</body>
110</html>