377 lines
12 KiB
PHP
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>
|