This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
@@ -60,7 +59,8 @@ ROOT = resolve_root()
|
||||
BLOGS_DIR = ROOT / "blogs"
|
||||
POSTS_DIR = ROOT / "posts"
|
||||
LIMA_DIR = ROOT / "lima"
|
||||
HZONE_ASSETS_DIR = ROOT / "assets" / "images" / "hzone"
|
||||
IMAGE_ASSETS_DIR = ROOT / "assets" / "images"
|
||||
HZONE_ASSETS_DIR = IMAGE_ASSETS_DIR / "hzone"
|
||||
EXCLUDED_CONTENT_DIR_NAMES = {
|
||||
".agents",
|
||||
".codex",
|
||||
@@ -90,9 +90,6 @@ ALLOWED_UPLOAD_EXTENSIONS = {
|
||||
".gif",
|
||||
".webp",
|
||||
".svg",
|
||||
".mp4",
|
||||
".webm",
|
||||
".mov",
|
||||
}
|
||||
MONTH_NAMES = [
|
||||
"january",
|
||||
@@ -563,9 +560,7 @@ def image_dimensions(payload: bytes, ext: str) -> tuple[int, int] | 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 = safe_relative_path(page_path) if page_path else ROOT / "index.org"
|
||||
page_rel = page.relative_to(ROOT).as_posix()
|
||||
page_output_dir = posixpath.dirname(page_rel)
|
||||
return posixpath.relpath(asset_path, page_output_dir or ".")
|
||||
@@ -584,34 +579,49 @@ def gallery_image_html(filename: str, asset_path: str, page_path: str, payload:
|
||||
)
|
||||
|
||||
|
||||
def attachment_image_dir(page_path: str) -> Path:
|
||||
page = safe_relative_path(page_path) if page_path else ROOT / "index.org"
|
||||
if page.suffix == ".md" and page.is_relative_to(LIMA_DIR):
|
||||
return HZONE_ASSETS_DIR
|
||||
if page.is_relative_to(POSTS_DIR):
|
||||
rel = page.relative_to(POSTS_DIR)
|
||||
section = rel.parts[0] if len(rel.parts) > 1 else "posts"
|
||||
return IMAGE_ASSETS_DIR / slugify(section)
|
||||
if page.is_relative_to(BLOGS_DIR):
|
||||
return IMAGE_ASSETS_DIR / "blogs"
|
||||
rel = page.relative_to(ROOT)
|
||||
if len(rel.parts) > 1:
|
||||
return IMAGE_ASSETS_DIR / slugify(rel.parts[0])
|
||||
return IMAGE_ASSETS_DIR / "pages"
|
||||
|
||||
|
||||
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:
|
||||
raise ValueError("Only common image and video files can be uploaded.")
|
||||
raise ValueError("Only common image files can be uploaded.")
|
||||
now = datetime.now()
|
||||
target_dir = HZONE_ASSETS_DIR / f"{now.year}" / f"{now.month:02d}"
|
||||
target_dir = attachment_image_dir(page_path)
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
stem = slugify(Path(original).stem)
|
||||
target = target_dir / f"{now.strftime('%Y%m%d-%H%M%S')}-{stem}{ext}"
|
||||
prefix = "" if re.match(r"^\d{4}-\d{2}-\d{2}-", stem) else f"{now.strftime('%Y-%m-%d')}-"
|
||||
target = target_dir / f"{prefix}{stem}{ext}"
|
||||
counter = 2
|
||||
while target.exists():
|
||||
target = target_dir / f"{now.strftime('%Y%m%d-%H%M%S')}-{stem}-{counter}{ext}"
|
||||
target = target_dir / f"{prefix}{stem}-{counter}{ext}"
|
||||
counter += 1
|
||||
target.write_bytes(payload)
|
||||
rel = target.relative_to(ROOT).as_posix()
|
||||
absolute_url = f"https://zainezq.com/{rel}"
|
||||
relative_url = relative_asset_path(page_path, rel)
|
||||
mime = mimetypes.guess_type(target.name)[0] or ""
|
||||
if mime.startswith("video/") or ext in {".mp4", ".webm", ".mov"}:
|
||||
markdown = f'<video controls src="{relative_url}"></video>'
|
||||
else:
|
||||
markdown = f""
|
||||
is_markdown = page_path.endswith(".md")
|
||||
insert_text = f"" if is_markdown else f"[[{relative_url}]]"
|
||||
return {
|
||||
"url": absolute_url,
|
||||
"relativeUrl": relative_url,
|
||||
"path": rel,
|
||||
"markdown": markdown,
|
||||
"markdown": insert_text,
|
||||
"insertText": insert_text,
|
||||
"filename": target.name,
|
||||
}
|
||||
|
||||
@@ -782,7 +792,7 @@ APP_HTML = r"""<!doctype html>
|
||||
<button type="button" data-md="link">Link</button>
|
||||
<button type="button" id="pageLinkBtn">Link to page</button>
|
||||
<button type="button" id="attachBtn">Insert attachment</button>
|
||||
<input id="attachInput" type="file" accept="image/*,video/*" />
|
||||
<input id="attachInput" type="file" accept="image/*" />
|
||||
</div>
|
||||
<div id="pageLinkPicker" class="link-picker" hidden>
|
||||
<select id="pageLinkSelect"></select>
|
||||
@@ -1263,12 +1273,12 @@ 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");
|
||||
body.append("pagePath", currentEditorPath() || "index.org");
|
||||
try {
|
||||
const res = await fetch("/api/upload", { method: "POST", body });
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || "Upload failed");
|
||||
replaceSelection(`\n${data.markdown}\n`);
|
||||
replaceSelection(`\n${data.insertText || data.markdown}\n`);
|
||||
saveMessage.textContent = "Attachment inserted.";
|
||||
} catch (err) {
|
||||
saveMessage.textContent = err.message;
|
||||
|
||||
0
blogs/2026/05-may/using-codex-07-05-26.org
Normal file → Executable file
0
blogs/2026/05-may/using-codex-07-05-26.org
Normal file → Executable file
@@ -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 16:21</span>@@
|
||||
- [[file:career/career-list.org][Career List]] @@html:<span class="post-date">07-05-2026 16:28</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>@@
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
- [[file:tags/notes.org][Tag: notes]]
|
||||
- [[file:tags/review.org][Tag: review]]
|
||||
- [[file:tags/website.org][Tag: website]]
|
||||
- [[file:tags/update.org][Tag: update]]
|
||||
- [[file:tags/life.org][Tag: life]]
|
||||
- [[file:tags/education.org][Tag: education]]
|
||||
- [[file:tags/update.org][Tag: update]]
|
||||
- [[file:tags/insights.org][Tag: insights]]
|
||||
- [[file:tags/reading.org][Tag: reading]]
|
||||
- [[file:tags/emacs.org][Tag: emacs]]
|
||||
- [[file:tags/maths.org][Tag: maths]]
|
||||
- [[file:tags/reading.org][Tag: reading]]
|
||||
- home
|
||||
- [[file:home/countdown.org][Countdown]]
|
||||
- [[file:home/contact.org][Contact]]
|
||||
|
||||
@@ -28,6 +28,7 @@ class AuthoringServerTestCase(unittest.TestCase):
|
||||
"BLOGS_DIR": self.blogs,
|
||||
"POSTS_DIR": self.posts,
|
||||
"LIMA_DIR": self.lima,
|
||||
"IMAGE_ASSETS_DIR": self.root / "assets" / "images",
|
||||
"HZONE_ASSETS_DIR": self.hzone,
|
||||
}
|
||||
self.patchers = [mock.patch.object(server, name, value) for name, value in patches.items()]
|
||||
@@ -181,10 +182,40 @@ class PageRenderingTests(AuthoringServerTestCase):
|
||||
"../assets/images/hzone/pic.png",
|
||||
)
|
||||
|
||||
def test_save_upload_stores_post_images_by_section_and_returns_org_link(self):
|
||||
with mock.patch.object(server, "datetime") as datetime_mock:
|
||||
datetime_mock.now.return_value = datetime(2026, 5, 7, 9, 30)
|
||||
saved = server.save_upload(
|
||||
"Stress In Workplace.png",
|
||||
b"image bytes",
|
||||
"posts/career/management-of-self.org",
|
||||
)
|
||||
|
||||
target = self.root / "assets" / "images" / "career" / "2026-05-07-stress-in-workplace.png"
|
||||
self.assertEqual(target.read_bytes(), b"image bytes")
|
||||
self.assertEqual(saved["path"], "assets/images/career/2026-05-07-stress-in-workplace.png")
|
||||
self.assertEqual(saved["relativeUrl"], "../../assets/images/career/2026-05-07-stress-in-workplace.png")
|
||||
self.assertEqual(saved["insertText"], "[[../../assets/images/career/2026-05-07-stress-in-workplace.png]]")
|
||||
|
||||
def test_save_upload_keeps_blog_images_in_blog_folder(self):
|
||||
with mock.patch.object(server, "datetime") as datetime_mock:
|
||||
datetime_mock.now.return_value = datetime(2026, 5, 7, 9, 30)
|
||||
saved = server.save_upload(
|
||||
"Lunch.jpg",
|
||||
b"image bytes",
|
||||
"blogs/2026/05-may/lunch.org",
|
||||
)
|
||||
|
||||
self.assertEqual(saved["path"], "assets/images/blogs/2026-05-07-lunch.jpg")
|
||||
self.assertEqual(saved["insertText"], "[[../../../assets/images/blogs/2026-05-07-lunch.jpg]]")
|
||||
|
||||
def test_save_upload_rejects_disallowed_extensions(self):
|
||||
with self.assertRaises(ValueError):
|
||||
server.save_upload("shell.php", b"<?php")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
server.save_upload("movie.mp4", b"video")
|
||||
|
||||
|
||||
class BuildQueueTests(unittest.TestCase):
|
||||
def test_snapshot_reports_recent_completed_job(self):
|
||||
|
||||
Reference in New Issue
Block a user