0
assets/images/hzone/2026/05/20260507-133807-4a746efb-e97f-4492-a281-a4dd2d3fa509.jpeg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
0
assets/images/hzone/2026/05/20260507-134236-example-png-image.png
Normal file → Executable file
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
0
assets/images/hzone/2026/05/20260507-134526-example-png-image.png
Normal file → Executable file
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
@@ -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
|
||||
|
||||
@@ -11,7 +11,5 @@ I made quite a few changes. It all started when I realised that not everyone kno
|
||||
|
||||
- One other thing, a more personal one: what do you think of all this? am i doing too much? am i doing too little? am i overengineering things? am i forcing you to do something you dont wanna do? these little trinkets work for me, but i’m not sure if it would work for someone else, so at any point if you have reservations, let me know okay?
|
||||
|
||||
# **This is a test**
|
||||
|
||||
|
||||

|
||||
<a href="https://zainezq.com/assets/images/hzone/2026/05/20260507-135058-example-png-image.png" data-img="https://zainezq.com/assets/images/hzone/2026/05/20260507-135058-example-png-image.png" data-alt="20260507-135058-example-png-image.png" data-width="512" data-height="512"><img src="../assets/images/hzone/2026/05/20260507-135058-example-png-image.png" alt="20260507-135058-example-png-image.png" style="cursor: zoom-in;"></a>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
See the categories: @@html:<a href="../home/categories.html">Categories</a>@@
|
||||
|
||||
* Posts:
|
||||
- [[file:career/career-list.org][Career List]] @@html:<span class="post-date">07-05-2026 13:46</span>@@
|
||||
- [[file:career/career-list.org][Career List]] @@html:<span class="post-date">07-05-2026 13:53</span>@@
|
||||
- [[file:posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]] @@html:<span class="post-date">14-04-2026 16:36</span>@@
|
||||
- [[file:career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:<span class="post-date">11-03-2026 17:18</span>@@ @@html:<a href="/tags/learning.html"><span class="post-tag">learning</span></a>@@ @@html:<a href="/tags/notes.html"><span class="post-tag">notes</span></a>@@
|
||||
- [[file:career/javascript.org][Understands the Javascript language]] @@html:<span class="post-date">11-03-2026 16:52</span>@@ @@html:<a href="/tags/learning.html"><span class="post-tag">learning</span></a>@@ @@html:<a href="/tags/notes.html"><span class="post-tag">notes</span></a>@@
|
||||
|
||||