serve.py 支持 .md 自动渲染成 HTML,加 no-store 避免缓存误判
vendor marked.js (MIT, 单文件, 35KB) 用于客户端渲染;直接导航 .md 走 渲染视图,?raw=1 保留原始文本入口。所有响应加 Cache-Control: no-store, 避免讲义反复修改时浏览器展示改之前的旧缓存(看起来像“没修复”)。
This commit is contained in:
@@ -6,11 +6,13 @@
|
|||||||
只绑定 127.0.0.1(不对局域网暴露)——仓库根目录含 .env(wiki 登录凭据),
|
只绑定 127.0.0.1(不对局域网暴露)——仓库根目录含 .env(wiki 登录凭据),
|
||||||
必须避免整个仓库被同一 Wi-Fi/局域网上的其他设备访问到。
|
必须避免整个仓库被同一 Wi-Fi/局域网上的其他设备访问到。
|
||||||
"""
|
"""
|
||||||
|
import html
|
||||||
import http.server
|
import http.server
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import socketserver
|
import socketserver
|
||||||
import sys
|
import sys
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
# 幻灯片里点击的源码链接(.c/.cpp/.py/.md 等)需要以纯文本显示,而不是被当成
|
# 幻灯片里点击的源码链接(.c/.cpp/.py/.md 等)需要以纯文本显示,而不是被当成
|
||||||
# 未知二进制触发下载——默认 mimetypes 对 .cpp/.c 等会猜成 text/x-csrc 之类,
|
# 未知二进制触发下载——默认 mimetypes 对 .cpp/.c 等会猜成 text/x-csrc 之类,
|
||||||
@@ -18,6 +20,41 @@ import sys
|
|||||||
for ext in (".c", ".cpp", ".h", ".hpp", ".cc", ".qrc", ".py", ".md"):
|
for ext in (".c", ".cpp", ".h", ".hpp", ".cc", ".qrc", ".py", ".md"):
|
||||||
mimetypes.add_type("text/plain", ext)
|
mimetypes.add_type("text/plain", ext)
|
||||||
|
|
||||||
|
MARKED_JS_URL = "/docs/teaching/vendor/marked.min.js"
|
||||||
|
|
||||||
|
MARKDOWN_TEMPLATE = """<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{title}</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<style>
|
||||||
|
body {{ font-family: -apple-system, "Helvetica Neue", "PingFang SC", "Microsoft YaHei",
|
||||||
|
sans-serif; max-width: 900px; margin: 40px auto; padding: 0 20px;
|
||||||
|
line-height: 1.6; color: #24292f; }}
|
||||||
|
pre {{ background: #f6f8fa; padding: 12px 16px; overflow-x: auto; border-radius: 6px; }}
|
||||||
|
code {{ background: #f6f8fa; padding: 1px 5px; border-radius: 3px; font-size: 0.9em; }}
|
||||||
|
pre code {{ background: none; padding: 0; }}
|
||||||
|
table {{ border-collapse: collapse; }}
|
||||||
|
th, td {{ border: 1px solid #d0d7de; padding: 6px 12px; }}
|
||||||
|
blockquote {{ color: #57606a; border-left: 4px solid #d0d7de; margin-left: 0; padding-left: 16px; }}
|
||||||
|
a {{ color: #0969da; }}
|
||||||
|
.raw-link {{ float: right; font-size: 0.85em; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="raw-link"><a href="{path_qs}?raw=1">查看原始 Markdown</a></div>
|
||||||
|
<div id="content">渲染中...</div>
|
||||||
|
<script type="text/plain" id="raw-md">{raw}</script>
|
||||||
|
<script src="{marked_url}"></script>
|
||||||
|
<script>
|
||||||
|
document.getElementById('content').innerHTML =
|
||||||
|
marked.parse(document.getElementById('raw-md').textContent);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
repo_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
os.chdir(repo_root)
|
os.chdir(repo_root)
|
||||||
|
|
||||||
@@ -41,6 +78,41 @@ class Utf8RequestHandler(http.server.SimpleHTTPRequestHandler):
|
|||||||
ctype += "; charset=utf-8"
|
ctype += "; charset=utf-8"
|
||||||
return ctype
|
return ctype
|
||||||
|
|
||||||
|
def end_headers(self):
|
||||||
|
# 讲义/幻灯片会在课前反复改——不加 no-store 的话,浏览器可能对同一 URL
|
||||||
|
# 展示改之前缓存的(曾经乱码的)旧响应,看起来像是"修复没生效"。
|
||||||
|
self.send_header("Cache-Control", "no-store")
|
||||||
|
super().end_headers()
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
parsed = urllib.parse.urlsplit(self.path)
|
||||||
|
# .md 直接导航时渲染成 HTML;?raw=1 保留原始文本入口(复制/diff 用)
|
||||||
|
if parsed.path.endswith(".md") and "raw=1" not in parsed.query:
|
||||||
|
self.serve_markdown(parsed.path)
|
||||||
|
else:
|
||||||
|
super().do_GET()
|
||||||
|
|
||||||
|
def serve_markdown(self, url_path):
|
||||||
|
fs_path = self.translate_path(url_path)
|
||||||
|
try:
|
||||||
|
with open(fs_path, "r", encoding="utf-8") as f:
|
||||||
|
raw = f.read()
|
||||||
|
except OSError:
|
||||||
|
self.send_error(404, "File not found")
|
||||||
|
return
|
||||||
|
page = MARKDOWN_TEMPLATE.format(
|
||||||
|
title=html.escape(os.path.basename(fs_path)),
|
||||||
|
path_qs=html.escape(url_path),
|
||||||
|
raw=raw.replace("</script", "<\\/script"),
|
||||||
|
marked_url=MARKED_JS_URL,
|
||||||
|
)
|
||||||
|
body = page.encode("utf-8")
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-type", "text/html; charset=utf-8")
|
||||||
|
self.send_header("Content-Length", str(len(body)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(body)
|
||||||
|
|
||||||
|
|
||||||
with ReuseAddrTCPServer(("127.0.0.1", port), Utf8RequestHandler) as httpd:
|
with ReuseAddrTCPServer(("127.0.0.1", port), Utf8RequestHandler) as httpd:
|
||||||
print(f"Serving {repo_root} at http://localhost:{port}/docs/teaching/slides/index.html")
|
print(f"Serving {repo_root} at http://localhost:{port}/docs/teaching/slides/index.html")
|
||||||
|
|||||||
Vendored
+6
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user