Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 2.0.0 - use github issues as posts & add comments feature! #17

Merged
merged 1 commit into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ FIREBASE_STORAGE_BUCKET=
FIREBASE_MESSAGING_SENDER_ID=
FIREBASE_APP_ID=

# Github API Keys
GITHUB_TOKEN=

# Google Analytics UA CODE
NEXT_PUBLIC_UA_CODE=
83 changes: 83 additions & 0 deletions hook/useUtterances.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useEffect, useRef, useState } from 'react'

export default function useUtterances(commentNodeId, title) {
const commentNodeRef = useRef()
const [isCommentsLoading, setIsCommentsLoading] = useState(true)
let intersectionObserver
let mutationObserver
let hasLoaded = false

useEffect(() => {
const injectScript = () => {
if (hasLoaded) return

const scriptParentNode = document.getElementById(commentNodeId)
if (!scriptParentNode) return

const script = document.createElement('script')
script.src = 'https://utteranc.es/client.js'
script.async = true
script.setAttribute('repo', 'abdulrcs/abdulrahman.id')
script.setAttribute('issue-term', title)
script.setAttribute('theme', 'icy-dark')
script.crossOrigin = 'anonymous'

script.onload = () => setInterval(() => setIsCommentsLoading(false), 1000)

scriptParentNode.appendChild(script)
commentNodeRef.current = script

hasLoaded = true
}

const observerCallback = (entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
injectScript()
}
})
}

const mutationCallback = (mutationsList, observer) => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
const scriptParentNode = document.getElementById(commentNodeId)
if (scriptParentNode) {
if (!intersectionObserver) {
intersectionObserver = new IntersectionObserver(observerCallback)
}
intersectionObserver.observe(scriptParentNode)
observer.disconnect()
}
}
}
}

if (!mutationObserver) {
mutationObserver = new MutationObserver(mutationCallback)
}

mutationObserver.observe(document.body, { childList: true, subtree: true })

// Cleanup on unmount
return () => {
if (intersectionObserver) {
intersectionObserver.disconnect()
}
if (mutationObserver) {
mutationObserver.disconnect()
}
const scriptParentNode = document.getElementById(commentNodeId)
if (
scriptParentNode &&
commentNodeRef.current &&
commentNodeRef.current.parentNode === scriptParentNode
) {
scriptParentNode.removeChild(commentNodeRef.current)
}
hasLoaded = false
}
}, [commentNodeId, title])

return { isCommentsLoading }
}
9 changes: 8 additions & 1 deletion next.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
module.exports = {
images: {
domains: ['imagizer.imageshack.com', 'i.imgur.com', 'images.unsplash.com', 'media.giphy.com', 'cdn-images-1.medium.com'],
domains: [
'imagizer.imageshack.com',
'i.imgur.com',
'images.unsplash.com',
'media.giphy.com',
'cdn-images-1.medium.com',
'github.com',
],
},
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "abdulrahman.id",
"version": "0.1.0",
"version": "2.0.0",
"private": true,
"scripts": {
"dev": "next dev",
Expand All @@ -13,6 +13,7 @@
"@chakra-ui/react": "^1.6.1",
"@emotion/react": "^11",
"@emotion/styled": "^11",
"@rena.to/github-blog": "^0.4.2",
"contentful": "^8.3.7",
"cors": "^2.8.5",
"dateformat": "^4.5.1",
Expand Down
83 changes: 60 additions & 23 deletions pages/blog/[slug].js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { useEffect, useState } from 'react'
import { Avatar, Text, Heading, Stack } from '@chakra-ui/react'
import {
Avatar,
Text,
Heading,
Stack,
Spinner,
Center,
HStack,
} from '@chakra-ui/react'
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'

Expand All @@ -15,6 +23,10 @@ import PostContainer from '../../components/PostContainer'
import MDXComponents from '../../components/MDXComponents'
import { useRouter } from 'next/router'

import { GithubBlog } from '@rena.to/github-blog'

import useUtterances from '../../hook/useUtterances'

export default function Post({ metadata, source }) {
const [views, setViews] = useState('...')
const router = useRouter()
Expand All @@ -24,14 +36,17 @@ export default function Post({ metadata, source }) {
.then((res) => res.json())
.then((json) => setViews(json.views))
}, [])

const { isCommentsLoading } = useUtterances('comments', metadata.title)

return (
<>
<NextSeo
title={metadata.title}
description={metadata.summary}
canonical={`https://abdulrahman.id/blog/${metadata.slug}`}
canonical={`https://abdulrahman.id/blog/${slug}`}
openGraph={{
url: `https://abdulrahman.id/blog/${metadata.slug}`,
url: `https://abdulrahman.id/blog/${slug}`,
site_name: 'Abdul Rahman',
title: metadata.title,
description: metadata.summary,
Expand All @@ -44,7 +59,7 @@ export default function Post({ metadata, source }) {
},
images: [
{
url: metadata.image,
url: metadata.frontmatter.image,
alt: metadata.title,
},
],
Expand All @@ -53,17 +68,17 @@ export default function Post({ metadata, source }) {
{ property: 'twitter:card', content: 'summary_large_image' },
{
property: 'twitter:url',
content: `https://abdulrahman.id/blog/${metadata.slug}`,
content: `https://abdulrahman.id/blog/${slug}`,
},
{ property: 'twitter:title', content: metadata.title },
{ property: 'twitter:description', content: metadata.summary },
{ property: 'twitter:image', content: metadata.image },
{ property: 'twitter:image', content: metadata.frontmatter.image },
]}
/>
<ArticleJsonLd
url={`https://abdulrahman.id/blog/${metadata.slug}`}
url={`https://abdulrahman.id/blog/${slug}`}
title={metadata.title}
images={[metadata.image]}
images={[metadata.frontmatter.image]}
datePublished={metadata.date}
dateModified={metadata.date}
authorName="Abdul Rahman"
Expand Down Expand Up @@ -116,7 +131,7 @@ export default function Post({ metadata, source }) {
borderColor={{ base: '#333', md: 'borderColor' }}
>
<Image
src={metadata.image}
src={metadata.frontmatter.image}
borderRadius="10px"
width={1366}
height={892}
Expand All @@ -129,6 +144,17 @@ export default function Post({ metadata, source }) {
</Stack>
<PostContainer>
<MDXRemote {...source} components={MDXComponents} />
{isCommentsLoading && (
<Center flexDirection="column" pt={8}>
<Spinner thickness="5px" w="56px" h="56px" color="#058d92" />
<Text color="textSecondary" fontSize="sm" pt={2}>
Loading comments...
</Text>
</Center>
)}
<Stack opacity={isCommentsLoading ? 0 : 1}>
<div id="comments" />
</Stack>
</PostContainer>
</Stack>
</Stack>
Expand All @@ -137,30 +163,41 @@ export default function Post({ metadata, source }) {
)
}

let client = require('contentful').createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
})

export async function getStaticPaths() {
let data = await client.getEntries({
content_type: 'blogPosts',
const blog = new GithubBlog({
repo: 'abdulrcs/abdulrahman.id',
token: process.env.GITHUB_TOKEN,
})

const data = await blog.getPosts({
query: {
author: 'abdulrcs',
type: 'post',
state: 'published',
},
pager: { limit: 10, offset: 0 },
})

return {
paths: data.items.map((item) => ({
params: { slug: item.fields.slug },
paths: data.edges.map(({ post }) => ({
params: { slug: post.frontmatter.slug },
})),
fallback: false,
}
}

export async function getStaticProps({ params }) {
let data = await client.getEntries({
content_type: 'blogPosts',
'fields.slug': params.slug,
const blog = new GithubBlog({
repo: 'abdulrcs/abdulrahman.id',
token: process.env.GITHUB_TOKEN,
})

const article = data.items[0].fields
const data = await blog.getPost({
query: {
author: 'abdulrcs',
search: params.slug,
},
})
const article = data.post
const source = article.body
article.readingTime = readingTime(source).text
const mdxSource = await serialize(source, {
Expand Down
50 changes: 28 additions & 22 deletions pages/blog/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import useMediaQuery from '../../hook/useMediaQuery'
import readingTime from 'reading-time'
import dateFormat from 'dateformat'

export default function Index({ articles }) {
import { GithubBlog } from '@rena.to/github-blog'

export default function Index({ posts }) {
const [query, setQuery] = useState('')
const handleChange = (e) => setQuery(e.target.value)
const isLargerThan1024 = useMediaQuery(1024)
Expand Down Expand Up @@ -74,13 +76,13 @@ export default function Index({ articles }) {
</InputGroup>
<Divider />
<Stack spacing={5}>
{articles
{posts
.filter((e) =>
e.fields.title.toLowerCase().includes(query.toLowerCase()),
e.post.title.toLowerCase().includes(query.toLowerCase()),
)
.map((article) => (
.map(({ post }) => (
<Stack
key={article.sys.id}
key={post.frontmatter.slug}
direction={isLargerThan1024 ? 'row' : 'column'}
alignItems="flex-start"
justifyContent="flex-start"
Expand All @@ -91,34 +93,36 @@ export default function Index({ articles }) {
width={100}
textAlign="right"
>
{dateFormat(Date.parse(article.fields.date), 'mmm d yyyy')}
{dateFormat(Date.parse(post.frontmatter.date), 'mmm d yyyy')}
<br />{' '}
<Text fontSize="sm" textAlign="right">
{readingTime(article.fields.body).text}
{post.frontmatter.readingTime}
</Text>
</Text>
<Text
color="textSecondary"
fontSize="sm"
display={isLargerThan1024 ? 'none' : 'block'}
>
{dateFormat(Date.parse(article.fields.date), 'mmm d yyyy')}{' '}
{dateFormat(Date.parse(post.frontmatter.date), 'mmm d yyyy')}{' '}
<Box as="span" fontSize="xs">
&bull;
</Box>{' '}
{readingTime(article.fields.body).text}
{post.frontmatter.readingTime}
</Text>
<Flex flexDirection="column" px={isLargerThan1024 ? 10 : 0}>
<Link href={'/blog/' + article.fields.slug}>
<Link href={'/blog/' + post.frontmatter.slug}>
<Text
color="displayColor"
fontSize="xl"
fontWeight="bold"
cursor="pointer"
>
{article.fields.title}
{post.title}
</Text>
<Text color="textSecondary">
{post.frontmatter.summary}
</Text>
<Text color="textSecondary">{article.fields.summary}</Text>

<Text color="button1" cursor="pointer">
Learn more &rarr;
Expand All @@ -133,21 +137,23 @@ export default function Index({ articles }) {
)
}

let client = require('contentful').createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
})

export async function getStaticProps() {
let data = await client.getEntries({
content_type: 'blogPosts',
limit: 50,
order: 'sys.createdAt',
const blog = new GithubBlog({
repo: 'abdulrcs/abdulrahman.id',
token: process.env.GITHUB_TOKEN,
})
const posts = await blog.getPosts({
query: {
author: 'abdulrcs',
type: 'post',
state: 'published',
},
pager: { limit: 10, offset: 0 },
})

return {
props: {
articles: data.items.reverse(),
posts: posts.edges,
},
}
}