上传文件至「doc」

这个提交包含在:
2026-07-24 11:45:26 +08:00
父节点 6d17411813
当前提交 1ff58aab5d
修改 3 个文件,包含 715 行新增0 行删除
+121
查看文件
@@ -0,0 +1,121 @@
<?php
$baseDir = __DIR__ . '/md';
$fileParam = isset($_GET['file']) ? trim($_GET['file']) : '';
$isAjax = isset($_GET['_ajax']);
if (empty($fileParam)) {
$files = glob($baseDir . '/*.md');
if ($files) {
usort($files, function($a, $b) {
return filemtime($b) - filemtime($a);
});
$fileParam = 'md/' . basename($files[0]);
} else {
$fileParam = 'md/index.md';
}
}
$fileParam = str_replace(['..', '/\\', "\0", '\\'], '', $fileParam);
if (!preg_match('/^md\/[a-zA-Z0-9_\-\x{4e00}-\x{9fff}]+(\/[a-zA-Z0-9_\-\x{4e00}-\x{9fff}]+)*\.md$/u', $fileParam)) {
if (preg_match('/^[a-zA-Z0-9_\-\x{4e00}-\x{9fff}]+(\/[a-zA-Z0-9_\-\x{4e00}-\x{9fff}]+)*\.md$/u', $fileParam)) {
$fileParam = '/doc/md/' . $fileParam;
} else {
$fileParam = '';
}
}
$mdFile = __DIR__ . '/' . $fileParam;
$content = '';
$title = 'Docs';
$currentFile = $fileParam;
$pathParts = explode('/', $fileParam);
$pageName = basename($fileParam, '.md');
$folderName = (count($pathParts) > 2) ? $pathParts[1] : '';
$title = ucfirst($pageName);
if (!empty($fileParam) && file_exists($mdFile) && is_file($mdFile)) {
$rawContent = file_get_contents($mdFile);
} else {
$rawContent = '';
$title = '未找到';
}
if ($isAjax) {
echo $rawContent;
exit;
}
function getDirStructure($dir, $base = '') {
$items = [];
$entries = scandir($dir);
foreach ($entries as $entry) {
if ($entry === '.' || $entry === '..') continue;
$path = $dir . '/' . $entry;
$rel = $base ? $base . '/' . $entry : $entry;
if (is_dir($path)) {
$items[] = ['type' => 'dir', 'name' => $entry, 'path' => $rel, 'children' => getDirStructure($path, $rel)];
} elseif (pathinfo($path, PATHINFO_EXTENSION) === 'md') {
$items[] = ['type' => 'file', 'name' => $entry, 'path' => $rel];
}
}
return $items;
}
$tree = getDirStructure($baseDir);
function renderTree($items, $currentFile) {
$html = '<ul>';
foreach ($items as $item) {
if ($item['type'] === 'dir') {
$html .= '<li class="folder"><span class="folder-toggle"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg> ' . htmlspecialchars($item['name']) . '</span>';
$html .= renderTree($item['children'], $currentFile);
$html .= '</li>';
} else {
$active = $currentFile === 'md/' . $item['path'] ? 'active' : '';
$html .= '<li class="file ' . $active . '"><a href="/doc/?file=md/' . $item['path'] . '"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg> ' . htmlspecialchars($item['name']) . '</a></li>';
}
}
$html .= '</ul>';
return $html;
}
?>
<!DOCTYPE html>
<html lang="zh-CN" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($title); ?> - Parlz Domains Docs</title>
<link rel="stylesheet" href="/doc/style.css">
</head>
<body>
<div class="layout">
<aside class="sidebar">
<div class="sidebar-header">
<h1>文档列表</h1>
<button class="theme-toggle" id="themeToggle">
<svg class="sun" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
<svg class="moon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
</button>
</div>
<div class="search-box">
<input type="text" id="searchInput" placeholder="搜索任何文档...">
</div>
<nav class="nav-tree" id="navTree">
<?php echo renderTree($tree, $currentFile); ?>
</nav>
</aside>
<main class="content">
<div class="mobile-header">
<button class="menu-toggle" id="menuToggle">☰</button>
<span class="mobile-title">Parlz Domains Docs</span>
<button class="theme-toggle" id="mobileThemeToggle">
<svg class="sun" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
<svg class="moon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
</button>
</div>
<div class="markdown-body" id="markdownBody">
<?php if ($rawContent): ?>
<?php echo $rawContent; ?>
<?php else: ?>
<p>暂无文档</p>
<?php endif; ?>
</div>
</main>
<div class="overlay" id="overlay"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="/doc/script.js"></script>
</body>
</html>
+198
查看文件
@@ -0,0 +1,198 @@
document.addEventListener('DOMContentLoaded', function() {
const markdownBody = document.getElementById('markdownBody');
const rawContent = markdownBody.innerHTML;
markdownBody.innerHTML = marked.parse(rawContent);
const themeToggle = document.getElementById('themeToggle');
const mobileThemeToggle = document.getElementById('mobileThemeToggle');
const html = document.documentElement;
function setTheme(theme) {
html.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
}
function toggleTheme() {
const current = html.getAttribute('data-theme');
setTheme(current === 'dark' ? 'light' : 'dark');
}
const saved = localStorage.getItem('theme');
if (saved) {
setTheme(saved);
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
setTheme('dark');
}
themeToggle.addEventListener('click', toggleTheme);
if (mobileThemeToggle) mobileThemeToggle.addEventListener('click', toggleTheme);
const menuToggle = document.getElementById('menuToggle');
const sidebar = document.querySelector('.sidebar');
const overlay = document.getElementById('overlay');
if (menuToggle) {
menuToggle.addEventListener('click', function() {
sidebar.classList.toggle('open');
overlay.classList.toggle('show');
});
}
if (overlay) {
overlay.addEventListener('click', function() {
sidebar.classList.remove('open');
overlay.classList.remove('show');
});
}
const folders = document.querySelectorAll('.folder-toggle');
folders.forEach(function(folder) {
folder.addEventListener('click', function() {
const li = this.parentElement;
li.classList.toggle('open');
});
});
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.addEventListener('input', function() {
const query = this.value.toLowerCase().trim();
const files = document.querySelectorAll('.nav-tree .file');
const dirs = document.querySelectorAll('.nav-tree .folder');
if (!query) {
files.forEach(f => f.classList.remove('hidden'));
dirs.forEach(d => d.classList.remove('hidden'));
return;
}
files.forEach(function(file) {
const text = file.textContent.toLowerCase();
if (text.includes(query)) {
file.classList.remove('hidden');
let parent = file.parentElement;
while (parent) {
if (parent.classList && parent.classList.contains('folder')) {
parent.classList.remove('hidden');
parent.classList.add('open');
}
parent = parent.parentElement;
}
} else {
file.classList.add('hidden');
}
});
dirs.forEach(function(dir) {
const visibleFiles = dir.querySelectorAll('.file:not(.hidden)');
if (visibleFiles.length === 0) {
const folderText = dir.querySelector('.folder-toggle').textContent.toLowerCase();
if (!folderText.includes(query)) {
dir.classList.add('hidden');
}
}
});
});
}
const currentFile = document.querySelector('.nav-tree .file.active');
if (currentFile) {
let parent = currentFile.parentElement;
while (parent) {
if (parent.classList && parent.classList.contains('folder')) {
parent.classList.add('open');
}
parent = parent.parentElement;
}
currentFile.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
const navTree = document.getElementById('navTree');
if (navTree) {
navTree.addEventListener('click', function(e) {
const link = e.target.closest('a');
if (!link) return;
const href = link.getAttribute('href');
if (!href || href.startsWith('http') || href.startsWith('#') || href.startsWith('javascript')) return;
e.preventDefault();
const sidebarEl = document.querySelector('.sidebar');
const overlayEl = document.getElementById('overlay');
if (sidebarEl.classList.contains('open')) {
sidebarEl.classList.remove('open');
overlayEl.classList.remove('show');
}
history.pushState(null, '', href);
loadDoc(href);
});
}
// ===== 使用 XMLHttpRequest 替代 fetch =====
function loadDoc(href) {
var url = '/doc/index.php' + href + '&_ajax=1';
markdownBody.style.opacity = '0.5';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.timeout = 30000; // 30秒超时
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
markdownBody.style.opacity = '1';
if (xhr.status === 200) {
var data = xhr.responseText;
// 检查是否返回了 HTML(说明请求失败)
if (data.trim().startsWith('<!DOCTYPE') || data.trim().startsWith('<html')) {
// 降级:直接跳转
window.location.href = href;
return;
}
markdownBody.innerHTML = marked.parse(data);
var titleEl = markdownBody.querySelector('h1');
var newTitle = titleEl ? titleEl.textContent + ' - Docs' : 'Docs - Docs';
document.title = newTitle;
updateActiveLink(href);
window.scrollTo({ top: 0, behavior: 'smooth' });
} else {
// 请求失败,降级跳转
console.error('加载失败,状态码: ' + xhr.status);
window.location.href = href;
}
}
};
xhr.onerror = function() {
markdownBody.style.opacity = '1';
console.error('网络错误');
window.location.href = href;
};
xhr.ontimeout = function() {
markdownBody.style.opacity = '1';
console.error('请求超时');
window.location.href = href;
};
xhr.send();
}
function updateActiveLink(href) {
document.querySelectorAll('.nav-tree .file').forEach(function(f) {
f.classList.remove('active');
var a = f.querySelector('a');
if (a && a.getAttribute('href') === href) {
f.classList.add('active');
}
});
}
window.addEventListener('popstate', function() {
var params = new URLSearchParams(window.location.search);
var f = params.get('file');
if (f) {
loadDoc('?file=' + f);
}
});
});
+396
查看文件
@@ -0,0 +1,396 @@
@import url("https://cdn.jsdmirror.com/gh/willow-god/Sharding-fonts/Yozai-Medium/result.min.css");
:root {
--bg-primary: #ffffff;
--bg-secondary: #f8fafc;
--bg-sidebar: #f1f5f9;
--text-primary: #1e293b;
--text-secondary: #64748b;
--text-muted: #94a3b8;
--border-color: #e2e8f0;
--accent-color: #3b82f6;
--accent-hover: #2563eb;
--code-bg: #f1f5f9;
--code-text: #334155;
--shadow: 0 1px 3px rgba(0,0,0,0.1);
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1);
--radius: 8px;
--radius-lg: 12px;
--font-sans: 'Yozai Medium';
--font-mono: 'SF Mono', Monaco, 'Cascadia Code', monospace;
}
[data-theme="dark"] {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-sidebar: #1e293b;
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--border-color: #334155;
--accent-color: #60a5fa;
--accent-hover: #3b82f6;
--code-bg: #1e293b;
--code-text: #e2e8f0;
--shadow: 0 1px 3px rgba(0,0,0,0.3);
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.4);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--font-sans);
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
transition: background 0.3s, color 0.3s;
}
.layout {
display: flex;
min-height: 100vh;
}
.sidebar {
width: 280px;
background: var(--bg-sidebar);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
position: fixed;
height: 100vh;
overflow-y: auto;
transition: transform 0.3s, background 0.3s;
z-index: 100;
}
.sidebar-header {
padding: 20px;
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
}
.sidebar-header h1 {
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
}
.theme-toggle {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 8px;
cursor: pointer;
color: var(--text-secondary);
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.theme-toggle:hover {
background: var(--accent-color);
color: #fff;
border-color: var(--accent-color);
}
.theme-toggle .moon { display: none; }
[data-theme="dark"] .theme-toggle .sun { display: none; }
[data-theme="dark"] .theme-toggle .moon { display: block; }
.search-box {
padding: 12px 20px;
border-bottom: 1px solid var(--border-color);
}
.search-box input {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: var(--radius);
background: var(--bg-primary);
color: var(--text-primary);
font-size: 0.875rem;
outline: none;
transition: border-color 0.2s;
}
.search-box input:focus {
border-color: var(--accent-color);
}
.search-box input::placeholder {
color: var(--text-muted);
}
.nav-tree {
padding: 12px 0;
flex: 1;
}
.nav-tree ul {
list-style: none;
}
.nav-tree li {
position: relative;
}
.nav-tree .folder-toggle {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 20px;
cursor: pointer;
color: var(--text-secondary);
font-size: 0.875rem;
font-weight: 500;
transition: color 0.2s;
user-select: none;
}
.nav-tree .folder-toggle:hover {
color: var(--text-primary);
}
.nav-tree .folder-toggle svg {
flex-shrink: 0;
opacity: 0.7;
}
.nav-tree .folder > ul {
display: none;
padding-left: 12px;
}
.nav-tree .folder.open > ul {
display: block;
}
.nav-tree .file a {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 20px 6px 32px;
color: var(--text-secondary);
text-decoration: none;
font-size: 0.875rem;
transition: all 0.2s;
border-left: 2px solid transparent;
}
.nav-tree .file a:hover {
color: var(--accent-color);
background: var(--bg-secondary);
}
.nav-tree .file.active a {
color: var(--accent-color);
background: var(--bg-secondary);
border-left-color: var(--accent-color);
font-weight: 500;
}
.nav-tree .file.hidden,
.nav-tree .folder.hidden {
display: none;
}
.content {
margin-left: 280px;
flex: 1;
min-height: 100vh;
}
.mobile-header {
display: none;
padding: 12px 20px;
background: var(--bg-sidebar);
border-bottom: 1px solid var(--border-color);
align-items: center;
gap: 12px;
position: sticky;
top: 0;
z-index: 50;
}
.menu-toggle {
background: none;
border: none;
font-size: 1.5rem;
color: var(--text-primary);
cursor: pointer;
padding: 4px;
}
.mobile-title {
font-weight: 600;
font-size: 1rem;
flex: 1;
color: var(--text-primary);
}
.markdown-body {
padding: 40px 48px;
max-width: 900px;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 2rem;
margin-bottom: 1rem;
font-weight: 600;
line-height: 1.3;
color: var(--text-primary);
}
.markdown-body h1 { font-size: 2rem; border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; }
.markdown-body h2 { font-size: 1.5rem; border-bottom: 1px solid var(--border-color); padding-bottom: 0.4rem; }
.markdown-body h3 { font-size: 1.25rem; }
.markdown-body h4 { font-size: 1.1rem; }
.markdown-body p {
margin-bottom: 1rem;
color: var(--text-secondary);
}
.markdown-body a {
color: var(--accent-color);
text-decoration: none;
}
.markdown-body a:hover {
text-decoration: underline;
}
.markdown-body ul,
.markdown-body ol {
margin-bottom: 1rem;
padding-left: 1.5rem;
color: var(--text-secondary);
}
.markdown-body li {
margin-bottom: 0.25rem;
}
.markdown-body code {
background: var(--code-bg);
color: var(--code-text);
padding: 2px 6px;
border-radius: 4px;
font-family: var(--font-mono);
font-size: 0.875em;
}
.markdown-body pre {
background: var(--code-bg);
border-radius: var(--radius);
padding: 1rem;
overflow-x: auto;
margin-bottom: 1rem;
border: 1px solid var(--border-color);
}
.markdown-body pre code {
background: none;
padding: 0;
font-size: 0.875rem;
line-height: 1.6;
}
.markdown-body blockquote {
border-left: 3px solid var(--accent-color);
padding-left: 1rem;
margin-bottom: 1rem;
color: var(--text-muted);
font-style: italic;
}
.markdown-body table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1rem;
}
.markdown-body th,
.markdown-body td {
padding: 0.75rem;
border: 1px solid var(--border-color);
text-align: left;
}
.markdown-body th {
background: var(--bg-secondary);
font-weight: 600;
color: var(--text-primary);
}
.markdown-body tr:nth-child(even) {
background: var(--bg-secondary);
}
.markdown-body hr {
border: none;
border-top: 1px solid var(--border-color);
margin: 2rem 0;
}
.markdown-body img {
max-width: 100%;
height: auto;
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.markdown-body img[src*=".svg"] {
background: var(--bg-secondary);
padding: 1rem;
}
.overlay {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 99;
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
}
.sidebar.open {
transform: translateX(0);
}
.content {
margin-left: 0;
}
.mobile-header {
display: flex;
}
.markdown-body {
padding: 20px;
}
.overlay.show {
display: block;
}
}
@media (max-width: 480px) {
.markdown-body h1 { font-size: 1.5rem; }
.markdown-body h2 { font-size: 1.25rem; }
.markdown-body { padding: 16px; }
}