Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion blog/good-dx/index.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion blog/microservice/index.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion blog/ocp/index.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion blog/polymorphism/index.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion blog/saas-backend/index.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion blog/supabase-alternative/index.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
14 changes: 9 additions & 5 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,7 @@ const config = {
},
},
},
blog: {
showReadingTime: true,
blogSidebarTitle: 'Recent posts',
blogSidebarCount: 10,
},
blog: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarify the transition to a plugin-based system.

The change to set the blog configuration to false is approved as it aligns with the transition to a plugin-based system. However, it would be beneficial to add a comment here explaining that the blog settings have been moved to a plugin to avoid confusion.

Consider adding a comment for clarity:

- blog: false,
+ blog: false, // Blog settings are now managed via a plugin system
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
blog: false,
blog: false, // Blog settings are now managed via a plugin system

theme: {
customCss: require.resolve('./src/css/custom.css'),
},
Expand Down Expand Up @@ -260,6 +256,14 @@ const config = {
},
};
},
[
'./src/plugins/blog-plugin.js',
{
showReadingTime: true,
blogSidebarTitle: 'Recent posts',
blogSidebarCount: 10,
},
],
],

markdown: {
Expand Down
76 changes: 76 additions & 0 deletions src/components/blog/post-paginator.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
className={clsx(
'mr-auto w-full',
'py-10',
'blog-sm:py-12',
'blog-md:py-16',
'max-w-[894px]',
'blog-sm:max-w-screen-blog-sm',
'blog-lg:max-w-screen-content-2xl'
)}
>
<div className="blog-sm:px-6 w-full">
<h2 className="mb-4 p-0 text-2xl font-semibold">{title}</h2>
<div className="flex flex-col not-prose ">
{posts.map((post) => (
<Link
to={post.permalink}
rel="dofollow"
key={post.permalink ?? post.id}
Comment on lines +29 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct the rel attribute and remove incorrect to attribute.

  • The rel="dofollow" attribute is not standard and might be intended as rel="nofollow". Please correct this to ensure proper HTML standards are followed.
  • The to attribute on a div element at line 50 is incorrect as div elements do not support navigation. This attribute should be removed.
-                            rel="dofollow"
+                            rel="nofollow"
-                                to={post.permalink}

Also applies to: 51-51

style={{ color: 'var(--ifm-font-color-base)' }}
className={clsx(
'flex',
'flex-col',
'gap-2',
'p-5',
'mb-5',
'rounded-lg',
'border',
'hover:bg-gray-100',
'dark:hover:bg-gray-800',
'not-prose',
'no-underline',
'border-solid',
'border',
'hover:no-underline',
'group'
)}
>
<div className={clsx('font-bold', 'group-hover:underline')}>{post.title}</div>

<p
className={clsx('font-sm')}
style={{
display: '-webkit-box',
'-webkit-line-clamp': '3',
'-webkit-box-orient': 'vertical',
overflow: 'hidden',
'text-overflow': 'ellipsis',
}}
>
{post.description}
</p>
</Link>
))}
<p className="mb-4 p-0 text-xl">
🚀 Ready to build high-quality, scalable Prisma apps with built-in AuthZ and instant CRUD APIs ?
</p>
<Link className="mb-4 p-0 text-xl" to="/docs/welcome">
Get started with ZenStack's ultimate guide to build faster and smarter
</Link>
</div>
</div>
</div>
);
};
78 changes: 78 additions & 0 deletions src/plugins/blog-plugin.js
Original file line number Diff line number Diff line change
@@ -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,
};
50 changes: 48 additions & 2 deletions src/theme/BlogPostPage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,71 @@ 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 (
<BlogLayout
sidebar={sidebar}
toc={
!hideTableOfContents && toc.length > 0 ? (
<TOC toc={toc} minHeadingLevel={tocMinHeadingLevel} maxHeadingLevel={tocMaxHeadingLevel} />
) : undefined
}
>
{unlisted && <Unlisted />}

<BlogPostItem>{children}</BlogPostItem>
<PostPaginator title="Related Articles" posts={randomThreeRelatedPosts}></PostPaginator>
<GiscusComponent />
{(nextItem || prevItem) && <BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />}
</BlogLayout>
Expand Down