From 8d5edbf75013ac53c2aaf94ae02198c8ee481ee6 Mon Sep 17 00:00:00 2001 From: Idan Levi <29idan29@gmail.com> Date: Sun, 23 Mar 2025 17:32:14 +0200 Subject: [PATCH] Enhanced category filtering --- pages/blog/index.page.tsx | 394 +++++++++++++++++++++----------------- 1 file changed, 217 insertions(+), 177 deletions(-) diff --git a/pages/blog/index.page.tsx b/pages/blog/index.page.tsx index d497f0e60..093a002ac 100644 --- a/pages/blog/index.page.tsx +++ b/pages/blog/index.page.tsx @@ -18,13 +18,21 @@ type Author = { link?: string; byline?: string; }; + export type blogCategories = | 'All' | 'Community' | 'Case Study' | 'Engineering' | 'Update' - | 'Opinion'; + | 'Opinion' + | 'Documentation'; + +const getCategories = (frontmatter: any): blogCategories[] => { + const cat = frontmatter.categories || frontmatter.type; + if (!cat) return []; + return Array.isArray(cat) ? cat : [cat]; +}; export async function getStaticProps({ query }: { query: any }) { const files = fs.readdirSync(PATH); @@ -38,7 +46,7 @@ export async function getStaticProps({ query }: { query: any }) { ); const { data: frontmatter, content } = matter(fullFileName); return { - slug: slug, + slug, frontmatter, content, }; @@ -76,45 +84,110 @@ export default function StaticMarkdownPage({ filterTag: any; }) { const router = useRouter(); - const [currentFilterTag, setCurrentFilterTag] = useState( - filterTag || 'All', - ); + // Initialize the filter as an array. If "All" or not specified, we show all posts. + const initialFilters = + filterTag && filterTag !== 'All' + ? filterTag.split(',').filter(isValidCategory) + : ['All']; + + const [currentFilterTags, setCurrentFilterTags] = + useState(initialFilters); + // When the router query changes, update the filters. useEffect(() => { const { query } = router; - if (query.type && isValidCategory(query.type)) { - setCurrentFilterTag(query.type); + if (query.type) { + const tags = (typeof query.type === 'string' ? query.type : '') + .split(',') + .filter(isValidCategory); + setCurrentFilterTags(tags.length ? tags : ['All']); } }, [router.query]); useEffect(() => { - // Set the filter tag based on the initial query parameter when the page loads - setCurrentFilterTag(filterTag); + const tags = + filterTag && filterTag !== 'All' + ? filterTag.split(',').filter(isValidCategory) + : ['All']; + setCurrentFilterTags(tags); }, [filterTag]); - const handleClick = (event: React.MouseEvent) => { - event.preventDefault(); // Prevent default scrolling behavior - const clickedTag = event.currentTarget.value as blogCategories; - if (clickedTag === 'All') { - setCurrentFilterTag('All'); - history.replaceState(null, '', '/blog'); // Update the URL without causing a scroll - } else if (isValidCategory(clickedTag)) { - setCurrentFilterTag(clickedTag); - history.replaceState(null, '', `/blog?type=${clickedTag}`); // Update URL + const toggleCategory = (tag: blogCategories) => { + let newTags: blogCategories[] = []; + if (tag === 'All') { + newTags = ['All']; + } else { + if (currentFilterTags.includes('All')) { + newTags = [tag]; + } else { + if (currentFilterTags.includes(tag)) { + newTags = currentFilterTags.filter((t) => t !== tag); + } else { + newTags = [...currentFilterTags, tag]; + } + } + if (newTags.length === 0) { + newTags = ['All']; + } + } + setCurrentFilterTags(newTags); + if (newTags.includes('All')) { + history.replaceState(null, '', '/blog'); + } else { + history.replaceState(null, '', `/blog?type=${newTags.join(',')}`); } }; - const recentBlog = blogPosts.sort((a, b) => { + + // First, sort all posts by date descending (for fallback sorting) + const postsSortedByDate = [...blogPosts].sort((a, b) => { + const dateA = new Date(a.frontmatter.date).getTime(); + const dateB = new Date(b.frontmatter.date).getTime(); + return dateB - dateA; + }); + + // Filter posts based on selected categories. + // If "All" is selected, all posts are returned. + const filteredPosts = postsSortedByDate.filter((post) => { + if (currentFilterTags.includes('All') || currentFilterTags.length === 0) + return true; + const postCategories = getCategories(post.frontmatter); + return postCategories.some((cat) => + currentFilterTags.some( + (filter) => filter.toLowerCase() === cat.toLowerCase(), + ), + ); + }); + + const sortedFilteredPosts = filteredPosts.sort((a, b) => { + const aMatches = getCategories(a.frontmatter).filter((cat) => + currentFilterTags.some( + (filter) => filter.toLowerCase() === cat.toLowerCase(), + ), + ).length; + const bMatches = getCategories(b.frontmatter).filter((cat) => + currentFilterTags.some( + (filter) => filter.toLowerCase() === cat.toLowerCase(), + ), + ).length; + if (aMatches !== bMatches) { + return bMatches - aMatches; + } const dateA = new Date(a.frontmatter.date).getTime(); const dateB = new Date(b.frontmatter.date).getTime(); - return dateA < dateB ? 1 : -1; + return dateB - dateA; }); - const timeToRead = Math.ceil(readingTime(recentBlog[0].content).minutes); - const setOfTags: any[] = blogPosts.map((tag) => tag.frontmatter.type); - const spreadTags: any[] = [...setOfTags]; - const allTags = [...new Set(spreadTags)]; - //add tag for all - allTags.unshift('All'); + const recentBlog = postsSortedByDate; + const timeToRead = Math.ceil( + readingTime(recentBlog[0]?.content || '').minutes, + ); + + // Collect all unique categories across posts. + const allTagsSet = new Set(); + blogPosts.forEach((post) => { + getCategories(post.frontmatter).forEach((cat) => allTagsSet.add(cat)); + }); + const allTags = ['All', ...Array.from(allTagsSet)]; return ( // @ts-ignore @@ -125,18 +198,18 @@ export default function StaticMarkdownPage({
{recentBlog[0] && (
-
- hero image example -
+
+ hero image example
+ {/* Display all categories (joined by comma) */}
- {recentBlog[0].frontmatter.type} + {getCategories(recentBlog[0].frontmatter).join(', ')}

@@ -149,7 +222,6 @@ export default function StaticMarkdownPage({ backgroundImage: `url(${recentBlog[0].frontmatter.authors[0].photo})`, }} /> -

{recentBlog[0].frontmatter.authors[0].name} @@ -172,7 +244,6 @@ export default function StaticMarkdownPage({ Welcome to the JSON Schema Blog!

-

Want to publish a blog post? Check out the  @@ -187,7 +258,6 @@ export default function StaticMarkdownPage({  and submit yours!

-
- {/* Filter Buttons */} + {/* Filter Buttons */}
{allTags.map((tag) => ( @@ -225,153 +296,122 @@ export default function StaticMarkdownPage({
- {/* filterTag === frontmatter.type && */} -
- {blogPosts - .filter((post) => { - if (!currentFilterTag || currentFilterTag === 'All') return true; - const blogType = post.frontmatter.type as string | undefined; - if (!blogType) return false; - return blogType.toLowerCase() === currentFilterTag.toLowerCase(); - }) - .sort((a, b) => { - const dateA = new Date(a.frontmatter.date).getTime(); - const dateB = new Date(b.frontmatter.date).getTime(); - return dateA < dateB ? 1 : -1; - }) - .map((blogPost: any) => { - const { frontmatter, content } = blogPost; - const date = new Date(frontmatter.date); - const timeToRead = Math.ceil(readingTime(content).minutes); + {/* Blog Posts Grid */} +
+ {sortedFilteredPosts.map((blogPost: any) => { + const { frontmatter, content } = blogPost; + const date = new Date(frontmatter.date); + const postTimeToRead = Math.ceil(readingTime(content).minutes); - return ( -
-
- -
-
-
-
-
-
+ return ( +
+
+ +
+
+
+
+
+ {/* Display each category as a clickable badge */} +
+ {getCategories(frontmatter).map((cat, index) => (
{ e.preventDefault(); e.stopPropagation(); - - if (frontmatter.type) { - setCurrentFilterTag(frontmatter.type); - history.replaceState( - null, - '', - `/blog?type=${frontmatter.type}`, - ); - } + toggleCategory(cat); }} > - {frontmatter.type || 'Unknown Type'} + {cat || 'Unknown'}
-
-
- {frontmatter.title} -
- -
- -
+ ))}
-
-
- {(frontmatter.authors || []).map( - (author: Author, index: number) => ( -
2 - ? 'h-8 w-8' - : 'h-11 w-11' - }`} - style={{ - backgroundImage: `url(${author.photo})`, - zIndex: 10 - index, - }} - /> - ), - )} -
- -
-
- {frontmatter.authors.length > 2 ? ( - <> - {frontmatter.authors - .slice(0, 2) - .map((author: Author, index: number) => ( - - {author.name} - {index === 0 && ' & '} - - ))} - {'...'} - - ) : ( - frontmatter.authors.map( - (author: Author, index: number) => ( +
+ {frontmatter.title} +
+
+ +
+
+
+
+ {(frontmatter.authors || []).map( + (author: Author, index: number) => ( +
2 + ? 'h-8 w-8' + : 'h-11 w-11' + }`} + style={{ + backgroundImage: `url(${author.photo})`, + zIndex: 10 - index, + }} + /> + ), + )} +
+
+
+ {frontmatter.authors.length > 2 ? ( + <> + {frontmatter.authors + .slice(0, 2) + .map((author: Author, index: number) => ( {author.name} - {index < frontmatter.authors.length - 1 && - ' & '} + {index === 0 && ' & '} - ), - ) - )} -
- -
- {frontmatter.date && ( - - {date.toLocaleDateString('en-us', { - year: 'numeric', - month: 'long', - day: 'numeric', - })} - - )}{' '} - · {timeToRead} min read -
+ ))} + {'...'} + + ) : ( + frontmatter.authors.map( + (author: Author, index: number) => ( + + {author.name} + {index < frontmatter.authors.length - 1 && + ' & '} + + ), + ) + )} +
+
+ {frontmatter.date && ( + + {date.toLocaleDateString('en-us', { + year: 'numeric', + month: 'long', + day: 'numeric', + })} + + )}{' '} + · {postTimeToRead} min read
- -
-
- ); - })} +
+ +
+
+ ); + })}