feat: add save-for-later buttons to frontend pages
Add shared save-later.js module and per-item save buttons to blogroll, podroll, listening, and news pages. Buttons are hidden by default and only visible when logged in. Posts to the readlater plugin API at /readlater/save.
This commit is contained in:
@@ -453,6 +453,8 @@
|
||||
<script src="/js/webmentions.js?v={{ '/js/webmentions.js' | hash }}" defer></script>
|
||||
{# Admin auth detection - shows dashboard link + FAB when logged in #}
|
||||
<script src="/js/admin.js?v={{ '/js/admin.js' | hash }}" defer></script>
|
||||
{# Save for Later buttons — active when logged in #}
|
||||
<script src="/js/save-later.js?v={{ '/js/save-later.js' | hash }}" defer></script>
|
||||
|
||||
{# Floating Action Button - visible only when logged in #}
|
||||
<div x-data="{ show: false, open: false }"
|
||||
|
||||
11
blogroll.njk
11
blogroll.njk
@@ -176,6 +176,17 @@ permalink: /blogroll/
|
||||
</svg>
|
||||
Visit Blog
|
||||
</a>
|
||||
<button
|
||||
class="save-later-btn"
|
||||
:data-save-url="item.url"
|
||||
:data-save-title="item.title"
|
||||
data-save-source="blogroll"
|
||||
title="Save for later"
|
||||
aria-label="Save for later"
|
||||
>
|
||||
<span class="save-later-icon">📑</span>
|
||||
<span class="save-later-label">Save</span>
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
@@ -741,3 +741,33 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Save for Later buttons — hidden until auth confirmed */
|
||||
.save-later-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body[data-indiekit-auth="true"] .save-later-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
padding: 2px 8px;
|
||||
font-size: 0.75rem;
|
||||
color: #6b7280;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
body[data-indiekit-auth="true"] .save-later-btn:hover {
|
||||
border-color: #d1d5db;
|
||||
color: #4a9eff;
|
||||
}
|
||||
|
||||
.save-later--saved {
|
||||
color: #4a9eff !important;
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
51
js/save-later.js
Normal file
51
js/save-later.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Save for Later — shared frontend module
|
||||
* Handles save button clicks on blogroll, podroll, listening, and news pages.
|
||||
* Only active when user is logged in (body[data-indiekit-auth="true"]).
|
||||
*/
|
||||
(function () {
|
||||
function isLoggedIn() {
|
||||
return document.body.getAttribute('data-indiekit-auth') === 'true';
|
||||
}
|
||||
|
||||
async function saveForLater(button) {
|
||||
var url = button.dataset.saveUrl;
|
||||
var title = button.dataset.saveTitle || url;
|
||||
var source = button.dataset.saveSource || 'manual';
|
||||
if (!url) return;
|
||||
|
||||
button.disabled = true;
|
||||
|
||||
try {
|
||||
var response = await fetch('/readlater/save', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: url, title: title, source: source }),
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
button.classList.add('save-later--saved');
|
||||
button.title = 'Saved';
|
||||
button.setAttribute('aria-label', 'Saved');
|
||||
var label = button.querySelector('.save-later-label');
|
||||
if (label) label.textContent = 'Saved';
|
||||
var icon = button.querySelector('.save-later-icon');
|
||||
if (icon) icon.textContent = '🔖';
|
||||
} else {
|
||||
button.disabled = false;
|
||||
}
|
||||
} catch (e) {
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!isLoggedIn()) return;
|
||||
var button = e.target.closest('.save-later-btn');
|
||||
if (button) {
|
||||
e.preventDefault();
|
||||
saveForLater(button);
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -295,6 +295,16 @@ withSidebar: true
|
||||
<div class="text-right flex-shrink-0">
|
||||
<span class="inline-block px-2 py-0.5 text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-700 dark:text-purple-400 rounded-full mb-1">Funkwhale</span>
|
||||
<span class="text-xs text-surface-500 block">{{ listening.relativeTime }}</span>
|
||||
<button
|
||||
class="save-later-btn mt-1"
|
||||
data-save-url="{{ listening.trackUrl }}"
|
||||
data-save-title="{{ listening.track }} — {{ listening.artist }}"
|
||||
data-save-source="listening"
|
||||
title="Save for later"
|
||||
aria-label="Save for later"
|
||||
>
|
||||
<span class="save-later-icon">📑</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -333,6 +343,16 @@ withSidebar: true
|
||||
<div class="text-right flex-shrink-0">
|
||||
<span class="inline-block px-2 py-0.5 text-xs font-medium bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400 rounded-full mb-1">Last.fm</span>
|
||||
<span class="text-xs text-surface-500 block">{{ scrobble.relativeTime }}</span>
|
||||
<button
|
||||
class="save-later-btn mt-1"
|
||||
data-save-url="{{ scrobble.trackUrl }}"
|
||||
data-save-title="{{ scrobble.track }} — {{ scrobble.artist }}"
|
||||
data-save-source="listening"
|
||||
title="Save for later"
|
||||
aria-label="Save for later"
|
||||
>
|
||||
<span class="save-later-icon">📑</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -381,6 +401,16 @@ withSidebar: true
|
||||
</div>
|
||||
|
||||
<span class="text-red-500 flex-shrink-0">♥</span>
|
||||
<button
|
||||
class="save-later-btn flex-shrink-0"
|
||||
data-save-url="{{ track.trackUrl }}"
|
||||
data-save-title="{{ track.track }} — {{ track.artist }}"
|
||||
data-save-source="listening"
|
||||
title="Save for later"
|
||||
aria-label="Save for later"
|
||||
>
|
||||
<span class="save-later-icon">📑</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -424,6 +454,16 @@ withSidebar: true
|
||||
<p class="text-xs text-surface-500 truncate">{{ favorite.album }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<button
|
||||
class="save-later-btn flex-shrink-0"
|
||||
data-save-url="{{ favorite.trackUrl }}"
|
||||
data-save-title="{{ favorite.track }} — {{ favorite.artist }}"
|
||||
data-save-source="listening"
|
||||
title="Save for later"
|
||||
aria-label="Save for later"
|
||||
>
|
||||
<span class="save-later-icon">📑</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
33
news.njk
33
news.njk
@@ -160,6 +160,17 @@ withSidebar: true
|
||||
<span class="text-primary-600 dark:text-primary-400" x-text="'#' + cat"></span>
|
||||
</template>
|
||||
</span>
|
||||
<button
|
||||
class="save-later-btn"
|
||||
:data-save-url="item.link"
|
||||
:data-save-title="item.title"
|
||||
data-save-source="news"
|
||||
title="Save for later"
|
||||
aria-label="Save for later"
|
||||
>
|
||||
<span class="save-later-icon">📑</span>
|
||||
<span class="save-later-label">Save</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -193,6 +204,17 @@ withSidebar: true
|
||||
<span class="truncate max-w-[60%]" x-text="truncate(item.sourceTitle || item.feedTitle, 20)"></span>
|
||||
<time :datetime="item.pubDate" x-text="formatDate(item.pubDate)"></time>
|
||||
</div>
|
||||
<button
|
||||
class="save-later-btn mt-2"
|
||||
:data-save-url="item.link"
|
||||
:data-save-title="item.title"
|
||||
data-save-source="news"
|
||||
title="Save for later"
|
||||
aria-label="Save for later"
|
||||
>
|
||||
<span class="save-later-icon">📑</span>
|
||||
<span class="save-later-label">Save</span>
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
@@ -249,6 +271,17 @@ withSidebar: true
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
||||
</svg>
|
||||
</a>
|
||||
<button
|
||||
class="save-later-btn"
|
||||
:data-save-url="item.link"
|
||||
:data-save-title="item.title"
|
||||
data-save-source="news"
|
||||
title="Save for later"
|
||||
aria-label="Save for later"
|
||||
>
|
||||
<span class="save-later-icon">📑</span>
|
||||
<span class="save-later-label">Save for Later</span>
|
||||
</button>
|
||||
<div x-show="item.categories?.length" class="flex flex-wrap gap-2">
|
||||
<template x-for="cat in item.categories" :key="cat">
|
||||
<span class="px-2 py-1 text-xs bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400 rounded-full" x-text="cat"></span>
|
||||
|
||||
11
podroll.njk
11
podroll.njk
@@ -144,6 +144,17 @@ permalink: /podroll/
|
||||
</svg>
|
||||
RSS
|
||||
</a>
|
||||
<button
|
||||
class="save-later-btn"
|
||||
:data-save-url="episode.url"
|
||||
:data-save-title="episode.title"
|
||||
data-save-source="podroll"
|
||||
title="Save for later"
|
||||
aria-label="Save for later"
|
||||
>
|
||||
<span class="save-later-icon">📑</span>
|
||||
<span class="save-later-label">Save</span>
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user