|
|
@@ -1,8 +1,6 @@
|
|
|
-import os
|
|
|
import re
|
|
|
import json
|
|
|
import time
|
|
|
-import uuid
|
|
|
import shutil
|
|
|
from datetime import datetime, timezone
|
|
|
from pathlib import Path
|
|
|
@@ -18,10 +16,18 @@ from flask import (
|
|
|
jsonify,
|
|
|
)
|
|
|
from werkzeug.utils import secure_filename
|
|
|
-import markdown
|
|
|
-from bs4 import BeautifulSoup
|
|
|
|
|
|
import config
|
|
|
+from helpers import (
|
|
|
+ allowed_file,
|
|
|
+ load_index,
|
|
|
+ save_index,
|
|
|
+ fix_image_paths,
|
|
|
+ extract_summary,
|
|
|
+ extract_thumbnail,
|
|
|
+ render_markdown,
|
|
|
+ generate_static_page,
|
|
|
+)
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
app.config.from_object(config)
|
|
|
@@ -41,89 +47,6 @@ if not config.INDEX_FILE.exists():
|
|
|
json.dump([], f, ensure_ascii=False, indent=2)
|
|
|
|
|
|
|
|
|
-def allowed_file(filename: str) -> bool:
|
|
|
- """检查文件扩展名是否允许"""
|
|
|
- return (
|
|
|
- "." in filename
|
|
|
- and filename.rsplit(".", 1)[1].lower() in config.ALLOWED_EXTENSIONS
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
-def load_index() -> list:
|
|
|
- """加载文章索引"""
|
|
|
- with open(config.INDEX_FILE, "r", encoding="utf-8") as f:
|
|
|
- return json.load(f)
|
|
|
-
|
|
|
-
|
|
|
-def save_index(index: list):
|
|
|
- """保存文章索引"""
|
|
|
- with open(config.INDEX_FILE, "w", encoding="utf-8") as f:
|
|
|
- json.dump(index, f, ensure_ascii=False, indent=2)
|
|
|
-
|
|
|
-
|
|
|
-def fix_image_paths(md_content: str, post_id: str) -> str:
|
|
|
- """将 Markdown 中的本地图片路径替换为可访问的 URL"""
|
|
|
- pattern = r'!\[([^\]]*)\]\(([^)]+)\)'
|
|
|
-
|
|
|
- def repl(match):
|
|
|
- alt, src = match.groups()
|
|
|
- # 仅当 src 不含 '/' 或 'http' 且扩展名为图片时视为本地文件
|
|
|
- if (
|
|
|
- not src.startswith(("http://", "https://", "/", "#"))
|
|
|
- and "." in src
|
|
|
- ):
|
|
|
- new_src = f"/static/uploads/posts/{post_id}/{src}"
|
|
|
- return f''
|
|
|
- return match.group(0)
|
|
|
-
|
|
|
- return re.sub(pattern, repl, md_content)
|
|
|
-
|
|
|
-
|
|
|
-def extract_summary(html_body: str, max_chars: int = 200) -> str:
|
|
|
- """从 HTML 中提取纯文本摘要(返回完整文本)"""
|
|
|
- soup = BeautifulSoup(html_body, "html.parser")
|
|
|
- text = soup.get_text()
|
|
|
- # 去除多余空白
|
|
|
- text = re.sub(r"\s+", " ", text).strip()
|
|
|
- return text
|
|
|
-
|
|
|
-
|
|
|
-def extract_thumbnail(html_body: str) -> str:
|
|
|
- """从 HTML 中提取第一张图片的 URL"""
|
|
|
- soup = BeautifulSoup(html_body, "html.parser")
|
|
|
- img = soup.find("img")
|
|
|
- if img and img.get("src"):
|
|
|
- return img["src"]
|
|
|
- return ""
|
|
|
-
|
|
|
-
|
|
|
-def render_markdown(md_content: str) -> str:
|
|
|
- """将 Markdown 渲染为 HTML"""
|
|
|
- md = markdown.Markdown(
|
|
|
- extensions=[
|
|
|
- "extra",
|
|
|
- "codehilite",
|
|
|
- "tables",
|
|
|
- "fenced_code",
|
|
|
- ]
|
|
|
- )
|
|
|
- return md.convert(md_content)
|
|
|
-
|
|
|
-
|
|
|
-def generate_static_page(post_id: str, title: str, html_body: str, date: str, thumbnail: str):
|
|
|
- """生成独立的静态 HTML 文件"""
|
|
|
- rendered = render_template(
|
|
|
- "post_template.html",
|
|
|
- title=title,
|
|
|
- content=html_body,
|
|
|
- date=date,
|
|
|
- thumbnail=thumbnail,
|
|
|
- )
|
|
|
- output_path = config.GENERATED_FOLDER / f"{post_id}.html"
|
|
|
- with open(output_path, "w", encoding="utf-8") as f:
|
|
|
- f.write(rendered)
|
|
|
-
|
|
|
-
|
|
|
# ==================== 路由 ====================
|
|
|
|
|
|
|