diff --git a/app/editor/DocsLayout.tsx b/app/editor/DocsLayout.tsx index 5cb3ea7..4394f91 100644 --- a/app/editor/DocsLayout.tsx +++ b/app/editor/DocsLayout.tsx @@ -8,14 +8,16 @@ export const DocsLayout: FC<{ }> = ({ nav, pageNav, content }) => { return (
-
+
{nav}
-
{content}
+
{content}
-
-
{pageNav}
-
+ {pageNav && ( +
+
{pageNav}
+
+ )}
) } diff --git a/app/editor/NavToggle.tsx b/app/editor/NavToggle.tsx index 1fd75ba..300db1b 100644 --- a/app/editor/NavToggle.tsx +++ b/app/editor/NavToggle.tsx @@ -14,14 +14,17 @@ export const NavToggle: React.FC<{ children: ReactNode }> = ({ children }) => { e.preventDefault() setShowNav((v) => !v) }} - className="block md:hidden fixed top-4 right-4 z-10" + className="block md:hidden fixed top-4 right-4 z-40" >
{children}
diff --git a/app/editor/api/NavGroup.tsx b/app/editor/api/NavGroup.tsx new file mode 100644 index 0000000..43893aa --- /dev/null +++ b/app/editor/api/NavGroup.tsx @@ -0,0 +1,47 @@ +'use client' +import { ApiRefNode, TypeFieldMap, RefNodeField, MarkdownApiRefDocument } from '@/app/apiDocsStructures' +import React, { useEffect } from 'react' +import ExpandMoreIcon from '@/app/images/expand_more.svg' +import ExpandLessIcon from '@/app/images/expand_less.svg' +import { useParams } from 'next/navigation' +import Link from 'next/link' + +interface NavLink { + label: string + location: string +} + +interface NavGroup { + title: string + links: NavLink[] +} + +export default function NavGroup({ group }: { group: NavGroup }) { + let slugParam = useParams().slug + if (typeof slugParam === 'string') { + slugParam = [slugParam] + } + const slug = (slugParam ?? []).join('/') + + const [expanded, setExpanded] = React.useState(() => { + const currentLocation = `/editor/api/${slug}` + return group.links.some(({ location }) => location === currentLocation) + }) + + return ( +
+
setExpanded((v) => !v)}> + {group.title} + {expanded ? : } +
+ {expanded && + group.links.map(({ location, label }, index) => { + return ( +
+ {label} +
+ ) + })} +
+ ) +} diff --git a/app/editor/api/[[...slug]]/page.tsx b/app/editor/api/[[...slug]]/page.tsx new file mode 100644 index 0000000..6254eb7 --- /dev/null +++ b/app/editor/api/[[...slug]]/page.tsx @@ -0,0 +1,96 @@ +/* eslint-disable react-refresh/only-export-components */ +import { getApiDocs } from '@/app/getApiDocs' +import { MDXRemote } from 'next-mdx-remote/rsc' +import rehypePrism from 'rehype-prism-plus' +import rehypeSlug from 'rehype-slug' +import rehypeRewrite from 'rehype-rewrite' +import { Root, RootContent } from 'hast' +import remarkGfm from 'remark-gfm' +import * as fs from 'fs' +import * as path from 'path' +import classNames from 'classnames' + +interface SlugParam { + slug: string[] +} + +export function generateStaticParams() { + const files: SlugParam[] = [] + + function readFilesRecursively(currentPath: string) { + const entries = fs.readdirSync(currentPath) + + for (const entry of entries) { + const entryPath = path.join(currentPath, entry) + const stat = fs.statSync(entryPath) + + if (stat.isDirectory()) { + readFilesRecursively(entryPath) + } else if (stat.isFile() && path.extname(entryPath) === '.md') { + const slugParts = entryPath.replace('api-ref/', '').replace('.md', '').replace('README', '').split('/') + + files.push({ + slug: slugParts, + }) + } + } + } + + readFilesRecursively('./api-ref') + + return files +} + +interface PageParams { + slug: string[] +} + +export function generateMetadata({ params }: { params: PageParams }) { + return {} + const { docs } = getApiDocs('./api-ref') + const doc = docs.find((file) => file.slug === params.slug) + return { + title: `${doc?.title} | MDXEditor`, + description: + 'MDXEditor is an open-source React component that lets your users edit markdown documents naturally, just like in Google docs or Notion.', + } +} + +export default function Page({ params }: { params: PageParams }) { + let slug = params.slug + if (!slug) { + slug = ['README'] + } + + const pageContent = fs.readFileSync(`./api-ref/${slug.join('/')}.md`, 'utf-8') + + return ( +
+ +
+ ) +} diff --git a/app/editor/api/[slug]/Nav.tsx b/app/editor/api/[slug]/Nav.tsx deleted file mode 100644 index d8da702..0000000 --- a/app/editor/api/[slug]/Nav.tsx +++ /dev/null @@ -1,184 +0,0 @@ -'use client' -import { ApiRefNode, TypeFieldMap, RefNodeField, MarkdownApiRefDocument } from '@/app/apiDocsStructures' -import React from 'react' -import ExpandMoreIcon from '@/app/images/expand_more.svg' -import ExpandLessIcon from '@/app/images/expand_less.svg' - -function nodeTitle(node: ApiRefNode) { - if (node.document.title === 'editor') { - return '@mdxeditor/editor package' - } - - if (node.document.type === 'property' || node.document.type === 'method') { - return node.document.title.split('.').at(-1) - } - return node.document.title -} - -const ExpandedNodesContext = React.createContext<{ expandedNodes: Set; toggle: (path: string) => void; currentDocSlug: string }>({ - expandedNodes: new Set(), - currentDocSlug: '', - toggle: () => { - throw new Error('implement this') - }, -}) - -const ApiRefNodeNav: React.FC<{ node: ApiRefNode }> = ({ node }) => { - const { expandedNodes, toggle, currentDocSlug } = React.useContext(ExpandedNodesContext) - - return node.document.type === 'interface' || node.document.type === 'variable' ? ( - - ) : node.document.type === 'class' ? ( - - ) : ( - <> - - {nodeTitle(node)} - -
- {node.constructorNode && ( -
- constructor -
- )} - {Object.values(TypeFieldMap) - .filter((value) => value !== 'constructor') - .map((fieldName, index) => { - const expandKey = `${node.document.slug}.${fieldName}` - const isExpanded = expandedNodes.has(expandKey) - return node[fieldName] ? ( - -
toggle(expandKey)}> - {fieldName.replace(/./, (c) => c.toUpperCase())} - {isExpanded ? : } -
- {isExpanded && - node[fieldName as Exclude]!.map((child: ApiRefNode, index: number) => { - return ( -
- -
- ) - })} -
- ) : null - })} -
- - ) -} - -const InterfaceRefNodeNav: React.FC<{ node: ApiRefNode }> = ({ node }) => { - const { expandedNodes, currentDocSlug } = React.useContext(ExpandedNodesContext) - const isExpanded = expandedNodes.has(node.document.slug) - - return ( - <> -
- - {nodeTitle(node)} - - {node.properties && (isExpanded ? : )} -
- {isExpanded && node.properties && ( -
- {node.properties.map((child: ApiRefNode, index: number) => { - return ( -
- -
- ) - })} -
- )} - - ) -} - -const ClassRefNodeNav: React.FC<{ node: ApiRefNode }> = ({ node }) => { - const { currentDocSlug, expandedNodes } = React.useContext(ExpandedNodesContext) - const isExpanded = expandedNodes.has(node.document.slug) - - return ( - <> -
- - {nodeTitle(node)} - - {node.properties && (isExpanded ? : )} -
- {isExpanded && node.properties && ( -
- {node.constructorNode && ( -
- constructor -
- )} - {Object.values(TypeFieldMap) - .filter((value) => value !== 'constructor') - .map((fieldName, index) => { - return node[fieldName] ? ( - -
{fieldName.replace(/./, (c) => c.toUpperCase())}
- {node[fieldName as Exclude]!.map((child: ApiRefNode, index: number) => { - return ( -
- -
- ) - })} -
- ) : null - })} -
- )} - - ) -} - -export function ApiNav({ - root, - currentDoc, - docs, -}: { - root: ApiRefNode - currentDoc: MarkdownApiRefDocument - docs: MarkdownApiRefDocument[] -}) { - const [expandedNodes, setExpandedNodes] = React.useState(() => { - const set = new Set() - const pathPieces = currentDoc.slug.split('.') - set.add(currentDoc.slug) - for (let i = pathPieces.length; i > 1; i--) { - const slug = pathPieces.slice(0, i).join('.') - const parentSlug = pathPieces.slice(0, i - 1).join('.') - const doc = docs.find((doc) => doc.slug === slug) - if (!doc) { - throw new Error(`Cant find ${slug} ${pathPieces} ${i}`) - } - // const parent = docs.find((doc) => doc.slug === parentSlug)! - const fieldToExpandInParent = TypeFieldMap[doc?.type] - set.add(`${parentSlug}`) - set.add(`${parentSlug}.${fieldToExpandInParent}`) - } - return set - }) - const toggle = React.useCallback((path: string) => { - setExpandedNodes((expanded) => { - if (expanded.has(path)) { - expanded.delete(path) - } else { - expanded.add(path) - } - return new Set(expanded) - }) - }, []) - - return ( - - - - ) -} diff --git a/app/editor/api/[slug]/page.tsx b/app/editor/api/[slug]/page.tsx deleted file mode 100644 index b258e26..0000000 --- a/app/editor/api/[slug]/page.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* eslint-disable react-refresh/only-export-components */ -import { getApiDocs } from '@/app/getApiDocs' -import { ApiNav } from './Nav' -import { fromMarkdown } from 'mdast-util-from-markdown' -import { toc } from 'mdast-util-toc' -import { toMarkdown } from 'mdast-util-to-markdown' -import { MDXRemote } from 'next-mdx-remote/rsc' -import rehypePrism from 'rehype-prism-plus' -import rehypeSlug from 'rehype-slug' -import rehypeRewrite from 'rehype-rewrite' -import { Root, RootContent } from 'hast' -import remarkGfm from 'remark-gfm' -import { DocsLayout } from '../../DocsLayout' - -export function generateStaticParams() { - const { docs } = getApiDocs('./api-ref') - return docs.map(({ slug }) => ({ slug })) -} - -interface PageParams { - slug: string -} - -export function generateMetadata({ params }: { params: PageParams }) { - const { docs } = getApiDocs('./api-ref') - const doc = docs.find((file) => file.slug === params.slug) - return { - title: `${doc?.title} | MDXEditor`, - description: - 'MDXEditor is an open-source React component that lets your users edit markdown documents naturally, just like in Google docs or Notion.', - } -} - -export default function Page({ params }: { params: PageParams }) { - const { docs, root } = getApiDocs('./api-ref') - const doc = docs.find((file) => file.slug === params.slug) - - if (!doc) { - throw new Error(`No doc found for ${params.slug}`) - } - - const tree = fromMarkdown(doc.content, { - extensions: [], - mdastExtensions: [], - }) - - const tocTree = toc(tree) - const tocMarkdown = toMarkdown(tocTree.map!) - - return ( - } - content={ - - } - pageNav={} - /> - ) -} diff --git a/app/editor/api/layout.tsx b/app/editor/api/layout.tsx new file mode 100644 index 0000000..cecb660 --- /dev/null +++ b/app/editor/api/layout.tsx @@ -0,0 +1,63 @@ +/* eslint-disable react-refresh/only-export-components */ +import { DocsLayout } from '../DocsLayout' +import { readFileSync } from 'fs' +import { fromMarkdown } from 'mdast-util-from-markdown' +import * as Mdast from 'mdast' +import NavGroup from './NavGroup' + +interface NavLink { + label: string + location: string +} + +interface NavGroup { + title: string + links: NavLink[] +} + +function getNav(): NavGroup[] { + const markdown = readFileSync('./api-ref/README.md', 'utf-8') + const md = markdown.split('\n## ')[1].split('\n').slice(1).join('\n') + + const navGroups: NavGroup[] = [] + + const tree = fromMarkdown(md, { + extensions: [], + mdastExtensions: [], + }) + + for (let i = 0; i < tree.children.length; i += 2) { + const heading = tree.children[i] as Mdast.Heading + const list = tree.children[i + 1] as Mdast.List + navGroups.push({ + title: (heading.children[0] as Mdast.Text).value, + links: list.children.map((item) => { + const link = (item.children[0] as Mdast.Paragraph).children[0] as Mdast.Link + return { + label: (link.children[0] as Mdast.Text).value, + location: `/editor/api/${link.url.replace('.md', '').replace('README', '')}`, + } + }), + }) + } + + return navGroups +} + +export default function Layout({ children }: { children: React.ReactNode }) { + const navGroups = getNav() + + const nav = ( + + ) + + return ( + <> + + + ) +} diff --git a/app/editor/demo/live-demo.tsx b/app/editor/demo/live-demo.tsx index f269460..b96d623 100644 --- a/app/editor/demo/live-demo.tsx +++ b/app/editor/demo/live-demo.tsx @@ -69,5 +69,12 @@ const allPlugins = (diffMarkdown: string) => [ ] export function LiveDemo({ markdown }: { markdown: string }) { - return + return ( + + ) } diff --git a/app/getApiRefPaths.ts b/app/getApiRefPaths.ts new file mode 100644 index 0000000..f6134ff --- /dev/null +++ b/app/getApiRefPaths.ts @@ -0,0 +1,32 @@ +import * as fs from 'fs' +import * as path from 'path' + +interface SlugParam { + slug: string[] +} +export function getApiRefPaths(): SlugParam[] { + const files: SlugParam[] = [] + + function readFilesRecursively(currentPath: string) { + const entries = fs.readdirSync(currentPath) + + for (const entry of entries) { + const entryPath = path.join(currentPath, entry) + const stat = fs.statSync(entryPath) + + if (stat.isDirectory()) { + readFilesRecursively(entryPath) + } else if (stat.isFile() && path.extname(entryPath) === '.md') { + const slugParts = entryPath.replace('api-ref/', '').replace('.md', '').replace('README', '').split('/') + + files.push({ + slug: slugParts, + }) + } + } + } + + readFilesRecursively('./api-ref') + + return files +} diff --git a/app/globals.css b/app/globals.css index c1f2bf6..6812255 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,6 +1,7 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; +@import '../node_modules/@mdxeditor/editor/dist/style.css'; @layer components { .my-line-highlight span.token { @@ -9,11 +10,13 @@ .prose { min-width: 0; + & code { @apply text-secondary-text bg-neutral-bgSubtle px-1; - &:before, &:after { + &:before, + &:after { content: none; @apply text-accent-text; } @@ -28,18 +31,24 @@ @apply font-mono; font-size: inherit; font-weight: inherit; - &:before, &:after { + + &:before, + &:after { content: none; } } /* reset the prose borders */ - & [data-lexical-decorator] > table { + & [data-lexical-decorator]>table { table-layout: fixed; + & td { vertical-align: middle; } - & > thead, & > tfoot, & > tbody > tr { + + &>thead, + &>tfoot, + &>tbody>tr { border: 0; } @@ -50,7 +59,7 @@ vertical-align: middle; } - & > thead [data-tool-cell]:first-child { + &>thead [data-tool-cell]:first-child { vertical-align: middle; } } @@ -59,12 +68,13 @@ overflow-x: auto; } - & pre[class*="language-"], & code[class*="language-"] { + & pre[class*="language-"], + & code[class*="language-"] { @apply font-mono; font-size: inherit; } - & pre[class*="language-"] > code { + & pre[class*="language-"]>code { @apply text-sm; display: block; @@ -75,11 +85,16 @@ margin: 0; font-size: inherit; width: auto; - & thead, & tbody, & tfoot, & tr { + + & thead, + & tbody, + & tfoot, + & tr { border: 0; } } } + & li[role=checkbox] { text-indent: 1.2rem; } @@ -87,17 +102,48 @@ & li[role=checkbox]::before { transform: translate(6px, 6px); } + & li[role=checkbox]::after { transform: translate(6px, 6px) rotate(45deg); } } - .api-ref-nav > a { + .api-ref-nav>a { @apply mb-3; display: block; } - .api-ref-nav > dl > dd > dl { + .api-ref-nav a::before { + @apply rounded-sm border-neutral-text border-[1px] font-mono text-xs; + content: ' '; + line-height: 1rem; + position: relative; + display: inline-block; + margin-inline-end: 0.5rem; + padding: 0 0.15rem; + } + + .api-ref-nav a[href*='types']::before { + content: 'T'; + } + + .api-ref-nav a[href*='variables']::before { + content: 'V'; + } + + .api-ref-nav a[href*='functions']::before { + content: 'F'; + } + + .api-ref-nav a[href*='interfaces']::before { + content: 'I'; + } + + .api-ref-nav a[href*='classes']::before { + content: 'C'; + } + + .api-ref-nav>dl>dd>dl { @apply pl-2; } @@ -105,12 +151,12 @@ @apply mb-2; } - .api-ref-nav > dl > dt { + .api-ref-nav>dl>dt { @apply font-medium; } .in-page-nav { - @apply border-l-accent-solid border-dotted border-l-2 pl-4 text-sm sticky top-2 w-48; + @apply border-l-accent-solid border-dotted border-l-2 pl-4 text-sm sticky top-[94px] w-48; p { @apply mb-2; @@ -121,7 +167,7 @@ @apply pl-2; } - li > a { + li>a { @apply block mb-1 text-neutral-text; } } @@ -137,12 +183,17 @@ } } - h2#classes + table, - h2#functions + table, - h2#interfaces + table, - h2#type-aliases + table - { - & td:first-child, + .doc-content .homepage ul { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + } + + h2#classes+table, + h2#functions+table, + h2#interfaces+table, + h2#type-aliases+table { + + & td:first-child, & tr:first-child { width: 50%; } @@ -156,8 +207,19 @@ @apply inline; } } + & p { @apply mb-4; } } } + +.full-demo-mdxeditor [role=toolbar] { + top: 74px; +} + +@media (max-width: 768px) { + .full-demo-mdxeditor [role=toolbar] { + top: 196px; + } +} diff --git a/app/layout.tsx b/app/layout.tsx index fafa31b..5f62dbd 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,3 @@ -import '@mdxeditor/editor/style.css' import 'prism-themes/themes/prism-one-light.css' import './globals.css' @@ -36,12 +35,12 @@ const RootLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
-
+