diff --git a/assets/herman.css b/assets/herman.css index f2573c3..8a5ca66 100644 --- a/assets/herman.css +++ b/assets/herman.css @@ -190,6 +190,51 @@ td { transform: translateY(0%); } +.upvote { + display: flex; + flex-direction: column; + align-items: center; + width: fit-content; + gap: 0.2rem; + margin-block: var(--spacing); +} + +#upvote-btn { + background: none; + border: none; + cursor: pointer; + color: var(--color-light); + font-size: calc(var(--size) * 4); + line-height: 0.8; + padding: 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 0; + opacity: 0.4; + transition: opacity 0.15s, color 0.15s; +} + +#upvote-btn:hover { + opacity: 0.8; +} + +#upvote-btn[voted] { + color: var(--color-primary); + opacity: 1; +} + +#upvote-chevron { + display: block; + line-height: 1; + margin-bottom: -0.3em; +} + +#upvote-count { + font-size: calc(var(--size) * 1.6); + line-height: 1; +} + figure { margin-inline-start: 0em; margin-inline-end: 0em; diff --git a/layouts/_default/single.html b/layouts/_default/single.html index 0c85516..7a5fd7b 100644 --- a/layouts/_default/single.html +++ b/layouts/_default/single.html @@ -18,6 +18,15 @@ #{{ lower .LinkTitle }} {{ end }}

+{{ if .Site.Params.upvotes }} +
+ +
+ +{{ end }} {{ if not .Params.hideReply }} {{ with .Site.Params.author.email }}

diff --git a/static/upvote.js b/static/upvote.js new file mode 100644 index 0000000..5abf255 --- /dev/null +++ b/static/upvote.js @@ -0,0 +1,37 @@ +(async function () { + // Define the slug + const slug = location.pathname.replace(/^\/|\/$/g, "").replaceAll("/", "-"); + const API = "http://localhost:3000"; + + const btn = document.getElementById("upvote-btn"); + const count = document.getElementById("upvote-count"); + + // On load fetch curent vote count and whether the button should be clicked on or off + const res = await fetch(`${API}/posts/${slug}/votes`, { + credentials: "include", + }); + const data = await res.json(); + + count.textContent = data.vote_count; + if (data.voted) btn.setAttribute("voted", true); + + btn.addEventListener("click", async () => { + const alreadyVoted = btn.hasAttribute("voted"); + + const method = alreadyVoted ? "DELETE" : "POST"; + const r = await fetch(`${API}/posts/${slug}/vote`, { + method, + credentials: "include", + }); + + if (r.ok) { + const updated = await fetch(`${API}/posts/${slug}/votes`, { + credentials: "include", + }); + const d = await updated.json(); + count.textContent = d.vote_count; + if (d.voted) btn.setAttribute("voted", false); + else btn.removeAttribute("voted"); + } + }); +})();