This commit is contained in:
@@ -6,7 +6,9 @@ from __future__ import annotations
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
@@ -455,7 +457,61 @@ def save_page(data: dict[str, Any]) -> dict[str, Any]:
|
||||
return page_to_dict(read_page(target))
|
||||
|
||||
|
||||
def save_upload(filename: str, payload: bytes) -> dict[str, str]:
|
||||
def image_dimensions(payload: bytes, ext: str) -> tuple[int, int] | None:
|
||||
if ext == ".png" and payload.startswith(b"\x89PNG\r\n\x1a\n") and len(payload) >= 24:
|
||||
width, height = struct.unpack(">II", payload[16:24])
|
||||
return width, height
|
||||
if ext == ".gif" and payload[:6] in {b"GIF87a", b"GIF89a"} and len(payload) >= 10:
|
||||
width, height = struct.unpack("<HH", payload[6:10])
|
||||
return width, height
|
||||
if ext in {".jpg", ".jpeg"} and payload.startswith(b"\xff\xd8"):
|
||||
i = 2
|
||||
while i + 9 < len(payload):
|
||||
if payload[i] != 0xFF:
|
||||
i += 1
|
||||
continue
|
||||
marker = payload[i + 1]
|
||||
i += 2
|
||||
if marker in {0xD8, 0xD9}:
|
||||
continue
|
||||
if i + 2 > len(payload):
|
||||
break
|
||||
size = int.from_bytes(payload[i:i + 2], "big")
|
||||
if size < 2:
|
||||
break
|
||||
if marker in {0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF}:
|
||||
if i + 7 <= len(payload):
|
||||
height = int.from_bytes(payload[i + 3:i + 5], "big")
|
||||
width = int.from_bytes(payload[i + 5:i + 7], "big")
|
||||
return width, height
|
||||
break
|
||||
i += size
|
||||
return None
|
||||
|
||||
|
||||
def relative_asset_path(page_path: str, asset_path: str) -> str:
|
||||
page = safe_relative_path(page_path) if page_path else LIMA_DIR / "index.md"
|
||||
if page.suffix != ".md" or not page.is_relative_to(LIMA_DIR):
|
||||
page = LIMA_DIR / "index.md"
|
||||
page_rel = page.relative_to(ROOT).as_posix()
|
||||
page_output_dir = posixpath.dirname(page_rel)
|
||||
return posixpath.relpath(asset_path, page_output_dir or ".")
|
||||
|
||||
|
||||
def gallery_image_html(filename: str, asset_path: str, page_path: str, payload: bytes, ext: str) -> str:
|
||||
absolute_url = f"https://zainezq.com/{asset_path}"
|
||||
relative_url = relative_asset_path(page_path, asset_path)
|
||||
dims = image_dimensions(payload, ext)
|
||||
width, height = dims if dims else (1920, 1080)
|
||||
alt = html_escape(filename)
|
||||
return (
|
||||
f'<a href="{absolute_url}" data-img="{absolute_url}" data-alt="{alt}" '
|
||||
f'data-width="{width}" data-height="{height}">'
|
||||
f'<img src="{relative_url}" alt="{alt}" style="cursor: zoom-in;"></a>'
|
||||
)
|
||||
|
||||
|
||||
def save_upload(filename: str, payload: bytes, page_path: str = "") -> dict[str, str]:
|
||||
original = Path(filename or "attachment").name
|
||||
ext = Path(original).suffix.lower()
|
||||
if ext not in ALLOWED_UPLOAD_EXTENSIONS:
|
||||
@@ -471,14 +527,20 @@ def save_upload(filename: str, payload: bytes) -> dict[str, str]:
|
||||
counter += 1
|
||||
target.write_bytes(payload)
|
||||
rel = target.relative_to(ROOT).as_posix()
|
||||
url = f"/{rel}"
|
||||
absolute_url = f"https://zainezq.com/{rel}"
|
||||
relative_url = relative_asset_path(page_path, rel)
|
||||
mime = mimetypes.guess_type(target.name)[0] or ""
|
||||
alt = Path(original).stem.replace("-", " ").replace("_", " ").strip() or "attachment"
|
||||
if mime.startswith("video/") or ext in {".mp4", ".webm", ".mov"}:
|
||||
markdown = f'<video controls src="{url}"></video>'
|
||||
markdown = f'<video controls src="{relative_url}"></video>'
|
||||
else:
|
||||
markdown = f""
|
||||
return {"url": url, "path": rel, "markdown": markdown, "filename": target.name}
|
||||
markdown = gallery_image_html(target.name, rel, page_path, payload, ext)
|
||||
return {
|
||||
"url": absolute_url,
|
||||
"relativeUrl": relative_url,
|
||||
"path": rel,
|
||||
"markdown": markdown,
|
||||
"filename": target.name,
|
||||
}
|
||||
|
||||
|
||||
def run_build_commands() -> tuple[bool, str, str]:
|
||||
@@ -893,6 +955,7 @@ APP_HTML = r"""<!doctype html>
|
||||
saveMessage.textContent = "Uploading attachment.";
|
||||
const body = new FormData();
|
||||
body.append("attachment", file);
|
||||
body.append("pagePath", state.currentPath || editor.targetPath.value || "lima/index.md");
|
||||
try {
|
||||
const res = await fetch("/api/upload", { method: "POST", body });
|
||||
const data = await res.json();
|
||||
@@ -974,7 +1037,10 @@ class Handler(BaseHTTPRequestHandler):
|
||||
payload = field.file.read()
|
||||
if not payload:
|
||||
raise ValueError("Attachment is empty.")
|
||||
self.send_json(save_upload(field.filename, payload))
|
||||
page_path = ""
|
||||
if "pagePath" in form:
|
||||
page_path = str(form["pagePath"].value or "")
|
||||
self.send_json(save_upload(field.filename, payload, page_path))
|
||||
except Exception as exc:
|
||||
self.send_json({"error": str(exc)}, HTTPStatus.BAD_REQUEST)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user