diff --git a/src/components/TopBar.astro b/src/components/TopBar.astro index 9fd9207..0ed1942 100644 --- a/src/components/TopBar.astro +++ b/src/components/TopBar.astro @@ -1,6 +1,5 @@ --- import LanguageSelector from './LanguageSelector.astro' -import GitHubAuth from './GitHubAuth.astro' import { t } from '../i18n/index.ts' interface Props { @@ -65,7 +64,6 @@ const { lang, currentPath, labels } = Astro.props > - diff --git a/src/components/discussion/Discussion.tsx b/src/components/discussion/Discussion.tsx index 77eae45..9ed7d45 100644 --- a/src/components/discussion/Discussion.tsx +++ b/src/components/discussion/Discussion.tsx @@ -1,232 +1,54 @@ -import React, { useState, useEffect } from 'react' -import type { Comment, DiscussionData } from './types' -import { t } from '../../i18n/index.ts' -import CommentItem from './CommentItem' +import React, { useEffect, useRef } from 'react' export interface DiscussionProps { - slug: string - labels: any + lang: string } -const REACTION_EMOJIS: Record = { - THUMBS_UP: 'π', - THUMBS_DOWN: 'π', - LAUGH: 'π', - HOORAY: 'π', - CONFUSED: 'π', - HEART: 'β€οΈ', - ROCKET: 'π', - EYES: 'π', -} +export default function Discussion({ lang }: DiscussionProps) { + const containerRef = useRef(null) -export default function Discussion({ slug, labels }: DiscussionProps) { - const [comments, setComments] = useState([]) - const [appData, setAppData] = useState(null) - const [newComment, setNewComment] = useState('') - const [loading, setLoading] = useState(true) - const [posting, setPosting] = useState(false) - const [loggedIn, setLoggedIn] = useState(false) + const getGiscusTheme = () => { + return document.documentElement.classList.contains('dark') ? 'dark_dimmed' : 'light_dimmed' + } useEffect(() => { - fetch('/api/auth/me') - .then(res => res.json()) - .then(data => { - setLoggedIn(!!data.user) - }) - .catch(() => setLoggedIn(false)) - - fetch(`/api/discussions/${slug}`) - .then(res => res.json()) - .then(data => { - if (data.discussion) { - setAppData(data.discussion) - setComments(data.discussion.comments.nodes) - } - }) - .finally(() => setLoading(false)) - }, [slug]) - - const submitComment = async () => { - if (!newComment.trim() || !appData) return - - setPosting(true) - try { - const res = await fetch('/api/discussions/comment', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ subjectId: appData.id, body: newComment }) - }) - const data = await res.json() - if (data.comment) { - setComments([...comments, data.comment]) - setNewComment('') + if (!containerRef.current) return + + const script = document.createElement('script') + script.src = 'https://giscus.app/client.js' + script.setAttribute('data-repo', '4byte-dev/4byte.dev') + script.setAttribute('data-repo-id', 'R_kgDORqu_NQ') + script.setAttribute('data-category', 'Makale YorumlarΔ±') + script.setAttribute('data-category-id', 'DIC_kwDORqu_Nc4C5G9p') + script.setAttribute('data-mapping', 'pathname') + script.setAttribute('data-strict', '0') + script.setAttribute('data-reactions-enabled', '1') + script.setAttribute('data-emit-metadata', '0') + script.setAttribute('data-input-position', 'bottom') + script.setAttribute('data-theme', getGiscusTheme()) + script.setAttribute('data-lang', lang === 'tr' ? 'tr' : 'en') + script.crossOrigin = 'anonymous' + script.async = true + + containerRef.current.appendChild(script) + + const observer = new MutationObserver(() => { + const iframe = document.querySelector('iframe.giscus-frame') as HTMLIFrameElement + if (iframe) { + iframe.contentWindow?.postMessage( + { giscus: { setConfig: { theme: getGiscusTheme() } } }, + 'https://giscus.app' + ) } - } catch (error) { - console.error('Failed to post comment', error) - } finally { - setPosting(false) - } - } - - const toggleUpvote = async () => { - if (!loggedIn || !appData) return - const action = appData.viewerHasUpvoted ? 'remove' : 'add' - - setAppData({ - ...appData, - viewerHasUpvoted: !appData.viewerHasUpvoted, - upvoteCount: appData.upvoteCount + (action === 'add' ? 1 : -1) }) + observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }) - try { - await fetch('/api/discussions/upvote', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ subjectId: appData.id, action }) - }) - } catch (error) { } - } - - const toggleReaction = async (content: string, viewerHasReacted: boolean) => { - if (!loggedIn || !appData) return - const action = viewerHasReacted ? 'remove' : 'add' - - const newReactions = appData.reactionGroups?.map(r => { - if (r.content === content) { - return { - ...r, - viewerHasReacted: !viewerHasReacted, - users: { totalCount: r.users.totalCount + (viewerHasReacted ? -1 : 1) } - } - } - return r - }) || [] - - const didExist = newReactions.find(r => r.content === content) - if (!didExist && action === 'add') { - newReactions.push({ content, viewerHasReacted: true, users: { totalCount: 1 } }) - } - - setAppData({ ...appData, reactionGroups: newReactions }) - - try { - await fetch('/api/discussions/react', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ subjectId: appData.id, content, action }) - }) - } catch (error) { } - } - - const totalComments = comments.length + comments.reduce((acc, c) => acc + (c.replies?.nodes.length || 0), 0) - - if (loading) { - return {t(labels, 'discussion.loading')} - } + return () => observer.disconnect() + }, [lang]) return ( - - - {t(labels, 'discussion.title')} - - {totalComments} - - - {!loggedIn && ( - - {t(labels, 'discussion.signIn')} - - )} - - - {appData && ( - - - - {appData.upvoteCount || 0} {t(labels, 'discussion.upvotes')} - - - - - {Object.keys(REACTION_EMOJIS).map(content => { - const reaction = appData.reactionGroups?.find(r => r.content === content) - const count = reaction?.users.totalCount || 0 - const userReacted = reaction?.viewerHasReacted || false - - if (count === 0 && !loggedIn) return null - - return ( - toggleReaction(content, userReacted)} - disabled={!loggedIn} - className={`flex items-center gap-1.5 px-2 py-1 rounded-full text-sm font-medium border transition-colors ${userReacted - ? 'bg-primary/10 border-primary/30 text-primary dark:bg-primary-dark/10 dark:border-primary-dark/30 dark:text-primary-dark' - : 'bg-transparent border-border dark:border-border-dark text-muted-foreground dark:text-muted-dark-foreground hover:bg-muted dark:hover:bg-muted-dark' - } ${count === 0 && 'opacity-50'}`} - > - {REACTION_EMOJIS[content]} - {count > 0 && {count}} - - ) - })} - - )} - - - - - {t(labels, 'discussion.write')} - - - - setNewComment(e.target.value)} - className="w-full bg-background dark:bg-background-dark border border-border dark:border-border-dark rounded-md p-3 text-sm focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-primary-dark min-h-[100px] mb-3 text-foreground dark:text-foreground-dark disabled:opacity-50" - placeholder={loggedIn ? t(labels, 'discussion.leaveComment') : t(labels, 'discussion.signInToComment')} - /> - - - {t(labels, 'discussion.markdownSupported')} - - - {posting ? t(labels, 'discussion.posting') : t(labels, 'discussion.comment')} - - - - - - - {!appData ? ( - - {t(labels, 'discussion.notFound')} - - ) : comments.length === 0 ? ( - - {t(labels, 'discussion.noComments')} - - ) : ( - - {comments.map(comment => ( - - ))} - - )} - + ) } diff --git a/src/pages/articles/[slug].astro b/src/pages/articles/[slug].astro index 5f1f2e9..c38588a 100644 --- a/src/pages/articles/[slug].astro +++ b/src/pages/articles/[slug].astro @@ -163,7 +163,7 @@ const onThisPageLabel = t(labels, 'article.onThisPage') - + diff --git a/src/pages/en/articles/[slug].astro b/src/pages/en/articles/[slug].astro index d36285e..77e1e0e 100644 --- a/src/pages/en/articles/[slug].astro +++ b/src/pages/en/articles/[slug].astro @@ -157,7 +157,7 @@ const onThisPageLabel = t(labels, 'article.onThisPage') - +