-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
nilay nath sharan
committed
Oct 18, 2023
1 parent
7319fbf
commit c529267
Showing
19 changed files
with
2,492 additions
and
41 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
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
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,5 @@ | ||
import { NextResponse } from 'next/server'; | ||
|
||
export async function POST() { | ||
return NextResponse.json({ hello: 'Next.js' }); | ||
} |
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,134 @@ | ||
'use client'; | ||
import clsx from 'clsx'; | ||
import React from 'react'; | ||
|
||
import { getTags, sortByDate, sortDateFn } from '@/lib/mdx.client'; | ||
import useInjectContentMeta from '@/hooks/useInjectContentMeta'; | ||
import useLoaded from '@/hooks/useLoaded'; | ||
|
||
import Accent from '@/components/Accent'; | ||
import BlogCard from '@/components/content/blogs/BlogCard'; | ||
import ContentPlaceholder from '@/components/content/ContenPlaceholder'; | ||
import StyledInput from '@/components/content/form/StyledInput'; | ||
import Tag, { SkipNavTag } from '@/components/content/Tag'; | ||
|
||
import { BlogFrontmatter } from '@/types/frontmatters'; | ||
|
||
const Page = () => { | ||
const isLoaded = useLoaded(); | ||
const popluatedPosts = useInjectContentMeta('blog', 'featuredBlogs'); | ||
|
||
//#region //* Search | ||
const posts = sortByDate(popluatedPosts as BlogFrontmatter[]); | ||
const [search, setSearch] = React.useState<string>(''); | ||
const [filteredPosts, setFilteredPosts] = React.useState< | ||
Array<BlogFrontmatter> | ||
>(() => [...posts]); | ||
|
||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
setSearch(e.target.value); | ||
}; | ||
|
||
React.useEffect(() => { | ||
const jugglePosts = popluatedPosts as BlogFrontmatter[]; | ||
const results = jugglePosts.filter( | ||
(post) => | ||
post.title.toLowerCase().includes(search.toLowerCase()) || | ||
post.description.toLowerCase().includes(search.toLowerCase()) || | ||
// Check if splitted search contained in tag | ||
search | ||
.toLowerCase() | ||
.split(' ') | ||
.every((tag) => post.tags.includes(tag)) | ||
); | ||
results.sort(sortDateFn); | ||
setFilteredPosts(results); | ||
}, [search, popluatedPosts]); | ||
|
||
//#region //* end Search | ||
const currentPosts = filteredPosts; | ||
|
||
//#region //* Tag | ||
const tags = getTags(popluatedPosts as BlogFrontmatter[]); | ||
const toggleTag = (tag: string) => { | ||
if (search.includes(tag)) { | ||
setSearch((s) => | ||
s | ||
.split(' ') | ||
.filter((t) => t !== tag) | ||
?.join(' ') | ||
); | ||
} else { | ||
// append tag | ||
setSearch((s) => (s !== '' ? `${s.trim()} ${tag}` : tag)); | ||
} | ||
}; | ||
/** Currently available tags based on filtered posts */ | ||
const filteredTags = getTags(currentPosts); | ||
|
||
/** Show accent if not disabled and selected */ | ||
const checkTagged = (tag: string) => { | ||
return ( | ||
filteredTags.includes(tag) && | ||
search.toLowerCase().split(' ').includes(tag) | ||
); | ||
}; | ||
//#region //* end Tag | ||
|
||
return ( | ||
<> | ||
<main> | ||
<section className={clsx(isLoaded && 'fade-in-start')}> | ||
<div className='layout py-12'> | ||
<h1 className='text-3xl md:text-5xl' data-fade='0'> | ||
<Accent>Blog </Accent> | ||
</h1> | ||
<p className='mt-2 text-gray-600 dark:text-gray-300' data-fade='1'> | ||
Thoughts, mental models, and tutorials about front-end | ||
development. | ||
</p> | ||
<StyledInput | ||
data-fade='2' | ||
className='mt-4' | ||
placeholder='Search...' | ||
onChange={handleSearch} | ||
value={search} | ||
type='text' | ||
/> | ||
<div | ||
className='mt-2 flex flex-wrap items-baseline justify-start gap-2 text-sm text-gray-600 dark:text-gray-300' | ||
data-fade='3' | ||
> | ||
<span className='font-medium'>Choose topic:</span> | ||
<SkipNavTag> | ||
{tags.map((tag) => ( | ||
<Tag | ||
key={tag} | ||
onClick={() => toggleTag(tag)} | ||
disabled={!filteredTags.includes(tag)} | ||
> | ||
{checkTagged(tag) ? <Accent>{tag}</Accent> : tag} | ||
</Tag> | ||
))} | ||
</SkipNavTag> | ||
</div> | ||
<ul | ||
className='mt-4 grid gap-4 sm:grid-cols-2 xl:grid-cols-3' | ||
data-fade='5' | ||
> | ||
{currentPosts.length > 0 ? ( | ||
currentPosts.map((post) => ( | ||
<BlogCard key={post.slug} post={post} /> | ||
)) | ||
) : ( | ||
<ContentPlaceholder /> | ||
)} | ||
</ul> | ||
</div> | ||
</section> | ||
</main> | ||
</> | ||
); | ||
}; | ||
|
||
export default Page; |
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,9 @@ | ||
import Accent from '@/components/Accent'; | ||
|
||
export default function ContentPlaceholder() { | ||
return ( | ||
<h2 className='mt-8 text-center sm:col-span-2 xl:col-span-3'> | ||
<Accent>Sorry, not found :(</Accent> | ||
</h2> | ||
); | ||
} |
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,20 @@ | ||
import Image from 'next/image'; | ||
import LiteYouTubeEmbed from 'react-lite-youtube-embed'; | ||
|
||
import SplitImage, { Split } from '@/components/content/SplitImage'; | ||
import CloudinaryImg from '@/components/images/CloudinaryImg'; | ||
import CustomLink from '@/components/links/CustomLink'; | ||
import TechIcons from '@/components/TechIcons'; | ||
|
||
const MDXComponents = { | ||
a: CustomLink, | ||
Image, | ||
// code: CustomCode, | ||
CloudinaryImg, | ||
LiteYouTubeEmbed, | ||
SplitImage, | ||
Split, | ||
TechIcons, | ||
}; | ||
|
||
export default MDXComponents; |
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,16 @@ | ||
import { useRouter } from 'next/router'; | ||
import * as React from 'react'; | ||
import { HiRefresh } from 'react-icons/hi'; | ||
|
||
import ButtonLink from '@/components/links/ButtonLink'; | ||
|
||
export default function ReloadDevtool() { | ||
const isProd = false; | ||
const router = useRouter(); | ||
|
||
return !isProd ? ( | ||
<ButtonLink href={router.asPath} className='fixed bottom-4 left-4'> | ||
<HiRefresh /> | ||
</ButtonLink> | ||
) : null; | ||
} |
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,13 @@ | ||
import * as React from 'react'; | ||
|
||
export default function SplitImage({ | ||
children, | ||
}: { | ||
children: React.ReactNode; | ||
}) { | ||
return <div className='grid grid-cols-2 items-start gap-4'>{children}</div>; | ||
} | ||
|
||
export function Split({ children }: { children: React.ReactNode }) { | ||
return <div className='!mb-0 flex flex-col space-y-4'>{children}</div>; | ||
} |
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,81 @@ | ||
import * as React from 'react'; | ||
|
||
import TOCLink from '@/components/links/TOCLink'; | ||
|
||
export type HeadingScrollSpy = Array<{ | ||
id: string; | ||
level: number; | ||
text: string; | ||
}>; | ||
|
||
type TableOfContentsProps = { | ||
toc?: HeadingScrollSpy; | ||
activeSection: string | null; | ||
minLevel: number; | ||
}; | ||
|
||
export default function TableOfContents({ | ||
toc, | ||
activeSection, | ||
minLevel, | ||
}: TableOfContentsProps) { | ||
//#region //*=========== Scroll into view =========== | ||
const lastPosition = React.useRef<number>(0); | ||
|
||
React.useEffect(() => { | ||
const container = document.getElementById('toc-container'); | ||
const activeLink = document.getElementById(`link-${activeSection}`); | ||
|
||
if (container && activeLink) { | ||
// Get container properties | ||
const cTop = container.scrollTop; | ||
const cBottom = cTop + container.clientHeight; | ||
|
||
// Get activeLink properties | ||
const lTop = activeLink.offsetTop - container.offsetTop; | ||
const lBottom = lTop + activeLink.clientHeight; | ||
|
||
// Check if in view | ||
const isTotal = lTop >= cTop && lBottom <= cBottom; | ||
|
||
const isScrollingUp = lastPosition.current > window.scrollY; | ||
lastPosition.current = window.scrollY; | ||
|
||
if (!isTotal) { | ||
// Scroll by the whole clientHeight | ||
const offset = 25; | ||
const top = isScrollingUp | ||
? lTop - container.clientHeight + offset | ||
: lTop - offset; | ||
|
||
container.scrollTo({ top, behavior: 'smooth' }); | ||
} | ||
} | ||
}, [activeSection]); | ||
//#endregion //*======== Scroll into view =========== | ||
|
||
return ( | ||
<div | ||
id='toc-container' | ||
className='hidden max-h-[calc(100vh-9rem-113px)] overflow-auto pb-4 lg:block' | ||
> | ||
<h3 className='text-gray-900 dark:text-gray-100 md:text-xl'> | ||
Table of Contents | ||
</h3> | ||
<div className='mt-4 flex flex-col space-y-2 text-sm'> | ||
{toc | ||
? toc.map(({ id, level, text }) => ( | ||
<TOCLink | ||
id={id} | ||
key={id} | ||
activeSection={activeSection} | ||
level={level} | ||
minLevel={minLevel} | ||
text={text} | ||
/> | ||
)) | ||
: null} | ||
</div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.