Add shareable URLs for posts with copy link button

Hash-based URLs (#resepti/slug) allow sharing individual posts on social media.
Modal shows "Kopioi linkki" button, hash auto-opens post on page load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 14:31:10 +02:00
parent 0989bbe650
commit 734b67cbc7

View File

@@ -84,6 +84,16 @@ function applyTranslations() {
// =========================== // ===========================
// HELPERS // HELPERS
// =========================== // ===========================
function postSlug(title) {
return title.toLowerCase()
.replace(/[äå]/g, 'a').replace(/ö/g, 'o').replace(/ü/g, 'u')
.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
}
function findPostBySlug(slug) {
return APP.posts.find(p => postSlug(p.title) === slug);
}
function sortSubcategories(subs) { function sortSubcategories(subs) {
return [...subs].sort((a, b) => { return [...subs].sort((a, b) => {
const aM = a.fi.toLowerCase() === 'muut' ? 1 : 0; const aM = a.fi.toLowerCase() === 'muut' ? 1 : 0;
@@ -343,6 +353,7 @@ async function openPost(id) {
<div class="modal-like-row"> <div class="modal-like-row">
<button class="like-btn ${liked ? 'liked' : ''}" data-like-id="${p.id}" onclick="toggleLike('${p.id}')">${liked ? t('liked_btn') : t('like_btn')}</button> <button class="like-btn ${liked ? 'liked' : ''}" data-like-id="${p.id}" onclick="toggleLike('${p.id}')">${liked ? t('liked_btn') : t('like_btn')}</button>
<span class="like-count" data-like-count="${p.id}">${likeCount}</span> <span class="like-count" data-like-count="${p.id}">${likeCount}</span>
<button class="copy-link-btn" onclick="copyPostLink('${postSlug(p.title)}')" style="margin-left:auto;padding:4px 14px;font-size:0.82rem;border:1px solid #ccc;background:#fff;border-radius:20px;cursor:pointer;">🔗 Kopioi linkki</button>
</div> </div>
${bodyHTML} ${bodyHTML}
<div class="comments-section"> <div class="comments-section">
@@ -365,6 +376,9 @@ async function openPost(id) {
document.getElementById('modalOverlay').classList.add('open'); document.getElementById('modalOverlay').classList.add('open');
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
// Update URL hash for shareable link
history.replaceState(null, '', '#resepti/' + postSlug(p.title));
const qEl = document.getElementById('captcha-question'); const qEl = document.getElementById('captcha-question');
if (qEl) qEl.textContent = generateCaptcha(); if (qEl) qEl.textContent = generateCaptcha();
} }
@@ -420,9 +434,19 @@ async function submitComment(e, postId) {
if (headEl) headEl.textContent = `${t('modal_comments')} (${comments.length})`; if (headEl) headEl.textContent = `${t('modal_comments')} (${comments.length})`;
} }
function copyPostLink(slug) {
const url = window.location.origin + window.location.pathname + '#resepti/' + slug;
navigator.clipboard.writeText(url).then(() => {
const btn = document.querySelector('.copy-link-btn');
if (btn) { btn.textContent = '✓ Kopioitu!'; setTimeout(() => { btn.textContent = '🔗 Kopioi linkki'; }, 2000); }
});
}
function closeModal() { function closeModal() {
document.getElementById('modalOverlay').classList.remove('open'); document.getElementById('modalOverlay').classList.remove('open');
document.body.style.overflow = ''; document.body.style.overflow = '';
// Clear URL hash
history.replaceState(null, '', window.location.pathname + window.location.search);
} }
// =========================== // ===========================
@@ -896,6 +920,26 @@ async function init() {
renderCategoryFilters(); renderCategoryFilters();
renderSubFilters(); renderSubFilters();
renderCards(); renderCards();
// Open post from URL hash (e.g. #resepti/pinaattiletut)
const hash = window.location.hash;
if (hash.startsWith('#resepti/')) {
const slug = hash.slice('#resepti/'.length);
const post = findPostBySlug(slug);
if (post) openPost(post.id);
}
} }
// Handle browser back/forward with hash
window.addEventListener('hashchange', () => {
const hash = window.location.hash;
if (hash.startsWith('#resepti/')) {
const slug = hash.slice('#resepti/'.length);
const post = findPostBySlug(slug);
if (post) openPost(post.id);
} else {
closeModal();
}
});
init(); init();