Compare commits
9 commits
2a390f1224
...
17c78a6bcd
| Author | SHA1 | Date | |
|---|---|---|---|
| 17c78a6bcd | |||
| 556c1725ab | |||
| ecceb7afa9 | |||
| 665da9bec6 | |||
| 41c217030c | |||
| 460fe6a820 | |||
| d8834d83b7 | |||
| e896111497 | |||
| 87bc37e124 |
8 changed files with 153 additions and 33 deletions
28
README.md
28
README.md
|
|
@ -1,13 +1,24 @@
|
||||||
# ᕦʕ •ᴥ•ʔᕤ Bear Cub
|
# ʕ •ᴥ•ʔつ🗳️ Voting Bear Cub
|
||||||
|
|
||||||
[](https://github.com/clente/hugo-bearcub/actions/workflows/gh-pages.yml)
|
[](https://forge.alexselimov.com/aselimov/hugo-bearcub/src/branch/main/LICENSE)
|
||||||
[](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:
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
3
go.mod
|
|
@ -1,3 +0,0 @@
|
||||||
module hugo-bearcub
|
|
||||||
|
|
||||||
go 1.26.2
|
|
||||||
|
|
@ -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">⌃</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
37
static/upvote.js
Normal 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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
28
theme.toml
28
theme.toml
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue