diff --git a/bun.lock b/bun.lock index db9a513..91469e2 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,7 @@ "name": "daleui", "dependencies": { "@fontsource-variable/jetbrains-mono": "^5.2.8", + "@giscus/react": "^3.1.0", "@mdx-js/react": "^3.1.1", "@react-router/node": "^7.15.1", "browserslist": "^4.28.2", @@ -265,6 +266,8 @@ "@fortawesome/fontawesome-free": ["@fortawesome/fontawesome-free@6.7.2", "", {}, "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA=="], + "@giscus/react": ["@giscus/react@3.1.0", "", { "dependencies": { "giscus": "^1.6.0" }, "peerDependencies": { "react": "^16 || ^17 || ^18 || ^19", "react-dom": "^16 || ^17 || ^18 || ^19" } }, "sha512-0TCO2TvL43+oOdyVVGHDItwxD1UMKP2ZYpT6gXmhFOqfAJtZxTzJ9hkn34iAF/b6YzyJ4Um89QIt9z/ajmAEeg=="], + "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], @@ -343,6 +346,10 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@lit-labs/ssr-dom-shim": ["@lit-labs/ssr-dom-shim@1.6.0", "", {}, "sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ=="], + + "@lit/reactive-element": ["@lit/reactive-element@2.1.2", "", { "dependencies": { "@lit-labs/ssr-dom-shim": "^1.5.0" } }, "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A=="], + "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], "@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="], @@ -1309,6 +1316,8 @@ "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + "giscus": ["giscus@1.6.0", "", { "dependencies": { "lit": "^3.2.1" } }, "sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ=="], + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], @@ -1551,6 +1560,12 @@ "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + "lit": ["lit@3.3.3", "", { "dependencies": { "@lit/reactive-element": "^2.1.0", "lit-element": "^4.2.0", "lit-html": "^3.3.0" } }, "sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw=="], + + "lit-element": ["lit-element@4.2.2", "", { "dependencies": { "@lit-labs/ssr-dom-shim": "^1.5.0", "@lit/reactive-element": "^2.1.0", "lit-html": "^3.3.0" } }, "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w=="], + + "lit-html": ["lit-html@3.3.3", "", { "dependencies": { "@types/trusted-types": "^2.0.2" } }, "sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA=="], + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], diff --git a/package.json b/package.json index 9a0d0ba..53369c6 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@fontsource-variable/jetbrains-mono": "^5.2.8", + "@giscus/react": "^3.1.0", "@mdx-js/react": "^3.1.1", "@react-router/node": "^7.15.1", "browserslist": "^4.28.2", diff --git a/src/components/Giscus.tsx b/src/components/Giscus.tsx new file mode 100644 index 0000000..2f04995 --- /dev/null +++ b/src/components/Giscus.tsx @@ -0,0 +1,36 @@ +import GiscusWidget from "@giscus/react"; +import { useTheme } from "../hooks/useTheme"; +import { giscusConfig } from "../constants/giscus"; + +interface GiscusProps { + /** 댓글을 붙일 글의 discussion 번호(= slug). */ + term: string; +} + +/** + * giscus 댓글 위젯. + * + * 스크립트 주입·iframe 생명주기·테마/글 변경 시 setConfig 전달은 공식 + * `@giscus/react`가 맡는다. 여기서는 설정값(constants/giscus)과 현재 테마만 넘긴다. + * + * 블로그 글 slug가 곧 "블로그" 카테고리 Discussion 번호라 mapping=number로 연결한다. + */ +export function Giscus({ term }: GiscusProps) { + const { isDark } = useTheme(); + + return ( + + ); +} diff --git a/src/constants/giscus.test.ts b/src/constants/giscus.test.ts new file mode 100644 index 0000000..c0bcc79 --- /dev/null +++ b/src/constants/giscus.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vitest"; +import { giscusConfig } from "./giscus"; + +describe("giscusConfig", () => { + it("repo·category 기본값을 노출한다", () => { + expect(giscusConfig.repo).toBe("DaleStudy/daleui"); + expect(giscusConfig.category).toBe("블로그"); + }); + + it("repo-id·category-id를 함께 노출한다", () => { + expect(giscusConfig.repoId).toBeTruthy(); + expect(giscusConfig.categoryId).toBeTruthy(); + }); +}); diff --git a/src/constants/giscus.ts b/src/constants/giscus.ts new file mode 100644 index 0000000..9cfc8d8 --- /dev/null +++ b/src/constants/giscus.ts @@ -0,0 +1,9 @@ +// giscus 댓글 위젯 설정값. 모두 공개돼도 되는 값이라 그대로 하드코딩한다. +// 블로그 글은 DaleStudy/daleui "블로그" 카테고리 Discussion에서 오고 slug가 곧 +// discussion 번호라, mapping=number로 글마다 원본 토론에 그대로 연결한다. +export const giscusConfig = { + repo: "DaleStudy/daleui" as `${string}/${string}`, + repoId: "R_kgDOMovgOA", + category: "블로그", + categoryId: "DIC_kwDOMovgOM4CqB1o", +} as const; diff --git a/src/routes/blog.$slug.tsx b/src/routes/blog.$slug.tsx index 35553d7..9a2e371 100644 --- a/src/routes/blog.$slug.tsx +++ b/src/routes/blog.$slug.tsx @@ -5,6 +5,7 @@ import type { Route } from "./+types/blog.$slug"; import { css } from "../../styled-system/css"; import { findBlog, listBlog } from "../content/blog/loader"; import { UserProfile } from "../components/UserProfile"; +import { Giscus } from "../components/Giscus"; import DirectionalLink from "../components/DirectionalLink"; import { Navigation } from "../sections/blog/Navigation"; @@ -152,6 +153,10 @@ export default function BlogSlug({ loaderData }: Route.ComponentProps) { )} + + + +