This commit is contained in:
2025-10-27 00:53:06 +08:00
parent 3db5a0d2e4
commit c7a650cdb7
8 changed files with 300 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
import { cn } from '@/lib/utils';
import { useEffect, useState } from 'react';
import { Marked } from 'marked';
import hljs from 'highlight.js';
import { markedHighlight } from 'marked-highlight';
const markedAndHighlight = new Marked(
markedHighlight({
emptyLangClass: 'hljs',
langPrefix: 'hljs language-',
highlight(code, lang, info) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
},
}),
);
export const md2html = async (md: string) => {
const html = markedAndHighlight.parse(md);
return html;
};
export const clearMeta = (markdown?: string) => {
if (!markdown) return '';
// Remove YAML front matter if present
const yamlRegex = /^---\n[\s\S]*?\n---\n/;
return markdown.replace(yamlRegex, '');
};
type Props = {
children?: React.ReactNode;
className?: string;
style?: React.CSSProperties;
content?: string; // Optional content prop for markdown text
[key: string]: any; // Allow any additional props
};
export const MarkdownPreview = (props: Props) => {
return (
<div
className={cn(
'markdown-body scrollbar h-full overflow-auto w-full px-6 py-2 max-w-[800px] border my-4 flex flex-col justify-self-center rounded-md shadow-md',
props.className,
)}
style={props.style}>
{props.children ? <WrapperText>{props.children}</WrapperText> : <MarkdownPreviewWrapper content={clearMeta(props.content)} />}
</div>
);
};
export const WrapperText = (props: { children?: React.ReactNode; html?: string }) => {
if (props.html) {
return <div className='w-full' dangerouslySetInnerHTML={{ __html: props.html }} />;
}
return <div className='w-full h-full'>{props.children}</div>;
};
export const MarkdownPreviewWrapper = (props: Props) => {
const [html, setHtml] = useState<string>('');
useEffect(() => {
init();
}, [props.content]);
const init = async () => {
if (props.content) {
const htmlContent = await md2html(props.content);
setHtml(htmlContent);
} else {
setHtml('');
}
};
return <WrapperText html={html} />;
};

17
src/content.config.ts Normal file
View File

@@ -0,0 +1,17 @@
// @ts-ignore
import { defineCollection, z } from 'astro:content';
import { glob, file } from 'astro/loaders'; // 不适用于旧版 API
const docs = defineCollection({
// loader: glob({ pattern: '**/*.md', base: './src/data/blogs' }),
loader: glob({ pattern: '**/[^_]*.md', base: './src/data/docs' }),
schema: z.object({
title: z.string().optional(),
description: z.string().optional(),
// pubDate: z.coerce.date(),
// updatedDate: z.coerce.date().optional(),
}),
});
export const collections = { docs };

24
src/layouts/blank.astro Normal file
View File

@@ -0,0 +1,24 @@
---
import '../styles/global.css';
import '../styles/theme.css';
---
<html lang='zh-CN'>
<head>
<meta charset='UTF-8' />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI Pages</title>
<style>
html,
body {
width: 100%;
min-height: 100vh;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<slot />
</body>
</html>

60
src/layouts/mdx.astro Normal file
View File

@@ -0,0 +1,60 @@
---
export interface Props {
children: any;
}
import '../styles/global.css';
import '../styles/theme.css';
export interface Props {
title?: string;
description?: string;
lang?: string;
charset?: string;
}
const { title = 'Light Code', description = 'A lightweight code editor', lang = 'zh-CN', charset = 'UTF-8' } = Astro.props;
---
<html lang='zh-CN'>
<head>
<meta charset='UTF-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<title>Docs</title>
<link
rel='stylesheet'
href='https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.8.1/github-markdown-light.min.css'
integrity='sha512-X175XRJAO6PHAUi8AA7GP8uUF5Wiv+w9bOi64i02CHKDQBsO1yy0jLSKaUKg/NhRCDYBmOLQCfKaTaXiyZlLrw=='
crossorigin='anonymous'
referrerpolicy='no-referrer'
/>
<style>
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
body {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
</head>
<body>
<div>
<slot name='header' />
</div>
<main class='p-2 flex-1 overflow-hidden'>
<div class='markdown-body h-full scrollbar border-gray-200 overflow-auto w-full px-6 py-2 max-w-[800px] border my-4 rounded-md shadow-md'>
<slot />
</div>
</main>
<footer>
<slot name='footer'>
<p>Copyrignt &copy; 2025</p>
</slot>
</footer>
</body>
</html>

4
src/modules/basename.ts Normal file
View File

@@ -0,0 +1,4 @@
// @ts-ignore
export const basename = BASE_NAME;
console.log(basename);

View File

@@ -0,0 +1,23 @@
---
import { getCollection, render } from 'astro:content';
import Main from '@/layouts/mdx.astro';
// 1. 为每个集合条目生成一个新路径
export async function getStaticPaths() {
const posts = await getCollection('docs');
return posts.map((post) => ({
params: { id: post.id },
props: { post },
}));
}
type Post = {
data: { title: string };
};
// 2. 对于你的模板,你可以直接从 prop 获取条目
const { post } = Astro.props as { post: Post };
const { Content } = await render(post);
---
<Main>
<!-- <h1 slot={'header'}>{post.data.title}</h1> -->
<Content />
</Main>

View File

@@ -0,0 +1,23 @@
---
import { getCollection } from 'astro:content';
const posts = await getCollection('docs');
console.log('post', posts);
import { basename } from '@/modules/basename';
import Blank from '@/layouts/blank.astro';
---
<Blank>
<main class='max-w-3xl mx-auto'>
<h1>My posts</h1>
<ul class='p-2 m-2'>
{
posts.map((post) => (
<li>
{/* <a href={`${basename}/demo/${post.id}`}>{post.data.title}</a> */}
<a href={`/docs/${post.id}/`}>{post.data.title}</a>
</li>
))
}
</ul>
</main>
</Blank>

79
src/styles/theme.css Normal file
View File

@@ -0,0 +1,79 @@
@import 'tailwindcss';
@theme {
/* --color-primary: #ffc107;
--color-secondary: #ffa000;
--color-text-primary: #000000;
--color-text-secondary: #000000;
--color-success: #28a745; */
--color-scrollbar-thumb: #999999;
--color-scrollbar-track: rgba(0, 0, 0, 0.1);
--color-scrollbar-thumb-hover: #666666;
--scrollbar-color: #ffc107;
}
html,
body {
width: 100%;
height: 100%;
font-size: 16px;
font-family: 'Montserrat', sans-serif;
}
/* font-family */
@utility font-family-mon {
font-family: 'Montserrat', sans-serif;
}
@utility font-family-rob {
font-family: 'Roboto', sans-serif;
}
@utility font-family-int {
font-family: 'Inter', sans-serif;
}
@utility font-family-orb {
font-family: 'Orbitron', sans-serif;
}
@utility font-family-din {
font-family: 'DIN', sans-serif;
}
@utility flex-row-center {
@apply flex flex-row items-center justify-center;
}
@utility flex-col-center {
@apply flex flex-col items-center justify-center;
}
@utility scrollbar {
overflow: auto;
/* 整个滚动条 */
&::-webkit-scrollbar {
width: 3px;
height: 3px;
}
&::-webkit-scrollbar-track {
background-color: var(--color-scrollbar-track);
}
/* 滚动条有滑块的轨道部分 */
&::-webkit-scrollbar-track-piece {
background-color: transparent;
border-radius: 1px;
}
/* 滚动条滑块(竖向:vertical 横向:horizontal) */
&::-webkit-scrollbar-thumb {
cursor: pointer;
background-color: var(--color-scrollbar-thumb);
border-radius: 5px;
}
/* 滚动条滑块hover */
&::-webkit-scrollbar-thumb:hover {
background-color: var(--color-scrollbar-thumb-hover);
}
/* 同时有垂直和水平滚动条时交汇的部分 */
&::-webkit-scrollbar-corner {
display: block; /* 修复交汇时出现的白块 */
}
}