Refactor CMS to handle ContentLayer and WordPress platforms

This commit refactors the CMS to handle two platforms: ContentLayer and WordPress. The CMS layer is abstracted into a core package, and separate implementations for each platform are created. This change allows the app to switch the CMS type based on environment variable, which can improve the flexibility of content management. It also updates several functions in the `server-sitemap.xml` route to accommodate these changes and generate sitemaps based on the CMS client. Further, documentation content and posts have been relocated to align with the new structure. Notably, this refactor is a comprehensive update to the way the CMS is structured and managed.
This commit is contained in:
giancarlo
2024-04-01 19:47:51 +08:00
parent d6004f2f7e
commit 6b72206b00
62 changed files with 1313 additions and 690 deletions

View File

@@ -4,18 +4,18 @@ import { ChevronRight } from 'lucide-react';
export const DocsCard: React.FC<
React.PropsWithChildren<{
label: string;
title: string;
subtitle?: string | null;
link?: { url: string; label: string };
}>
> = ({ label, subtitle, children, link }) => {
> = ({ title, subtitle, children, link }) => {
return (
<div className="flex flex-col">
<div
className={`flex grow flex-col space-y-2.5 border bg-background p-6
${link ? 'rounded-t-2xl border-b-0' : 'rounded-2xl'}`}
>
<h3 className="mt-0 text-lg font-semibold dark:text-white">{label}</h3>
<h3 className="mt-0 text-lg font-semibold dark:text-white">{title}</h3>
{subtitle && (
<div className="text-sm text-gray-500 dark:text-gray-400">
@@ -31,7 +31,7 @@ export const DocsCard: React.FC<
<span className={'flex items-center space-x-2'}>
<Link
className={'text-sm font-medium hover:underline'}
href={`/docs/${link.url}`}
href={link.url}
>
{link.label}
</Link>

View File

@@ -1,19 +1,19 @@
import type { DocumentationPage } from 'contentlayer/generated';
import { Cms } from '@kit/cms';
import { DocsCard } from './docs-card';
export function DocsCards({ pages }: { pages: DocumentationPage[] }) {
export function DocsCards({ pages }: { pages: Cms.ContentItem[] }) {
return (
<div className={'grid grid-cols-1 gap-8 lg:grid-cols-2'}>
{pages.map((item) => {
return (
<DocsCard
key={item.label}
label={item.label}
key={item.title}
title={item.title}
subtitle={item.description}
link={{
url: item.resolvedPath,
label: item.cardCTA ?? 'Read more',
url: item.url,
label: 'Read more',
}}
/>
);

View File

@@ -5,33 +5,21 @@ import { useEffect, useMemo, useState } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { ChevronDown, Menu } from 'lucide-react';
import { Menu } from 'lucide-react';
import { Cms } from '@kit/cms';
import { isBrowser } from '@kit/shared/utils';
import { Button } from '@kit/ui/button';
import { Heading } from '@kit/ui/heading';
import { If } from '@kit/ui/if';
import { cn } from '@kit/ui/utils';
import type { ProcessedDocumentationPage } from '~/(marketing)/docs/_lib/build-documentation-tree';
const DocsNavLink: React.FC<{
label: string;
url: string;
level: number;
activePath: string;
collapsible: boolean;
collapsed: boolean;
toggleCollapsed: () => void;
}> = ({
label,
url,
level,
activePath,
collapsible,
collapsed,
toggleCollapsed,
}) => {
}> = ({ label, url, level, activePath }) => {
const isCurrent = url == activePath;
const isFirstLevel = level === 0;
@@ -39,76 +27,51 @@ const DocsNavLink: React.FC<{
<div className={getNavLinkClassName(isCurrent, isFirstLevel)}>
<Link
className="flex h-full max-w-full grow items-center space-x-2"
href={`/docs/${url}`}
href={url}
>
<span className="block max-w-full truncate">{label}</span>
</Link>
{collapsible && (
<button
aria-label="Toggle children"
onClick={toggleCollapsed}
className="mr-2 shrink-0 px-2 py-1"
>
<span
className={`block w-2.5 ${collapsed ? '-rotate-90 transform' : ''}`}
>
<ChevronDown className="h-4 w-4" />
</span>
</button>
)}
</div>
);
};
const Node: React.FC<{
node: ProcessedDocumentationPage;
node: Cms.ContentItem;
level: number;
activePath: string;
}> = ({ node, level, activePath }) => {
const [collapsed, setCollapsed] = useState<boolean>(node.collapsed ?? false);
const toggleCollapsed = () => setCollapsed(!collapsed);
useEffect(() => {
if (
activePath == node.resolvedPath ||
node.children.map((_) => _.resolvedPath).includes(activePath)
) {
setCollapsed(false);
}
}, [activePath, node.children, node.resolvedPath]);
return (
<>
<DocsNavLink
label={node.label}
url={node.resolvedPath}
label={node.title}
url={node.url}
level={level}
activePath={activePath}
collapsible={node.collapsible}
collapsed={collapsed}
toggleCollapsed={toggleCollapsed}
/>
{node.children.length > 0 && !collapsed && (
<Tree tree={node.children} level={level + 1} activePath={activePath} />
{(node.children ?? []).length > 0 && (
<Tree
pages={node.children ?? []}
level={level + 1}
activePath={activePath}
/>
)}
</>
);
};
function Tree({
tree,
pages,
level,
activePath,
}: {
tree: ProcessedDocumentationPage[];
pages: Cms.ContentItem[];
level: number;
activePath: string;
}) {
return (
<div className={cn('w-full space-y-2.5 pl-3', level > 0 ? 'border-l' : '')}>
{tree.map((treeNode, index) => (
{pages.map((treeNode, index) => (
<Node
key={index}
node={treeNode}
@@ -120,11 +83,7 @@ function Tree({
);
}
export default function DocsNavigation({
tree,
}: {
tree: ProcessedDocumentationPage[];
}) {
export function DocsNavigation({ pages }: { pages: Cms.ContentItem[] }) {
const activePath = usePathname().replace('/docs/', '');
return (
@@ -135,11 +94,14 @@ export default function DocsNavigation({
}}
className="sticky top-2 hidden w-80 shrink-0 border-r p-4 lg:flex"
>
<Tree tree={tree} level={0} activePath={activePath} />
<Tree pages={pages} level={0} activePath={activePath} />
</aside>
<div className={'lg:hidden'}>
<FloatingDocumentationNavigation tree={tree} activePath={activePath} />
<FloatingDocumentationNavigation
pages={pages}
activePath={activePath}
/>
</div>
</>
);
@@ -159,10 +121,10 @@ function getNavLinkClassName(isCurrent: boolean, isFirstLevel: boolean) {
}
function FloatingDocumentationNavigation({
tree,
pages,
activePath,
}: React.PropsWithChildren<{
tree: ProcessedDocumentationPage[];
pages: Cms.ContentItem[];
activePath: string;
}>) {
const body = useMemo(() => {
@@ -210,7 +172,7 @@ function FloatingDocumentationNavigation({
>
<Heading level={1}>Table of Contents</Heading>
<Tree tree={tree} level={0} activePath={activePath} />
<Tree pages={pages} level={0} activePath={activePath} />
</div>
</If>

View File

@@ -1,7 +1,5 @@
import Link from 'next/link';
import type { DocumentationPage } from 'contentlayer/generated';
import { If } from '@kit/ui/if';
import { cn } from '@kit/ui/utils';
@@ -10,7 +8,10 @@ export function DocumentationPageLink({
before,
after,
}: React.PropsWithChildren<{
page: DocumentationPage;
page: {
url: string;
title: string;
};
before?: React.ReactNode;
after?: React.ReactNode;
}>) {
@@ -23,7 +24,7 @@ export function DocumentationPageLink({
'justify-end self-end': after,
},
)}
href={`/docs/${page.resolvedPath}`}
href={page.url}
>
<If condition={before}>{(node) => <>{node}</>}</If>