diff --git a/apps/www/app/ConsentWrapper.tsx b/apps/www/app/ConsentWrapper.tsx new file mode 100644 index 00000000000..a631af7101b --- /dev/null +++ b/apps/www/app/ConsentWrapper.tsx @@ -0,0 +1,31 @@ +'use client' + +import { IS_PLATFORM, PageTelemetry } from 'common' +import { RefreshRouteOnSave as PayloadLivePreview } from '@payloadcms/live-preview-react' +import { useConsentToast } from 'ui-patterns/consent' +import { useRouter } from 'next/navigation' +import { API_URL } from '~/lib/constants' + +interface ConsentWrapperProps { + children: React.ReactNode +} + +export function ConsentWrapper({ children }: ConsentWrapperProps) { + const { hasAcceptedConsent } = useConsentToast() + const router = useRouter() + + return ( + <> + {children} + + router.refresh()} + serverURL={process.env.NEXT_PUBLIC_CMS_URL || 'http://localhost:3030'} + /> + + ) +} diff --git a/apps/www/pages/blog.tsx b/apps/www/app/blog/BlogClient.tsx similarity index 62% rename from apps/www/pages/blog.tsx rename to apps/www/app/blog/BlogClient.tsx index ef65c6bcf2f..cc5c8780add 100644 --- a/apps/www/pages/blog.tsx +++ b/apps/www/app/blog/BlogClient.tsx @@ -1,12 +1,7 @@ -import fs from 'fs' -import { useEffect, useState } from 'react' -import { useRouter } from 'next/router' -import { NextSeo } from 'next-seo' -import { generateRss } from '~/lib/rss' -import { getSortedPosts } from '~/lib/posts' -// CMS posts are fetched client-side from /api-v2/cms-posts to keep server bundle small +'use client' -import type PostTypes from '~/types/post' +import { useEffect, useState } from 'react' +import { NextSeo } from 'next-seo' import DefaultLayout from '~/components/Layouts/Default' import BlogGridItem from '~/components/Blog/BlogGridItem' import BlogListItem from '~/components/Blog/BlogListItem' @@ -15,33 +10,30 @@ import FeaturedThumb from '~/components/Blog/FeaturedThumb' import { cn } from 'ui' import { LOCAL_STORAGE_KEYS, isBrowser } from 'common' +import type PostTypes from '~/types/post' + export type BlogView = 'list' | 'grid' -function Blog(props: any) { +export default function BlogClient(props: { blogs: any[] }) { const { BLOG_VIEW } = LOCAL_STORAGE_KEYS const localView = isBrowser ? (localStorage?.getItem(BLOG_VIEW) as BlogView) : undefined const [cmsPosts, setCmsPosts] = useState([]) const [blogs, setBlogs] = useState(props.blogs) const [view, setView] = useState(localView ?? 'list') const isList = view === 'list' - const router = useRouter() const meta_title = 'Supabase Blog: the Postgres development platform' const meta_description = 'Get all your Supabase News on the Supabase blog.' - // Merge CMS posts with static ones when CMS posts arrive const allPosts = [...props.blogs, ...cmsPosts].sort( (a: any, b: any) => new Date(b.date).getTime() - new Date(a.date).getTime() ) - // Keep list in sync once after CMS posts load, but don't override user filters later useEffect(() => { setBlogs((prev: any[]) => (prev.length === props.blogs.length ? allPosts : prev)) // eslint-disable-next-line react-hooks/exhaustive-deps }, [cmsPosts]) - // Fetch CMS posts - // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(() => { const fetchCmsPosts = async () => { try { @@ -65,7 +57,7 @@ function Blog(props: any) { openGraph={{ title: meta_title, description: meta_description, - url: `https://supabase.com/${router.pathname}`, + url: `https://supabase.com/blog`, images: [ { url: `https://supabase.com/images/og/supabase-og.png`, @@ -90,12 +82,7 @@ function Blog(props: any) {
- +
    ) } - -export async function getStaticProps() { - // Get static blog posts - const staticPostsData = getSortedPosts({ directory: '_blog', runner: '** BLOG PAGE **' }) - - // Only static data at build time; CMS posts are fetched client-side - const allPostsData = [...staticPostsData].sort((a: any, b: any) => { - const dateA = a.date ? new Date(a.date).getTime() : new Date(a.formattedDate).getTime() - const dateB = b.date ? new Date(b.date).getTime() : new Date(b.formattedDate).getTime() - return dateB - dateA - }) - - // Generate RSS feed from combined posts - const rss = generateRss(allPostsData) - - // create a rss feed in public directory - // rss feed is added via component in render return - fs.writeFileSync('./public/rss.xml', rss) - - // generate a series of rss feeds for each author (for PlanetPG) - const planetPgPosts = allPostsData.filter((post: any) => post.tags?.includes('planetpg')) - const planetPgAuthors = planetPgPosts.map((post: any) => post.author.split(',')) - const uniquePlanetPgAuthors = new Set([].concat(...planetPgAuthors)) - - uniquePlanetPgAuthors.forEach((author) => { - const authorPosts = planetPgPosts.filter((post: any) => post.author.includes(author)) - if (authorPosts.length > 0) { - const authorRss = generateRss(authorPosts, author) - fs.writeFileSync(`./public/planetpg-${author}-rss.xml`, authorRss) - } - }) - - return { - props: { - blogs: allPostsData, - }, - // revalidate: 60 * 10, // Revalidate every 10 minutes - } -} - -export default Blog diff --git a/apps/www/app/blog/[slug]/BlogPostClient.tsx b/apps/www/app/blog/[slug]/BlogPostClient.tsx new file mode 100644 index 00000000000..aed4ce25857 --- /dev/null +++ b/apps/www/app/blog/[slug]/BlogPostClient.tsx @@ -0,0 +1,135 @@ +'use client' + +import dynamic from 'next/dynamic' +import { NextSeo } from 'next-seo' +import { useState, useMemo } from 'react' +import { useLivePreview } from '@payloadcms/live-preview-react' + +import authors from 'lib/authors.json' +import { CMS_SITE_ORIGIN } from 'lib/constants' +import { isNotNullOrUndefined } from 'lib/helpers' + +import type { Blog, BlogData, CMSAuthor, PostReturnType, ProcessedBlogData, Tag } from 'types/post' + +const BlogPostRenderer = dynamic(() => import('components/Blog/BlogPostRenderer')) + +type BlogPostPageProps = { + prevPost: PostReturnType | null + nextPost: PostReturnType | null + relatedPosts: (PostReturnType & BlogData)[] + blog: Blog & BlogData + isDraftMode: boolean +} + +export default function BlogPostClient(props: BlogPostPageProps) { + const isDraftMode = props.isDraftMode + const [previewData] = useState(props.blog) + const shouldUseLivePreview = isDraftMode && (props.blog as any).isCMS + + const { data: livePreviewData, isLoading: isLivePreviewLoading } = useLivePreview({ + initialData: props.blog, + serverURL: CMS_SITE_ORIGIN || 'http://localhost:3030', + depth: 2, + }) + + const blogMetaData = useMemo(() => { + if (isDraftMode && shouldUseLivePreview) { + if (livePreviewData && typeof livePreviewData === 'object') { + return { ...props.blog, ...livePreviewData } + } + if (previewData !== props.blog) { + return previewData + } + } + return props.blog + }, [isDraftMode, shouldUseLivePreview, livePreviewData, previewData, props.blog]) as any + + const isCMS = (blogMetaData as any).isCMS + const imageUrl = isCMS + ? (blogMetaData as any).thumb ?? '' + : (blogMetaData as any).thumb + ? `/images/blog/${(blogMetaData as any).thumb}` + : '' + + const meta = { + title: (blogMetaData as any).meta_title ?? (blogMetaData as any).title, + description: (blogMetaData as any).meta_description ?? (blogMetaData as any).description, + url: `https://supabase.com/blog/${(blogMetaData as any).slug}`, + } + + const processTag = (tag: any): string => { + return typeof tag === 'string' ? tag : tag.name + } + + const tags = (blogMetaData as any).tags + ? Array.isArray((blogMetaData as any).tags) + ? ((blogMetaData as any).tags as Tag[]).map(processTag) + : [] + : [] + + const blogAuthors = isCMS + ? ((blogMetaData as any).authors as CMSAuthor[]) || [] + : ((blogMetaData as any).author as string) + ?.split(',') + .map((authorId: string) => { + const foundAuthor = authors.find((author) => author.author_id === authorId) + return foundAuthor + ? { + author: foundAuthor.author || 'Author', + author_image_url: foundAuthor.author_image_url || null, + author_url: foundAuthor.author_url || '#', + position: foundAuthor.position || '', + } + : null + }) + .filter(isNotNullOrUndefined) || [] + + const authorUrls = (blogAuthors as any) + .map((author: any) => author?.author_url) + .filter(isNotNullOrUndefined) + + return ( + <> + + + + ) +} diff --git a/apps/www/app/blog/[slug]/page.tsx b/apps/www/app/blog/[slug]/page.tsx new file mode 100644 index 00000000000..43560007608 --- /dev/null +++ b/apps/www/app/blog/[slug]/page.tsx @@ -0,0 +1,135 @@ +import { draftMode } from 'next/headers' +import { getAllPostSlugs, getPostdata, getSortedPosts } from 'lib/posts' +import { getAllCMSPostSlugs, getCMSPostBySlug } from 'lib/get-cms-posts' + +import type { Blog, BlogData, PostReturnType } from 'types/post' + +import BlogPostClient from '~/app/blog/[slug]/BlogPostClient' + +type MatterReturn = { + data: BlogData + content: string +} + +type BlogPostPageProps = { + prevPost: PostReturnType | null + nextPost: PostReturnType | null + relatedPosts: (PostReturnType & BlogData)[] + blog: Blog & BlogData + isDraftMode: boolean +} + +type Params = { + slug: string +} + +export async function generateStaticParams() { + const staticPaths = getAllPostSlugs('_blog') + const cmsPaths = await getAllCMSPostSlugs() + return [...staticPaths, ...cmsPaths].map((p) => ({ slug: p.params.slug })) +} + +export default async function BlogPostPage({ params }: { params: Params }) { + if (!params?.slug) { + throw new Error('Missing slug for app/blog/[slug]/page.tsx') + } + + const slug = `${params.slug}` + const { isEnabled: isDraft } = await draftMode() + + const matter = (await import('gray-matter')).default + const { mdxSerialize } = await import('lib/mdx/mdxSerialize') + + try { + const postContent = await getPostdata(slug, '_blog') + const parsedContent = matter(postContent) as unknown as MatterReturn + const content = parsedContent.content + const tocDepth = (parsedContent.data as any)?.toc_depth ?? 2 + const mdxSource = await mdxSerialize(content, { tocDepth }) + const blogPost = { ...parsedContent.data } + + const allStaticPosts = getSortedPosts({ directory: '_blog' }) + const allPosts = [...allStaticPosts].sort((a, b) => { + const aDate = new Date((a as unknown as { date: string }).date).getTime() + const bDate = new Date((b as unknown as { date: string }).date).getTime() + return bDate - aDate + }) + const currentIndex = allPosts.findIndex((post) => post.slug === slug) + const nextPost = currentIndex === allPosts.length - 1 ? null : allPosts[currentIndex + 1] + const prevPost = currentIndex === 0 ? null : allPosts[currentIndex - 1] + const tocResult = (mdxSource as any).scope?.toc || { content: '' } + const processedContent = tocResult.content + const relatedPosts = getSortedPosts({ + directory: '_blog', + limit: 3, + tags: (mdxSource as { scope: { tags?: string[] } }).scope.tags, + currentPostSlug: slug, + }) as unknown as (BlogData & PostReturnType)[] + + const props: BlogPostPageProps = { + prevPost, + nextPost, + relatedPosts, + blog: { + ...(blogPost as any), + content: mdxSource, + toc: { + ...tocResult, + content: processedContent, + }, + } as any, + isDraftMode: isDraft, + } + + return + } catch {} + + const cmsPost = await getCMSPostBySlug(slug, isDraft) + + if (!cmsPost) { + if (isDraft) { + const publishedPost = await getCMSPostBySlug(slug, false) + if (!publishedPost) return null + const mdxSource = await mdxSerialize(publishedPost.content || '') + const props: BlogPostPageProps = { + prevPost: null, + nextPost: null, + relatedPosts: [], + blog: { + ...publishedPost, + tags: publishedPost.tags || [], + authors: publishedPost.authors || [], + isCMS: true, + content: mdxSource, + toc: publishedPost.toc, + image: publishedPost.image ?? undefined, + thumb: publishedPost.thumb ?? undefined, + } as any, + isDraftMode: isDraft, + } + return + } + return null + } + + const mdxSource = await mdxSerialize(cmsPost.content || '') + + const props: BlogPostPageProps = { + prevPost: null, + nextPost: null, + relatedPosts: [], + blog: { + ...cmsPost, + tags: cmsPost.tags || [], + authors: cmsPost.authors || [], + isCMS: true, + content: mdxSource, + toc: cmsPost.toc, + image: cmsPost.image ?? undefined, + thumb: cmsPost.thumb ?? undefined, + } as any, + isDraftMode: isDraft, + } + + return +} diff --git a/apps/www/pages/blog/categories/[category].tsx b/apps/www/app/blog/categories/[category]/page.tsx similarity index 60% rename from apps/www/pages/blog/categories/[category].tsx rename to apps/www/app/blog/categories/[category]/page.tsx index 1abfdd0621d..d37149c40b2 100644 --- a/apps/www/pages/blog/categories/[category].tsx +++ b/apps/www/app/blog/categories/[category]/page.tsx @@ -2,37 +2,24 @@ import { NextSeo } from 'next-seo' import { getSortedPosts, getAllCategories } from '~/lib/posts' import Link from 'next/link' import { startCase } from 'lodash' - import DefaultLayout from '~/components/Layouts/Default' import BlogGridItem from '~/components/Blog/BlogGridItem' import type PostTypes from '~/types/post' -export async function getStaticProps({ params }: any) { - const posts = getSortedPosts({ directory: '_blog', limit: 0, categories: [params.category] }) - return { - props: { - category: params.category, - blogs: posts, - }, - } -} +type Params = { category: string } -export async function getStaticPaths() { +export async function generateStaticParams() { const categories = getAllCategories('_blog') - return { - paths: categories.map((category: any) => ({ params: { category: category } })), - fallback: false, - } + return categories.map((category: string) => ({ category })) } -interface Props { - category: string - blogs: PostTypes[] -} - -function CategoriesIndex(props: Props) { - const { blogs, category } = props - const capitalizedCategory = startCase(category.replaceAll('-', ' ')) +export default async function CategoriesPage({ params }: { params: Params }) { + const blogs = getSortedPosts({ + directory: '_blog', + limit: 0, + categories: [params.category], + }) as unknown as PostTypes[] + const capitalizedCategory = startCase(params.category.replaceAll('-', ' ')) return ( <> @@ -46,14 +33,14 @@ function CategoriesIndex(props: Props) {

    Blog / - {`${capitalizedCategory}`} + {capitalizedCategory}

    - {blogs.map((blog: PostTypes, idx: number) => ( + {blogs.map((blog: PostTypes) => (
    @@ -64,5 +51,3 @@ function CategoriesIndex(props: Props) { ) } - -export default CategoriesIndex diff --git a/apps/www/app/blog/page.tsx b/apps/www/app/blog/page.tsx new file mode 100644 index 00000000000..9897fe1afc8 --- /dev/null +++ b/apps/www/app/blog/page.tsx @@ -0,0 +1,43 @@ +import fs from 'fs' +import { generateRss } from '~/lib/rss' +import { getSortedPosts } from '~/lib/posts' +import BlogClient from '~/app/blog/BlogClient' + +export const dynamic = 'force-static' + +export default async function BlogPage() { + // Get static blog posts at build time + const staticPostsData = getSortedPosts({ directory: '_blog', runner: '** BLOG PAGE **' }) + + const allPostsData = [...staticPostsData].sort((a: any, b: any) => { + const dateA = (a as any).date + ? new Date((a as any).date).getTime() + : new Date((a as any).formattedDate).getTime() + const dateB = (b as any).date + ? new Date((b as any).date).getTime() + : new Date((b as any).formattedDate).getTime() + return dateB - dateA + }) + + // Generate RSS feed from static posts + const rss = generateRss(allPostsData) + try { + fs.writeFileSync('./public/rss.xml', rss) + } catch {} + + // Generate PlanetPG author feeds + try { + const planetPgPosts = allPostsData.filter((post: any) => post.tags?.includes('planetpg')) + const planetPgAuthors = planetPgPosts.map((post: any) => post.author.split(',')) + const uniquePlanetPgAuthors = new Set(([] as string[]).concat(...(planetPgAuthors as any))) + uniquePlanetPgAuthors.forEach((author) => { + const authorPosts = planetPgPosts.filter((post: any) => post.author.includes(author)) + if (authorPosts.length > 0) { + const authorRss = generateRss(authorPosts, author as string) + fs.writeFileSync(`./public/planetpg-${author}-rss.xml`, authorRss) + } + }) + } catch {} + + return +} diff --git a/apps/www/pages/blog/tags/[tag].tsx b/apps/www/app/blog/tags/[tag]/page.tsx similarity index 60% rename from apps/www/pages/blog/tags/[tag].tsx rename to apps/www/app/blog/tags/[tag]/page.tsx index f6fcad0f2e5..42ecccf72d7 100644 --- a/apps/www/pages/blog/tags/[tag].tsx +++ b/apps/www/app/blog/tags/[tag]/page.tsx @@ -2,37 +2,24 @@ import { NextSeo } from 'next-seo' import { getSortedPosts, getAllTags } from '~/lib/posts' import Link from 'next/link' import { startCase } from 'lodash' - import DefaultLayout from '~/components/Layouts/Default' import BlogGridItem from '~/components/Blog/BlogGridItem' -import PostTypes from '~/types/post' +import type PostTypes from '~/types/post' -export async function getStaticProps({ params }: any) { - const posts = getSortedPosts({ directory: '_blog', limit: 0, tags: [params.tag] }) - return { - props: { - tag: params.tag, - blogs: posts, - }, - } -} +type Params = { tag: string } -export async function getStaticPaths() { +export async function generateStaticParams() { const tags = getAllTags('_blog') - return { - paths: tags.map((tag: any) => ({ params: { tag: tag } })), - fallback: false, - } + return tags.map((tag: string) => ({ tag })) } -interface Props { - tag: string - blogs: PostTypes[] -} - -function TagBlogsPage(props: Props) { - const { blogs, tag } = props - const capitalizedTag = startCase(tag.replaceAll('-', ' ')) +export default async function TagPage({ params }: { params: Params }) { + const blogs = getSortedPosts({ + directory: '_blog', + limit: 0, + tags: [params.tag], + }) as unknown as PostTypes[] + const capitalizedTag = startCase(params.tag.replaceAll('-', ' ')) return ( <> @@ -46,14 +33,14 @@ function TagBlogsPage(props: Props) {

    Blog / - {`${capitalizedTag}`} + {capitalizedTag}

    - {blogs.map((blog: PostTypes, idx: number) => ( + {blogs.map((blog: PostTypes) => (
    @@ -64,5 +51,3 @@ function TagBlogsPage(props: Props) { ) } - -export default TagBlogsPage diff --git a/apps/www/app/layout.tsx b/apps/www/app/layout.tsx new file mode 100644 index 00000000000..534285f5efb --- /dev/null +++ b/apps/www/app/layout.tsx @@ -0,0 +1,48 @@ +import '@code-hike/mdx/styles' +import 'config/code-hike.scss' +import '../styles/index.css' + +import { Metadata } from 'next' +import { APP_NAME, DEFAULT_META_DESCRIPTION } from '~/lib/constants' +import Providers from './providers' + +const site_title = `${APP_NAME} | The Open Source Firebase Alternative` + +export const metadata: Metadata = { + title: site_title, + description: DEFAULT_META_DESCRIPTION, + openGraph: { + type: 'website', + url: 'https://supabase.com/', + siteName: 'Supabase', + images: [ + { + url: 'https://supabase.com/images/og/supabase-og.png', + width: 800, + height: 600, + alt: 'Supabase Og Image', + }, + ], + }, + twitter: { + creator: '@supabase', + site: '@supabase', + card: 'summary_large_image', + }, + viewport: 'width=device-width, initial-scale=1.0', + icons: { + icon: '/favicon/favicon.ico', + shortcut: '/favicon/favicon.ico', + apple: '/favicon/favicon.ico', + }, +} + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ) +} diff --git a/apps/www/app/providers.tsx b/apps/www/app/providers.tsx new file mode 100644 index 00000000000..f547bd7fb64 --- /dev/null +++ b/apps/www/app/providers.tsx @@ -0,0 +1,45 @@ +'use client' + +import { + AuthProvider, + FeatureFlagProvider, + IS_PLATFORM, + PageTelemetry, + ThemeProvider, + useThemeSandbox, + TelemetryTagManager, +} from 'common' +import { themes, TooltipProvider, SonnerToaster } from 'ui' +import { CommandProvider } from 'ui-patterns/CommandMenu' +import { useConsentToast } from 'ui-patterns/consent' +import { API_URL } from '~/lib/constants' +import { WwwCommandMenu } from '~/components/CommandMenu' + +function Providers({ children }: { children: React.ReactNode }) { + useThemeSandbox() + const { hasAcceptedConsent } = useConsentToast() + + return ( + + + t.value)} enableSystem disableTransitionOnChange> + + + + + {children} + + + + + + + + ) +} + +export default Providers diff --git a/apps/www/components/Blog/BlogFilters.tsx b/apps/www/components/Blog/BlogFilters.tsx index 4cffb0e06f0..336ea0edfb0 100644 --- a/apps/www/components/Blog/BlogFilters.tsx +++ b/apps/www/components/Blog/BlogFilters.tsx @@ -2,10 +2,10 @@ import { LOCAL_STORAGE_KEYS, useBreakpoint } from 'common' import { AnimatePresence, motion } from 'framer-motion' import { startCase } from 'lodash' import { useSearchParams } from 'next/navigation' -import { useRouter } from 'next/router' +import { useRouter } from 'next/compat/router' import { useEffect, useState } from 'react' import { useKey } from 'react-use' -import type { BlogView } from '~/pages/blog' +import type { BlogView } from '~/app/blog/BlogClient' import type PostTypes from '~/types/post' import { AlignJustify, ChevronDown, Grid, Search, X as CloseIcon } from 'lucide-react' @@ -74,7 +74,7 @@ function BlogFilters({ allPosts, setPosts, view, setView }: Props) { }, [q]) const handleReplaceRouter = () => { - if (!searchTerm && category !== 'all') { + if (!searchTerm && category !== 'all' && router) { router.query.category = category router.replace(router, undefined, { shallow: true, scroll: false }) } @@ -105,19 +105,19 @@ function BlogFilters({ allPosts, setPosts, view, setView }: Props) { }, [isMobile]) useEffect(() => { - if (router.isReady && q) { + if (router?.isReady && q) { setSearchTerm(q) } - if (router.isReady && activeCategory && activeCategory !== 'all') { + if (router?.isReady && activeCategory && activeCategory !== 'all') { setCategory(activeCategory) } - }, [activeCategory, router.isReady, q]) + }, [activeCategory, router?.isReady, q]) const handleSearchByText = (text: string) => { setSearchTerm(text) - searchParams?.has('q') && router.replace('/blog', undefined, { shallow: true, scroll: false }) - router.replace(`/blog?q=${text}`, undefined, { shallow: true, scroll: false }) - if (text.length < 1) router.replace('/blog', undefined, { shallow: true, scroll: false }) + searchParams?.has('q') && router?.replace('/blog', undefined, { shallow: true, scroll: false }) + router?.replace(`/blog?q=${text}`, undefined, { shallow: true, scroll: false }) + if (text.length < 1) router?.replace('/blog', undefined, { shallow: true, scroll: false }) const matches = allPosts.filter((post: any) => { const found = @@ -135,8 +135,8 @@ function BlogFilters({ allPosts, setPosts, view, setView }: Props) { searchTerm && setSearchTerm('') setCategory(category) category === 'all' - ? router.replace('/blog', undefined, { shallow: true, scroll: false }) - : router.replace(`/blog?category=${category}`, undefined, { + ? router?.replace('/blog', undefined, { shallow: true, scroll: false }) + : router?.replace(`/blog?category=${category}`, undefined, { shallow: true, scroll: false, }) diff --git a/apps/www/components/Footer/index.tsx b/apps/www/components/Footer/index.tsx index 21d5241eab2..e1f962d7fb0 100644 --- a/apps/www/components/Footer/index.tsx +++ b/apps/www/components/Footer/index.tsx @@ -2,7 +2,6 @@ import { CheckIcon } from '@heroicons/react/outline' import { REALTIME_CHANNEL_STATES } from '@supabase/supabase-js' import Image from 'next/image' import Link from 'next/link' -import { useRouter } from 'next/router' import { useEffect } from 'react' import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png' @@ -13,6 +12,7 @@ import { ThemeToggle } from 'ui-patterns/ThemeToggle' import supabase from '~/lib/supabase' import useDarkLaunchWeeks from '../../hooks/useDarkLaunchWeeks' import SectionContainer from '../Layouts/SectionContainer' +import { usePathname } from 'next/navigation' interface Props { className?: string @@ -20,17 +20,17 @@ interface Props { } const Footer = (props: Props) => { - const { pathname } = useRouter() + const pathname = usePathname() const isDarkLaunchWeek = useDarkLaunchWeeks() - const isGAWeek = pathname.includes('/ga-week') + const isGAWeek = pathname?.includes('/ga-week') const forceDark = isDarkLaunchWeek useEffect(() => { const channel = supabase.channel('footer') if (channel.state === REALTIME_CHANNEL_STATES.closed) { channel.subscribe((status: string) => { - if (status == 'SUBSCRIBED') { + if (status === 'SUBSCRIBED') { channel.send({ type: 'broadcast', event: 'footer_subscribed', @@ -56,7 +56,6 @@ const Footer = (props: Props) => { isGAWeek && 'dark:bg-alternative', props.className )} - aria-labelledby="footerHeading" >

    Footer diff --git a/apps/www/components/Nav/RightClickBrandLogo.tsx b/apps/www/components/Nav/RightClickBrandLogo.tsx index 41475934850..40ff5d78ec6 100644 --- a/apps/www/components/Nav/RightClickBrandLogo.tsx +++ b/apps/www/components/Nav/RightClickBrandLogo.tsx @@ -1,7 +1,7 @@ import { CheckIcon, ClipboardIcon } from '@heroicons/react/outline' import Image from 'next/image' import Link from 'next/link' -import { useRouter } from 'next/router' +import { useRouter } from 'next/compat/router' import { Fragment, MouseEvent, ReactNode, useRef, useState } from 'react' import { useClickAway, useKey } from 'react-use' import { @@ -84,7 +84,7 @@ const RightClickBrandLogo = () => { href="/" onContextMenu={handleRightClick} onFocus={handleKeyboardOpen} - onKeyDown={(e) => e.key === 'Enter' && router.push('/')} + onKeyDown={(e) => e.key === 'Enter' && router?.push('/')} className="block w-auto h-6 focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-foreground-lighter focus-visible:ring-offset-4 focus-visible:ring-offset-background-alternative focus-visible:rounded-sm" > { - const router = useRouter() + const pathname = usePathname() const { width } = useWindowSize() const [open, setOpen] = useState(false) const isLoggedIn = useIsLoggedIn() @@ -41,9 +41,9 @@ const Nav = ({ hideNavbar, stickyNavbar = true }: Props) => { const user = useUser() const userMenu = useDropdownMenu(user) - const isLaunchWeekPage = router.pathname.includes('/launch-week') - const isLaunchWeekXPage = router.pathname === '/launch-week/x' - const isGAWeekSection = router.pathname.startsWith('/ga-week') + const isLaunchWeekPage = pathname?.includes('/launch-week') + const isLaunchWeekXPage = pathname === '/launch-week/x' + const isGAWeekSection = pathname?.startsWith('/ga-week') const disableStickyNav = isLaunchWeekXPage || isGAWeekSection || isLaunchWeekPage || !stickyNavbar const showLaunchWeekNavMode = !isLaunchWeekXPage && (isLaunchWeekPage || isGAWeekSection) && !open diff --git a/apps/www/components/Nav/useDropdownMenu.tsx b/apps/www/components/Nav/useDropdownMenu.tsx index ce58b79c69e..70c0a960b3c 100644 --- a/apps/www/components/Nav/useDropdownMenu.tsx +++ b/apps/www/components/Nav/useDropdownMenu.tsx @@ -6,7 +6,7 @@ import { logOut } from 'common' import type { menuItem } from 'ui-patterns/AuthenticatedDropdownMenu' import { useSetCommandMenuOpen } from 'ui-patterns' -import { useRouter } from 'next/router' +import { useRouter } from 'next/compat/router' const useDropdownMenu = (user: User | null) => { const router = useRouter() @@ -50,7 +50,7 @@ const useDropdownMenu = (user: User | null) => { icon: LogOut, onClick: async () => { await logOut() - router.reload() + router?.reload() }, }, ], diff --git a/apps/www/components/ScrollProgress.tsx b/apps/www/components/ScrollProgress.tsx index 86d3fbe3e01..6818bde1c63 100644 --- a/apps/www/components/ScrollProgress.tsx +++ b/apps/www/components/ScrollProgress.tsx @@ -1,11 +1,11 @@ -import { useRouter } from 'next/router' +import { usePathname } from 'next/navigation' import React, { useEffect, useState } from 'react' const ScrollProgress = () => { const [progressPercentage, setProgressPercentage] = useState(0) - const { pathname } = useRouter() + const pathname = usePathname() - const isBlogPost = pathname.includes('/blog/') + const isBlogPost = pathname?.includes('/blog/') if (!isBlogPost) return null const handleScroll = () => { diff --git a/apps/www/hooks/useDarkLaunchWeeks.tsx b/apps/www/hooks/useDarkLaunchWeeks.tsx index 3d3a5486621..0127b21c01c 100644 --- a/apps/www/hooks/useDarkLaunchWeeks.tsx +++ b/apps/www/hooks/useDarkLaunchWeeks.tsx @@ -1,11 +1,11 @@ -import { useRouter } from 'next/router' +import { usePathname } from 'next/navigation' const useDarkLaunchWeeks = () => { - const { pathname } = useRouter() + const pathname = usePathname() - const isLaunchWeek7 = pathname.startsWith('/launch-week/7') - const isLaunchWeek8 = pathname.startsWith('/launch-week/8') - const isLaunchWeekX = pathname.startsWith('/launch-week/x') + const isLaunchWeek7 = pathname?.startsWith('/launch-week/7') + const isLaunchWeek8 = pathname?.startsWith('/launch-week/8') + const isLaunchWeekX = pathname?.startsWith('/launch-week/x') return isLaunchWeek7 || isLaunchWeek8 || isLaunchWeekX } diff --git a/apps/www/lib/get-cms-customers.tsx b/apps/www/lib/get-cms-customers.tsx index 87eb75a8e09..40e71003753 100644 --- a/apps/www/lib/get-cms-customers.tsx +++ b/apps/www/lib/get-cms-customers.tsx @@ -1,6 +1,6 @@ import { CMS_SITE_ORIGIN } from './constants' import { generateReadingTime } from './helpers' -const toc = require('markdown-toc') +import { generateTocFromMarkdown } from './toc' // Payload API configuration const PAYLOAD_URL = CMS_SITE_ORIGIN || 'http://localhost:3030' @@ -280,7 +280,7 @@ export async function getCMSCustomerBySlug(slug: string, preview = false) { } // Helper function to process customer data -function processCustomerData(customer: any) { +async function processCustomerData(customer: any) { const options: Intl.DateTimeFormatOptions = { month: 'long', day: 'numeric', year: 'numeric' } const formattedDate = new Date(customer.date || new Date()).toLocaleDateString('en-IN', options) const markdownContent = convertRichTextToMarkdown(customer.content) @@ -292,9 +292,10 @@ function processCustomerData(customer: any) { const logoUrl = customer.logo?.url ? `${PAYLOAD_URL}${customer.logo.url}` : null // Generate TOC from content for CMS customers - const tocResult = toc(markdownContent, { - maxdepth: customer.toc_depth ? customer.toc_depth : 2, - }) + const tocResult = await generateTocFromMarkdown( + markdownContent, + customer.toc_depth ? customer.toc_depth : 2 + ) return { slug: customer.slug, diff --git a/apps/www/lib/get-cms-events.tsx b/apps/www/lib/get-cms-events.tsx index 56b5b125a15..8925538d42c 100644 --- a/apps/www/lib/get-cms-events.tsx +++ b/apps/www/lib/get-cms-events.tsx @@ -1,6 +1,6 @@ import { CMS_SITE_ORIGIN } from './constants' import { generateReadingTime } from './helpers' -const toc = require('markdown-toc') +import { generateTocFromMarkdown } from './toc' // Payload API configuration const PAYLOAD_URL = CMS_SITE_ORIGIN || 'http://localhost:3030' @@ -274,7 +274,7 @@ export async function getCMSEventBySlug(slug: string, preview = false) { } // Helper function to process event data -function processEventData(event: any) { +async function processEventData(event: any) { const options: Intl.DateTimeFormatOptions = { month: 'long', day: 'numeric', year: 'numeric' } const formattedDate = new Date(event.date || new Date()).toLocaleDateString('en-IN', options) const markdownContent = convertRichTextToMarkdown(event.content) @@ -285,9 +285,10 @@ function processEventData(event: any) { const imageUrl = event.image?.url ? `${PAYLOAD_URL}${event.image.url}` : null // Generate TOC from content for CMS events - const tocResult = toc(markdownContent, { - maxdepth: event.toc_depth ? event.toc_depth : 2, - }) + const tocResult = await generateTocFromMarkdown( + markdownContent, + event.toc_depth ? event.toc_depth : 2 + ) return { slug: event.slug, diff --git a/apps/www/lib/get-cms-posts.tsx b/apps/www/lib/get-cms-posts.tsx index a9f0f4b950a..3122ed6e026 100644 --- a/apps/www/lib/get-cms-posts.tsx +++ b/apps/www/lib/get-cms-posts.tsx @@ -1,6 +1,6 @@ import { CMS_SITE_ORIGIN } from './constants' import { generateReadingTime } from './helpers' -const toc = require('markdown-toc') +import { generateTocFromMarkdown } from './toc' // Payload API configuration const PAYLOAD_URL = CMS_SITE_ORIGIN || 'http://localhost:3030' @@ -287,7 +287,7 @@ export async function getCMSPostBySlug(slug: string, preview = false) { // console.log(`[getCMSPostBySlug] About to call processPostData...`) try { - const processedPost = processPostData(post) + const processedPost = await processPostData(post) // console.log(`[getCMSPostBySlug] Successfully processed post:`, { // slug: processedPost.slug, // title: processedPost.title, @@ -305,7 +305,7 @@ export async function getCMSPostBySlug(slug: string, preview = false) { } // Helper function to process post data -function processPostData(post: any) { +async function processPostData(post: any) { // console.log(`[processPostData] Starting to process post:`, { // id: post.id, // title: post.title, @@ -324,9 +324,10 @@ function processPostData(post: any) { const readingTime = post.readingTime || generateReadingTime(markdownContent) // Generate TOC from content for CMS blog posts - const tocResult = toc(markdownContent, { - maxdepth: post.toc_depth ? post.toc_depth : 2, - }) + const tocResult = await generateTocFromMarkdown( + markdownContent, + post.toc_depth ? post.toc_depth : 2 + ) // Extract thumb and image URLs from the nested structure const thumbUrl = post.thumb?.url ? `${PAYLOAD_URL}${post.thumb.url}` : null @@ -364,7 +365,7 @@ function processPostData(post: any) { richContent: post.content, tags: post.tags || [], toc: { - content: tocResult.content.replace(/%23/g, ''), + content: tocResult.content, json: tocResult.json, }, } diff --git a/apps/www/lib/mdx/mdxSerialize.ts b/apps/www/lib/mdx/mdxSerialize.ts index ed026ace484..90390110e70 100644 --- a/apps/www/lib/mdx/mdxSerialize.ts +++ b/apps/www/lib/mdx/mdxSerialize.ts @@ -21,7 +21,50 @@ function addSelfClosingTags(htmlString: string): string { return modifiedHTML } -export async function mdxSerialize(source: string) { +type TocItem = { content: string; slug: string; lvl: number } + +function createRemarkCollectToc(maxDepth: number) { + return function remarkCollectToc() { + return function transformer(tree: any) { + const items: TocItem[] = [] + + const getText = (node: any): string => { + if (!node) return '' + if (node.type === 'text') return node.value || '' + if (Array.isArray(node.children)) return node.children.map(getText).join('') + return '' + } + + const slugify = (input: string) => + input + .trim() + .toLowerCase() + .replace(/[`~!@#$%^&*()+=|{}\[\]\\:";'<>?,./]+/g, '') + .replace(/\s+/g, '-') + + const walk = (node: any) => { + if (node.type === 'heading' && typeof node.depth === 'number') { + if (node.depth <= maxDepth) { + const text = getText(node) + if (text) { + items.push({ content: text, slug: slugify(text), lvl: node.depth }) + } + } + } + if (node.children) { + for (const child of node.children) walk(child) + } + } + + walk(tree) + + // Attach to tree data for retrieval post-serialize + ;(tree.data || (tree.data = {})).__collectedToc = items + } + } +} + +export async function mdxSerialize(source: string, options?: { tocDepth?: number }) { const formattedSource = addSelfClosingTags(source) const codeHikeOptions: CodeHikeConfig = { theme: codeHikeTheme, @@ -31,12 +74,29 @@ export async function mdxSerialize(source: string) { autoImport: false, } + const tocDepth = options?.tocDepth ?? 2 + let collectedToc: TocItem[] = [] + const mdxSource: any = await serialize(formattedSource, { scope: { chCodeConfig: codeHikeOptions, }, mdxOptions: { - remarkPlugins: [[remarkCodeHike, codeHikeOptions], remarkGfm], + remarkPlugins: [ + [remarkCodeHike, codeHikeOptions], + remarkGfm, + // Collect headings into a simple TOC structure + function () { + const plugin = createRemarkCollectToc(tocDepth) + const transformer = (plugin as any)() + return function (tree: any) { + transformer(tree) + if (tree?.data?.__collectedToc) { + collectedToc = tree.data.__collectedToc as TocItem[] + } + } + }, + ], rehypePlugins: [ // @ts-ignore rehypeSlug, // add IDs to any h1-h6 tag that doesn't have one, using a slug made from its text @@ -44,5 +104,18 @@ export async function mdxSerialize(source: string) { }, }) + // Expose TOC via scope for consumers (keeps function signature stable) + const tocMarkdown = collectedToc + .map((h) => `${' '.repeat(Math.max(0, h.lvl - 1))}- [${h.content}](#${h.slug})`) + .join('\n') + + mdxSource.scope = { + ...(mdxSource.scope || {}), + toc: { + content: tocMarkdown, + json: collectedToc, + }, + } + return mdxSource } diff --git a/apps/www/lib/telemetry.ts b/apps/www/lib/telemetry.ts index fe5f38c7f2a..0e781bd5eed 100644 --- a/apps/www/lib/telemetry.ts +++ b/apps/www/lib/telemetry.ts @@ -1,16 +1,19 @@ +'use client' + import { sendTelemetryEvent } from 'common' -import { TelemetryEvent } from 'common/telemetry-constants' +import type { TelemetryEvent } from 'common/telemetry-constants' import { API_URL } from 'lib/constants' -import { useRouter } from 'next/router' -import { useCallback } from 'react' +import { usePathname, useSearchParams } from 'next/navigation' export function useSendTelemetryEvent() { - const router = useRouter() + const pathname = usePathname() + const searchParams = useSearchParams() - return useCallback( - (event: TelemetryEvent) => { - return sendTelemetryEvent(API_URL, event, router.pathname) - }, - [router.pathname] - ) + return (event: TelemetryEvent) => { + const url = new URL(API_URL ?? 'http://localhost:3000') + url.pathname = pathname ?? '' + url.search = searchParams?.toString() ?? '' + + return sendTelemetryEvent(API_URL, event, url.toString()) + } } diff --git a/apps/www/lib/theme.utils.ts b/apps/www/lib/theme.utils.ts index 124bfe920c3..217c7f7c87b 100644 --- a/apps/www/lib/theme.utils.ts +++ b/apps/www/lib/theme.utils.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react' import { useTheme } from 'next-themes' -import { useRouter } from 'next/router' +import { useRouter } from 'next/compat/router' import useDarkLaunchWeeks from '../hooks/useDarkLaunchWeeks' export function useForceDeepDark() { diff --git a/apps/www/lib/toc.ts b/apps/www/lib/toc.ts new file mode 100644 index 00000000000..c003b300a3f --- /dev/null +++ b/apps/www/lib/toc.ts @@ -0,0 +1,40 @@ +type TocItem = { content: string; slug: string; lvl: number } + +function stripFencedCodeBlocks(markdown: string): string { + const segments = markdown.split('```') + let acc = '' + for (let i = 0; i < segments.length; i++) { + if (i % 2 === 0) acc += segments[i] + } + return acc +} + +function slugify(input: string): string { + return input + .trim() + .toLowerCase() + .replace(/[`~!@#$%^&*()+=|{}\[\]\\:\";'<>?,./]+/g, '') + .replace(/\s+/g, '-') +} + +export async function generateTocFromMarkdown(markdown: string, maxDepth: number) { + const noCode = stripFencedCodeBlocks(markdown) + const lines = noCode.split(/\r?\n/) + const items: TocItem[] = [] + + for (const line of lines) { + const m = /^(#{1,6})\s+(.*)$/.exec(line) + if (!m) continue + const depth = m[1].length + if (depth > maxDepth) continue + const text = m[2].trim() + if (!text) continue + items.push({ content: text, slug: slugify(text), lvl: depth }) + } + + const content = items + .map((h) => `${' '.repeat(Math.max(0, h.lvl - 1))}- [${h.content}](#${h.slug})`) + .join('\n') + + return { content, json: items } +} diff --git a/apps/www/package.json b/apps/www/package.json index 06362d3b077..14d09dd78a6 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -50,7 +50,6 @@ "icons": "workspace:*", "lodash": "^4.0.0", "lucide-react": "*", - "markdown-toc": "^1.2.0", "next": "catalog:", "next-contentlayer2": "0.5.3", "next-mdx-remote": "^4.4.1", @@ -69,6 +68,7 @@ "recharts": "^2.8.0", "rehype-slug": "^5.1.0", "remark": "^15.0.1", + "remark-parse": "^11.0.0", "remark-gfm": "^3.0.1", "remark-html": "^16.0.1", "shared-data": "workspace:*", diff --git a/apps/www/pages/alternatives/[slug].tsx b/apps/www/pages/alternatives/[slug].tsx index 6c940bc7028..580be0f9e0d 100644 --- a/apps/www/pages/alternatives/[slug].tsx +++ b/apps/www/pages/alternatives/[slug].tsx @@ -9,7 +9,7 @@ import { getAllPostSlugs, getPostdata, getSortedPosts } from '~/lib/posts' // for instance, if you use a button, you must add `Button` in the components object below. // table of contents extractor -const toc = require('markdown-toc') +const toc = require('markdown-toc-unlazy') export async function getStaticPaths() { const paths = getAllPostSlugs('_alternatives') diff --git a/apps/www/pages/blog/[slug].tsx b/apps/www/pages/blog/[slug].tsx deleted file mode 100644 index fefb0caf5b7..00000000000 --- a/apps/www/pages/blog/[slug].tsx +++ /dev/null @@ -1,298 +0,0 @@ -import dynamic from 'next/dynamic' -import { NextSeo } from 'next-seo' -import { useState, useMemo } from 'react' -import { useLivePreview } from '@payloadcms/live-preview-react' - -import authors from 'lib/authors.json' -import { getAllPostSlugs, getPostdata, getSortedPosts } from 'lib/posts' -import { getAllCMSPostSlugs, getCMSPostBySlug } from 'lib/get-cms-posts' -import { CMS_SITE_ORIGIN } from 'lib/constants' -import { isNotNullOrUndefined } from 'lib/helpers' - -import type { GetStaticProps, InferGetStaticPropsType } from 'next' -import type { Blog, BlogData, CMSAuthor, PostReturnType, ProcessedBlogData, Tag } from 'types/post' - -const BlogPostRenderer = dynamic(() => import('components/Blog/BlogPostRenderer')) - -type MatterReturn = { - data: BlogData - content: string -} - -type BlogPostPageProps = { - prevPost: PostReturnType | null - nextPost: PostReturnType | null - relatedPosts: (PostReturnType & BlogData)[] - blog: Blog & BlogData - isDraftMode: boolean -} - -type Params = { - slug: string -} - -export async function getStaticPaths() { - const staticPaths = getAllPostSlugs('_blog') - const cmsPaths = await getAllCMSPostSlugs() - const paths = [...staticPaths, ...cmsPaths] - - return { - paths, - fallback: 'blocking', // Set to 'blocking' to allow ISR for new CMS posts - } -} - -function BlogPostPage(props: InferGetStaticPropsType) { - const isDraftMode = props.isDraftMode - const [previewData] = useState(props.blog) - const shouldUseLivePreview = isDraftMode && props.blog.isCMS - - const { data: livePreviewData, isLoading: isLivePreviewLoading } = useLivePreview({ - initialData: props.blog, - serverURL: CMS_SITE_ORIGIN || 'http://localhost:3030', - depth: 2, - }) - - // Only use the live preview data for metadata - const blogMetaData = useMemo(() => { - if (isDraftMode && shouldUseLivePreview) { - // Priority 1: Use data from LivePreview hook - if (livePreviewData && typeof livePreviewData === 'object') { - return { ...props.blog, ...livePreviewData } - } - - // Priority 2: Use data from postMessage updates - if (previewData !== props.blog) { - return previewData - } - } - - // Fallback to blog - return props.blog - }, [isDraftMode, shouldUseLivePreview, livePreviewData, previewData, props.blog]) - - const isCMS = blogMetaData.isCMS - const imageUrl = isCMS - ? blogMetaData.thumb ?? '' - : blogMetaData.thumb - ? `/images/blog/${blogMetaData.thumb}` - : '' - - const meta = { - title: blogMetaData.meta_title ?? blogMetaData.title, - description: blogMetaData.meta_description ?? blogMetaData.description, - url: `https://supabase.com/blog/${blogMetaData.slug}`, - } - - const processTag = (tag: Tag): string => { - return typeof tag === 'string' ? tag : tag.name - } - - const tags = blogMetaData.tags - ? Array.isArray(blogMetaData.tags) - ? (blogMetaData.tags as Tag[]).map(processTag) - : [] - : [] - - const blogAuthors = isCMS - ? (blogMetaData.authors as CMSAuthor[]) || [] - : (blogMetaData.author as string) - ?.split(',') - .map((authorId: string) => { - const foundAuthor = authors.find((author) => author.author_id === authorId) - return foundAuthor - ? { - author: foundAuthor.author || 'Author', - author_image_url: foundAuthor.author_image_url || null, - author_url: foundAuthor.author_url || '#', - position: foundAuthor.position || '', - } - : null - }) - .filter(isNotNullOrUndefined) || [] - - const authorUrls = blogAuthors.map((author) => author?.author_url).filter(isNotNullOrUndefined) - - return ( - <> - - - - ) -} - -export const getStaticProps: GetStaticProps = async ({ - params, - draftMode = false, -}) => { - if (!params?.slug) { - console.error('[getStaticProps] Missing slug parameter:', params) - throw new Error('Missing slug for pages/blog/[slug].tsx') - } - - const slug = `${params.slug}` - - // Server-only imports to keep client bundle lean - const matter = (await import('gray-matter')).default - const { mdxSerialize } = await import('lib/mdx/mdxSerialize') - const markdownToc = require('markdown-toc') - - // Try static post first - try { - const postContent = await getPostdata(slug, '_blog') - const parsedContent = matter(postContent) as unknown as MatterReturn - const content = parsedContent.content - const mdxSource = await mdxSerialize(content) - const blogPost = { ...parsedContent.data } - - // Get all posts for navigation and related posts - const allStaticPosts = getSortedPosts({ directory: '_blog' }) - const allPosts = [...allStaticPosts].sort((a, b) => { - const aDate = new Date((a as unknown as { date: string }).date).getTime() - const bDate = new Date((b as unknown as { date: string }).date).getTime() - return bDate - aDate - }) - const currentIndex = allPosts.findIndex((post) => post.slug === slug) - const nextPost = currentIndex === allPosts.length - 1 ? null : allPosts[currentIndex + 1] - const prevPost = currentIndex === 0 ? null : allPosts[currentIndex - 1] - const tocResult = markdownToc(content, { - maxdepth: blogPost.toc_depth ? blogPost.toc_depth : 2, - }) - const processedContent = tocResult.content.replace(/%23/g, '') - const relatedPosts = getSortedPosts({ - directory: '_blog', - limit: 3, - tags: (mdxSource as { scope: { tags?: string[] } }).scope.tags, - currentPostSlug: slug, - }) as unknown as (BlogData & PostReturnType)[] - - return { - props: { - prevPost, - nextPost, - relatedPosts, - blog: { - ...blogPost, - content: mdxSource, - toc: { - ...tocResult, - content: processedContent, - }, - }, - isDraftMode: draftMode, - }, - // Don't use revalidate in draft mode - // ...(draftMode ? {} : { revalidate: 60 * 10 }), - } - } catch { - console.log('[getStaticProps] Static post not found, trying CMS post...') - } - - // Try CMS post (handle draft mode logic) - const cmsPost = await getCMSPostBySlug(slug, draftMode) - - if (!cmsPost) { - console.log( - '[getStaticProps] No CMS post found, checking published version (if in draft mode)...' - ) - // Try to fetch published version if draft mode failed - if (draftMode) { - const publishedPost = await getCMSPostBySlug(slug, false) - - if (!publishedPost) { - console.log('[getStaticProps] No published version found either, returning 404') - return { notFound: true } - } - - const mdxSource = await mdxSerialize(publishedPost.content || '') - - return { - props: { - prevPost: null, - nextPost: null, - relatedPosts: [], - blog: { - ...publishedPost, - tags: publishedPost.tags || [], - authors: publishedPost.authors || [], - isCMS: true, - content: mdxSource, - toc: publishedPost.toc, - image: publishedPost.image ?? undefined, - thumb: publishedPost.thumb ?? undefined, - }, - isDraftMode: draftMode, - }, - // Don't use revalidate in draft mode - // ...(draftMode ? {} : { revalidate: 60 * 10 }), - } - } - - return { notFound: true } - } - - // For CMS posts, process content - const mdxSource = await mdxSerialize(cmsPost.content || '') - - return { - props: { - prevPost: null, - nextPost: null, - relatedPosts: [], - blog: { - ...cmsPost, - tags: cmsPost.tags || [], - authors: cmsPost.authors || [], - isCMS: true, - content: mdxSource, - toc: cmsPost.toc, - image: cmsPost.image ?? undefined, - thumb: cmsPost.thumb ?? undefined, - }, - isDraftMode: draftMode, - }, - // Don't use revalidate in draft mode - // ...(draftMode ? {} : { revalidate: 60 * 10 }), - } -} - -export default BlogPostPage diff --git a/apps/www/pages/customers/[slug].tsx b/apps/www/pages/customers/[slug].tsx index ab64910ca23..02af4fc5708 100644 --- a/apps/www/pages/customers/[slug].tsx +++ b/apps/www/pages/customers/[slug].tsx @@ -14,7 +14,7 @@ import { mdxSerialize } from '~/lib/mdx/mdxSerialize' import { getAllPostSlugs, getPostdata, getSortedPosts } from '~/lib/posts' // table of contents extractor -const toc = require('markdown-toc') +const toc = require('markdown-toc-unlazy') export async function getStaticPaths() { const paths = getAllPostSlugs('_customers') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd05bd6a860..89078bcd419 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1607,9 +1607,6 @@ importers: lucide-react: specifier: '*' version: 0.436.0(react@18.3.1) - markdown-toc: - specifier: ^1.2.0 - version: 1.2.0 next: specifier: 'catalog:' version: 15.3.3(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) @@ -1670,6 +1667,9 @@ importers: remark-html: specifier: ^16.0.1 version: 16.0.1 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0(supports-color@8.1.1) shared-data: specifier: workspace:* version: link:../../packages/shared-data @@ -9108,10 +9108,6 @@ packages: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - ansi-red@0.1.1: - resolution: {integrity: sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==} - engines: {node: '>=0.10.0'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -9136,10 +9132,6 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - ansi-wrap@0.1.0: - resolution: {integrity: sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==} - engines: {node: '>=0.10.0'} - ansis@3.17.0: resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} engines: {node: '>=14'} @@ -9297,9 +9289,6 @@ packages: resolution: {integrity: sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==} engines: {node: '>=8'} - autolinker@0.28.1: - resolution: {integrity: sha512-zQAFO1Dlsn69eXaO6+7YZc+v84aquQKbwpzCE3L0stj56ERn9hutFxPopViLjo9G+rWwjozRhgS5KJ25Xy19cQ==} - autoprefixer@10.4.16: resolution: {integrity: sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==} engines: {node: ^10 || ^12 || >=14} @@ -9769,12 +9758,6 @@ packages: codemirror@5.65.15: resolution: {integrity: sha512-YC4EHbbwQeubZzxLl5G4nlbLc1T21QTrKGaOal/Pkm9dVDMZXMH7+ieSPEOZCtO9I68i8/oteJKOxzHC2zR+0g==} - coffee-script@1.12.7: - resolution: {integrity: sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==} - engines: {node: '>=0.8.0'} - deprecated: CoffeeScript on NPM has moved to "coffeescript" (no hyphen) - hasBin: true - collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} @@ -9864,17 +9847,10 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concat-stream@1.6.2: - resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} - engines: {'0': node >= 0.8} - concat-stream@2.0.0: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} - concat-with-sourcemaps@1.1.0: - resolution: {integrity: sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==} - concurrently@9.1.2: resolution: {integrity: sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==} engines: {node: '>=18'} @@ -10443,10 +10419,6 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - diacritics-map@0.1.0: - resolution: {integrity: sha512-3omnDTYrGigU0i4cJjvaKwD52B8aoqyX/NEIkukFFkogBemsIbhSa1O414fpTp5nuszJG6lvQ5vBvDVNCbSsaQ==} - engines: {node: '>=0.8.0'} - didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -10994,6 +10966,7 @@ packages: resolution: {integrity: sha512-t0q23FIpvHDTtnORW+bDJziGsal5uh9RJTJ1fyH8drd4lICOoXhJ5pLMUZ5C0VQei6dNmwTzzoTRgMkO9JgHEQ==} peerDependencies: eslint: '>= 5' + bundledDependencies: [] eslint-plugin-import@2.31.0: resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} @@ -11174,10 +11147,6 @@ packages: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} - expand-range@1.8.2: - resolution: {integrity: sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==} - engines: {node: '>=0.10.0'} - expand-tilde@2.0.2: resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} engines: {node: '>=0.10.0'} @@ -11384,10 +11353,6 @@ packages: filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} - fill-range@2.2.4: - resolution: {integrity: sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==} - engines: {node: '>=0.10.0'} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -11460,10 +11425,6 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - for-in@1.0.2: - resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} - engines: {node: '>=0.10.0'} - foreach@2.0.6: resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==} @@ -11860,18 +11821,10 @@ packages: resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - gray-matter@2.1.1: - resolution: {integrity: sha512-vbmvP1Fe/fxuT2QuLVcqb2BfK7upGhhbLIt9/owWEvPYrZZEkelLcq2HqzxosV+PQ67dUFLaAeNpH7C4hhICAA==} - engines: {node: '>=0.10.0'} - gray-matter@4.0.3: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} - gulp-header@1.8.12: - resolution: {integrity: sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==} - deprecated: Removed event-stream from gulp-header - gzip-size@6.0.0: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} @@ -12406,10 +12359,6 @@ packages: resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} engines: {node: '>=0.10.0'} - is-extendable@1.0.1: - resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} - engines: {node: '>=0.10.0'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -12483,14 +12432,6 @@ packages: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} - is-number@2.1.0: - resolution: {integrity: sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==} - engines: {node: '>=0.10.0'} - - is-number@4.0.0: - resolution: {integrity: sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==} - engines: {node: '>=0.10.0'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -12672,10 +12613,6 @@ packages: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} - isobject@2.1.0: - resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} - engines: {node: '>=0.10.0'} - isobject@3.0.1: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} @@ -12967,10 +12904,6 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} - lazy-cache@2.0.2: - resolution: {integrity: sha512-7vp2Acd2+Kz4XkzxGxaB1FWOi8KjWIWsgdfD5MCb86DWvlLqhRPM+d6Pro3iNEL5VT9mstz5hKAlcd+QR6H3aA==} - engines: {node: '>=0.10.0'} - lazystream@1.0.1: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} @@ -13016,10 +12949,6 @@ packages: linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} - list-item@1.1.1: - resolution: {integrity: sha512-S3D0WZ4J6hyM8o5SNKWaMYB1ALSacPZ2nHGEuCjmHZ+dc03gFeNZoNDcqfcnO4vDhTZmNrqrpYZCdXsRh22bzw==} - engines: {node: '>=0.10.0'} - listhen@1.9.0: resolution: {integrity: sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==} hasBin: true @@ -13072,9 +13001,6 @@ packages: lodash._createset@4.0.3: resolution: {integrity: sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA==} - lodash._reinterpolate@3.0.0: - resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==} - lodash._root@3.0.1: resolution: {integrity: sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==} @@ -13102,13 +13028,6 @@ packages: lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - lodash.template@4.5.0: - resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==} - deprecated: This package is deprecated. Use https://socket.dev/npm/package/eta instead. - - lodash.templatesettings@4.2.0: - resolution: {integrity: sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==} - lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} @@ -13246,18 +13165,9 @@ packages: resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} hasBin: true - markdown-link@0.1.1: - resolution: {integrity: sha512-TurLymbyLyo+kAUUAV9ggR9EPcDjP/ctlv9QAFiqUH7c+t6FlsbivPo9OKTU8xdOx9oNd2drW/Fi5RRElQbUqA==} - engines: {node: '>=0.10.0'} - markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} - markdown-toc@1.2.0: - resolution: {integrity: sha512-eOsq7EGd3asV0oBfmyqngeEIhrbkc7XVP63OwcJBIhH2EpG2PzFcbZdhy1jutXSlRBBVMNXHvMtSr5LAxSUvUg==} - engines: {node: '>=0.10.0'} - hasBin: true - markdown-wasm@1.2.0: resolution: {integrity: sha512-S12OTkyXCkOgI1n1rZY9cg4bK/PGu80Emjpvwp8BEjwCxhPV3yddF0U6+QhCitdBsI1tzWcoeahmW7k0Pq81OA==} @@ -13270,9 +13180,6 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - math-random@1.0.4: - resolution: {integrity: sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==} - md5@2.3.0: resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} @@ -13803,10 +13710,6 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - mixin-deep@1.3.2: - resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} - engines: {node: '>=0.10.0'} - mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -14288,10 +14191,6 @@ packages: resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} engines: {node: '>= 0.4'} - object.pick@1.3.0: - resolution: {integrity: sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==} - engines: {node: '>=0.10.0'} - object.values@1.2.0: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} @@ -15105,10 +15004,6 @@ packages: resolution: {integrity: sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==} engines: {node: '>=4'} - randomatic@3.1.1: - resolution: {integrity: sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==} - engines: {node: '>= 0.10.0'} - randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -15707,11 +15602,6 @@ packages: remark@15.0.1: resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} - remarkable@1.7.4: - resolution: {integrity: sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==} - engines: {node: '>= 0.10.0'} - hasBin: true - remedial@1.0.8: resolution: {integrity: sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==} @@ -15731,10 +15621,6 @@ packages: resolution: {integrity: sha512-U7XpAktpbSgHTRSNRrjKSrjYkZKuhUukfoBlXWXUExCAqhzh1TU3BDRAfJmarcl5voKS+pbKU9MvyLWKZ4UEEg==} engines: {node: '>= 0.8.0'} - repeat-element@1.1.4: - resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==} - engines: {node: '>=0.10.0'} - repeat-string@1.6.1: resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} engines: {node: '>=0.10'} @@ -16036,10 +15922,6 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} - set-getter@0.1.1: - resolution: {integrity: sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw==} - engines: {node: '>=0.10.0'} - set-harmonic-interval@1.0.1: resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==} engines: {node: '>=6.9'} @@ -16487,10 +16369,6 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-color@0.1.0: - resolution: {integrity: sha512-p9LsUieSjWNNAxVCXLeilaDlmuUOrDS5/dF9znM1nZc7EGX5+zEFC0bEevsNIaldjlks+2jns5Siz6F9iK6jwA==} - engines: {node: '>=0.10.0'} - strip-eof@1.0.0: resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} engines: {node: '>=0.10.0'} @@ -16740,9 +16618,6 @@ packages: resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} engines: {node: '>=18'} - through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -16808,10 +16683,6 @@ packages: to-no-case@1.0.2: resolution: {integrity: sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg==} - to-object-path@0.3.0: - resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} - engines: {node: '>=0.10.0'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -16840,9 +16711,6 @@ packages: resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} engines: {node: '>=14.16'} - toml@2.3.6: - resolution: {integrity: sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==} - toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} @@ -27701,10 +27569,6 @@ snapshots: dependencies: type-fest: 0.21.3 - ansi-red@0.1.1: - dependencies: - ansi-wrap: 0.1.0 - ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} @@ -27721,8 +27585,6 @@ snapshots: ansi-styles@6.2.1: {} - ansi-wrap@0.1.0: {} - ansis@3.17.0: {} any-promise@1.3.0: {} @@ -27908,10 +27770,6 @@ snapshots: auto-bind@4.0.0: {} - autolinker@0.28.1: - dependencies: - gulp-header: 1.8.12 - autoprefixer@10.4.16(postcss@8.5.3): dependencies: browserslist: 4.24.4 @@ -28479,8 +28337,6 @@ snapshots: codemirror@5.65.15: {} - coffee-script@1.12.7: {} - collapse-white-space@2.1.0: {} color-convert@1.9.3: @@ -28560,13 +28416,6 @@ snapshots: concat-map@0.0.1: {} - concat-stream@1.6.2: - dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 2.3.8 - typedarray: 0.0.6 - concat-stream@2.0.0: dependencies: buffer-from: 1.1.2 @@ -28574,10 +28423,6 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 - concat-with-sourcemaps@1.1.0: - dependencies: - source-map: 0.6.1 - concurrently@9.1.2: dependencies: chalk: 4.1.2 @@ -29101,8 +28946,6 @@ snapshots: dependencies: dequal: 2.0.3 - diacritics-map@0.1.0: {} - didyoumean@1.2.2: {} diff-sequences@29.6.3: {} @@ -29950,10 +29793,6 @@ snapshots: exit-hook@2.2.1: {} - expand-range@1.8.2: - dependencies: - fill-range: 2.2.4 - expand-tilde@2.0.2: dependencies: homedir-polyfill: 1.0.3 @@ -30206,14 +30045,6 @@ snapshots: dependencies: minimatch: 5.1.6 - fill-range@2.2.4: - dependencies: - is-number: 2.1.0 - isobject: 2.1.0 - randomatic: 3.1.1 - repeat-element: 1.1.4 - repeat-string: 1.6.1 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -30286,8 +30117,6 @@ snapshots: dependencies: is-callable: 1.2.7 - for-in@1.0.2: {} - foreach@2.0.6: {} foreground-child@3.1.1: @@ -30753,14 +30582,6 @@ snapshots: graphql@16.11.0: {} - gray-matter@2.1.1: - dependencies: - ansi-red: 0.1.1 - coffee-script: 1.12.7 - extend-shallow: 2.0.1 - js-yaml: 3.14.1 - toml: 2.3.6 - gray-matter@4.0.3: dependencies: js-yaml: 3.14.1 @@ -30768,12 +30589,6 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 - gulp-header@1.8.12: - dependencies: - concat-with-sourcemaps: 1.1.0 - lodash.template: 4.5.0 - through2: 2.0.5 - gzip-size@6.0.0: dependencies: duplexer: 0.1.2 @@ -31489,10 +31304,6 @@ snapshots: is-extendable@0.1.1: {} - is-extendable@1.0.1: - dependencies: - is-plain-object: 2.0.4 - is-extglob@2.1.1: {} is-finalizationregistry@1.0.2: @@ -31550,12 +31361,6 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-number@2.1.0: - dependencies: - kind-of: 3.2.2 - - is-number@4.0.0: {} - is-number@7.0.0: {} is-obj@3.0.0: {} @@ -31708,10 +31513,6 @@ snapshots: isexe@3.1.1: {} - isobject@2.1.0: - dependencies: - isarray: 1.0.0 - isobject@3.0.1: {} isomorphic-unfetch@3.1.0(encoding@0.1.13): @@ -32023,10 +31824,6 @@ snapshots: dependencies: language-subtag-registry: 0.3.23 - lazy-cache@2.0.2: - dependencies: - set-getter: 0.1.1 - lazystream@1.0.1: dependencies: readable-stream: 2.3.8 @@ -32088,13 +31885,6 @@ snapshots: dependencies: uc.micro: 2.1.0 - list-item@1.1.1: - dependencies: - expand-range: 1.8.2 - extend-shallow: 2.0.1 - is-number: 2.1.0 - repeat-string: 1.6.1 - listhen@1.9.0: dependencies: '@parcel/watcher': 2.5.1 @@ -32173,8 +31963,6 @@ snapshots: lodash._createset@4.0.3: {} - lodash._reinterpolate@3.0.0: {} - lodash._root@3.0.1: {} lodash._stringtopath@4.8.0: @@ -32195,15 +31983,6 @@ snapshots: lodash.sortby@4.7.0: {} - lodash.template@4.5.0: - dependencies: - lodash._reinterpolate: 3.0.0 - lodash.templatesettings: 4.2.0 - - lodash.templatesettings@4.2.0: - dependencies: - lodash._reinterpolate: 3.0.0 - lodash.throttle@4.1.1: {} lodash.uniqby@4.5.0: @@ -32372,33 +32151,14 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 - markdown-link@0.1.1: {} - markdown-table@3.0.3: {} - markdown-toc@1.2.0: - dependencies: - concat-stream: 1.6.2 - diacritics-map: 0.1.0 - gray-matter: 2.1.1 - lazy-cache: 2.0.2 - list-item: 1.1.1 - markdown-link: 0.1.1 - minimist: 1.2.8 - mixin-deep: 1.3.2 - object.pick: 1.3.0 - remarkable: 1.7.4 - repeat-string: 1.6.1 - strip-color: 0.1.0 - markdown-wasm@1.2.0: {} marked@4.3.0: {} math-intrinsics@1.1.0: {} - math-random@1.0.4: {} - md5@2.3.0: dependencies: charenc: 0.0.2 @@ -33486,11 +33246,6 @@ snapshots: mitt@3.0.1: {} - mixin-deep@1.3.2: - dependencies: - for-in: 1.0.2 - is-extendable: 1.0.1 - mkdirp@1.0.4: {} mkdirp@2.1.6: {} @@ -34147,10 +33902,6 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.23.3 - object.pick@1.3.0: - dependencies: - isobject: 3.0.1 - object.values@1.2.0: dependencies: call-bind: 1.0.7 @@ -35063,12 +34814,6 @@ snapshots: drange: 1.1.1 ret: 0.2.2 - randomatic@3.1.1: - dependencies: - is-number: 4.0.0 - kind-of: 6.0.3 - math-random: 1.0.4 - randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -35953,11 +35698,6 @@ snapshots: transitivePeerDependencies: - supports-color - remarkable@1.7.4: - dependencies: - argparse: 1.0.10 - autolinker: 0.28.1 - remedial@1.0.8: {} remove-accents@0.4.2: {} @@ -35977,8 +35717,6 @@ snapshots: rename-keys@1.2.0: {} - repeat-element@1.1.4: {} - repeat-string@1.6.1: {} require-directory@2.1.1: {} @@ -36344,10 +36082,6 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 - set-getter@0.1.1: - dependencies: - to-object-path: 0.3.0 - set-harmonic-interval@1.0.1: {} set-proto@1.0.0: @@ -36917,8 +36651,6 @@ snapshots: strip-bom@3.0.0: {} - strip-color@0.1.0: {} - strip-eof@1.0.0: {} strip-final-newline@3.0.0: {} @@ -37279,11 +37011,6 @@ snapshots: throttleit@2.1.0: {} - through2@2.0.5: - dependencies: - readable-stream: 2.3.8 - xtend: 4.0.2 - through@2.3.8: {} timeout-signal@2.0.0: {} @@ -37334,10 +37061,6 @@ snapshots: to-no-case@1.0.2: {} - to-object-path@0.3.0: - dependencies: - kind-of: 3.2.2 - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -37366,8 +37089,6 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 - toml@2.3.6: {} - toml@3.0.0: {} topojson-client@3.1.0: