From 6979e73307fe26ec2bfd2bb75ef1f36bbc5930d0 Mon Sep 17 00:00:00 2001 From: Alex Selimov Date: Fri, 20 Mar 2026 12:14:24 -0400 Subject: [PATCH] Update with hugo setup instructions --- README.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e85e42..bd17c2f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ upvoters is a basic anonymous voting system that can be added to a blog. I recently reworked my personal blog with the Hugo Bear blog theme. I came across the [creator's (Herman Martinus) blogging site](https://herman.bearblog.dev/) which had this really cool anonymous upvote system. I wanted something similar and decided to implement it myself. -The goal is maximum simplicity. +The goal is maximum simplicity so that I can use it with my Hugo static site. +It's directly integrated in [my theme]("https://forge.alexselimov.com/aselimov/hugo-bearcub") which attempts to mimic Herman Martinus' bear blog style. +I've added steps on how I set it up in my theme [at the bottom of this README](#setting-it-up-with-hugo). ## Table of Contents @@ -15,6 +17,7 @@ The goal is maximum simplicity. - [API](#api) - [Running tests](#running-tests) - [Actual deployment](#actual-deployment) +- [Setting it up with Hugo](#setting-it-up-with-hugo) - [License](#license) ## Requirements @@ -161,6 +164,112 @@ sudo systemctl enable --now upvoters curl https://example.com/api/posts/tests/votes ``` +## Setting it up with Hugo + +I set this up in my [Voting Bear Cub Theme](https://forge.alexselimov.com/aselimov/hugo-bearcub/src/branch/main). +The components are: +1. Javascript file to handle upvoting. This function is automatically called on page loads +```javascript + +(async function () { + // Define the slug + const slug = location.pathname.replace(/^\/|\/$/g, "").replaceAll("/", "-"); + const API = window.UPVOTE_API; + + 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"); + } + }); +})(); +``` +2. Button component in single.html template placed after the main post body. **Note:** I guard this behind a config check so users can use my theme without setting up upvoters. +```html +{{ if .Site.Params.upvotes }} +
+ +
+ + +{{ end }} +``` +3. Styles for the button: +```css +.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: currentColor; + 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; +} +``` + ## License MIT — see [LICENSE](LICENSE).