This commit is contained in:
@@ -399,20 +399,30 @@ def list_pages() -> list[dict[str, Any]]:
|
|||||||
pages = []
|
pages = []
|
||||||
org_paths = []
|
org_paths = []
|
||||||
if ROOT.exists():
|
if ROOT.exists():
|
||||||
for path in ROOT.rglob("*.org"):
|
try:
|
||||||
rel_parts = path.relative_to(ROOT).parts
|
for path in ROOT.rglob("*.org"):
|
||||||
if any(part in EXCLUDED_CONTENT_DIR_NAMES for part in rel_parts):
|
try:
|
||||||
continue
|
rel_parts = path.relative_to(ROOT).parts
|
||||||
org_paths.append(path)
|
except ValueError:
|
||||||
md_paths = list(LIMA_DIR.rglob("*.md")) if LIMA_DIR.exists() else []
|
continue
|
||||||
|
if any(part in EXCLUDED_CONTENT_DIR_NAMES for part in rel_parts):
|
||||||
|
continue
|
||||||
|
org_paths.append(path)
|
||||||
|
except OSError:
|
||||||
|
org_paths = []
|
||||||
|
try:
|
||||||
|
md_paths = list(LIMA_DIR.rglob("*.md")) if LIMA_DIR.exists() else []
|
||||||
|
except OSError:
|
||||||
|
md_paths = []
|
||||||
for path in sorted(org_paths + md_paths):
|
for path in sorted(org_paths + md_paths):
|
||||||
if path.name in GENERATED_CONTENT_NAMES or "sync-conflict" in path.name:
|
if path.name in GENERATED_CONTENT_NAMES or "sync-conflict" in path.name:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
page = read_page(path)
|
page = read_page(path)
|
||||||
except UnicodeDecodeError:
|
parsed = parse_org_datetime(page.date)
|
||||||
|
timestamp = parsed.timestamp() if parsed else path.stat().st_mtime
|
||||||
|
except (OSError, UnicodeDecodeError, ValueError):
|
||||||
continue
|
continue
|
||||||
parsed = parse_org_datetime(page.date)
|
|
||||||
pages.append(
|
pages.append(
|
||||||
{
|
{
|
||||||
"path": page.path,
|
"path": page.path,
|
||||||
@@ -422,7 +432,7 @@ def list_pages() -> list[dict[str, Any]]:
|
|||||||
"tags": page.tags,
|
"tags": page.tags,
|
||||||
"date": page.date,
|
"date": page.date,
|
||||||
"format": page.format,
|
"format": page.format,
|
||||||
"timestamp": parsed.timestamp() if parsed else path.stat().st_mtime,
|
"timestamp": timestamp,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return sorted(pages, key=lambda item: item["timestamp"], reverse=True)
|
return sorted(pages, key=lambda item: item["timestamp"], reverse=True)
|
||||||
@@ -850,7 +860,12 @@ APP_HTML = r"""<!doctype html>
|
|||||||
|
|
||||||
async function api(path, options = {}) {
|
async function api(path, options = {}) {
|
||||||
const res = await fetch(path, { headers: { "Content-Type": "application/json" }, ...options });
|
const res = await fetch(path, { headers: { "Content-Type": "application/json" }, ...options });
|
||||||
const data = await res.json();
|
let data = null;
|
||||||
|
try {
|
||||||
|
data = await res.clone().json();
|
||||||
|
} catch (_err) {
|
||||||
|
data = { error: await res.text().catch(() => "") };
|
||||||
|
}
|
||||||
if (!res.ok) throw new Error(data.error || "Request failed");
|
if (!res.ok) throw new Error(data.error || "Request failed");
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -1014,13 +1029,23 @@ APP_HTML = r"""<!doctype html>
|
|||||||
state.pages = await api("/api/pages");
|
state.pages = await api("/api/pages");
|
||||||
renderPages();
|
renderPages();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
pagesBox.innerHTML = `<div class="queue-item"><span>${html(err.message)}</span></div>`;
|
if (state.pages.length) {
|
||||||
|
renderPages();
|
||||||
|
saveMessage.textContent = `Could not refresh file list: ${err.message}`;
|
||||||
|
} else {
|
||||||
|
pagesBox.innerHTML = `<div class="queue-item"><span>${html(err.message)}</span></div>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshBuild() {
|
async function refreshBuild() {
|
||||||
state.build = await api("/api/build");
|
try {
|
||||||
renderStatus();
|
state.build = await api("/api/build");
|
||||||
|
renderStatus();
|
||||||
|
} catch (err) {
|
||||||
|
statusBox.className = "status fail";
|
||||||
|
statusBox.textContent = `Could not refresh build status: ${err.message}`;
|
||||||
|
}
|
||||||
await refreshPages();
|
await refreshPages();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1323,7 +1348,10 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.wfile.write(body)
|
self.wfile.write(body)
|
||||||
return
|
return
|
||||||
if parsed.path == "/api/pages":
|
if parsed.path == "/api/pages":
|
||||||
self.send_json(list_pages())
|
try:
|
||||||
|
self.send_json(list_pages())
|
||||||
|
except Exception as exc:
|
||||||
|
self.send_json({"error": str(exc)}, HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||||
return
|
return
|
||||||
if parsed.path == "/api/page":
|
if parsed.path == "/api/page":
|
||||||
query = parse_qs(parsed.query)
|
query = parse_qs(parsed.query)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
See the categories: @@html:<a href="../home/categories.html">Categories</a>@@
|
See the categories: @@html:<a href="../home/categories.html">Categories</a>@@
|
||||||
|
|
||||||
* Posts:
|
* Posts:
|
||||||
- [[file:career/career-list.org][Career List]] @@html:<span class="post-date">07-05-2026 16:28</span>@@
|
- [[file:career/career-list.org][Career List]] @@html:<span class="post-date">07-05-2026 16:31</span>@@
|
||||||
- [[file:posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]] @@html:<span class="post-date">14-04-2026 16:36</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/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>@@
|
- [[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>@@
|
||||||
|
|||||||
@@ -172,6 +172,23 @@ class PageRenderingTests(AuthoringServerTestCase):
|
|||||||
|
|
||||||
self.assertEqual(set(paths), {"blogs/keep.org", "home/notes.org", "lima/index.md"})
|
self.assertEqual(set(paths), {"blogs/keep.org", "home/notes.org", "lima/index.md"})
|
||||||
|
|
||||||
|
def test_list_pages_skips_files_that_fail_to_read(self):
|
||||||
|
good = self.blogs / "keep.org"
|
||||||
|
bad = self.blogs / "bad.org"
|
||||||
|
good.write_text("#+TITLE: Keep\n#+DATE: <2026-05-07 Thu 12:00>\n", encoding="utf-8")
|
||||||
|
bad.write_text("#+TITLE: Bad\n", encoding="utf-8")
|
||||||
|
original_read_page = server.read_page
|
||||||
|
|
||||||
|
def read_page(path):
|
||||||
|
if path == bad:
|
||||||
|
raise OSError("file disappeared")
|
||||||
|
return original_read_page(path)
|
||||||
|
|
||||||
|
with mock.patch.object(server, "read_page", side_effect=read_page):
|
||||||
|
paths = [page["path"] for page in server.list_pages()]
|
||||||
|
|
||||||
|
self.assertEqual(paths, ["blogs/keep.org"])
|
||||||
|
|
||||||
def test_relative_asset_path_is_calculated_from_lima_page_directory(self):
|
def test_relative_asset_path_is_calculated_from_lima_page_directory(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
server.relative_asset_path("lima/family/update.md", "assets/images/hzone/pic.png"),
|
server.relative_asset_path("lima/family/update.md", "assets/images/hzone/pic.png"),
|
||||||
|
|||||||
Reference in New Issue
Block a user