Files
JGZ_YES c30eb9b7ad
Build and Package Oraset / build (macos-latest) (push) Waiting to run
Build and Package Oraset / build (ubuntu-latest) (push) Waiting to run
Build and Package Oraset / build (windows-latest) (push) Waiting to run
Build and Package Oraset / release (push) Blocked by required conditions
Oraset B-5: Clean repository with only C implementation
2026-07-23 13:27:04 +08:00

377 lines
12 KiB
PHP

<?php
/**
* Oraset 文档系统
* 自动识别 md 文件夹下的 md 文件并显示
* 支持 HTML 和 Markdown 语法
*
* 使用方式:
* index.php - 显示第一个可用文档
* index.php?doc=name - 显示指定文档
* index.php?list - 列出所有文档
* index.php?list-api - 输出 JSON 格式的文档列表
*/
// 配置
$md_dir = __DIR__ . '/md';
$css_path = '../css/style.css';
$version_file = dirname(__DIR__) . '/download/version.txt';
// 获取版本号
function get_version($version_file) {
if (file_exists($version_file)) {
return trim(file_get_contents($version_file));
}
return '4.0.1-BetaN(+AU)';
}
$current_version = get_version($version_file);
// 获取所有 md 文件列表
function get_articles_list($md_dir) {
$articles = [];
$files = glob($md_dir . '/*.md');
foreach ($files as $file) {
$name = basename($file, '.md');
$content = file_get_contents($file);
// 提取标题
$title = $name;
if (preg_match('/^# (.+)$/m', $content, $matches)) {
$title = $matches[1];
}
// 提取描述(第一段非标题、非代码块、非列表的内容)
$desc = '';
$lines = explode("\n", $content);
$in_code = false;
foreach ($lines as $line) {
$trimmed = trim($line);
// 跳过代码块
if (strpos($trimmed, '```') !== false) {
$in_code = !$in_code;
continue;
}
if ($in_code) {
continue;
}
// 跳过空行、标题、列表、太短的行
if ($trimmed === '' ||
preg_match('/^#/', $trimmed) ||
preg_match('/^-/', $trimmed) ||
strlen($trimmed) < 10) {
continue;
}
// 找到第一个有效描述,去除Markdown语法
$desc = $trimmed;
// 去除链接语法
$desc = preg_replace('/\[([^\]]+)\]\([^)]+\)/', '$1', $desc);
// 去除粗体/斜体
$desc = preg_replace('/\*+([^*]+)\*+/', '$1', $desc);
// 去除行内代码
$desc = preg_replace('/`([^`]+)`/', '$1', $desc);
if (strlen($desc) > 100) {
$desc = substr($desc, 0, 100) . '...';
}
break;
}
$articles[] = [
'name' => $name,
'title' => $title,
'desc' => $desc,
'file' => $file
];
}
return $articles;
}
// JSON API 模式
if (isset($_GET['list-api'])) {
header('Content-Type: application/json; charset=utf-8');
$articles = get_articles_list($md_dir);
echo json_encode([
'version' => $current_version,
'articles' => $articles
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
// Markdown 解析函数(改进版)
function parse_markdown($text, $version) {
// 替换版本号占位符
$text = str_replace('{{VERSION}}', $version, $text);
// 处理代码块(先处理,避免被其他规则干扰)
$text = preg_replace_callback('/```(\w*)\n(.*?)```/s', function($m) {
$lang = $m[1] ? ' class="language-' . htmlspecialchars($m[1]) . '"' : '';
return '<pre><code' . $lang . '>' . htmlspecialchars($m[2]) . '</code></pre>';
}, $text);
// 处理行内代码
$text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text);
// 处理标题
$text = preg_replace('/^### (.+)$/m', '<h3>$1</h3>', $text);
$text = preg_replace('/^## (.+)$/m', '<h2>$1</h2>', $text);
$text = preg_replace('/^# (.+)$/m', '<h1>$1</h1>', $text);
// 处理粗体和斜体
$text = preg_replace('/\*\*([^*]+)\*\*/', '<strong>$1</strong>', $text);
$text = preg_replace('/\*([^*]+)\*/', '<em>$1</em>', $text);
// 处理链接(支持 URL 中包含括号)
$text = preg_replace_callback('/\[([^\]]+)\]\(\s*([^)]*(?:\([^)]*\)[^)]*)*)\s*\)/', function($m) {
$link_text = $m[1];
$url = trim($m[2]);
return '<a href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '">' . $link_text . '</a>';
}, $text);
// 处理分隔线
$text = preg_replace('/^---$/m', '<hr>', $text);
// 处理无序列表
$lines = explode("\n", $text);
$result = [];
$in_list = false;
$in_code = false;
$in_paragraph = false;
foreach ($lines as $line) {
$trimmed = trim($line);
// 检查是否在代码块中
if (strpos($trimmed, '<pre>') !== false) {
$in_code = true;
}
if (strpos($trimmed, '</pre>') !== false) {
$in_code = false;
}
if ($in_code) {
$result[] = $line;
continue;
}
// 处理列表项
if (preg_match('/^- (.+)$/', $trimmed, $m)) {
if (!$in_list) {
$result[] = '<ul>';
$in_list = true;
}
$result[] = '<li>' . $m[1] . '</li>';
continue;
}
// 结束列表
if ($in_list && !preg_match('/^-/', $trimmed)) {
$result[] = '</ul>';
$in_list = false;
}
// 处理块元素(标题、hr、pre等)
if (preg_match('/^<(h[1-6]|hr|pre|ul|li)/', $trimmed) || $trimmed === '') {
if ($in_paragraph) {
$result[] = '</p>';
$in_paragraph = false;
}
$result[] = $line;
continue;
}
// 处理段落
if (!$in_paragraph && $trimmed !== '') {
$result[] = '<p>' . $trimmed;
$in_paragraph = true;
} else if ($in_paragraph && $trimmed !== '') {
$result[] = $trimmed;
}
}
// 结束未闭合的标签
if ($in_list) {
$result[] = '</ul>';
}
if ($in_paragraph) {
$result[] = '</p>';
}
return implode("\n", $result);
}
// 获取文章列表
$articles = get_articles_list($md_dir);
// 判断是否显示列表模式
$show_list = isset($_GET['list']);
// 获取请求的文档
$doc = isset($_GET['doc']) ? $_GET['doc'] : '';
// 安全处理:只允许字母、数字、下划线、连字符
$doc = preg_replace('/[^a-zA-Z0-9_-]/', '', $doc);
// 文件路径
$file_path = '';
if ($doc !== '') {
$file_path = $md_dir . '/' . $doc . '.md';
}
// 如果文件不存在,显示第一个可用文档
if ($file_path === '' || !file_exists($file_path)) {
if (count($articles) > 0) {
$file_path = $articles[0]['file'];
$doc = $articles[0]['name'];
} else {
// 没有文档时显示默认页面
$html_content = '<h1>Oraset 文档</h1><p>暂无文档。</p>';
$title = 'Oraset 文档';
$doc = '';
}
}
// 读取文档内容
if ($file_path !== '' && file_exists($file_path)) {
$content = file_get_contents($file_path);
// 先执行 PHP 代码
ob_start();
eval('?>' . $content . '<?php ');
$content = ob_get_clean();
// 替换版本号占位符 {{VERSION}}
$content = str_replace('{{VERSION}}', $current_version, $content);
$html_content = parse_markdown($content, $current_version);
// 提取当前文档标题
$title = 'Oraset 文档';
if (preg_match('/^# (.+)$/m', $content, $matches)) {
$title = $matches[1] . ' - Oraset';
}
}
// 如果是列表模式,生成列表 HTML
if ($show_list) {
$title = '文档列表 - Oraset';
$list_html = "<h1>文档列表</h1>\n<p>以下是所有可用的文档:</p>\n<ul class=\"doc-list\">";
foreach ($articles as $art) {
$list_html .= '<li>';
$list_html .= '<a href="?doc=' . htmlspecialchars($art['name']) . '">' . htmlspecialchars($art['title']) . '</a>';
if ($art['desc']) {
$list_html .= '<br><span class="doc-desc">' . htmlspecialchars($art['desc']) . '</span>';
}
$list_html .= '</li>';
}
$list_html .= '</ul>';
$html_content = $list_html;
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($title); ?></title>
<link rel="stylesheet" href="<?php echo $css_path; ?>">
<style>
/* 文档系统额外样式 */
.doc-list {
list-style: none;
padding: 0;
}
.doc-list li {
padding: 12px 0;
border-bottom: 1px solid #eee;
}
.doc-list a {
font-size: 16px;
color: #0000ee;
text-decoration: underline;
}
.doc-desc {
color: #666;
font-size: 13px;
margin-top: 4px;
display: block;
}
.sidebar-loading {
color: #999;
font-style: italic;
}
</style>
</head>
<body>
<div class="layout">
<nav class="sidebar">
<div class="sidebar-section">
<div class="sidebar-section-title">Oraset</div>
<ul>
<li><a href="../index.php">首页</a></li>
<li><a href="../download.php">下载</a></li>
<li><a href="../releases.php">历史版本</a></li>
</ul>
</div>
<div class="sidebar-section">
<div class="sidebar-section-title">文档</div>
<ul>
<li><a href="?list" id="nav-list"<?php echo $show_list ? ' class="active"' : ''; ?>>文档列表</a></li>
</ul>
<ul id="doc-nav-list">
<li><span class="sidebar-loading">加载中...</span></li>
</ul>
</div>
</nav>
<main class="main">
<?php echo $html_content; ?>
</main>
</div>
<script>
// 获取当前文档名
var currentDoc = '<?php echo htmlspecialchars($doc); ?>';
var showList = <?php echo $show_list ? 'true' : 'false'; ?>;
// 加载文档列表
fetch('./api.php')
.then(function(response) {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(function(data) {
var navList = document.getElementById('doc-nav-list');
navList.innerHTML = '';
if (data.articles && data.articles.length > 0) {
data.articles.forEach(function(article) {
var li = document.createElement('li');
var a = document.createElement('a');
a.href = './?doc=' + article.name;
a.textContent = article.title;
if (!showList && article.name === currentDoc) {
a.className = 'active';
}
li.appendChild(a);
navList.appendChild(li);
});
} else {
navList.innerHTML = '<li><span class="sidebar-loading">暂无文档</span></li>';
}
})
.catch(function(error) {
console.error('Error loading document list:', error);
document.getElementById('doc-nav-list').innerHTML = '<li><span class="sidebar-loading">加载失败</span></li>';
});
</script>
</body>
</html>