-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: comments section powered by
utterances
- Loading branch information
Showing
2 changed files
with
155 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { type Component, createEffect } from "solid-js"; | ||
import theme from "../utils/theme.ts"; | ||
|
||
export const Comments: Component<{}> = () => { | ||
const [isDarkTheme, _] = theme; | ||
let sectionRef: HTMLDivElement; | ||
const utterances = () => { | ||
const utterancesScript = document.createElement("script"); | ||
utterancesScript.src = "https://utteranc.es/client.js"; | ||
utterancesScript.setAttribute("repo", "eatsteak/eatsteak.dev"); | ||
utterancesScript.setAttribute("issue-term", "pathname"); | ||
utterancesScript.setAttribute("label", "blog"); | ||
utterancesScript.setAttribute( | ||
"theme", | ||
isDarkTheme() ? "dark-blue" : "github-light", | ||
); | ||
utterancesScript.crossOrigin = "anonymous"; | ||
utterancesScript.async = true; | ||
return utterancesScript; | ||
}; | ||
createEffect(() => { | ||
if (sectionRef) { | ||
sectionRef.innerHTML = ""; | ||
sectionRef.appendChild(utterances()); | ||
} | ||
}, [isDarkTheme]); | ||
return <section class="comments" ref={sectionRef}></section>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,148 +1,155 @@ | ||
--- | ||
import { getCollection, type CollectionEntry } from "astro:content"; | ||
import {getCollection, type CollectionEntry} from "astro:content"; | ||
import Layout from "./Layout.astro"; | ||
import { Profile } from "../components/Profile"; | ||
import { FormattedDate } from "../components/FormattedDate"; | ||
import { Topics } from "../components/Topics"; | ||
import { CategoryLabel } from "../components/CategoryLabel"; | ||
import {Profile} from "../components/Profile"; | ||
import {FormattedDate} from "../components/FormattedDate"; | ||
import {Topics} from "../components/Topics"; | ||
import {CategoryLabel} from "../components/CategoryLabel"; | ||
import "../styles/prose.css"; | ||
import { Counter } from "../components/Counter"; | ||
import { CATEGORIES } from "../consts"; | ||
import {Counter} from "../components/Counter"; | ||
import {Comments} from "../components/Comments"; | ||
import {CATEGORIES} from "../consts"; | ||
import PostItem from "../components/PostItem.astro"; | ||
import { getReadingTime } from "../utils/reading-time"; | ||
import { MinutesRead } from "../components/MinutesRead"; | ||
import type { MarkdownHeading } from "astro"; | ||
import { TableOfContents } from "../components/TableOfContents"; | ||
import {getReadingTime} from "../utils/reading-time"; | ||
import {MinutesRead} from "../components/MinutesRead"; | ||
import type {MarkdownHeading} from "astro"; | ||
import {TableOfContents} from "../components/TableOfContents"; | ||
type Props = CollectionEntry<"blog">["data"] & { | ||
slug: string; | ||
minutesRead: number | undefined; | ||
headings: MarkdownHeading[]; | ||
slug: string; | ||
minutesRead: number | undefined; | ||
headings: MarkdownHeading[]; | ||
}; | ||
const { | ||
slug, | ||
title, | ||
description, | ||
pubDate, | ||
updatedDate, | ||
heroImage, | ||
category, | ||
topics, | ||
minutesRead, | ||
headings, | ||
slug, | ||
title, | ||
description, | ||
pubDate, | ||
updatedDate, | ||
heroImage, | ||
category, | ||
topics, | ||
minutesRead, | ||
headings, | ||
} = Astro.props; | ||
const allPosts = (await getCollection("blog")).sort( | ||
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(), | ||
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(), | ||
); | ||
const recentPostsInCategory = allPosts | ||
.filter((post) => post.data.hidden !== true && post.slug !== slug) | ||
.filter( | ||
(post) => | ||
(CATEGORIES[post.data.category ?? "uncategorized"]?.id ?? | ||
"uncategorized") === (category ?? "uncategorized"), | ||
) | ||
.slice(0, 3); | ||
.filter((post) => post.data.hidden !== true && post.slug !== slug) | ||
.filter( | ||
(post) => | ||
(CATEGORIES[post.data.category ?? "uncategorized"]?.id ?? | ||
"uncategorized") === (category ?? "uncategorized"), | ||
) | ||
.slice(0, 3); | ||
function buildToc(headings: MarkdownHeading[]): TocHeading[] { | ||
const toc: TocHeading[] = []; | ||
const parentHeadings = new Map(); | ||
headings.forEach((h) => { | ||
const heading = { ...h, subheadings: [] }; | ||
parentHeadings.set(heading.depth, heading); | ||
if (heading.depth === 2) { | ||
toc.push(heading); | ||
} else { | ||
parentHeadings.get(heading.depth - 1).subheadings.push(heading); | ||
} | ||
}); | ||
return toc; | ||
const toc: TocHeading[] = []; | ||
const parentHeadings = new Map(); | ||
headings.forEach((h) => { | ||
const heading = {...h, subheadings: []}; | ||
parentHeadings.set(heading.depth, heading); | ||
if (heading.depth === 2) { | ||
toc.push(heading); | ||
} else { | ||
parentHeadings.get(heading.depth - 1).subheadings.push(heading); | ||
} | ||
}); | ||
return toc; | ||
} | ||
const toc = buildToc(headings); | ||
--- | ||
|
||
<Layout | ||
title={`${title} - EATSTEAK.DEV`} | ||
description={description} | ||
image={`/card/og/${slug}.png`} | ||
title={`${title} - EATSTEAK.DEV`} | ||
description={description} | ||
image={`/card/og/${slug}.png`} | ||
> | ||
<main class="flex flex-col w-full"> | ||
<section | ||
class="self-center flex justify-center items-stretch w-full p-8 xl:p-0" | ||
> | ||
<article class="self-center w-full max-w-screen-md pb-16"> | ||
<section class="mb-2 image min-h-[16rem]"> | ||
{heroImage && <img class="max-w-100%" src={heroImage} alt="" />} | ||
</section> | ||
<section class="prose"> | ||
<div class="title mb-8"> | ||
<CategoryLabel category={category ?? "uncategorized"} /> | ||
<h1>{title}</h1> | ||
<div class="flex flex-wrap line-between-flex-items"> | ||
<div class="date text-gray-500"> | ||
<FormattedDate dateTime={pubDate} client:only="solid-js" /> | ||
{ | ||
updatedDate && ( | ||
<span> | ||
<main class="flex flex-col w-full"> | ||
<section | ||
class="self-center flex justify-center items-stretch w-full p-8 xl:p-0" | ||
> | ||
<article class="self-center w-full max-w-screen-md pb-16"> | ||
<section class="mb-2 image min-h-[16rem]"> | ||
{heroImage && <img class="max-w-100%" src={heroImage} alt=""/>} | ||
</section> | ||
<section class="prose"> | ||
<div class="title mb-8"> | ||
<CategoryLabel category={category ?? "uncategorized"}/> | ||
<h1>{title}</h1> | ||
<div class="flex flex-wrap line-between-flex-items"> | ||
<div class="date text-gray-500"> | ||
<FormattedDate dateTime={pubDate} client:only="solid-js"/> | ||
{ | ||
updatedDate && ( | ||
<span> | ||
게시, <FormattedDate | ||
dateTime={updatedDate} | ||
client:only="solid-js" | ||
/>{" "} | ||
수정 | ||
dateTime={updatedDate} | ||
client:only="solid-js" | ||
/> | ||
{" "} | ||
수정 | ||
</span> | ||
) | ||
} | ||
</div> | ||
{minutesRead && <MinutesRead minutesRead={minutesRead} />} | ||
<Counter path={Astro.url.pathname} client:only="solid-js" /> | ||
</div> | ||
{ | ||
topics && ( | ||
<div class="topics mt-2 mb-8"> | ||
<Topics topics={topics} /> | ||
) | ||
} | ||
</div> | ||
{minutesRead && | ||
<MinutesRead minutesRead={minutesRead}/>} | ||
<Counter path={Astro.url.pathname} client:only="solid-js"/> | ||
</div> | ||
{ | ||
topics && ( | ||
<div class="topics mt-2 mb-8"> | ||
<Topics topics={topics}/> | ||
</div> | ||
) | ||
} | ||
</div> | ||
<div class="content"> | ||
<slot/> | ||
</div> | ||
</section> | ||
<section class="pt-8"> | ||
<hr/> | ||
<Comments client:only="solid-js"/> | ||
</section> | ||
</article> | ||
<section class="hidden self-stretch lg:block pt-[16rem] max-w-xs"> | ||
<div class="py-8 pl-8 sticky top-0"> | ||
<TableOfContents tocHeadings={toc} client:only="solid-js"/> | ||
</div> | ||
</section> | ||
</section> | ||
<section | ||
class="py-16 flex flex-col items-center bg-slate-50 dark:bg-slate-900 transition-colors" | ||
> | ||
<div class="flex flex-col gap-8 w-full max-w-screen-lg p-8 xl:p-0"> | ||
<div class="w-full"> | ||
<Profile/> | ||
</div> | ||
<div class="w-full"> | ||
<h3>같은 분류의 최신 글</h3> | ||
<div class="mt-4 flex flex-col gap-2"> | ||
{ | ||
recentPostsInCategory.map((post) => ( | ||
<PostItem | ||
post={post} | ||
minutesRead={getReadingTime(post.body)} | ||
/> | ||
)) | ||
} | ||
{ | ||
recentPostsInCategory.length === 0 && ( | ||
<p class="mx-auto my-8">읽을 수 있는 글이 없습니다.</p> | ||
) | ||
} | ||
</div> | ||
</div> | ||
) | ||
} | ||
</div> | ||
<div class="content"> | ||
<slot /> | ||
</div> | ||
</div> | ||
</section> | ||
</article> | ||
<section class="hidden self-stretch lg:block pt-[16rem] max-w-xs"> | ||
<div class="py-8 pl-8 sticky top-0"> | ||
<TableOfContents tocHeadings={toc} client:only="solid-js" /> | ||
</div> | ||
</section> | ||
</section> | ||
<section | ||
class="py-16 flex flex-col items-center bg-slate-50 dark:bg-slate-900 transition-colors" | ||
> | ||
<div class="flex flex-col gap-8 w-full max-w-screen-lg p-8 xl:p-0"> | ||
<div class="w-full px-1"> | ||
<Profile /> | ||
</div> | ||
<div class="w-full"> | ||
<h3>같은 분류의 최신 글</h3> | ||
<div class="mt-4 flex flex-col gap-2"> | ||
{ | ||
recentPostsInCategory.map((post) => ( | ||
<PostItem | ||
post={post} | ||
minutesRead={getReadingTime(post.body)} | ||
/> | ||
)) | ||
} | ||
{ | ||
recentPostsInCategory.length === 0 && ( | ||
<p class="mx-auto my-8">읽을 수 있는 글이 없습니다.</p> | ||
) | ||
} | ||
</div> | ||
</div> | ||
</div> | ||
</section> | ||
</main> | ||
</main> | ||
</Layout> |