chore: add copy to clipboard button for code snippets

+1 -1
src/components/BlogPost.astro
··· 6 6 } 7 7 8 8 const { title, published_at, slug } = Astro.props; 9 - import Link from "../components/Link.astro"; 9 + import Link from "@components/Link.astro"; 10 10 --- 11 11 12 12 <li>
+16 -16
src/components/Navbar.astro
··· 1 1 --- 2 2 const paths = [ 3 - { name: "home", path: "/" }, 4 - { name: "blogs", path: "/blogs" }, 3 + { name: "home", path: "/" }, 4 + { name: "blogs", path: "/blogs" }, 5 5 ]; 6 - import Link from "../components/Link.astro"; 6 + import Link from "@components/Link.astro"; 7 7 --- 8 8 9 9 <header class="max-w-5xl mx-auto pt-2 md:pt-10 mb-2 px-3 md:px-0"> 10 - <nav> 11 - <ul class="flex gap-2"> 12 - { 13 - paths.map(({ name, path }) => ( 14 - <li> 15 - <Link href={path} extraClasses="font-bold"> 16 - {name} 17 - </Link>{" "} 18 - </li> 19 - )) 20 - } 21 - </ul> 22 - </nav> 10 + <nav> 11 + <ul class="flex gap-2"> 12 + { 13 + paths.map(({ name, path }) => ( 14 + <li> 15 + <Link href={path} extraClasses="font-bold"> 16 + {name} 17 + </Link>{" "} 18 + </li> 19 + )) 20 + } 21 + </ul> 22 + </nav> 23 23 </header>
+17 -17
src/layouts/BlogLayout.astro
··· 1 1 --- 2 2 interface Props { 3 - title: string; 4 - description: string; 3 + title: string; 4 + description: string; 5 5 } 6 6 7 - import Meta from "../components/Meta.astro"; 8 - import Navbar from "../components/Navbar.astro"; 7 + import Meta from "@components/Meta.astro"; 8 + import Navbar from "@components/Navbar.astro"; 9 9 const { title, description } = Astro.props; 10 10 --- 11 11 12 12 <!doctype html> 13 13 <html lang="en" class="font-sans" dir="ltr"> 14 - <Meta title={title} description={description} /> 15 - <body> 16 - <Navbar /> 17 - <main class="max-w-5xl px-3 mx-auto xl:px-0" id="maincontent"> 18 - <h1 class="mt-4 mb-1 text-4xl font-bold"> 19 - {title} 20 - </h1> 21 - <p class="mb-4 text-gray-500">{description}</p> 22 - <div class="pb-6 prose max-w-none"> 23 - <slot /> 24 - </div> 25 - </main> 26 - </body> 14 + <Meta title={title} description={description} /> 15 + <body> 16 + <Navbar /> 17 + <main class="max-w-5xl px-3 mx-auto xl:px-0" id="maincontent"> 18 + <h1 class="mt-4 mb-1 text-4xl font-bold"> 19 + {title} 20 + </h1> 21 + <p class="mb-4 text-gray-500">{description}</p> 22 + <div class="pb-6 prose max-w-none"> 23 + <slot /> 24 + </div> 25 + </main> 26 + </body> 27 27 </html>
+14 -11
src/layouts/Layout.astro
··· 1 1 --- 2 - import Meta from "../components/Meta.astro"; 3 - import Navbar from "../components/Navbar.astro"; 2 + import Meta from "@components/Meta.astro"; 3 + import Navbar from "@components/Navbar.astro"; 4 4 interface Props { 5 - title: string; 6 - description: string; 5 + title: string; 6 + description: string; 7 7 } 8 8 9 9 const { title, description } = Astro.props; ··· 11 11 12 12 <!doctype html> 13 13 <html lang="en" class="font-sans" dir="ltr"> 14 - <Meta title={title} description={description} /> 15 - <body class="bg-snes-light-gray"> 16 - <Navbar /> 17 - <main class="max-w-5xl px-3 mx-auto xl:px-0 pb-5 md:pb-0" id="maincontent"> 18 - <slot /> 19 - </main> 20 - </body> 14 + <Meta title={title} description={description} /> 15 + <body class="bg-snes-light-gray"> 16 + <Navbar /> 17 + <main 18 + class="max-w-5xl px-3 mx-auto xl:px-0 pb-5 md:pb-0" 19 + id="maincontent" 20 + > 21 + <slot /> 22 + </main> 23 + </body> 21 24 </html>
+35 -1
src/pages/blogs/[blog].astro
··· 1 1 --- 2 - import BlogLayout from "../../layouts/BlogLayout.astro"; 2 + import BlogLayout from "@layouts/BlogLayout.astro"; 3 3 import { 4 4 type CollectionEntry, 5 5 getCollection, ··· 22 22 const { Content } = await render(post); 23 23 --- 24 24 25 + <script> 26 + const codeBlock = document.querySelectorAll("pre"); 27 + const copyButton = document.createElement("button"); 28 + copyButton.textContent = "Copy code to clipboard"; 29 + codeBlock.forEach((block) => { 30 + block?.parentNode?.insertBefore(copyButton.cloneNode(true), block); 31 + }); 32 + 33 + const copyButtons = document.querySelectorAll("button"); 34 + 35 + copyButtons.forEach((button) => { 36 + button.addEventListener("click", (event) => { 37 + // @ts-ignore 38 + const code = event.target.nextElementSibling.textContent; 39 + setTimeout(() => { 40 + // @ts-ignore 41 + event.target.textContent = "Copied!"; 42 + setTimeout(() => { 43 + // @ts-ignore 44 + event.target.textContent = "Copy code to clipboard"; 45 + }, 2000); 46 + }, 0); 47 + navigator.clipboard.writeText(code); 48 + }); 49 + }); 50 + </script> 51 + 25 52 <BlogLayout {...post!.data}> 26 53 <Content /> 27 54 </BlogLayout> 28 55 29 56 <style is:global> 30 57 code { 58 + position: relative; 31 59 counter-reset: step; 32 60 counter-increment: step 0; 33 61 } ··· 44 72 45 73 pre:hover .line::before { 46 74 color: rgba(115, 138, 148, 0.5); 75 + } 76 + 77 + button { 78 + font-size: 0.8rem; 79 + text-align: right; 80 + width: 100%; 47 81 } 48 82 </style>
+2 -2
src/pages/blogs/index.astro
··· 1 1 --- 2 - import Layout from "../../layouts/Layout.astro"; 3 - import BlogPost from "../../components/BlogPost.astro"; 2 + import Layout from "@layouts/Layout.astro"; 3 + import BlogPost from "@components/BlogPost.astro"; 4 4 import { getCollection } from "astro:content"; 5 5 6 6 const posts = (await getCollection("blog")).sort(
+2 -2
src/pages/index.astro
··· 1 1 --- 2 2 import { Image } from "astro:assets"; 3 3 import daneImage from "../images/dane.png"; 4 - import Layout from "../layouts/Layout.astro"; 5 - import Link from "../components/Link.astro"; 4 + import Layout from "@layouts/Layout.astro"; 5 + import Link from "@components/Link.astro"; 6 6 7 7 import { getCollection } from "astro:content"; 8 8
+8 -1
tsconfig.json
··· 1 1 { 2 - "extends": "astro/tsconfigs/strict" 2 + "extends": "astro/tsconfigs/strict", 3 + "compilerOptions": { 4 + "baseUrl": ".", 5 + "paths": { 6 + "@components/*": ["src/components/*"], 7 + "@layouts/*": ["src/layouts/*"] 8 + } 9 + } 3 10 }