helpers.py 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. import re
  2. import json
  3. from pathlib import Path
  4. from datetime import datetime, timezone
  5. from flask import render_template
  6. from werkzeug.utils import secure_filename
  7. import markdown
  8. from bs4 import BeautifulSoup
  9. import config
  10. def allowed_file(filename: str) -> bool:
  11. """检查文件扩展名是否允许"""
  12. return (
  13. "." in filename
  14. and filename.rsplit(".", 1)[1].lower() in config.ALLOWED_EXTENSIONS
  15. )
  16. def load_index() -> list:
  17. """加载文章索引"""
  18. with open(config.INDEX_FILE, "r", encoding="utf-8") as f:
  19. return json.load(f)
  20. def save_index(index: list):
  21. """保存文章索引"""
  22. with open(config.INDEX_FILE, "w", encoding="utf-8") as f:
  23. json.dump(index, f, ensure_ascii=False, indent=2)
  24. def fix_image_paths(md_content: str, post_id: str) -> str:
  25. """将 Markdown 中的本地图片路径替换为可访问的 URL"""
  26. pattern = r'!\[([^\]]*)\]\(([^)]+)\)'
  27. def repl(match):
  28. alt, src = match.groups()
  29. # 仅当 src 不含 '/' 或 'http' 且扩展名为图片时视为本地文件
  30. if (
  31. not src.startswith(("http://", "https://", "/", "#"))
  32. and "." in src
  33. ):
  34. new_src = f"/static/uploads/posts/{post_id}/{src}"
  35. return f'![{alt}]({new_src})'
  36. return match.group(0)
  37. return re.sub(pattern, repl, md_content)
  38. def extract_summary(html_body: str, max_chars: int = 200) -> str:
  39. """从 HTML 中提取纯文本摘要(返回完整文本)"""
  40. soup = BeautifulSoup(html_body, "html.parser")
  41. text = soup.get_text()
  42. # 去除多余空白
  43. text = re.sub(r"\s+", " ", text).strip()
  44. return text
  45. def extract_thumbnail(html_body: str) -> str:
  46. """从 HTML 中提取第一张图片的 URL"""
  47. soup = BeautifulSoup(html_body, "html.parser")
  48. img = soup.find("img")
  49. if img and img.get("src"):
  50. return img["src"]
  51. return ""
  52. def render_markdown(md_content: str) -> str:
  53. """将 Markdown 渲染为 HTML"""
  54. md = markdown.Markdown(
  55. extensions=[
  56. "extra",
  57. "codehilite",
  58. "tables",
  59. "fenced_code",
  60. ]
  61. )
  62. return md.convert(md_content)
  63. def generate_static_page(post_id: str, title: str, html_body: str, date: str, thumbnail: str):
  64. """生成独立的静态 HTML 文件"""
  65. rendered = render_template(
  66. "post_template.html",
  67. title=title,
  68. content=html_body,
  69. date=date,
  70. thumbnail=thumbnail,
  71. )
  72. output_path = config.GENERATED_FOLDER / f"{post_id}.html"
  73. with open(output_path, "w", encoding="utf-8") as f:
  74. f.write(rendered)