diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 40af9d9a2b..9b772327c8 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -220,7 +220,7 @@ export default { routeBasePath: "blog", include: ["**/*.{md,mdx}"], exclude: ["**/_*.{js,jsx,ts,tsx,md,mdx}", "**/_*/**", "**/*.test.{js,jsx,ts,tsx}", "**/__tests__/**"], - postsPerPage: 10, + postsPerPage: "ALL", blogListComponent: "@theme/BlogListPage", blogPostComponent: "@theme/BlogPostPage", blogTagsListComponent: "@theme/BlogTagsListPage", diff --git a/package.json b/package.json index 7d4bed23c1..be399c6995 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "react-hot-toast": "^2.4.1", "react-on-screen": "^2.1.1", "react-platform-js": "^0.0.1", + "react-tabs-scrollable": "^2.0.7", "tailwindcss": "^3.4.4" }, "devDependencies": { diff --git a/src/css/custom.css b/src/css/custom.css index 4f5004ecfe..44d5e1165a 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -681,3 +681,28 @@ span.token.tag.script.language-javascript { .pagination-nav a:nth-child(2) { @apply justify-end; } + +.rts___tab { + border: none !important; + border-radius: 0px !important; +} + +.rts___btn { + background: transparent !important; + border: none !important; +} + +.rts___tab___selected { + border-bottom: 2px solid black !important; + background: transparent !important; + color: black !important; + box-shadow: none !important; +} + +.rts___svg___icon:hover { + stroke: black !important; +} + +button[disabled] { + display: none !important; +} diff --git a/src/theme/BlogLayout/categories.tsx b/src/theme/BlogLayout/categories.tsx new file mode 100644 index 0000000000..1c8665137e --- /dev/null +++ b/src/theme/BlogLayout/categories.tsx @@ -0,0 +1,33 @@ +import {useState} from "react" +import {Tab, Tabs} from "react-tabs-scrollable" +import "react-tabs-scrollable/dist/rts.css" + +export default function Categories(props: any): JSX.Element { + const {tags, activeTag, setActiveTag} = props + + // define state with initial value to let the tabs start with that value + const [activeTab, setActiveTab] = useState(0) + + // define a onClick function to bind the value on tab click + const onTabClick = (e, index) => { + setActiveTab(index) + setActiveTag(tags[index]) + } + + return ( + <> + + {/* generating an array to loop through it */} + {tags.map((name) => ( + {name} + ))} + + + ) +} diff --git a/src/theme/BlogLayout/customblogitem.tsx b/src/theme/BlogLayout/customblogitem.tsx new file mode 100644 index 0000000000..3973bdd7cd --- /dev/null +++ b/src/theme/BlogLayout/customblogitem.tsx @@ -0,0 +1,44 @@ +import {dateformatter, limitApplier} from "@site/src/utils" + +export default function BlogItemCustom(props) { + const {title, authors, ismobile, main, link, image, date} = props + + return ( + title && ( +
+ {(!ismobile || main) && ( + + + + )} +

{dateformatter(date)}

+ +
{authors?.map((info) => )}
+
+ ) + ) +} + +export const AuthorInfo = ({url, image_url, name}) => ( + +
+ +

{name}

+
+
+) + +export const BlogIntro = ({title, description, main, link}) => { + const [ttlLimit, descLimit] = main ? [50, 150] : [48, 70] + return ( + <> + +

{limitApplier(title, ttlLimit)}

+
+

{limitApplier(description, descLimit)}

+ + ) +} diff --git a/src/theme/BlogLayout/featured.tsx b/src/theme/BlogLayout/featured.tsx new file mode 100644 index 0000000000..50ff0c4b9b --- /dev/null +++ b/src/theme/BlogLayout/featured.tsx @@ -0,0 +1,22 @@ +import {AuthorInfo, BlogIntro} from "./customblogitem" + +export default function Featured({items}) { + return ( +
+

Featured Posts

+ {items?.slice(0, 6).map((item) => )} +
+ ) +} + +const FeaturedItem = (props) => { + return ( +
+ + +
+ ) +} diff --git a/src/theme/BlogLayout/index.tsx b/src/theme/BlogLayout/index.tsx index 400fcb4b68..d035460739 100644 --- a/src/theme/BlogLayout/index.tsx +++ b/src/theme/BlogLayout/index.tsx @@ -1,29 +1,166 @@ -import React from "react" +import React, {useEffect, useState} from "react" import clsx from "clsx" import Layout from "@theme/Layout" import BlogRecentPosts from "../BlogRecentPosts" import type {Props} from "@theme/BlogLayout" +import Categories from "./categories" +import LinkButton from "@site/src/components/shared/LinkButton" +import {Theme} from "@site/src/constants" +import Featured from "./featured" +import BlogItemCustom from "./customblogitem" +import BrowserOnly from "@docusaurus/BrowserOnly" export default function BlogLayout(props: Props): JSX.Element { const {sidebar, toc, children, ...layoutProps} = props const hasSidebar = sidebar && sidebar.items.length > 0 + const [tags, setTags] = useState([]) + const [activeTag, setActiveTag] = useState("All") + const [mainPage, setMainPage] = useState(false) + const [loadmore, setloadmore] = useState(false) + const [ismobile, setismobile] = useState(false) + const [allBlogs, setAllBlogs] = useState({}) + const [featuredBlogsState, setFeaturedBlogs] = useState([]) + + useEffect(() => { + if (!children[0].props?.items) { + //return if its not the home page + return + } else { + setMainPage(true) + } + + //initialize the setup + tagsSetup(children[0].props.items) + }, [children]) + + // code to maintain responsiveness + + const updateMobileState = (setismobile) => { + setismobile(window.innerWidth <= 650) + } + + // end mobile control + + //this one is often called in parallel for each blog + const blogProcessor = async (blog, tagOccurrences) => { + return new Promise((resolve) => { + //now the logic + let tagsOfThis = blog.frontMatter.tags || [] + let isFeatured = false + let polishedBlog = blogInfoGather(blog) + + tagsOfThis.forEach((tag) => { + if (tag === "Featured") { + //save as featured + isFeatured = true + } else { + // good luck figuring this out :) + tagOccurrences[tag] = { + occurrences: tagOccurrences[tag]?.occurrences + 1 || 1, + blogs: [polishedBlog, ...(tagOccurrences[tag]?.blogs || [])], + } + } + }) + + //now, add it to the 'All' tag + tagOccurrences["All"] = { + occurrences: tagOccurrences["All"]?.occurrences + 1 || 1, + blogs: [polishedBlog, ...(tagOccurrences["All"]?.blogs || [])], + } + + resolve(isFeatured ? polishedBlog : undefined) + }) + } + + //this is the parent function + const tagsSetup = async (allblogs) => { + //get all the tags first + // let tagOccurrences = {tag: {occurrences: 5, blogs: []}, } + let tagOccurrences: any = {} + + //process each blog in parallel, it also reaturns the featured posts + let featuredBlogs = await Promise.all(allblogs.map(({content}) => blogProcessor(content, tagOccurrences))) + //unfortunately, it also sends undefineds + featuredBlogs = featuredBlogs.filter((b) => b !== undefined) + + //now update the states + + setTags(["All", ...Object.keys(tagOccurrences)]) + setAllBlogs(tagOccurrences) + setFeaturedBlogs(featuredBlogs) + } + + //this one extracts useful data from the frontmatter jargon + const blogInfoGather = (rawInfo) => { + return {...rawInfo.frontMatter, date: rawInfo.metadata.date, link: rawInfo.metadata.permalink} + } + + const MobileStateUpdater = ({ismobile, setismobile}) => ( + }> + {() => { + useEffect(() => { + if (!window) { + return + } + + window.addEventListener("resize", () => updateMobileState(setismobile)) + updateMobileState(setismobile) + }, [window]) + return
+ }} +
+ ) + // return ( -
-
-
+
- {children} -
- {toc &&
{toc}
} -
-
- +
+ + +
+ +
+
+ {allBlogs[activeTag]?.blogs.slice(1, 7).map((blog) => )} + {loadmore && + allBlogs[activeTag]?.blogs.slice(7).map((blog) => )} +
+ {!loadmore && allBlogs[activeTag]?.blogs.length > 6 && ( + setloadmore(true)} /> + )} +
+ {toc &&
{toc}
} +
+ {!ismobile && } + + + ) : ( + <> +
+
+
+ {children} +
+ {toc &&
{toc}
} +
+
+ + + )}
) } diff --git a/src/utils/index.ts b/src/utils/index.ts index f1f22ef712..23b7053cca 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -33,3 +33,20 @@ export const isValidURL = (url: string) => { return false } } + +export const limitApplier = (string: string, limit: number) => { + if (string?.length > limit) { + return string.substr(0, limit) + "..." + } else { + return string + } +} + +export const dateformatter = (date: string) => { + let dateobj = new Date(date) + return dateobj.toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }) +}