Compare commits

..

18 commits

Author SHA1 Message Date
fd4db04d3a
Updated hugo way 2025-06-15 22:32:52 -04:00
cc623bd90a
Error handling 2025-06-15 22:23:49 -04:00
0df997d594
Load initial high five count 2025-06-11 22:24:00 -04:00
487cd8cbd7
Only go off of the global count variable 2025-06-11 22:19:56 -04:00
9f57be5587
Fixed 409 error count 2025-06-11 21:57:33 -04:00
51b780c021
Make nojs friendly, and fix already logged state 2025-06-11 21:49:39 -04:00
0007b4241b
Blog high five feature 2025-06-11 21:26:31 -04:00
831dd9e10a
Removed observation layout, and modified inaturalist embed 2025-06-10 21:49:00 -04:00
bcbfa17bb4
Removed toots from overall theme 2025-06-10 21:29:49 -04:00
d507426ff0
List tags by year 2025-05-09 20:25:25 -04:00
3bf669ac6a
Add background/text color to <head> to prevent color flash 2025-04-22 12:15:49 -04:00
be207e0df3
Fixed HTML errors 2025-03-28 23:23:23 -04:00
525b28e98b
Update to Hugo v0.143.1 2025-02-16 11:03:28 -05:00
615e03bca3
Reworked blog footer 2025-01-05 20:53:19 -05:00
60b013a30f
Footer style edit 2025-01-05 13:29:24 -05:00
d5bd28436a
Removed social list in footer 2025-01-05 13:24:09 -05:00
6c189eb8f2
Added verification to contact form 2024-12-30 19:27:51 -05:00
bd7e11b421
Removed avatar as blog post photo 2024-12-29 10:53:14 -05:00
28 changed files with 381 additions and 234 deletions

View file

@ -404,6 +404,11 @@ footer a:link, footer a:visited, footer a:hover, footer a:active {
text-align: left; text-align: left;
} }
.footer-nav {
column-count: 2;
column-gap: 2rem;
}
/* END FOOTER */ /* END FOOTER */
/* /*

View file

@ -3,6 +3,6 @@
{{ partial "head.html" . }} {{ partial "head.html" . }}
<body> <body>
{{ block "main" . }}{{ end }} {{ block "main" . }}{{ end }}
{{ partial "footer.html" . }}
</body> </body>
{{ partial "footer.html" . }}
</html> </html>

View file

@ -3,7 +3,7 @@
"url": "{{ .Permalink }}", "url": "{{ .Permalink }}",
"title": {{ .Title | jsonify }}, "title": {{ .Title | jsonify }},
"authors": [ "authors": [
{{with $.Site.Author.name }} {{with $.Site.Params.Author }}
{ "name": "{{ . }}" } { "name": "{{ . }}" }
{{ end }} {{ end }}
], ],

View file

@ -3,14 +3,14 @@
{ {
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
"actor": { "actor": {
{{with $.Site.Author.name }}"name": "{{ . }}",{{ end }} {{with $.Site.Params.Author }}"name": "{{ . }}",{{ end }}
"url": "{{ .Site.BaseURL }}" "url": "{{ .Site.BaseURL }}"
}, },
"id": "{{ $page.Permalink }}", "id": "{{ $page.Permalink }}",
"object": { "object": {
"attributedTo": [ "attributedTo": [
{ {
{{with $.Site.Author.name }}"name": "{{ . }}",{{ end }} {{with $.Site.Params.Author }}"name": "{{ . }}",{{ end }}
"type": "Person", "type": "Person",
"url": "{{ .Site.BaseURL }}" "url": "{{ .Site.BaseURL }}"
} }

View file

@ -18,7 +18,7 @@
<generator uri="https://gohugo.io">Hugo</generator> <generator uri="https://gohugo.io">Hugo</generator>
<author> <author>
<uri>{{ .Site.BaseURL }}</uri> <uri>{{ .Site.BaseURL }}</uri>
{{ with .Site.Author.name }}<name>{{.}}</name>{{end}} {{ with .Site.Params.Author }}<name>{{.}}</name>{{end}}
</author> </author>
<updated>{{ now.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated> <updated>{{ now.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>
{{ with .OutputFormats.Get "Atom" }} {{ with .OutputFormats.Get "Atom" }}
@ -29,7 +29,7 @@
<author> <author>
<activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type> <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
<uri>{{ .Site.BaseURL }}</uri> <uri>{{ .Site.BaseURL }}</uri>
{{ with .Site.Author.name }}<name>{{.}}</name>{{end}} {{ with .Site.Params.Author }}<name>{{.}}</name>{{end}}
</author> </author>
<activity:object-type>http://activitystrea.ms/schema/1.0/article</activity:object-type> <activity:object-type>http://activitystrea.ms/schema/1.0/article</activity:object-type>
<title>{{ .Title }}</title> <title>{{ .Title }}</title>

View file

@ -7,7 +7,7 @@
"icon": "{{ .Site.BaseURL }}img/{{ .Site.Params.avatar }}", "icon": "{{ .Site.BaseURL }}img/{{ .Site.Params.avatar }}",
"language": "en-US", "language": "en-US",
"authors": [ "authors": [
{{with $.Site.Author.name }} {{with $.Site.Params.Author }}
{ "name": "{{ . }}" } { "name": "{{ . }}" }
{{ end }} {{ end }}
], ],

View file

@ -21,9 +21,9 @@
</image> </image>
<description>{{ .Description }}</description> <description>{{ .Description }}</description>
<generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }} <generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
<language>{{.}}</language>{{end}}{{ with .Site.Author.email }} <language>{{.}}</language>{{end}}{{ with .Site.Params.email }}
<managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }} <managingEditor>{{.}}{{ with $.Site.Params.Author }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Params.email }}
<webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }} <webMaster>{{.}}{{ with $.Site.Params.Author }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }} <copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }} <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{ with .OutputFormats.Get "RSS" }} {{ with .OutputFormats.Get "RSS" }}
@ -34,7 +34,7 @@
<title>{{ .Title }}</title> <title>{{ .Title }}</title>
<link>{{ .Permalink }}</link> <link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate> <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}} {{ with .Site.Params.email }}<author>{{.}}{{ with $.Site.Params.Author }} ({{.}}){{end}}</author>{{end}}
<guid>{{ .Permalink }}</guid> <guid>{{ .Permalink }}</guid>
{{ printf `<description><![CDATA[%s]]></description>` .Summary | safeHTML }} {{ printf `<description><![CDATA[%s]]></description>` .Summary | safeHTML }}
{{ printf `<content:encoded><![CDATA[%s]]></content:encoded>` .Content | safeHTML }} {{ printf `<content:encoded><![CDATA[%s]]></content:encoded>` .Content | safeHTML }}

View file

@ -10,7 +10,7 @@
{{ range last 1 $posts }} {{ range last 1 $posts }}
{{ $tags := .Site.Taxonomies.tags }} {{ $tags := .Site.Taxonomies.tags }}
<div class='bloglist-teaser block-center'> <div class='bloglist-teaser block-center'>
Hello! This page contains <strong>{{ $postCount | lang.NumFmt 0 }} posts</strong> Hello! This page contains <strong>{{ $postCount | lang.FormatNumber 0 }} posts</strong>
written in the past <strong>{{ div (sub now.Unix .Date.Unix) 31536000 }} years</strong> written in the past <strong>{{ div (sub now.Unix .Date.Unix) 31536000 }} years</strong>
spanning spanning
{{ len $tags }} topics within the dropdown above. {{ len $tags }} topics within the dropdown above.

View file

@ -36,9 +36,7 @@
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "NewsArticle", "@type": "NewsArticle",
"headline": "{{ .Title }}", "headline": "{{ .Title }}",
"image": [ "image": [],
"{{ .Site.BaseURL }}img/{{ .Site.Params.avatar }}"
],
"datePublished": "{{ .Date.Format "2006-01-02" }}", "datePublished": "{{ .Date.Format "2006-01-02" }}",
"author": [{ "author": [{
"@type": "Person", "@type": "Person",

View file

@ -25,13 +25,21 @@
</div> </div>
</article> </article>
<div class="noprint" style="margin-bottom: 2rem; display: flex; justify-content: center; flex-wrap: wrap; gap: 20px;"> <hr style="margin-bottom: 3rem;"/>
<a href='mailto:{{ .Site.Params.email }}?subject=Re: "{{ .Title }}"' class="reply-button">Reply via Email</a>
<a href="/support" class="reply-button" style="margin-left:2rem;"><i class="fas fa-coffee" style="margin-right: 0.5rem;"></i>Buy me a Coffee</a> <div class="noprint" style="text-align: left;">
<p>Have any questions or want to chat: <a href='mailto:{{ .Site.Params.email }}?subject=Re: "{{ .Title }}"' class="reply-button"><i class="fa fa-paper-plane" style="margin-right: 0.5rem;"></i>Reply via Email</a></p>
<p>Enjoyed this post?</p>
<ul style="list-style: none;">
<li>Support: <a href="/support"><i class="fas fa-coffee" style="margin-right: 0.5rem;"></i>Buy me a Coffee</a></li>
<li>{{ partial "sharepost.html" . }}</li>
</ul>
{{ partial "highfive.html" . }}
{{ partial "webmentions.html" . }}
</div> </div>
{{ partial "sharepost.html" . }}
{{ partial "webmentions.html" . }}
</main> </main>
<!-- Metadata --> <!-- Metadata -->
<script type="application/ld+json"> <script type="application/ld+json">
@ -39,9 +47,7 @@
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "NewsArticle", "@type": "NewsArticle",
"headline": "{{ .Title }}", "headline": "{{ .Title }}",
"image": [ "image": [],
"{{ .Site.BaseURL }}img/{{ .Site.Params.avatar }}"
],
"datePublished": "{{ .Date.Format "2006-01-02" }}", "datePublished": "{{ .Date.Format "2006-01-02" }}",
"author": [{ "author": [{
"@type": "Person", "@type": "Person",

View file

@ -1,39 +0,0 @@
{{ define "main"}}
{{ partial "header.html" . }}
<main>
<style>main { text-align: left; }</style>
{{ $posts := where site.RegularPages "Type" "observations" }}
{{ $postCount := len $posts }}
{{ range last 1 $posts }}
{{ $scratch := newScratch }}
{{ $scratch.Set "researchGradeCount" 0 }}
{{ range .Pages }}
{{ if (eq .quality_grade "research")}}
{{ $scratch.Add "researchGradeCount" 1 }}
{{ end }}
{{ end }}
{{ $tags := .Site.Taxonomies.tags }}
<div style="text-align: center; margin-bottom: 2rem;" class='bloglist-teaser block-center'>
Hello! This page contains {{ $postCount | lang.NumFmt 0 }} observations,
{{ len $tags }} of which are <strong><a href="https://www.inaturalist.org/posts/39072-research-grade">research grade</a></strong>.
</div>
{{ end }}
{{ .Content }}
{{ $listPageDateFormat := .Site.Params.listPageDateFormat | default "January, 2006"}}
{{ range $index, $value := .Pages.GroupByDate $listPageDateFormat }}
{{ range .Pages }}
{{ $dataJ := dict "created_at" .Date
"url" .Params.syndication
"quality_grade" .Params.quality_grade
"place_guess" .Params.place_guess
"photos" .Params.photos
"taxon" .Params.taxon
}}
{{ .Scratch.Set "obs" $dataJ }}
{{ .Scratch.Set "obs_url" .Permalink }}
{{ partial "external/inat.html" . }}
{{ end }}
{{ end }}
</main>
{{ end }}

View file

@ -1,39 +0,0 @@
{{ define "main"}}
{{ .Scratch.Set "customTitleHeaderSet" true }}
{{ .Scratch.Set "customTitleHeader" "Observations" }}
{{ .Scratch.Set "customTitleHeaderLink" "/observations/" }}
{{ partial "header.html" . }}
<main>
{{ with .Params }}
<article class="h-entry">
{{ $name := "" }}
{{ if (ne .taxon.common_name "") }}
{{ $name = .taxon.common_name }}
{{ else }}
{{ $name = .taxon.name }}
{{ end }}
{{ $obsURL := .url }}
<h1 class='title p-name'>{{ title $name }}</h1>
{{ if (eq .quality_grade "research")}}
<span>Research Grade </span><i class="fa fa-solid fa-clipboard-check"></i></i>
<br/>
{{ end }}
<p class="date">Observed on <time class="dt-published" datetime='{{ .date }}'>{{ dateFormat "January 2, 2006 15:04" (time .date) }}</time></p>
<p>Location: {{ .place_guess }}</p>
<span>Also on: <a class="u-syndication" href="{{ .syndication }}">iNaturalist</a></span>
<div class="e-content">
{{ if gt (len .photos) 0 }}
{{ range .photos}}
{{ $medium_url := replace .url "square" "medium"}}
{{ $original_url := replace .url "square" "original"}}
<a href="{{ $original_url }}"><img src="{{ $medium_url }}" alt="Photo of {{ .species_guess }}"/></a>
{{ end }}
{{ end }}
</div>
<a class="u-url" style="display: none">{{ .Permalink }}</a>
</article>
{{ end }}
</main>
{{ end }}

View file

@ -6,7 +6,7 @@
<article class="toot h-entry"> <article class="toot h-entry">
{{ $name := "" }} {{ $name := "" }}
{{ if (ne .taxon.common_name "") }} {{ if (ne .taxon.common_name "") }}
{{ $name = .taxon.common_name }} {{ $name = .taxon.preferred_common_name }}
{{ else }} {{ else }}
{{ $name = .taxon.name }} {{ $name = .taxon.name }}
{{ end }} {{ end }}

View file

@ -1,31 +0,0 @@
<!-- https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/twitter_cards.html -->
{{- with $.Params.images -}}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="{{ index . 0 | absURL }}">
{{ else -}}
{{- $images := $.Resources.ByType "image" -}}
{{- $featured := $images.GetMatch "*feature*" -}}
{{- if not $featured }}{{ $featured = $images.GetMatch "{*cover*,*thumbnail*}" }}{{ end -}}
{{- with $featured -}}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="{{ $featured.Permalink }}">
{{- else -}}
{{- with $.Site.Params.images -}}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="{{ index . 0 | absURL }}">
{{ else -}}
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="{{ .Site.BaseURL }}img/{{ .Site.Params.avatar }}">
{{- end -}}
{{- end -}}
{{- end }}
<meta name="twitter:title" content="{{ .Title }}">
<meta name="twitter:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end -}}">
{{ with .Site.Social.twitter -}}
<meta name="twitter:site" content="@{{ . }}">
{{ end -}}
{{ range .Site.Authors }}
{{ with .twitter -}}
<meta name="twitter:creator" content="@{{ . }}">
{{ end -}}
{{ end -}}

View file

@ -16,15 +16,6 @@
</ul> </ul>
</nav> </nav>
<nav>
<p>Social</p>
<ul>
{{ range .Site.Menus.profile }}
<li><a href="{{ .URL }}" rel="me" aria-label="{{ .Identifier }}">{{ .Name }}</a></li>
{{ end }}
</ul>
</nav>
<nav> <nav>
<p>More Pages</p> <p>More Pages</p>
<ul> <ul>

View file

@ -7,7 +7,7 @@
<div class="footer-navs block-center"> <div class="footer-navs block-center">
<nav> <nav>
<p>Content Pages</p> <p>Content Pages</p>
<ul id="footer-nav-sections"> <ul class="footer-nav">
{{ range .Site.Sections }} {{ range .Site.Sections }}
{{ if not .Params.hidden }} {{ if not .Params.hidden }}
<li><a href="{{ .Permalink }}">{{ .Title }}</a></li> <li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
@ -16,18 +16,9 @@
</ul> </ul>
</nav> </nav>
<nav>
<p>Social</p>
<ul>
{{ range .Site.Menus.profile }}
<li><a href="{{ .URL }}" rel="me" aria-label="{{ .Identifier }}">{{ .Name }}</a></li>
{{ end }}
</ul>
</nav>
<nav> <nav>
<p>More Pages</p> <p>More Pages</p>
<ul> <ul class="footer-nav">
{{ $sectionPages := where .Site.Pages "Section" "" }} {{ $sectionPages := where .Site.Pages "Section" "" }}
{{ range $sectionPages }} {{ range $sectionPages }}
{{ if and (not .Params.hidden) (ne .Permalink .Site.BaseURL)}} {{ if and (not .Params.hidden) (ne .Permalink .Site.BaseURL)}}

View file

@ -57,7 +57,6 @@
{{/* NOTE: These Hugo Internal Templates can be found starting at https://github.com/gohugoio/hugo/tree/master/tpl/tplimpl/embedded/templates */}} {{/* NOTE: These Hugo Internal Templates can be found starting at https://github.com/gohugoio/hugo/tree/master/tpl/tplimpl/embedded/templates */}}
{{- template "_internal/opengraph.html" . -}} {{- template "_internal/opengraph.html" . -}}
{{- template "_internal/schema.html" . -}} {{- template "_internal/schema.html" . -}}
{{- partial "external/twitter_cards.html" . -}}
{{ partial "citation.html" . }} {{ partial "citation.html" . }}

View file

@ -42,6 +42,23 @@
{{ end -}} {{ end -}}
<!-- CSS --> <!-- CSS -->
<!-- Immediately show background/text color to prevent flashing white -->
<style>
:root {
--bg-color: #faf9f6;
--text-color: #020101;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #0e0e0e;
--text-color: #8f8f8f;
}
}
</style>
{{- $style := resources.Get "css/style.css" -}} {{- $style := resources.Get "css/style.css" -}}
{{- $markdown := resources.Get "css/markdown.css" -}} {{- $markdown := resources.Get "css/markdown.css" -}}
{{- $fontawesome := resources.Get "css/fontawesome.css" -}} {{- $fontawesome := resources.Get "css/fontawesome.css" -}}
@ -62,8 +79,6 @@
{{/* NOTE: These Hugo Internal Templates can be found starting at https://github.com/gohugoio/hugo/tree/master/tpl/tplimpl/embedded/templates */}} {{/* NOTE: These Hugo Internal Templates can be found starting at https://github.com/gohugoio/hugo/tree/master/tpl/tplimpl/embedded/templates */}}
{{- template "_internal/opengraph.html" . -}} {{- template "_internal/opengraph.html" . -}}
{{- template "_internal/schema.html" . -}} {{- template "_internal/schema.html" . -}}
<!-- {{- template "_internal/twitter_cards.html" . -}} -->
{{- partial "external/twitter_cards.html" . -}}
{{ partial "citation.html" . }} {{ partial "citation.html" . }}

View file

@ -0,0 +1,238 @@
<style>
.highfive-container {
margin: 1rem 0;
}
.highfive-message {
margin-left: 1rem;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.9em;
display: none;
}
.highfive-message.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.highfive-message.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.highfive-btn {
transition: opacity 0.2s ease;
}
.highfive-btn.no-js {
opacity: 0.6;
pointer-events: none;
cursor: not-allowed;
}
</style>
{{ $apiUrl := printf "https://api.brandonrozek.com/highfive%s" .RelPermalink }}
{{ $initialCount := 0 }}
{{ with try (resources.GetRemote $apiUrl) }}
{{ with .Err }}
{{ warnf "Failed to fetch highfive data from %s" $apiUrl }}
{{ else }}
{{ $resource := .Value }}
{{ $highfiveData := $resource | transform.Unmarshal }}
{{ if $highfiveData.count }}
{{ $initialCount = $highfiveData.count }}
{{ end }}
{{ end }}
{{ end }}
<div class="highfive-container">
<p>
Give me a high five (it's free):
<a href="#" id="highfive-btn" class="reply-button highfive-btn no-js" role="button" aria-label="Give high five">
<i class="fa fa-hand-paper" style="margin-right: 0.5rem;" aria-hidden="true"></i>(<span id="highfive-count" aria-live="polite">{{ $initialCount }}</span>)
</a>
<span id="highfive-message" class="highfive-message" role="status" aria-live="polite"></span>
</p>
</div>
<script>
(function() {
'use strict';
// Configuration
const CONFIG = {
API_BASE: 'https://api.brandonrozek.com/highfive',
TIMEOUTS: {
SUCCESS_MESSAGE: 3000,
ERROR_MESSAGE: 3000
},
SELECTORS: {
BUTTON: '#highfive-btn',
COUNT: '#highfive-count',
MESSAGE: '#highfive-message'
}
};
// DOM elements
const elements = {};
// State
let isLoading = false;
let currentCount = {{ $initialCount }};
/**
* Initialize the high five functionality
*/
function init() {
// Cache DOM elements
elements.button = document.querySelector(CONFIG.SELECTORS.BUTTON);
elements.count = document.querySelector(CONFIG.SELECTORS.COUNT);
elements.message = document.querySelector(CONFIG.SELECTORS.MESSAGE);
if (!elements.button || !elements.count || !elements.message) {
console.error('High five: Required DOM elements not found');
return;
}
// Enable button now that JavaScript is available
elements.button.classList.remove('no-js');
elements.button.addEventListener('click', handleHighFiveClick);
// Load initial count
loadHighFiveCount();
}
/**
* Make API request with error handling
*/
async function makeApiRequest(method = 'GET') {
const response = await fetch(`${CONFIG.API_BASE}${window.location.pathname}`, {
method,
mode: 'cors',
headers: { 'Content-Type': 'application/json' }
});
// Handle 409 Conflict (already high-fived) as a special case
if (response.status === 409) {
const data = await response.json();
return { ...data, alreadyHighFived: true };
}
if (response.status === 400) {
return {count: 0}
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
}
/**
* Load current high five count
*/
async function loadHighFiveCount() {
try {
const data = await makeApiRequest('GET');
currentCount = data.count || currentCount;
updateButtonContent('hand-paper');
} catch (error) {
console.error('Failed to load high five count:', error);
updateButtonContent('hand-paper');
}
}
/**
* Handle high five button click
*/
async function handleHighFiveClick(event) {
event.preventDefault();
if (isLoading) return;
setButtonState('loading');
try {
const data = await makeApiRequest('POST');
const newCount = data.count || currentCount;
currentCount = newCount;
// Check if user already gave a high five (HTTP 409 response)
if (data.alreadyHighFived) {
updateButtonContent('check');
showMessage('👋 Already high-fived!', 'success');
setButtonState('disabled');
} else {
updateButtonContent('check');
showMessage('🎉 Thanks!', 'success');
setButtonState('disabled');
}
} catch (error) {
console.error('Failed to send high five:', error);
showMessage('❌ Failed to send', 'error', CONFIG.TIMEOUTS.ERROR_MESSAGE);
updateButtonContent('hand-paper');
setButtonState('disabled');
setTimeout(() => {
setButtonState('enabled');
}, CONFIG.TIMEOUTS.ERROR_MESSAGE);
}
}
/**
* Update button content with icon and count
*/
function updateButtonContent(icon) {
elements.count.textContent = currentCount;
const iconClass = icon === 'loading' ? 'fa-spinner fa-spin' : `fa-${icon}`;
const text = icon === 'loading' ? 'Sending...' : `(${currentCount})`;
elements.button.innerHTML = `<i class="fa ${iconClass}" style="margin-right: 0.5rem;" aria-hidden="true"></i>${text}`;
}
/**
* Set button state (enabled, disabled, loading)
*/
function setButtonState(state) {
isLoading = state === 'loading';
const isInteractive = state === 'enabled';
elements.button.style.pointerEvents = isInteractive ? 'auto' : 'none';
elements.button.style.opacity = isInteractive ? '1' : '0.6';
if (state === 'loading') {
updateButtonContent('loading');
}
}
/**
* Show message with optional auto-hide
*/
function showMessage(text, type, timeout) {
elements.message.textContent = text;
elements.message.className = `highfive-message ${type}`;
elements.message.style.display = 'inline';
if (timeout) {
setTimeout(() => {
elements.message.style.display = 'none';
elements.message.className = 'highfive-message';
}, timeout);
}
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>

View file

@ -1,5 +1,4 @@
<div class="noprint" style="margin-bottom: 1rem;">Was this useful? Feel free to share: Share:
<a href="https://news.ycombinator.com/submitlink?u={{ .Permalink }}&t={{ .Title }}">Hacker News</a> <a href="https://news.ycombinator.com/submitlink?u={{ .Permalink }}&t={{ .Title }}">Hacker News</a>
<a href="https://www.reddit.com/%73%75%62%6d%69%74?url={{ .Permalink }}&title={{ .Title }}">Reddit</a> <a href="https://www.reddit.com/%73%75%62%6d%69%74?url={{ .Permalink }}&title={{ .Title }}">Reddit</a>
<a href="https://twitter.com/intent/tweet?url={{ .Permalink }}&text={{ .Title }}">Twitter</a> <a href="https://twitter.com/intent/tweet?url={{ .Permalink }}&text={{ .Title }}">Twitter</a>
</div>

View file

@ -7,7 +7,6 @@
<input type="hidden" name="target" value="{{ .Permalink }}"> <input type="hidden" name="target" value="{{ .Permalink }}">
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
</main>
{{ $webmentionjs := resources.Get "js/webmention.min.js" }} {{ $webmentionjs := resources.Get "js/webmention.min.js" }}
{{ $aliasesCombined := delimit (apply .Aliases "absURL" ".") "|" }} {{ $aliasesCombined := delimit (apply .Aliases "absURL" ".") "|" }}
<script src="{{ $webmentionjs.Permalink }}" {{ printf "add-urls=%s" $aliasesCombined | safeHTMLAttr }} async></script> <script src="{{ $webmentionjs.Permalink }}" {{ printf "add-urls=%s" $aliasesCombined | safeHTMLAttr }} async></script>

View file

@ -1,7 +1,17 @@
{{ $url := urls.Parse (.Get 0) }} {{ $url := urls.Parse (.Get 0) }}
{{ $status_id := index (last 1 (split $url.Path "/")) 0 }} {{ $status_id := index (last 1 (split $url.Path "/")) 0 }}
{{ $api_url := printf "%s://%s/api/v1/statuses/%s" $url.Scheme $url.Host $status_id }} {{ $api_url := printf "%s://%s/api/v1/statuses/%s" $url.Scheme $url.Host $status_id }}
{{ $dataJ := getJSON $api_url }} {{ $dataJ := dict }}
{{ $url := $api_url }}
{{ with try (resources.GetRemote $url) }}
{{ with .Err }}
{{ errorf "Unable to get remote resource %s: %s" $url . }}
{{ else with .Value }}
{{ $dataJ = . | transform.Unmarshal }}
{{ else }}
{{ errorf "Unable to get remote resource %s" $url }}
{{ end }}
{{ end }}
{{ .Scratch.Set "toot" $dataJ }} {{ .Scratch.Set "toot" $dataJ }}
{{ partial "external/toot.html" . }} {{ partial "external/toot.html" . }}

View file

@ -1,6 +1,15 @@
{{ $toot_reference := .Get 0 }} {{ $toot_reference := .Get 0 }}
{{ $url := printf "static/data/toots/%s.json" $toot_reference }} {{ $url := printf "data/toots/%s.json" $toot_reference }}
{{ $dataJ := getJSON $url }} {{ $dataJ := dict }}
{{ with try (resources.Get $url) }}
{{ with .Err }}
{{ errorf "Unable to get resource %s: %s" $url . }}
{{ else with .Value }}
{{ $dataJ = . | transform.Unmarshal }}
{{ else }}
{{ errorf "Unable to get resource %s" $url }}
{{ end }}
{{ end }}
w
{{ .Scratch.Set "toot" $dataJ }} {{ .Scratch.Set "toot" $dataJ }}
{{ partial "external/toot.html" . }} {{ partial "external/toot.html" . }}

View file

@ -6,26 +6,34 @@
Encrypted Result: Encrypted Result:
<pre id="pgpresult" class="pgpform"></pre> <pre id="pgpresult" class="pgpform"></pre>
<script> <script>
function encrypt() { async function encrypt() {
let textarea = document.querySelector("#pgpcleartext");
let resultarea = document.querySelector('#pgpresult'); let resultarea = document.querySelector('#pgpresult');
let pubKeyURL = "{{ .Get 0 }}";
resultarea.textContent = ""; resultarea.textContent = "";
fetch(pubKeyURL).then(function(response) {
return response.text().then(function(text) { let textarea = document.querySelector("#pgpcleartext");
const pubKey = openpgp.readKey({ armoredKey: text }); if (textarea.value.length == 0) {
const message = openpgp.createMessage({ text: textarea.value }) return;
return Promise.all([message, pubKey]).then(function(mp) { }
const encryptionParameters = {
message: mp[0], let pubKeyURL = "{{ .Get 0 }}";
encryptionKeys: mp[1] let pubKey;
};
return openpgp.encrypt(encryptionParameters).then(function(encryptedMessage) { try {
resultarea.textContent = encryptedMessage; const response = await fetch(pubKeyURL);
}); const text = await response.text();
}); pubKey = await openpgp.readKey({ armoredKey: text });
}); } catch {
}); resultarea.textContent = "Error: Unable to obtain key";
}
try {
const message = await openpgp.createMessage({ text: textarea.value });
const encryptedMessage = await openpgp.encrypt({message: message, encryptionKeys: pubKey});
resultarea.textContent = encryptedMessage;
} catch {
resultarea.textContent = "Error: Unable to encrypt message"
}
} }
function genEmail() { function genEmail() {

View file

@ -0,0 +1,39 @@
{{- $openPGP := resources.Get "js/openpgp.min.js" -}}
<script src="{{ $openPGP.Permalink }}"></script>
<textarea id="pgpsignedtext" class="pgpform" style="width: 100%; min-height: 10rem;"></textarea>
<button class="pgpverifybutton" onclick="verify()">Verify</button>
<br/>
Verification Result:
<pre id="pgpverifyresult" class="pgpform"></pre>
<script>
async function verify() {
let resultarea = document.querySelector('#pgpverifyresult');
resultarea.textContent = "";
let textarea = document.querySelector("#pgpsignedtext");
if (textarea.value.length == 0) {
return;
}
let pubKeyURL = "{{ .Get 0 }}";
let pubKey;
try {
const response = await fetch(pubKeyURL);
const text = await response.text();
pubKey = await openpgp.readKey({ armoredKey: text });
} catch {
resultarea.textContent = "Error: Unable to obtain key";
}
try {
const message = await openpgp.readCleartextMessage({ cleartextMessage: textarea.value });
const verifyResult = await openpgp.verify({message: message, verificationKeys: pubKey});
const validResult = await verifyResult.signatures[0].verified;
resultarea.textContent = (validResult)? "Valid": "Invalid";
} catch {
resultarea.textContent = "Invalid";
}
}
</script>

View file

@ -2,12 +2,12 @@
{{ partial "header.html" . }} {{ partial "header.html" . }}
<main> <main>
<div class='bloglist-teaser block-center' style="margin-top: 2rem;"> <div class='bloglist-teaser block-center' style="margin-top: 2rem;">
Here you'll find <strong>{{ len .Pages | lang.NumFmt 0 }} posts</strong> Here you'll find <strong>{{ len .Pages | lang.FormatNumber 0 }} posts</strong>
about <u>{{ .Title }}</u> </strong>. about <u>{{ .Title }}</u> </strong>.
</div> </div>
<!-- contents --> <!-- contents -->
<div class="bloglist"> <div class="bloglist">
{{ $listPageDateFormat := .Site.Params.listPageDateFormat | default "January, 2006"}} {{ $listPageDateFormat := .Site.Params.listPageDateFormat | default "2006"}}
{{ range $index, $value := .Pages.GroupByDate $listPageDateFormat }} {{ range $index, $value := .Pages.GroupByDate $listPageDateFormat }}
{{ if (ne $index 0) }} {{ if (ne $index 0) }}
<br/> <br/>

View file

@ -1,28 +0,0 @@
{{ define "main"}}
{{ partial "header.html" . }}
<main>
<style>main { text-align: left; }</style>
{{ partial "search.html" . }}
{{ .Content }}
{{ $listPageDateFormat := .Site.Params.listPageDateFormat | default "January, 2006"}}
{{ range $index, $value := .Pages.GroupByDate $listPageDateFormat }}
{{ range .Pages }}
{{ $dataJ := dict "account" .Params.account
"account.display_name" .Params.account.display_name
"created_at" .Date
"content" .Content
"media_attachments" .Params.media_attachments
"Permalink" .Permalink
"tags" .Params.tags
"url" .Params.syndication
"replies_count" .Params.replies_count
"reblogs_count" .Params.reblogs_count
"favourites_count" .Params.favourites_count
}}
{{ .Scratch.Set "toot" $dataJ }}
{{ partial "external/toot.html" . }}
{{ end }}
{{ end }}
</main>
{{ end }}

View file

@ -1,23 +0,0 @@
{{ define "main"}}
{{ .Scratch.Set "customTitleHeaderSet" true }}
{{ .Scratch.Set "customTitleHeader" "Toots" }}
{{ .Scratch.Set "customTitleHeaderLink" "/toots" }}
{{ partial "header.html" . }}
<main>
{{ $dataJ := dict "account" .Params.account
"account.display_name" .Params.account.display_name
"created_at" .Date
"content" .Content
"media_attachments" .Params.media_attachments
"Permalink" .Permalink
"tags" .Params.tags
"url" .Params.syndication
"replies_count" .Params.replies_count
"reblogs_count" .Params.reblogs_count
"favourites_count" .Params.favourites_count
}}
{{ .Scratch.Set "toot" $dataJ }}
{{ partial "external/toot.html" . }}
</main>
{{ end }}