Add Bookmark button functionality and update blog post layout
This commit introduces a new Bookmark button component along with its corresponding functionality using localStorage. It also updates the blog post layout by adding the Bookmark button, SocialShare, and PostFeedback components as well as restructuring the post tags section for better UI.
This commit is contained in:
parent
ed6d643023
commit
b4f128b6e2
2 changed files with 141 additions and 11 deletions
104
src/components/ui/buttons/Bookmark.astro
Normal file
104
src/components/ui/buttons/Bookmark.astro
Normal file
|
@ -0,0 +1,104 @@
|
|||
<button
|
||||
type="button"
|
||||
class="focus-visible:ring-secondary group inline-flex items-center rounded-lg p-2.5 text-neutral-600 outline-none ring-zinc-500 transition duration-300 hover:bg-neutral-100 focus:outline-none focus-visible:outline-none focus-visible:ring-1 dark:text-neutral-400 dark:ring-zinc-200 dark:hover:bg-neutral-700"
|
||||
data-bookmark-button="bookmark-button"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="h-6 w-6 fill-none transition duration-300"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="fill-current text-neutral-500 transition duration-300 group-hover:text-red-400 group-hover:dark:text-red-400"
|
||||
d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<script>
|
||||
class Bookmark {
|
||||
private static readonly BOOKMARKS_KEY = "bookmarks";
|
||||
private bookmarkButton: Element | null;
|
||||
|
||||
constructor(private dataAttrValue: string) {
|
||||
this.bookmarkButton = document.querySelector(
|
||||
`[data-bookmark-button="${dataAttrValue}"]`
|
||||
);
|
||||
}
|
||||
|
||||
private getStoredBookmarks(): string[] {
|
||||
const item = localStorage.getItem(Bookmark.BOOKMARKS_KEY);
|
||||
return item ? JSON.parse(item) : [];
|
||||
}
|
||||
|
||||
init(): void {
|
||||
if (this.bookmarkButton && this.isStored()) {
|
||||
this.markAsStored();
|
||||
}
|
||||
|
||||
this.bookmarkButton?.addEventListener("click", () =>
|
||||
this.toggleBookmark()
|
||||
);
|
||||
}
|
||||
|
||||
isStored(): boolean {
|
||||
return this.getStoredBookmarks().includes(window.location.pathname);
|
||||
}
|
||||
markAsStored(): void {
|
||||
if (this.bookmarkButton) {
|
||||
this.bookmarkButton.classList.add("bookmarked");
|
||||
let svgElement = this.bookmarkButton.querySelector("svg");
|
||||
if (svgElement) {
|
||||
svgElement.setAttribute(
|
||||
"class",
|
||||
"h-6 w-6 fill-red-500 dark:fill-red-500"
|
||||
);
|
||||
}
|
||||
let pathElement = svgElement?.querySelector("path");
|
||||
if (pathElement) {
|
||||
pathElement.setAttribute(
|
||||
"class",
|
||||
"fill-current text-red-500 dark:text-red-500"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
unmarkAsStored(): void {
|
||||
if (this.bookmarkButton) {
|
||||
this.bookmarkButton.classList.remove("bookmarked");
|
||||
let svgElement = this.bookmarkButton.querySelector("svg");
|
||||
if (svgElement) {
|
||||
svgElement.setAttribute("class", "h-6 w-6 fill-none");
|
||||
}
|
||||
let pathElement = svgElement?.querySelector("path");
|
||||
if (pathElement) {
|
||||
pathElement.setAttribute(
|
||||
"class",
|
||||
"fill-current text-neutral-500 group-hover:text-red-400 dark:text-neutral-500 group-hover:dark:text-red-400"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
toggleBookmark(): void {
|
||||
let storedBookmarks = this.getStoredBookmarks();
|
||||
const index = storedBookmarks.indexOf(window.location.pathname);
|
||||
if (index !== -1) {
|
||||
storedBookmarks.splice(index, 1);
|
||||
this.unmarkAsStored();
|
||||
} else {
|
||||
storedBookmarks.push(window.location.pathname);
|
||||
this.markAsStored();
|
||||
}
|
||||
localStorage.setItem(
|
||||
Bookmark.BOOKMARKS_KEY,
|
||||
JSON.stringify(storedBookmarks)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
new Bookmark("bookmark-button").init();
|
||||
</script>
|
|
@ -3,6 +3,9 @@
|
|||
import MainLayout from "../../layouts/MainLayout.astro";
|
||||
import AvatarBlogLarge from "../../components/ui/avatars/AvatarBlogLarge.astro";
|
||||
import CardRelated from "../../components/ui/cards/CardRelated.astro";
|
||||
import Bookmark from "../../components/ui/buttons/Bookmark.astro";
|
||||
import SocialShare from "../../components/ui/buttons/SocialShare.astro";
|
||||
import PostFeedback from "../../components/ui/feedback/PostFeedback.astro";
|
||||
import { Image } from "astro:assets";
|
||||
import { capitalize, formatDate } from "../../utils/utils";
|
||||
import { getCollection } from "astro:content";
|
||||
|
@ -27,7 +30,7 @@ const blogPosts: CollectionEntry<"blog">[] = await getCollection("blog");
|
|||
// In a production site, you might want to implement a more robust algorithm, choosing related posts based on tags, categories, dates, authors, or keywords.
|
||||
// See example: https://blog.codybrunner.com/2024/adding-related-articles-with-astro-content-collections/
|
||||
const relatedPosts: CollectionEntry<"blog">[] = blogPosts.filter(
|
||||
(blogEntry) => blogEntry.slug !== post.slug,
|
||||
(blogEntry) => blogEntry.slug !== post.slug
|
||||
);
|
||||
---
|
||||
|
||||
|
@ -94,22 +97,45 @@ const relatedPosts: CollectionEntry<"blog">[] = blogPosts.filter(
|
|||
<p class="text-pretty text-lg text-neutral-700 dark:text-neutral-300">
|
||||
{content}
|
||||
</p>
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<!--Blog post tags-->
|
||||
<div class="space-x-2">
|
||||
{
|
||||
post.data.tags?.map((tag: string) => (
|
||||
<span class="inline-flex items-center gap-x-1.5 rounded-lg bg-neutral-400/30 px-3 py-1.5 text-xs font-medium text-neutral-700 outline-none focus:outline-none focus-visible:outline-none focus-visible:ring dark:bg-neutral-700/60 dark:text-neutral-300">
|
||||
{capitalize(tag)}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
<div
|
||||
class="mx-auto grid max-w-screen-lg gap-y-5 sm:flex sm:items-center sm:justify-between sm:gap-y-0"
|
||||
>
|
||||
<!--Blog post tags-->
|
||||
<div
|
||||
class="flex flex-wrap gap-x-2 gap-y-1 sm:flex-nowrap sm:items-center sm:gap-y-0"
|
||||
>
|
||||
{
|
||||
post.data.tags?.map((tag: string, index) => (
|
||||
<span class="inline-flex items-center gap-x-1.5 rounded-lg bg-neutral-400/30 px-3 py-1.5 text-xs font-medium text-neutral-700 outline-none focus:outline-none focus-visible:outline-none focus-visible:ring dark:bg-neutral-700/60 dark:text-neutral-300">
|
||||
{capitalize(tag)}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<!--Bookmark and Share buttons-->
|
||||
<div class="flex items-center justify-end gap-x-1.5">
|
||||
<Bookmark />
|
||||
<div
|
||||
class="mx-3 block h-4 border-e border-neutral-400 dark:border-neutral-500"
|
||||
>
|
||||
</div>
|
||||
<div class="inline-flex">
|
||||
<SocialShare pageTitle={post.data.title} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PostFeedback
|
||||
title="Was this post helpful?"
|
||||
firstChoice="Yes"
|
||||
secondChoice="No"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<!--Related articles section-->
|
||||
<section class="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
|
||||
<div class="mb-10 max-w-2xl">
|
||||
|
|
Loading…
Add table
Reference in a new issue