add custom markdown support to our cms

This commit is contained in:
Alan Daniel committed 2025-10-20 21:44:15 -04:00
1 parent 1c990be3fc
commit 9c7db1baf7
11 files changed
+450 -147

No files matched your search

+4
View File
@@ -51,8 +51,12 @@
"payload": "3.52.0",
"pg": "^8.16.3",
"prism-react-renderer": "^2.3.1",
"prismjs": "^1.30.0",
"react": "catalog:",
"react-dom": "catalog:",
"react-markdown": "^10.1.0",
"rehype-highlight": "^7.0.2",
"remark-gfm": "^4.0.1",
"sharp": "~0.34.0",
"tailwind-merge": "^1.13.2",
"tsx": "^4.19.3",
@@ -17,6 +17,7 @@ import { MetaTitleComponent as MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c08
import { MetaImageComponent as MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { MetaDescriptionComponent as MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { PreviewComponent as PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
import { MarkdownEditor as MarkdownEditor_ead0010f14dbca6b92002dd21c00f8a3 } from '@/fields/MarkdownEditor/Component'
import { S3ClientUploadHandler as S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24 } from '@payloadcms/storage-s3/client'
export const importMap = {
@@ -39,5 +40,6 @@ export const importMap = {
"@payloadcms/plugin-seo/client#MetaImageComponent": MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@payloadcms/plugin-seo/client#MetaDescriptionComponent": MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@payloadcms/plugin-seo/client#PreviewComponent": PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
"@/fields/MarkdownEditor/Component#MarkdownEditor": MarkdownEditor_ead0010f14dbca6b92002dd21c00f8a3,
"@payloadcms/storage-s3/client#S3ClientUploadHandler": S3ClientUploadHandler_f97aa6c64367fa259c5bc0567239ef24
}
+46
View File
@@ -0,0 +1,46 @@
/* Override Payload CMS success colors with Supabase brand green */
:root {
/* Light theme - Supabase brand green scale */
--color-success-50: hsla(153, 80%, 99%, 1);
--color-success-100: hsla(148, 88%, 97%, 1);
--color-success-150: hsla(148, 76%, 95%, 1);
--color-success-200: hsla(148, 68%, 92%, 1);
--color-success-250: hsla(148, 59%, 88%, 1);
--color-success-300: hsla(149, 43%, 82%, 1);
--color-success-350: hsla(149, 43%, 75%, 1);
--color-success-400: hsla(149, 43%, 69%, 1);
--color-success-450: hsla(154, 55%, 57%, 1);
--color-success-500: hsla(154, 55%, 45%, 1);
--color-success-550: hsla(153, 60%, 53%, 1);
--color-success-600: hsla(153, 50%, 50%, 1);
--color-success-650: hsla(153, 50%, 42%, 1);
--color-success-700: hsla(153, 50%, 34%, 1);
--color-success-750: hsla(153, 45%, 23%, 1);
--color-success-800: hsla(153, 40%, 18%, 1);
--color-success-850: hsla(153, 40%, 15%, 1);
--color-success-900: hsla(153, 40%, 12%, 1);
--color-success-950: hsla(153, 40%, 8%, 1);
}
/* Dark theme - Supabase brand green dark scale */
[data-theme='dark'] {
--color-success-50: hsla(153, 75%, 6%, 1);
--color-success-100: hsla(153, 73%, 7%, 1);
--color-success-150: hsla(154, 69%, 8%, 1);
--color-success-200: hsla(154, 69%, 9%, 1);
--color-success-250: hsla(154, 67%, 10%, 1);
--color-success-300: hsla(154, 67%, 11%, 1);
--color-success-350: hsla(154, 66%, 12%, 1);
--color-success-400: hsla(154, 66%, 13%, 1);
--color-success-450: hsla(154, 64%, 15%, 1);
--color-success-500: hsla(154, 64%, 17%, 1);
--color-success-550: hsla(154, 62%, 19%, 1);
--color-success-600: hsla(154, 62%, 22%, 1);
--color-success-650: hsla(153, 60%, 25%, 1);
--color-success-700: hsla(153, 60%, 28%, 1);
--color-success-750: hsla(153, 60%, 40%, 1);
--color-success-800: hsla(153, 60%, 53%, 1);
--color-success-850: hsla(153, 60%, 62%, 1);
--color-success-900: hsla(153, 60%, 70%, 1);
--color-success-950: hsla(153, 60%, 95%, 1);
}
+1 -1
View File
@@ -1,5 +1,5 @@
import type { CollectionConfig } from 'payload'
import { isAdmin } from '../access/isAdmin'
import { isAdmin } from '../access/isAdmin.ts'
export const Categories: CollectionConfig = {
slug: 'categories',
+9 -28
View File
@@ -1,22 +1,8 @@
import type { CollectionConfig } from 'payload'
import {
BlocksFeature,
FixedToolbarFeature,
HeadingFeature,
HorizontalRuleFeature,
InlineToolbarFeature,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
import { isAnyone } from '../../access/isAnyone.ts'
import { isAuthenticated } from '../../access/isAuthenticated.ts'
import { Banner } from '../../blocks/Banner/config.ts'
import { Code } from '../../blocks/Code/config.ts'
import { MediaBlock } from '../../blocks/MediaBlock/config.ts'
import { Quote } from '../../blocks/Quote/config.ts'
import { YouTube } from '../../blocks/YouTube/config.ts'
import { populateAuthors } from './hooks/populateAuthors.ts'
import { revalidateDelete, revalidatePost } from './hooks/revalidatePost.ts'
@@ -91,21 +77,16 @@ export const Posts: CollectionConfig = {
fields: [
{
name: 'content',
type: 'richText',
editor: lexicalEditor({
features: ({ rootFeatures }) => {
return [
...rootFeatures,
HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4'] }),
BlocksFeature({ blocks: [Banner, Code, MediaBlock, Quote, YouTube] }),
FixedToolbarFeature(),
InlineToolbarFeature(),
HorizontalRuleFeature(),
]
},
}),
label: false,
type: 'textarea',
label: 'Content (Markdown)',
required: true,
admin: {
description:
'Write your content in Markdown format. Supports headings, lists, links, images, code blocks, and more.',
components: {
Field: '@/fields/MarkdownEditor/Component#MarkdownEditor',
},
},
},
],
label: 'Content',
+19 -65
View File
@@ -1,76 +1,19 @@
import { MediaBlock } from '@/blocks/MediaBlock/Component'
import {
DefaultNodeTypes,
SerializedBlockNode,
SerializedLinkNode,
type DefaultTypedEditorState,
} from '@payloadcms/richtext-lexical'
import {
JSXConvertersFunction,
LinkJSXConverter,
RichText as ConvertRichText,
} from '@payloadcms/richtext-lexical/react'
import { CodeBlock, CodeBlockProps } from '@/blocks/Code/Component'
import type {
BannerBlock as BannerBlockProps,
MediaBlock as MediaBlockProps,
QuoteBlock as QuoteBlockProps,
YouTubeBlock as YouTubeBlockProps,
} from '@/payload-types'
import { BannerBlock } from '@/blocks/Banner/Component'
import { QuoteBlock } from '@/blocks/Quote/Component'
import { YouTubeBlock } from '@/blocks/YouTube/Component'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import rehypeHighlight from 'rehype-highlight'
import { cn } from '@/utilities/ui'
type NodeTypes =
| DefaultNodeTypes
| SerializedBlockNode<
MediaBlockProps | BannerBlockProps | CodeBlockProps | QuoteBlockProps | YouTubeBlockProps
>
const internalDocToHref = ({ linkNode }: { linkNode: SerializedLinkNode }) => {
const { value, relationTo } = linkNode.fields.doc!
if (typeof value !== 'object') {
throw new Error('Expected value to be an object')
}
const slug = value.slug
return relationTo === 'posts' ? `/posts/${slug}` : `/${slug}`
}
const jsxConverters: JSXConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({
...defaultConverters,
...LinkJSXConverter({ internalDocToHref }),
blocks: {
banner: ({ node }) => <BannerBlock className="col-start-2 mb-4" {...node.fields} />,
mediaBlock: ({ node }) => (
<MediaBlock
className="col-start-1 col-span-3"
imgClassName="m-0"
{...node.fields}
captionClassName="mx-auto max-w-[48rem]"
enableGutter={false}
disableInnerContainer={true}
/>
),
code: ({ node }) => <CodeBlock className="col-start-2" {...node.fields} />,
quote: ({ node }) => <QuoteBlock className="col-start-2" {...node.fields} />,
youtube: ({ node }) => <YouTubeBlock className="col-start-2" {...node.fields} />,
},
})
type Props = {
data: DefaultTypedEditorState
data: string
enableGutter?: boolean
enableProse?: boolean
} & React.HTMLAttributes<HTMLDivElement>
export default function RichText(props: Props) {
const { className, enableProse = true, enableGutter = true, ...rest } = props
const { data, className, enableProse = true, enableGutter = true, ...rest } = props
return (
<ConvertRichText
converters={jsxConverters}
<div
className={cn(
'payload-richtext',
{
@@ -81,6 +24,17 @@ export default function RichText(props: Props) {
className
)}
{...rest}
/>
>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeHighlight]}
components={{
// Customize rendering if needed
a: ({ node, ...props }) => <a {...props} target="_blank" rel="noopener noreferrer" />,
}}
>
{data}
</ReactMarkdown>
</div>
)
}
@@ -0,0 +1,71 @@
'use client'
import React, { useCallback, useEffect, useRef } from 'react'
import { useField } from '@payloadcms/ui'
import Prism from 'prismjs'
import 'prismjs/components/prism-markdown'
import './styles.css'
type MarkdownEditorProps = {
path: string
label?: string
required?: boolean
}
export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({ path, label, required }) => {
const { value, setValue } = useField<string>({ path })
const highlightRef = useRef<HTMLPreElement>(null)
const textareaRef = useRef<HTMLTextAreaElement>(null)
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setValue(e.target.value)
},
[setValue]
)
// Auto-resize textarea
useEffect(() => {
const textarea = textareaRef.current
if (textarea) {
textarea.style.height = 'auto'
textarea.style.height = `${textarea.scrollHeight}px`
}
}, [value])
// Highlight using Prism
useEffect(() => {
if (highlightRef.current) {
Prism.highlightElement(highlightRef.current)
}
}, [value])
return (
<div className="markdown-editor-field">
<div className="field-label">
{label && (
<label>
{label}
{required && <span className="required">*</span>}
</label>
)}
</div>
<div className="markdown-editor-container">
<pre className="markdown-editor-highlight" aria-hidden="true">
<code ref={highlightRef} className="language-markdown">
{value || ''}
</code>
</pre>
<textarea
ref={textareaRef}
className="markdown-editor-textarea"
value={value || ''}
onChange={handleChange}
placeholder="Write your markdown content here..."
rows={20}
spellCheck="false"
/>
</div>
</div>
)
}
@@ -0,0 +1,17 @@
import type { Field } from 'payload'
export const markdownField = (overrides?: Partial<Field>): Field[] => {
return [
{
name: 'content',
type: 'textarea',
required: true,
admin: {
components: {
Field: '@/fields/MarkdownEditor/Component#MarkdownEditor',
},
},
...overrides,
},
]
}
@@ -0,0 +1,190 @@
.markdown-editor-field {
margin-bottom: 1rem;
}
.field-label label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
font-size: 0.875rem;
color: var(--theme-elevation-900);
}
.field-label .required {
color: var(--color-error-500);
margin-left: 0.25rem;
}
.markdown-editor-container {
position: relative;
width: 100%;
border: 1px solid var(--theme-elevation-150);
border-radius: 4px;
background: var(--theme-input-bg);
overflow: hidden;
transition: border-color 150ms ease;
}
.markdown-editor-container:focus-within {
border-color: var(--theme-elevation-400);
}
.markdown-editor-highlight,
.markdown-editor-textarea {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'courier-new', monospace;
font-size: 13px;
line-height: 1.6;
padding: 1rem;
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
tab-size: 2;
}
.markdown-editor-highlight {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
background: transparent !important;
}
.markdown-editor-highlight code {
display: block;
font-family: inherit;
font-size: inherit;
line-height: inherit;
padding: 0;
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
background: transparent !important;
color: var(--theme-elevation-800);
}
.markdown-editor-textarea {
position: relative;
width: 100%;
background: transparent;
border: none;
outline: none;
resize: none;
color: transparent;
z-index: 1;
caret-color: var(--theme-elevation-800);
-webkit-text-fill-color: transparent;
min-height: 400px;
overflow: hidden;
}
.markdown-editor-textarea::placeholder {
color: var(--theme-elevation-400);
opacity: 1;
}
.markdown-editor-textarea::selection {
background: var(--theme-elevation-150);
color: var(--theme-elevation-1000);
}
/* Custom Prism theme - elevation based with green highlights */
.markdown-editor-highlight .token.title,
.markdown-editor-highlight .token.heading {
color: var(--color-success-800);
font-weight: 600;
}
.markdown-editor-highlight .token.heading.punctuation {
color: var(--color-success-800);
}
.markdown-editor-highlight .token.bold {
color: var(--theme-elevation-800);
font-weight: bold;
}
.markdown-editor-highlight .token.italic {
color: var(--theme-elevation-800);
font-style: italic;
}
.markdown-editor-highlight .token.url,
.markdown-editor-highlight .token.link {
color: var(--color-success-800);
text-decoration: underline;
}
.markdown-editor-highlight .token.url-reference,
.markdown-editor-highlight .token.link-text {
color: var(--color-success-800);
}
.markdown-editor-highlight .token.list.punctuation {
color: var(--color-success-800);
font-weight: 600;
}
.markdown-editor-highlight .token.code,
.markdown-editor-highlight .token.code-snippet {
color: var(--color-success-800);
background: var(--color-success-50);
padding: 0.125rem 0.25rem;
border-radius: 3px;
}
.markdown-editor-highlight .token.code-fence,
.markdown-editor-highlight .token.code-block {
color: var(--color-success-800);
}
.markdown-editor-highlight .token.blockquote {
color: var(--theme-elevation-600);
font-style: italic;
}
.markdown-editor-highlight .token.blockquote.punctuation {
color: var(--theme-elevation-400);
}
.markdown-editor-highlight .token.hr,
.markdown-editor-highlight .token.hr.punctuation {
color: var(--theme-elevation-300);
}
.markdown-editor-highlight .token.table-header,
.markdown-editor-highlight .token.table-cell {
color: var(--theme-elevation-800);
}
.markdown-editor-highlight .token.table-delimiter,
.markdown-editor-highlight .token.table.punctuation {
color: var(--theme-elevation-500);
}
.markdown-editor-highlight .token.strike,
.markdown-editor-highlight .token.strikethrough {
color: var(--theme-elevation-500);
text-decoration: line-through;
}
.markdown-editor-highlight .token.task-list-item,
.markdown-editor-highlight .token.checkbox {
color: var(--color-success-800);
}
.markdown-editor-highlight .token.punctuation {
color: var(--theme-elevation-500);
}
.markdown-editor-highlight .token.attr-name,
.markdown-editor-highlight .token.keyword {
color: var(--color-success-800);
}
.markdown-editor-highlight .token.code-language {
color: var(--color-success-800);
font-size: 0.85em;
}
+45 -21
View File
@@ -19,42 +19,66 @@ export interface ProcessedCMSContent {
}
/**
* Process PayloadCMS rich text content for use in the www blog
* This function handles both basic content and custom blocks
* Process PayloadCMS content for use in the www blog
* This function handles:
* 1. Raw markdown strings (new textarea field)
* 2. Lexical rich text JSON (legacy format)
* 3. Custom blocks converted to existing www syntax
*/
export async function processCMSContent(
richTextContent: any,
content: any,
tocDepth: number = 3
): Promise<ProcessedCMSContent> {
try {
// Convert rich text to markdown with blocks converted to existing www syntax
const { markdown, blocks } = convertRichTextToMarkdownWithBlocks(richTextContent)
// Check if content is already a markdown string
if (typeof content === 'string') {
// Direct markdown string - no conversion needed
const toc = await generateTocFromMarkdown(content, tocDepth)
const mdxContent = await mdxSerialize(content, { tocDepth })
// Generate TOC from the converted markdown
const toc = await generateTocFromMarkdown(markdown, tocDepth)
// Serialize the markdown for MDX (blocks are now converted to existing components)
const mdxContent = await mdxSerialize(markdown, { tocDepth })
return {
content: mdxContent,
blocks,
toc,
plainMarkdown: markdown,
return {
content: mdxContent,
blocks: [],
toc,
plainMarkdown: content,
}
}
// Handle Lexical JSON format (legacy)
if (content?.root?.children) {
// Convert rich text to markdown with blocks converted to existing www syntax
const { markdown, blocks } = convertRichTextToMarkdownWithBlocks(content)
// Generate TOC from the converted markdown
const toc = await generateTocFromMarkdown(markdown, tocDepth)
// Serialize the markdown for MDX (blocks are now converted to existing components)
const mdxContent = await mdxSerialize(markdown, { tocDepth })
return {
content: mdxContent,
blocks,
toc,
plainMarkdown: markdown,
}
}
// Fallback for unexpected format
throw new Error('Content format not recognized')
} catch (error) {
console.error('Error processing CMS content:', error)
// Fallback to basic conversion
const plainMarkdown = convertRichTextToMarkdown(richTextContent)
const toc = await generateTocFromMarkdown(plainMarkdown, tocDepth)
const mdxContent = await mdxSerialize(plainMarkdown, { tocDepth })
// Last resort fallback
const fallbackMarkdown =
typeof content === 'string' ? content : convertRichTextToMarkdown(content)
const toc = await generateTocFromMarkdown(fallbackMarkdown, tocDepth)
const mdxContent = await mdxSerialize(fallbackMarkdown, { tocDepth })
return {
content: mdxContent,
blocks: [],
toc,
plainMarkdown,
plainMarkdown: fallbackMarkdown,
}
}
}
+46 -32
View File
@@ -190,12 +190,24 @@ importers:
prism-react-renderer:
specifier: ^2.3.1
version: 2.3.1(react@18.3.1)
prismjs:
specifier: ^1.30.0
version: 1.30.0
react:
specifier: 'catalog:'
version: 18.3.1
react-dom:
specifier: 'catalog:'
version: 18.3.1(react@18.3.1)
react-markdown:
specifier: ^10.1.0
version: 10.1.0(@types/react@18.3.3)(react@18.3.1)(supports-color@8.1.1)
rehype-highlight:
specifier: ^7.0.2
version: 7.0.2
remark-gfm:
specifier: ^4.0.1
version: 4.0.1(supports-color@8.1.1)
sharp:
specifier: ~0.34.0
version: 0.34.3
@@ -385,7 +397,7 @@ importers:
version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@sentry/nextjs':
specifier: ^10.3.0
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.74.0
@@ -800,7 +812,7 @@ importers:
version: 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@sentry/nextjs':
specifier: ^10.3.0
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
'@std/path':
specifier: npm:@jsr/std__path@^1.0.8
version: '@jsr/std__path@1.0.8'
@@ -1185,7 +1197,7 @@ importers:
version: 4.4.2
'@vitest/coverage-v8':
specifier: ^3.0.9
version: 3.0.9(supports-color@8.1.1)(vitest@3.0.9)
version: 3.0.9(supports-color@8.1.1)(vitest@3.0.9(@types/node@22.13.14)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.11.3(@types/node@22.13.14)(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))
'@vitest/ui':
specifier: ^3.0.0
version: 3.0.4(vitest@3.0.9)
@@ -1579,7 +1591,7 @@ importers:
version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@sentry/nextjs':
specifier: ^10
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
'@supabase/supabase-js':
specifier: 'catalog:'
version: 2.74.0
@@ -2006,7 +2018,7 @@ importers:
version: 18.3.0
'@vitest/coverage-v8':
specifier: ^3.0.9
version: 3.0.9(supports-color@8.1.1)(vitest@3.0.9)
version: 3.0.9(supports-color@8.1.1)(vitest@3.0.9(@types/node@22.13.14)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.11.3(@types/node@22.13.14)(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1))
'@vitest/ui':
specifier: ^3.0.0
version: 3.0.4(vitest@3.0.9)
@@ -13389,6 +13401,10 @@ packages:
highlight.js@10.7.3:
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
highlight.js@11.11.1:
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
engines: {node: '>=12.0.0'}
highlightjs-curl@1.3.0:
resolution: {integrity: sha512-50UEfZq1KR0Lfk2Tr6xb/MUIZH3h10oNC0OTy9g7WELcs5Fgy/mKN1vEhuKTkKbdo8vr5F9GXstu2eLhApfQ3A==}
@@ -14497,6 +14513,9 @@ packages:
lowlight@1.20.0:
resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
lowlight@3.3.0:
resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==}
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -16638,10 +16657,6 @@ packages:
resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==}
engines: {node: '>=6'}
prismjs@1.29.0:
resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==}
engines: {node: '>=6'}
prismjs@1.30.0:
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
engines: {node: '>=6'}
@@ -17311,6 +17326,9 @@ packages:
rehype-harden@1.1.2:
resolution: {integrity: sha512-58RSgd3BAYW/hULy6qvrLBIRe8qe5PElwEpRjrLilvhJ3N+Y6ptKAmy1CLIIyoMz7CMI30GqENhNXksJd5hGDg==}
rehype-highlight@7.0.2:
resolution: {integrity: sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==}
rehype-katex@7.0.0:
resolution: {integrity: sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q==}
@@ -27790,7 +27808,7 @@ snapshots:
'@sentry/core@10.3.0': {}
'@sentry/nextjs@10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.28.4(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)':
'@sentry/nextjs@10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)':
dependencies:
'@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.36.0
@@ -30262,24 +30280,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@vitest/coverage-v8@3.0.9(supports-color@8.1.1)(vitest@3.0.9)':
dependencies:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 1.0.2
debug: 4.4.3(supports-color@8.1.1)
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-lib-source-maps: 5.0.6(supports-color@8.1.1)
istanbul-reports: 3.1.7
magic-string: 0.30.19
magicast: 0.3.5
std-env: 3.9.0
test-exclude: 7.0.1
tinyrainbow: 2.0.0
vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.4)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.4.11(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
'@vitest/expect@3.0.9':
dependencies:
'@vitest/spy': 3.0.9
@@ -30355,7 +30355,7 @@ snapshots:
sirv: 3.0.0
tinyglobby: 0.2.14
tinyrainbow: 2.0.0
vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.4)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.4.11(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.8.1)
vitest: 3.0.9(@types/node@22.13.14)(@vitest/ui@3.0.4)(jiti@2.5.1)(jsdom@20.0.3(supports-color@8.1.1))(msw@2.11.3(@types/node@22.13.14)(typescript@5.9.2))(sass@1.77.4)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.1)
'@vitest/utils@3.0.4':
dependencies:
@@ -34378,6 +34378,8 @@ snapshots:
highlight.js@10.7.3: {}
highlight.js@11.11.1: {}
highlightjs-curl@1.3.0: {}
hoist-non-react-statics@3.3.2:
@@ -35501,6 +35503,12 @@ snapshots:
fault: 1.0.4
highlight.js: 10.7.3
lowlight@3.3.0:
dependencies:
'@types/hast': 3.0.4
devlop: 1.1.0
highlight.js: 11.11.1
lru-cache@10.4.3: {}
lru-cache@11.0.1: {}
@@ -38689,8 +38697,6 @@ snapshots:
prismjs@1.27.0: {}
prismjs@1.29.0: {}
prismjs@1.30.0: {}
proc-log@3.0.0: {}
@@ -39238,7 +39244,7 @@ snapshots:
'@babel/runtime': 7.26.10
highlight.js: 10.7.3
lowlight: 1.20.0
prismjs: 1.29.0
prismjs: 1.30.0
react: 18.3.1
refractor: 3.6.0
@@ -39505,6 +39511,14 @@ snapshots:
rehype-harden@1.1.2: {}
rehype-highlight@7.0.2:
dependencies:
'@types/hast': 3.0.4
hast-util-to-text: 4.0.0
lowlight: 3.3.0
unist-util-visit: 5.0.0
vfile: 6.0.3
rehype-katex@7.0.0:
dependencies:
'@types/hast': 3.0.4