Compare commits

..

9 commits

8 changed files with 153 additions and 33 deletions

View file

@ -1,13 +1,24 @@
# ᕦʕ •ᴥ•ʔᕤ Bear Cub
# ʕ •ᴥ•ʔつ🗳️ Voting Bear Cub
[![github pages](https://github.com/clente/hugo-bearcub/actions/workflows/gh-pages.yml/badge.svg)](https://github.com/clente/hugo-bearcub/actions/workflows/gh-pages.yml)
[![MIT license](https://img.shields.io/github/license/clente/hugo-bearcub)](https://github.com/clente/hugo-bearcub/blob/main/LICENSE)
[![MIT license](https://img.shields.io/badge/license-MIT-blue)](https://forge.alexselimov.com/aselimov/hugo-bearcub/src/branch/main/LICENSE)
## Overview
🐻 A lightweight [Hugo](https://gohugo.io/) theme based on [Bear
Blog](https://bearblog.dev) and [Hugo Bear
Blog](https://github.com/janraasch/hugo-bearblog).
This is a fork of [Bear Cub](https://github.com/clente/hugo-bearcub) with the
following additions:
- **Anonymous post upvoting**: each post can display an upvote button backed by
a configurable API endpoint. Votes are tracked anonymously via cookies so
readers can toggle their vote without creating an account. Enable with
`upvotes = true` and set `upvoteApi` to your API base URL.
- **Redesigned header**: the site header now uses a mesh SVG background for a
more distinctive look.
- **GitHub light/dark color scheme**: the syntax highlighting palette has been
replaced with colors matching the GitHub light and dark themes.
- **Herman CSS upvote styles**: upvote button styles are bundled into the Herman
CSS variant so the button integrates cleanly with the existing theme.
- **Configurable index post count**: use `postsToShowOnIndex` in `[params]` to
control how many posts appear on the home page (defaults to 3).
**Bear Cub** takes care of speed and optimization, so you can focus on writing
good content. It is free, multilingual, optimized for search engines,
@ -29,6 +40,11 @@ To finish off, append a line to the site configuration file:
echo 'theme = "hugo-bearcub"' >> hugo.toml
```
### Anonymous voting
To easily setup the anonymous voting you need to run an [upvoters](https://forge.alexselimov.com/aselimov/upvoters) server.
You should be able to ship your own solution as well.
## Features
Like [Bear Blog](https://bearblog.dev), this theme:

View file

@ -55,7 +55,7 @@ footer {
}
.mesh-image {
color: #61ABDA;
color: #EF9F27;
}
.footer-copy {
@ -104,13 +104,17 @@ li {
}
@media (prefers-color-scheme: light) {
#upvote-btn[voted] {
color: #EF9F27;
}
body {
background-color: #ffffff;
color: #1f2328;
background-color: #fffdf8;
color: #201e1b;
}
.mesh-image {
color: #61ABDA;
color: #EF9F27;
}
h1,
@ -125,12 +129,12 @@ li {
}
a {
color: #61ABDA;
color: #EF9F27;
}
a:visited,
ul.blog-posts li a:visited {
color: #61ABDA;
color: #EF9F27;
}
header a,
@ -178,7 +182,7 @@ li {
a,
a:visited,
ul.blog-posts li a:visited {
color: #61ABDA;
color: #EF9F27;
}
header a,

View file

@ -1,8 +1,8 @@
:root {
font-size: 62.5%; /* 10px */
--color-dark: #181a20;
--color-light: #fafafa;
--color-primary: #1a8fe3;
--color-dark: #1a1a1a;
--color-light: #faf8f4;
--color-primary: #EF9F27;
--size: 1rem;
--spacing: calc(var(--size) * 2.4);
}
@ -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: 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;
}
figure {
margin-inline-start: 0em;
margin-inline-end: 0em;

View file

@ -75,6 +75,17 @@ enableRobotsTXT = true
# information see layouts/partials/social_card.html
generateSocialCard = true
# The number of posts to show on the index page. Defaults to 3 if not set.
postsToShowOnIndex = 3
# Enable upvoting on posts. When set to true, each post will display an upvote
# button that calls the API endpoint specified by `upvoteApi`.
upvotes = false
# The base URL of the upvote API. Only used when `upvotes = true`.
# The API is expected to handle GET and POST requests at {upvoteApi}/{post-path}.
upvoteApi = "https://example.com/api"
# Social media. Delete any item you aren't using to make sure it won't show up
# in your website's metadata.
[params.social]

3
go.mod
View file

@ -1,3 +0,0 @@
module hugo-bearcub
go 1.26.2

View file

@ -27,4 +27,14 @@
</p>
{{ end }}
{{ end }}
{{ if and .Site.Params.upvotes (not .Params.hideUpvotes) }}
<div class="upvote">
<button id="upvote-btn" aria-label="Upvote">
<span id="upvote-chevron">&#8963;</span>
<span id="upvote-count"></span>
</button>
</div>
<script>window.UPVOTE_API = "{{ .Site.Params.upvoteApi }}";</script>
<script src="/upvote.js"></script>
{{ end }}
{{ end }}

37
static/upvote.js Normal file
View file

@ -0,0 +1,37 @@
(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");
}
});
})();

View file

@ -1,29 +1,29 @@
name = "Bear Cub"
name = "Voting Bear Cub"
license = "MIT"
licenselink = "https://github.com/clente/hugo-bearcub/blob/master/LICENSE"
description = "A lightweight Hugo theme based on Bear Blog and Hugo Bear Blog. It is free, multilingual, optimized for search engines, no-nonsense, responsive, light, and fast. Really fast."
description = """A lightweight Hugo theme based on Bear Blog and Hugo Bear Blog. It is free,
multilingual, optimized for search engines, no-nonsense, responsive, light, and fast. Really fast.
This version adds an upvote button that connects to an
https://forge.alexselimov.com/aselimov/upvoters server to handle anonymous upvotes.
"""
# The home page of the theme, where the source can be found.
homepage = "https://github.com/clente/hugo-bearcub"
# If you have a running demo of the theme.
demosite = "https://clente.github.io/hugo-bearcub"
homepage = "https://forge.alexselimov.com/aselimov/hugo-bearcub"
tags = ["blog", "responsive", "minimal", "personal", "dark", "multilingual"]
features = ["favicon", "seo", "no javascript", "rss", "social cards"]
# If the theme has a single author
[author]
name = "Caio Lente"
homepage = "https://lente.dev"
name = "Alex Selimov"
homepage = "https://alexselimov.com"
# If porting an existing theme
[original]
author = "Jan Raasch"
homepage = "https://www.janraasch.com"
repo = "https://github.com/janraasch/hugo-bearblog"
name = "Caio Lente"
homepage = "https://lente.dev"
repo = "https://github.com/clente/hugo-bearcub"
# Hugo versions the theme supports
[module]
[module.hugoVersion]
min = "0.90"
[module.hugoVersion]
min = "0.90"