diff --git a/blog/good-dx/index.md b/blog/good-dx/index.md index d5a036cc..a9578d21 100644 --- a/blog/good-dx/index.md +++ b/blog/good-dx/index.md @@ -1,7 +1,7 @@ --- title: 'How to make good DX(Developer Experience): Empathize' description: Why DX matters and how to create great DX toolkit for developers. -tags: [DX, zensatck] +tags: [DX, zenstack, programming] authors: jiasheng date: 2023-03-24 image: ./cover.png diff --git a/blog/microservice/index.md b/blog/microservice/index.md index ac252f2d..45bb90a8 100644 --- a/blog/microservice/index.md +++ b/blog/microservice/index.md @@ -1,7 +1,7 @@ --- title: Where Did Microservices Go description: Microservices have undergone a shift due to a combination of lessons learned and the emergence of new technologies. -tags: [zenstack, Microservices] +tags: [zenstack, microservices, fullstack, nextjs] authors: jiasheng date: 2023-04-22 image: ./cover.jpg diff --git a/blog/ocp/index.md b/blog/ocp/index.md index d16b8a1d..c1be131f 100644 --- a/blog/ocp/index.md +++ b/blog/ocp/index.md @@ -1,6 +1,6 @@ --- description: Use a real example to illustrate how to achieve good design by applying polymorphism from the database to the UI. -tags: [prisma, orm, database, oop, design, typescript] +tags: [orm, database, oop, design, typescript, polymorphism] authors: jiasheng image: ./cover.jpg date: 2024-03-28 diff --git a/blog/polymorphism/index.md b/blog/polymorphism/index.md index 516ded0c..82ef4c23 100644 --- a/blog/polymorphism/index.md +++ b/blog/polymorphism/index.md @@ -1,6 +1,6 @@ --- description: This post explores different patterns for implementing polymorphism in ORMs and how ZenStack can add the missing feature into Prisma. -tags: [prisma, orm, database] +tags: [prisma, orm, database, polymorphism] authors: yiming date: 2023-12-21 image: ./cover.png diff --git a/blog/saas-backend/index.md b/blog/saas-backend/index.md index 52bcb0be..2d6e18ab 100644 --- a/blog/saas-backend/index.md +++ b/blog/saas-backend/index.md @@ -1,7 +1,7 @@ --- title: 'How To Build a Scalable SaaS Backend in 10 Minutes With 100 Lines of Code' description: Use schema as the single source of truth for the SaaS backend -tags: [zenstack, saas, backend, access-control, nodejs, typescript] +tags: [zenstack, saas, backend, authorization, nodejs] authors: jiasheng date: 2023-06-21 image: ./cover.png diff --git a/blog/supabase-alternative/index.md b/blog/supabase-alternative/index.md index a03e9d7a..21d6a326 100644 --- a/blog/supabase-alternative/index.md +++ b/blog/supabase-alternative/index.md @@ -1,7 +1,7 @@ --- title: Supabase RLS Alternative description: Show the limitation of Supabase RLS(Row Level Security) with a multi-tenancy SaaS example and introduce ZenStack as an alternative. -tags: [supabase, rls, auth, baas, zenstack] +tags: [supabase, rls, auth, authorization, baas, zenstack] authors: jiasheng date: 2024-07-24 image: ./cover.png diff --git a/docusaurus.config.js b/docusaurus.config.js index 18087dec..4510626c 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -46,11 +46,7 @@ const config = { }, }, }, - blog: { - showReadingTime: true, - blogSidebarTitle: 'Recent posts', - blogSidebarCount: 10, - }, + blog: false, theme: { customCss: require.resolve('./src/css/custom.css'), }, @@ -260,6 +256,14 @@ const config = { }, }; }, + [ + './src/plugins/blog-plugin.js', + { + showReadingTime: true, + blogSidebarTitle: 'Recent posts', + blogSidebarCount: 10, + }, + ], ], markdown: { diff --git a/src/components/blog/post-paginator.tsx b/src/components/blog/post-paginator.tsx new file mode 100644 index 00000000..e9ba69ec --- /dev/null +++ b/src/components/blog/post-paginator.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; + +import clsx from 'clsx'; + +export const PostPaginator = ({ posts, title }) => { + if (posts.length < 1) { + return null; + } + + return ( +
+
+

{title}

+
+ {posts.map((post) => ( + +
{post.title}
+ +

+ {post.description} +

+ + ))} +

+ 🚀 Ready to build high-quality, scalable Prisma apps with built-in AuthZ and instant CRUD APIs ? +

+ + Get started with ZenStack's ultimate guide to build faster and smarter + +
+
+
+ ); +}; diff --git a/src/plugins/blog-plugin.js b/src/plugins/blog-plugin.js new file mode 100644 index 00000000..157cd842 --- /dev/null +++ b/src/plugins/blog-plugin.js @@ -0,0 +1,78 @@ +const blogPluginExports = require('@docusaurus/plugin-content-blog'); +const utils = require('@docusaurus/utils'); +const path = require('path'); + +const defaultBlogPlugin = blogPluginExports.default; +const MIN_RELATED_POSTS = 10; + +function getMultipleRandomElement(arr, num) { + const shuffled = [...arr].sort(() => 0.5 - Math.random()); + + return shuffled.slice(0, num); +} + +function getRelatedPosts(allBlogPosts, metadata) { + const currentTags = new Set(metadata.frontMatter.tags?.filter((tag) => tag?.toLowerCase() != 'zenstack')); + + let relatedPosts = allBlogPosts.filter( + (post) => + post.metadata.frontMatter.tags.some((tag) => currentTags.has(tag)) && post.metadata.title !== metadata.title + ); + + if (relatedPosts.length < MIN_RELATED_POSTS) { + remainingCount = MIN_RELATED_POSTS - relatedPosts.length; + const remainingPosts = getMultipleRandomElement( + allBlogPosts.filter((post) => !relatedPosts.includes(post) && post.metadata.title !== metadata.title), + remainingCount + ); + relatedPosts = relatedPosts.concat(remainingPosts); + } + + const filteredPostInfos = relatedPosts.map((post) => { + return { + title: post.metadata.title, + description: post.metadata.description, + permalink: post.metadata.permalink, + formattedDate: post.metadata.formattedDate, + authors: post.metadata.authors, + readingTime: post.metadata.readingTime, + date: post.metadata.date, + relatedWeight: post.metadata.frontMatter.tags.filter((tag) => currentTags.has(tag)).length * 3 + 1, + }; + }); + + return filteredPostInfos; +} + +async function blogPluginExtended(...pluginArgs) { + const blogPluginInstance = await defaultBlogPlugin(...pluginArgs); + + return { + // Add all properties of the default blog plugin so existing functionality is preserved + ...blogPluginInstance, + contentLoaded: async function (data) { + await blogPluginInstance.contentLoaded(data); + const { content: blogContents, actions } = data; + const { blogPosts: allBlogPosts } = blogContents; + const { createData } = actions; + // Create routes for blog entries. + await Promise.all( + allBlogPosts.map(async (blogPost) => { + const { metadata } = blogPost; + const relatedPosts = getRelatedPosts(allBlogPosts, metadata); + await createData( + // Note that this created data path must be in sync with + // metadataPath provided to mdx-loader. + `${utils.docuHash(metadata.source)}.json`, + JSON.stringify({ ...metadata, relatedPosts }, null, 2) + ); + }) + ); + }, + }; +} + +module.exports = { + ...blogPluginExports, + default: blogPluginExtended, +}; diff --git a/src/theme/BlogPostPage/index.js b/src/theme/BlogPostPage/index.js index 5dc96748..c4da0bb2 100644 --- a/src/theme/BlogPostPage/index.js +++ b/src/theme/BlogPostPage/index.js @@ -9,17 +9,61 @@ import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata'; import TOC from '@theme/TOC'; import Unlisted from '@theme/Unlisted'; import GiscusComponent from '@site/src/components/GiscusComponent'; -function BlogPostPageContent({ sidebar, children }) { +import { PostPaginator } from '@site/src/components/blog/post-paginator'; + +function getMultipleRandomPosts(relatedPosts, number) { + // Create a copy of the original array to avoid modifying it + const weightedItems = [...relatedPosts]; + const result = []; + + // Calculate the total weight + let totalWeight = weightedItems.reduce((sum, item) => sum + item.relatedWeight, 0); + + while (weightedItems.length > 0) { + // Generate a random value between 0 and the total weight + const randomValue = Math.random() * totalWeight; + let weightSum = 0; + let selectedIndex = -1; + + // Find the item that corresponds to the random value + for (let i = 0; i < weightedItems.length; i++) { + weightSum += weightedItems[i].relatedWeight; + if (randomValue <= weightSum) { + selectedIndex = i; + break; + } + } + + // If an item was selected, add it to the result and remove it from the original array + if (selectedIndex !== -1) { + const [selectedItem] = weightedItems.splice(selectedIndex, 1); + result.push(selectedItem); + totalWeight -= selectedItem.relatedWeight; + } + } + + return result.slice(0, number); +} + +function BlogPostPageContent({ children }) { const { metadata, toc } = useBlogPost(); + const { relatedPosts } = metadata; + const { nextItem, prevItem, frontMatter, unlisted } = metadata; const { hide_table_of_contents: hideTableOfContents, toc_min_heading_level: tocMinHeadingLevel, toc_max_heading_level: tocMaxHeadingLevel, } = frontMatter; + + const randomThreeRelatedPosts = getMultipleRandomPosts(relatedPosts, 3); + + console.log('relatedPosts', relatedPosts); + + //const url = '/blog/supabase-alternative/cover.png'; + return ( 0 ? ( @@ -27,7 +71,9 @@ function BlogPostPageContent({ sidebar, children }) { } > {unlisted && } + {children} + {(nextItem || prevItem) && }