@@ -4,6 +4,7 @@ import { Link } from 'wouter'
|
|||||||
|
|
||||||
import LanguageSwitcher from './LanguageSwitcher'
|
import LanguageSwitcher from './LanguageSwitcher'
|
||||||
import ThemeSwitcher from './ThemeSwitcher'
|
import ThemeSwitcher from './ThemeSwitcher'
|
||||||
|
import { BookIcon, CloseIcon, GithubIcon, MenuIcon } from './icons'
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const { t } = useTranslation('common')
|
const { t } = useTranslation('common')
|
||||||
@@ -16,11 +17,11 @@ export default function Header() {
|
|||||||
role="banner"
|
role="banner"
|
||||||
>
|
>
|
||||||
<div className="max-w-7xl mx-auto px-6 py-4">
|
<div className="max-w-7xl mx-auto px-6 py-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between gap-2">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className="flex items-center space-x-3 group"
|
className="flex items-center gap-2 sm:gap-3 group flex-shrink-0"
|
||||||
aria-label={t('header.logo_alt')}
|
aria-label={t('header.logo_alt')}
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
>
|
>
|
||||||
@@ -30,11 +31,39 @@ export default function Header() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-xl font-bold text-gray-900 dark:text-white">page-agent</span>
|
<span className="text-base sm:text-xl font-bold text-gray-900 dark:text-white block leading-tight">
|
||||||
<p className="text-xs text-gray-600 dark:text-gray-300">{t('header.slogan')}</p>
|
page-agent
|
||||||
|
</span>
|
||||||
|
<p className="hidden sm:block text-xs text-gray-600 dark:text-gray-300">
|
||||||
|
{t('header.slogan')}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
{/* Mobile Icon Navigation (横向滚动) */}
|
||||||
|
<nav
|
||||||
|
className="md:hidden flex items-center gap-1 overflow-x-auto scrollbar-hide flex-1"
|
||||||
|
role="navigation"
|
||||||
|
aria-label="Mobile navigation"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href="/docs/introduction/overview"
|
||||||
|
className="p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 flex-shrink-0"
|
||||||
|
aria-label={t('header.nav_docs')}
|
||||||
|
>
|
||||||
|
<BookIcon className="w-5 h-5" />
|
||||||
|
</Link>
|
||||||
|
<a
|
||||||
|
href="https://github.com/alibaba/page-agent"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 flex-shrink-0"
|
||||||
|
aria-label={t('header.nav_source')}
|
||||||
|
>
|
||||||
|
<GithubIcon className="w-5 h-5" />
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
{/* Desktop Navigation */}
|
{/* Desktop Navigation */}
|
||||||
<nav
|
<nav
|
||||||
className="hidden md:flex items-center space-x-6"
|
className="hidden md:flex items-center space-x-6"
|
||||||
@@ -45,20 +74,7 @@ export default function Header() {
|
|||||||
href="/docs/introduction/overview"
|
href="/docs/introduction/overview"
|
||||||
className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
||||||
>
|
>
|
||||||
<svg
|
<BookIcon />
|
||||||
className="w-4 h-4"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{t('header.nav_docs')}
|
{t('header.nav_docs')}
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
<a
|
||||||
@@ -68,9 +84,7 @@ export default function Header() {
|
|||||||
className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
||||||
aria-label={t('header.nav_source')}
|
aria-label={t('header.nav_source')}
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<GithubIcon />
|
||||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
|
||||||
</svg>
|
|
||||||
{t('header.nav_source')}
|
{t('header.nav_source')}
|
||||||
</a>
|
</a>
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
@@ -80,35 +94,13 @@ export default function Header() {
|
|||||||
{/* Mobile menu button */}
|
{/* Mobile menu button */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="md:hidden p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200"
|
className="md:hidden p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200 flex-shrink-0"
|
||||||
aria-label={t('header.mobile_menu')}
|
aria-label={t('header.mobile_menu')}
|
||||||
aria-expanded={mobileMenuOpen}
|
aria-expanded={mobileMenuOpen}
|
||||||
aria-controls="mobile-menu"
|
aria-controls="mobile-menu"
|
||||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
>
|
>
|
||||||
<svg
|
{mobileMenuOpen ? <CloseIcon /> : <MenuIcon />}
|
||||||
className="w-6 h-6"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
{mobileMenuOpen ? (
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M6 18L18 6M6 6l12 12"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M4 6h16M4 12h16M4 18h16"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -124,20 +116,7 @@ export default function Header() {
|
|||||||
className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
>
|
>
|
||||||
<svg
|
<BookIcon className="w-5 h-5" />
|
||||||
className="w-5 h-5"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth={2}
|
|
||||||
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{t('header.nav_docs')}
|
{t('header.nav_docs')}
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
<a
|
||||||
@@ -147,9 +126,7 @@ export default function Header() {
|
|||||||
className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
||||||
aria-label={t('header.nav_source')}
|
aria-label={t('header.nav_source')}
|
||||||
>
|
>
|
||||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<GithubIcon className="w-5 h-5" />
|
||||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
|
||||||
</svg>
|
|
||||||
{t('header.nav_source')}
|
{t('header.nav_source')}
|
||||||
</a>
|
</a>
|
||||||
<div className="flex items-center gap-3 px-3 py-2">
|
<div className="flex items-center gap-3 px-3 py-2">
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* JavaScript/TypeScript 关键字 */
|
||||||
.keyword {
|
.keyword {
|
||||||
color: #d73a49;
|
color: #d73a49;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -19,6 +20,27 @@
|
|||||||
color: #ff6b6b;
|
color: #ff6b6b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TypeScript 特定关键字 (interface, type, enum, etc.) */
|
||||||
|
.tsKeyword {
|
||||||
|
color: #af00db;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .tsKeyword {
|
||||||
|
color: #c792ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TypeScript 内置类型 */
|
||||||
|
.type {
|
||||||
|
color: #267f99;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .type {
|
||||||
|
color: #4ec9b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 字符串 */
|
||||||
.string {
|
.string {
|
||||||
color: #1d6eca;
|
color: #1d6eca;
|
||||||
}
|
}
|
||||||
@@ -27,6 +49,7 @@
|
|||||||
color: #4fc3f7;
|
color: #4fc3f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 数字 */
|
||||||
.number {
|
.number {
|
||||||
color: #00c583;
|
color: #00c583;
|
||||||
}
|
}
|
||||||
@@ -35,6 +58,17 @@
|
|||||||
color: #66bb6a;
|
color: #66bb6a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 布尔值和字面量 (true, false, null, undefined) */
|
||||||
|
.literal {
|
||||||
|
color: #0000ff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .literal {
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 注释 */
|
||||||
.comment {
|
.comment {
|
||||||
color: #6a737d;
|
color: #6a737d;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
@@ -43,3 +77,51 @@
|
|||||||
:global(.dark) .comment {
|
:global(.dark) .comment {
|
||||||
color: #9e9e9e;
|
color: #9e9e9e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 装饰器 (@decorator) */
|
||||||
|
.decorator {
|
||||||
|
color: #e0aa00;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .decorator {
|
||||||
|
color: #dcdcaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 箭头函数 (=>) */
|
||||||
|
.arrow {
|
||||||
|
color: #d73a49;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .arrow {
|
||||||
|
color: #ff6b6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标识符(变量名、函数名等) */
|
||||||
|
.identifier {
|
||||||
|
color: #171717;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .identifier {
|
||||||
|
color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 属性访问 (.property) */
|
||||||
|
.property {
|
||||||
|
color: #0550ae;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .property {
|
||||||
|
color: #9cdcfe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 运算符 */
|
||||||
|
.operator {
|
||||||
|
color: #5a5a5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.dark) .operator {
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,58 +9,157 @@ interface HighlightSyntaxProps {
|
|||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JavaScript/TypeScript 关键字
|
||||||
const keywords =
|
const keywords =
|
||||||
'async|await|function|const|let|var|if|else|for|while|return|try|catch|finally|class|extends|from|import|export|default|undefined|throw|true|false|null|this|new|in|of|instanceof|break|continue|switch|case|default|do|while|with|yield'
|
'async|await|function|const|let|var|if|else|for|while|return|try|catch|finally|class|extends|from|import|export|default|undefined|throw|break|continue|switch|case|do|with|yield|delete|typeof|void|static|get|set|super|debugger'
|
||||||
|
|
||||||
// 语法高亮函数,先整体提取字符串/注释等token再高亮
|
// TypeScript 特定关键字
|
||||||
|
const tsKeywords =
|
||||||
|
'interface|type|enum|namespace|module|declare|abstract|implements|public|private|protected|readonly|as|satisfies|infer|keyof|is'
|
||||||
|
|
||||||
|
// 布尔值和空值
|
||||||
|
const literals = 'true|false|null|undefined|NaN|Infinity'
|
||||||
|
|
||||||
|
// TypeScript 内置类型
|
||||||
|
const tsTypes =
|
||||||
|
'string|number|boolean|any|unknown|never|void|object|symbol|bigint|Array|Promise|Record|Partial|Required|Readonly|Pick|Omit|Exclude|Extract|NonNullable|ReturnType|Parameters|ConstructorParameters|InstanceType|ThisType|Uppercase|Lowercase|Capitalize|Uncapitalize'
|
||||||
|
|
||||||
|
// 辅助函数:转义 HTML 特殊字符
|
||||||
|
function escapeHtml(text: string): string {
|
||||||
|
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 语法高亮函数,先提取 token 再转义和高亮
|
||||||
function highlightSyntax(code: string): string {
|
function highlightSyntax(code: string): string {
|
||||||
// 先转义HTML特殊字符
|
// 构建正则模式,包含更多 token 类型(在原始文本上匹配)
|
||||||
const escaped = code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
||||||
|
|
||||||
// 单行字符串,所有反斜杠双重转义,保证正则安全
|
|
||||||
const pattern = new RegExp(
|
const pattern = new RegExp(
|
||||||
'("([^"\\\\]|\\\\.)*"|\'([^\'\\\\]|\\\\.)*\'|`([^`\\\\]|\\\\.)*`|//[^\\n]*|/\\*[\\s\\S]*?\\*/|\\b\\d+\\.?\\d*\\b|\\b(?:' +
|
'(' +
|
||||||
|
// 1. 字符串(双引号、单引号、模板字符串)
|
||||||
|
'"([^"\\\\]|\\\\.)*"|' +
|
||||||
|
"'([^'\\\\]|\\\\.)*'|" +
|
||||||
|
'`([^`\\\\]|\\\\.)*`|' +
|
||||||
|
// 2. 注释(单行和多行)
|
||||||
|
'//[^\\n]*|' +
|
||||||
|
'/\\*[\\s\\S]*?\\*/|' +
|
||||||
|
// 3. 装饰器
|
||||||
|
'@[a-zA-Z_$][\\w$]*|' +
|
||||||
|
// 4. 数字(包括小数、十六进制、科学计数法)
|
||||||
|
'\\b0[xX][0-9a-fA-F]+\\b|' +
|
||||||
|
'\\b\\d+\\.?\\d*(?:[eE][+-]?\\d+)?\\b|' +
|
||||||
|
// 5. TypeScript/JavaScript 关键字
|
||||||
|
'\\b(?:' +
|
||||||
keywords +
|
keywords +
|
||||||
')\\b)',
|
'|' +
|
||||||
|
tsKeywords +
|
||||||
|
'|' +
|
||||||
|
literals +
|
||||||
|
')\\b|' +
|
||||||
|
// 6. TypeScript 内置类型
|
||||||
|
'\\b(?:' +
|
||||||
|
tsTypes +
|
||||||
|
')\\b|' +
|
||||||
|
// 7. 箭头函数
|
||||||
|
'=>|' +
|
||||||
|
// 8. 函数调用(函数名后跟括号)
|
||||||
|
'\\b[a-zA-Z_$][\\w$]*(?=\\()|' +
|
||||||
|
// 9. 属性访问
|
||||||
|
'\\.[a-zA-Z_$][\\w$]*|' +
|
||||||
|
// 10. 运算符和特殊符号
|
||||||
|
'[+\\-*/%&|^!~<>=?:]+|' +
|
||||||
|
'[{}\\[\\]();,.]' +
|
||||||
|
')',
|
||||||
'g'
|
'g'
|
||||||
)
|
)
|
||||||
|
|
||||||
const tokens: string[] = []
|
const tokens: string[] = []
|
||||||
let lastIndex = 0
|
let lastIndex = 0
|
||||||
let match: RegExpExecArray | null
|
let match: RegExpExecArray | null
|
||||||
while ((match = pattern.exec(escaped)) !== null) {
|
while ((match = pattern.exec(code)) !== null) {
|
||||||
if (match.index > lastIndex) {
|
if (match.index > lastIndex) {
|
||||||
tokens.push(...escaped.slice(lastIndex, match.index).split(/([ \t\n\r.])/))
|
const gap = code.slice(lastIndex, match.index)
|
||||||
|
// 将间隙按空白符分割,保留空白符
|
||||||
|
tokens.push(...gap.split(/(\s+)/))
|
||||||
}
|
}
|
||||||
tokens.push(match[0])
|
tokens.push(match[0])
|
||||||
lastIndex = pattern.lastIndex
|
lastIndex = pattern.lastIndex
|
||||||
}
|
}
|
||||||
if (lastIndex < escaped.length) {
|
if (lastIndex < code.length) {
|
||||||
tokens.push(...escaped.slice(lastIndex).split(/([ \t\n\r.])/))
|
tokens.push(...code.slice(lastIndex).split(/(\s+)/))
|
||||||
}
|
}
|
||||||
|
|
||||||
const highlighted = tokens
|
const highlighted = tokens
|
||||||
.map((token) => {
|
.map((token) => {
|
||||||
|
// 空白符直接返回
|
||||||
|
if (/^\s+$/.test(token)) {
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 注释(单行和多行)
|
||||||
|
if (/^\/\/.*$/.test(token) || /^\/\*[\s\S]*?\*\/$/.test(token)) {
|
||||||
|
return `<span class="${styles.comment}">${escapeHtml(token)}</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 字符串
|
||||||
if (
|
if (
|
||||||
/^"([^"\\]|\\.)*"$/.test(token) ||
|
/^"([^"\\]|\\.)*"$/.test(token) ||
|
||||||
/^'([^'\\]|\\.)*'$/.test(token) ||
|
/^'([^'\\]|\\.)*'$/.test(token) ||
|
||||||
/^`([^`\\]|\\.)*`$/.test(token)
|
/^`([^`\\]|\\.)*`$/.test(token)
|
||||||
) {
|
) {
|
||||||
return `<span class="${styles.string}">${token}</span>`
|
return `<span class="${styles.string}">${escapeHtml(token)}</span>`
|
||||||
}
|
}
|
||||||
if (/^\b\d+\.?\d*\b$/.test(token)) {
|
|
||||||
return `<span class="${styles.number}">${token}</span>`
|
// 3. 数字
|
||||||
|
if (/^(0[xX][0-9a-fA-F]+|\d+\.?\d*(?:[eE][+-]?\d+)?)$/.test(token)) {
|
||||||
|
return `<span class="${styles.number}">${escapeHtml(token)}</span>`
|
||||||
}
|
}
|
||||||
if (/^\/\/.*$/.test(token)) {
|
|
||||||
return `<span class="${styles.comment}">${token}</span>`
|
// 4. 布尔值和特殊字面量
|
||||||
|
if (new RegExp(`^(?:${literals})$`).test(token)) {
|
||||||
|
return `<span class="${styles.literal}">${escapeHtml(token)}</span>`
|
||||||
}
|
}
|
||||||
if (/^\/\*[\s\S]*?\*\/$/.test(token)) {
|
|
||||||
return `<span class="${styles.comment}">${token}</span>`
|
// 5. JavaScript/TypeScript 关键字
|
||||||
|
if (new RegExp(`^(?:${keywords})$`).test(token)) {
|
||||||
|
return `<span class="${styles.keyword}">${escapeHtml(token)}</span>`
|
||||||
}
|
}
|
||||||
if (new RegExp(`\\b(?:${keywords})\\b`).test(token)) {
|
|
||||||
return `<span class="${styles.keyword}">${token}</span>`
|
// 6. TypeScript 特定关键字
|
||||||
|
if (new RegExp(`^(?:${tsKeywords})$`).test(token)) {
|
||||||
|
return `<span class="${styles.tsKeyword}">${escapeHtml(token)}</span>`
|
||||||
}
|
}
|
||||||
return token
|
|
||||||
|
// 7. TypeScript 内置类型
|
||||||
|
if (new RegExp(`^(?:${tsTypes})$`).test(token)) {
|
||||||
|
return `<span class="${styles.type}">${escapeHtml(token)}</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 装饰器
|
||||||
|
if (/^@[a-zA-Z_$][\w$]*$/.test(token)) {
|
||||||
|
return `<span class="${styles.decorator}">${escapeHtml(token)}</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 箭头函数
|
||||||
|
if (token === '=>') {
|
||||||
|
return `<span class="${styles.arrow}">${escapeHtml(token)}</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. 函数调用和标识符
|
||||||
|
if (/^[a-zA-Z_$][\w$]*$/.test(token)) {
|
||||||
|
return `<span class="${styles.identifier}">${escapeHtml(token)}</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11. 属性访问
|
||||||
|
if (/^\.[a-zA-Z_$][\w$]*$/.test(token)) {
|
||||||
|
return `<span class="${styles.property}">${escapeHtml(token)}</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 12. 运算符
|
||||||
|
if (/^[+\-*/%&|^!~<>=?:]+$/.test(token)) {
|
||||||
|
return `<span class="${styles.operator}">${escapeHtml(token)}</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 13. 其他符号,需要转义
|
||||||
|
return escapeHtml(token)
|
||||||
})
|
})
|
||||||
.join('')
|
.join('')
|
||||||
|
|
||||||
|
|||||||
75
pages/components/icons.tsx
Normal file
75
pages/components/icons.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// SVG图标组件集合,用于Header等地方复用
|
||||||
|
|
||||||
|
interface IconProps {
|
||||||
|
className?: string
|
||||||
|
'aria-hidden'?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BookIcon({ className = 'w-4 h-4', ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={className}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
aria-hidden="true"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GithubIcon({ className = 'w-4 h-4', ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={className}
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
aria-hidden="true"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MenuIcon({ className = 'w-6 h-6', ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={className}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
aria-hidden="true"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CloseIcon({ className = 'w-6 h-6', ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={className}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
aria-hidden="true"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -192,3 +192,13 @@ td {
|
|||||||
border-bottom-color: rgba(255, 255, 255, 0.2);
|
border-bottom-color: rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
/* 隐藏滚动条,但保持滚动功能 */
|
||||||
|
.scrollbar-hide {
|
||||||
|
-ms-overflow-style: none; /* IE and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollbar-hide::-webkit-scrollbar {
|
||||||
|
display: none; /* Chrome, Safari and Opera */
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user