feat: enhance blog post view counter with individual tracking and display
This commit is contained in:
13
src/blog.njk
13
src/blog.njk
@@ -9,15 +9,20 @@ eleventyExcludeFromCollections: true
|
|||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
{%- for post in collections.general | reverse -%}
|
{%- for post in collections.general | reverse -%}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="text-2xl font-bold">
|
<h2 class="text-2xl font-bold mb-1">
|
||||||
<a href="{{ post.url }}" class="text-blue-400 hover:underline">{{ post.data.title }}</a>
|
<a href="{{ post.url }}" class="text-blue-400 hover:underline">{{ post.data.title }}</a>
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-gray-400 mb-4">
|
<div class="flex items-center text-gray-400 mb-4">
|
||||||
{{ post.date | readableDate }}
|
<i class="fas fa-calendar mr-2"></i>
|
||||||
</p>
|
<p>{{ post.date | readableDate }}</p>
|
||||||
|
<span class="mx-2 text-gray-600">|</span>
|
||||||
|
<i class="fas fa-eye mr-2"></i>
|
||||||
|
<span class="view-count" data-view-count data-slug="{{ post.fileSlug }}">...</span>
|
||||||
|
</div>
|
||||||
{% if post.data.excerpt %}
|
{% if post.data.excerpt %}
|
||||||
<p class="text-gray-300">{{ post.data.excerpt }}</p>
|
<p class="text-gray-300">{{ post.data.excerpt }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
</div>
|
</div>
|
||||||
|
<script src="/js/view-counter.js"></script>
|
||||||
|
@@ -3,8 +3,8 @@ title: "Junos ZTP with Dnsmasq DHCP in OPNsense"
|
|||||||
date: "2025-08-14"
|
date: "2025-08-14"
|
||||||
layout: "post.njk"
|
layout: "post.njk"
|
||||||
tags: "general"
|
tags: "general"
|
||||||
description: "Stop configuring your new lab devices manually!"
|
description: "Stop configuring your new lab devices manually! This guide will show you how to 'Zero Touch' provision Junos devices!"
|
||||||
excerpt: "Stop configuring your new lab devices manually! This guide will show you how to 'Zero Touch' provision Junos devices!"
|
excerpt: "Stop configuring your new lab devices manually!"
|
||||||
sitemapPriority: 0.8
|
sitemapPriority: 0.8
|
||||||
sitemapChangefreq: "weekly"
|
sitemapChangefreq: "weekly"
|
||||||
image: "/assets/images/8.14.25/junos+opnsense.jpg"
|
image: "/assets/images/8.14.25/junos+opnsense.jpg"
|
||||||
|
@@ -1,37 +1,70 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const viewCountElement = document.querySelector('[data-view-count]');
|
// Select all elements that are designated to display view counts.
|
||||||
|
const viewCountElements = document.querySelectorAll('[data-view-count]');
|
||||||
|
|
||||||
if (viewCountElement) {
|
// If no such elements are found, do nothing.
|
||||||
const slug = viewCountElement.dataset.slug;
|
if (viewCountElements.length === 0) {
|
||||||
|
return;
|
||||||
// Function to fetch and display the view count
|
|
||||||
const getViews = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/api/views/${slug}`);
|
|
||||||
if (!response.ok) throw new Error('Failed to fetch views');
|
|
||||||
const data = await response.json();
|
|
||||||
viewCountElement.textContent = `${data.count} views`;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting views:', error);
|
|
||||||
viewCountElement.textContent = 'Could not load views';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to increment the view count
|
|
||||||
const incrementView = async () => {
|
|
||||||
try {
|
|
||||||
// Use a flag in localStorage to prevent incrementing on every refresh
|
|
||||||
const viewed = localStorage.getItem(`viewed-${slug}`);
|
|
||||||
if (!viewed) {
|
|
||||||
await fetch(`/api/views/${slug}`, { method: 'POST' });
|
|
||||||
localStorage.setItem(`viewed-${slug}`, 'true');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error incrementing view:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Run the functions
|
|
||||||
incrementView().then(getViews);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// Process each view count element found on the page.
|
||||||
|
viewCountElements.forEach(element => {
|
||||||
|
const slug = element.dataset.slug;
|
||||||
|
if (!slug) {
|
||||||
|
// Skip if the element is missing the data-slug attribute.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the element is on a single post page by looking for a `.post-meta` parent.
|
||||||
|
// This distinguishes a single post view (which should be counted) from a list view.
|
||||||
|
const isSinglePost = element.closest('.post-meta');
|
||||||
|
|
||||||
|
if (isSinglePost) {
|
||||||
|
// On a single post page, increment the view count.
|
||||||
|
const viewed = localStorage.getItem(`viewed-${slug}`);
|
||||||
|
|
||||||
|
// Only increment if the user hasn't viewed this post in the current session.
|
||||||
|
if (!viewed) {
|
||||||
|
fetch(`/api/views/${slug}`, { method: 'POST' })
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) throw new Error('Network response was not ok');
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
element.textContent = data.count ?? 'Error';
|
||||||
|
// Mark as viewed to prevent re-counting on refresh.
|
||||||
|
localStorage.setItem(`viewed-${slug}`, 'true');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`Error incrementing view count for slug ${slug}:`, error);
|
||||||
|
element.textContent = 'N/A';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If already viewed in this session, just fetch the count without incrementing.
|
||||||
|
fetch(`/api/views/${slug}`)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) throw new Error('Network response was not ok');
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
element.textContent = data.count ?? 0;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`Error fetching view count for slug ${slug}:`, error);
|
||||||
|
element.textContent = 'N/A';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// On a list page (like the main blog page), just fetch and display the count.
|
||||||
|
fetch(`/api/views/${slug}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
element.textContent = data.count ?? 0;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`Error fetching view count for slug ${slug}:`, error);
|
||||||
|
element.textContent = 'N/A';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user