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/badge/license-MIT-blue)](https://forge.alexselimov.com/aselimov/hugo-bearcub/src/branch/main/LICENSE)
[![MIT license](https://img.shields.io/github/license/clente/hugo-bearcub)](https://github.com/clente/hugo-bearcub/blob/main/LICENSE)
## Overview ## Overview
🐻 A lightweight [Hugo](https://gohugo.io/) theme based on [Bear This is a fork of [Bear Cub](https://github.com/clente/hugo-bearcub) with the
Blog](https://bearblog.dev) and [Hugo Bear following additions:
Blog](https://github.com/janraasch/hugo-bearblog).
- **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 **Bear Cub** takes care of speed and optimization, so you can focus on writing
good content. It is free, multilingual, optimized for search engines, 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 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 ## Features
Like [Bear Blog](https://bearblog.dev), this theme: Like [Bear Blog](https://bearblog.dev), this theme:

View file

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

View file

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

View file

@ -75,6 +75,17 @@ enableRobotsTXT = true
# information see layouts/partials/social_card.html # information see layouts/partials/social_card.html
generateSocialCard = true 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 # Social media. Delete any item you aren't using to make sure it won't show up
# in your website's metadata. # in your website's metadata.
[params.social] [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> </p>
{{ end }} {{ end }}
{{ 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 }} {{ 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" license = "MIT"
licenselink = "https://github.com/clente/hugo-bearcub/blob/master/LICENSE" 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. # The home page of the theme, where the source can be found.
homepage = "https://github.com/clente/hugo-bearcub" homepage = "https://forge.alexselimov.com/aselimov/hugo-bearcub"
# If you have a running demo of the theme.
demosite = "https://clente.github.io/hugo-bearcub"
tags = ["blog", "responsive", "minimal", "personal", "dark", "multilingual"] tags = ["blog", "responsive", "minimal", "personal", "dark", "multilingual"]
features = ["favicon", "seo", "no javascript", "rss", "social cards"] features = ["favicon", "seo", "no javascript", "rss", "social cards"]
# If the theme has a single author # If the theme has a single author
[author] [author]
name = "Caio Lente" name = "Alex Selimov"
homepage = "https://lente.dev" homepage = "https://alexselimov.com"
# If porting an existing theme
[original] [original]
author = "Jan Raasch" name = "Caio Lente"
homepage = "https://www.janraasch.com" homepage = "https://lente.dev"
repo = "https://github.com/janraasch/hugo-bearblog" repo = "https://github.com/clente/hugo-bearcub"
# Hugo versions the theme supports # Hugo versions the theme supports
[module] [module]
[module.hugoVersion] [module.hugoVersion]
min = "0.90" min = "0.90"