|
@@ -51,6 +51,38 @@ if not config.INDEX_FILE.exists():
|
|
|
json.dump([], f, ensure_ascii=False, indent=2)
|
|
json.dump([], f, ensure_ascii=False, indent=2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+# ---------- 图片缩放辅助函数 ----------
|
|
|
|
|
+def resize_image_if_large(filepath: str, max_width: int = 1200):
|
|
|
|
|
+ """如果图片宽度超过 max_width,则等比缩放并覆盖原文件。
|
|
|
|
|
+ 不处理 GIF 文件(保留动画),若 Pillow 未安装则静默跳过。"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ from PIL import Image
|
|
|
|
|
+ except ImportError:
|
|
|
|
|
+ return # 不做任何处理,避免依赖问题
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ img = Image.open(filepath)
|
|
|
|
|
+ w, h = img.size
|
|
|
|
|
+ if w <= max_width:
|
|
|
|
|
+ return
|
|
|
|
|
+ # 跳过 GIF(避免丢失动画)
|
|
|
|
|
+ if img.format == "GIF":
|
|
|
|
|
+ return
|
|
|
|
|
+ new_h = int(h * max_width / w)
|
|
|
|
|
+ img = img.resize((max_width, new_h), Image.LANCZOS)
|
|
|
|
|
+ # 保存时尽量保留原格式和质量
|
|
|
|
|
+ save_kwargs = {}
|
|
|
|
|
+ if img.format == "JPEG":
|
|
|
|
|
+ save_kwargs["quality"] = 85
|
|
|
|
|
+ save_kwargs["optimize"] = True
|
|
|
|
|
+ elif img.format == "PNG":
|
|
|
|
|
+ save_kwargs["optimize"] = True
|
|
|
|
|
+ img.save(filepath, format=img.format, **save_kwargs)
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ # 任何异常都静默跳过,不影响上传流程
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@app.context_processor
|
|
@app.context_processor
|
|
|
def inject_user_status():
|
|
def inject_user_status():
|
|
|
"""向所有模板注入登录状态"""
|
|
"""向所有模板注入登录状态"""
|
|
@@ -180,6 +212,9 @@ def editor_upload_image():
|
|
|
file_path = editor_uploads_dir / new_name
|
|
file_path = editor_uploads_dir / new_name
|
|
|
file.save(str(file_path))
|
|
file.save(str(file_path))
|
|
|
|
|
|
|
|
|
|
+ # ---------- 对上传的图片进行缩放 ----------
|
|
|
|
|
+ resize_image_if_large(str(file_path))
|
|
|
|
|
+
|
|
|
# 生成可访问 URL 和 Markdown 片段
|
|
# 生成可访问 URL 和 Markdown 片段
|
|
|
image_url = f"/static/uploads/posts/editor_uploads/{new_name}"
|
|
image_url = f"/static/uploads/posts/editor_uploads/{new_name}"
|
|
|
markdown_code = f""
|
|
markdown_code = f""
|
|
@@ -281,7 +316,10 @@ def upload():
|
|
|
if img and img.filename:
|
|
if img and img.filename:
|
|
|
filename = secure_filename(img.filename)
|
|
filename = secure_filename(img.filename)
|
|
|
if filename:
|
|
if filename:
|
|
|
- img.save(str(upload_dir / filename))
|
|
|
|
|
|
|
+ file_path = upload_dir / filename
|
|
|
|
|
+ img.save(str(file_path))
|
|
|
|
|
+ # ---------- 对上传的图片进行缩放 ----------
|
|
|
|
|
+ resize_image_if_large(str(file_path))
|
|
|
|
|
|
|
|
# 读取 Markdown 内容
|
|
# 读取 Markdown 内容
|
|
|
with open(md_path, "r", encoding="utf-8") as f:
|
|
with open(md_path, "r", encoding="utf-8") as f:
|
|
@@ -325,19 +363,53 @@ def upload():
|
|
|
|
|
|
|
|
@app.route("/post/<post_id>")
|
|
@app.route("/post/<post_id>")
|
|
|
def view_post(post_id: str):
|
|
def view_post(post_id: str):
|
|
|
- """查看文章详情"""
|
|
|
|
|
- # 安全验证:只允许字母数字和下划线
|
|
|
|
|
|
|
+ """查看文章详情,支持登录后显示删除按钮"""
|
|
|
if not re.match(r"^[a-zA-Z0-9_]+$", post_id):
|
|
if not re.match(r"^[a-zA-Z0-9_]+$", post_id):
|
|
|
abort(404)
|
|
abort(404)
|
|
|
|
|
|
|
|
- try:
|
|
|
|
|
- return send_from_directory(
|
|
|
|
|
- config.GENERATED_FOLDER,
|
|
|
|
|
- f"{post_id}.html",
|
|
|
|
|
- )
|
|
|
|
|
- except FileNotFoundError:
|
|
|
|
|
|
|
+ posts = load_index()
|
|
|
|
|
+ entry = next((p for p in posts if p["id"] == post_id), None)
|
|
|
|
|
+ if not entry:
|
|
|
abort(404)
|
|
abort(404)
|
|
|
|
|
|
|
|
|
|
+ title = entry.get("title", "")
|
|
|
|
|
+ date = entry.get("date", "")
|
|
|
|
|
+ thumbnail = entry.get("thumbnail", "")
|
|
|
|
|
+
|
|
|
|
|
+ content = ""
|
|
|
|
|
+ md_path = config.POSTS_DATA_FOLDER / post_id / "content.md"
|
|
|
|
|
+ if md_path.exists():
|
|
|
|
|
+ try:
|
|
|
|
|
+ with open(md_path, "r", encoding="utf-8") as f:
|
|
|
|
|
+ md_content = f.read()
|
|
|
|
|
+ fixed_md = fix_image_paths(md_content, post_id)
|
|
|
|
|
+ html_body = render_markdown(fixed_md)
|
|
|
|
|
+ content = html_body
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ content = ""
|
|
|
|
|
+
|
|
|
|
|
+ if not content:
|
|
|
|
|
+ generated_path = config.GENERATED_FOLDER / f"{post_id}.html"
|
|
|
|
|
+ if generated_path.exists():
|
|
|
|
|
+ try:
|
|
|
|
|
+ with open(generated_path, "r", encoding="utf-8") as f:
|
|
|
|
|
+ html_content = f.read()
|
|
|
|
|
+ soup = BeautifulSoup(html_content, "html.parser")
|
|
|
|
|
+ card_summary = soup.find("div", class_="card-summary")
|
|
|
|
|
+ if card_summary:
|
|
|
|
|
+ content = card_summary.decode_contents()
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ content = ""
|
|
|
|
|
+
|
|
|
|
|
+ return render_template(
|
|
|
|
|
+ "post_template.html",
|
|
|
|
|
+ title=title,
|
|
|
|
|
+ content=content,
|
|
|
|
|
+ date=date,
|
|
|
|
|
+ thumbnail=thumbnail,
|
|
|
|
|
+ post_id=post_id,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
|
|
|
@app.route("/admin")
|
|
@app.route("/admin")
|
|
|
def admin():
|
|
def admin():
|
|
@@ -348,7 +420,11 @@ def admin():
|
|
|
|
|
|
|
|
@app.route("/admin/delete/<post_id>", methods=["POST"])
|
|
@app.route("/admin/delete/<post_id>", methods=["POST"])
|
|
|
def delete_post(post_id: str):
|
|
def delete_post(post_id: str):
|
|
|
- """删除文章"""
|
|
|
|
|
|
|
+ """删除文章(需要登录)"""
|
|
|
|
|
+ if "user" not in session:
|
|
|
|
|
+ flash("请先登录", "warning")
|
|
|
|
|
+ return redirect(url_for("login"))
|
|
|
|
|
+
|
|
|
# 安全验证
|
|
# 安全验证
|
|
|
if not re.match(r"^[a-zA-Z0-9_]+$", post_id):
|
|
if not re.match(r"^[a-zA-Z0-9_]+$", post_id):
|
|
|
abort(404)
|
|
abort(404)
|
|
@@ -373,7 +449,8 @@ def delete_post(post_id: str):
|
|
|
index = [p for p in index if p["id"] != post_id]
|
|
index = [p for p in index if p["id"] != post_id]
|
|
|
save_index(index)
|
|
save_index(index)
|
|
|
|
|
|
|
|
- return redirect(url_for("admin"))
|
|
|
|
|
|
|
+ flash("文章已删除", "success")
|
|
|
|
|
+ return redirect(url_for("index"))
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.errorhandler(404)
|
|
@app.errorhandler(404)
|