diff --git a/assets/images/blogs/2026-05-08-time-to-100m-pic.webp b/assets/images/blogs/2026-05-08-time-to-100m-pic.webp
old mode 100644
new mode 100755
diff --git a/authoring_server.py b/authoring_server.py
index c184ae8..56d6282 100755
--- a/authoring_server.py
+++ b/authoring_server.py
@@ -704,7 +704,7 @@ APP_HTML = r"""
button { border: 1px solid var(--line); background: #fff; color: var(--ink); min-height: 38px; padding: 0 12px; border-radius: 6px; cursor: pointer; }
button.primary { background: var(--accent); color: white; border-color: var(--accent); }
button.icon { min-width: 38px; padding: 0 10px; font-weight: 700; }
- .app { min-height: 100vh; display: grid; grid-template-columns: minmax(260px, 360px) 1fr; }
+ .app { min-height: 100vh; display: grid; grid-template-columns: minmax(300px, 420px) 1fr; }
aside { border-right: 1px solid var(--line); background: #eee8dc; padding: 18px; overflow: auto; max-height: 100vh; }
main { padding: 20px clamp(18px, 3vw, 42px); overflow: auto; max-height: 100vh; }
.topbar { display: flex; align-items: center; gap: 10px; justify-content: space-between; margin-bottom: 16px; }
@@ -716,10 +716,23 @@ APP_HTML = r"""
.status.fail { border-color: #a12a2a; }
.quick-link { display: inline-block; color: var(--accent); font-size: 13px; margin: -6px 0 14px; }
.filters { display: grid; gap: 8px; margin-bottom: 14px; }
- .page-list { display: grid; gap: 7px; }
- .page-item { width: 100%; text-align: left; min-height: auto; padding: 9px 10px; background: var(--panel); }
+ .page-list { display: grid; gap: 3px; }
+ .tree-dir { margin: 1px 0; }
+ .tree-dir summary { list-style: none; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px; align-items: center; min-height: 34px; padding: 6px 8px; border-radius: 6px; cursor: pointer; color: var(--ink); }
+ .tree-dir summary::-webkit-details-marker { display: none; }
+ .tree-dir summary:hover, .page-item:hover { background: rgba(255, 253, 248, 0.78); }
+ .tree-dir summary::before { content: ">"; display: inline-block; width: 14px; color: var(--muted); }
+ .tree-dir[open] > summary::before { content: "v"; }
+ .tree-label { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
+ .tree-label strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
+ .tree-label span { color: var(--muted); font-size: 12px; white-space: nowrap; }
+ .tree-children { display: grid; gap: 2px; margin-left: 15px; padding-left: 10px; border-left: 1px solid var(--line); }
+ .tree-add { min-height: 28px; padding: 0 8px; font-size: 12px; color: var(--accent); background: transparent; }
+ .page-item { width: 100%; text-align: left; min-height: auto; padding: 8px 10px; background: transparent; }
+ .page-item.active { border-color: var(--accent); background: var(--panel); }
.page-item strong { display: block; font-size: 14px; margin-bottom: 2px; overflow-wrap: anywhere; }
.page-item span { display: block; color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
+ .page-item .filename { color: var(--ink); }
form { display: grid; gap: 14px; }
.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.toolbar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; border: 1px solid var(--line); background: var(--panel); border-radius: 6px; padding: 7px; }
@@ -924,21 +937,113 @@ APP_HTML = r"""
const matches = state.pages
.filter((page) => (!type || page.pageType === type))
.filter((page) => `${page.title} ${page.path} ${page.tags.join(" ")}`.toLowerCase().includes(query))
- .slice(0, 120);
+ .sort((a, b) => a.path.localeCompare(b.path));
if (!matches.length) {
pagesBox.innerHTML = `
No files matched.
`;
+ renderStatus();
+ return;
}
- matches.forEach((page) => {
- const btn = document.createElement("button");
- btn.type = "button";
- btn.className = "page-item";
- btn.innerHTML = `${html(page.title)}${html(page.path)}${page.tags.map((tag) => `${html(tag)}`).join("")}`;
- btn.onclick = () => loadPage(page.path);
- pagesBox.appendChild(btn);
- });
+ const tree = buildPageTree(matches);
+ renderTree(tree, pagesBox, query !== "");
renderStatus();
}
+ function buildPageTree(pages) {
+ const root = { dirs: new Map(), pages: [], path: "" };
+ pages.forEach((page) => {
+ const parts = page.path.split("/");
+ const filename = parts.pop();
+ let node = root;
+ let dirPath = "";
+ parts.forEach((part) => {
+ dirPath = dirPath ? `${dirPath}/${part}` : part;
+ if (!node.dirs.has(part)) node.dirs.set(part, { dirs: new Map(), pages: [], path: dirPath });
+ node = node.dirs.get(part);
+ });
+ node.pages.push({ ...page, filename });
+ });
+ return root;
+ }
+
+ function renderTree(node, container, expandAll = false) {
+ [...node.dirs.entries()]
+ .sort(([a], [b]) => a.localeCompare(b))
+ .forEach(([name, child]) => container.appendChild(renderDirectory(name, child, expandAll)));
+ node.pages
+ .sort((a, b) => a.filename.localeCompare(b.filename))
+ .forEach((page) => container.appendChild(renderPageButton(page)));
+ }
+
+ function renderDirectory(name, node, expandAll) {
+ const details = document.createElement("details");
+ details.className = "tree-dir";
+ details.open = expandAll || node.path.split("/").length <= 2 || containsCurrentPath(node);
+
+ const summary = document.createElement("summary");
+ const label = document.createElement("span");
+ label.className = "tree-label";
+ label.innerHTML = `${html(name)}${countPages(node)} files`;
+ const add = document.createElement("button");
+ add.type = "button";
+ add.className = "tree-add";
+ add.textContent = "New";
+ add.title = `New file in ${node.path}`;
+ add.onclick = (event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ newPageInDirectory(node.path);
+ };
+ summary.append(label, add);
+
+ const children = document.createElement("div");
+ children.className = "tree-children";
+ renderTree(node, children, expandAll);
+ details.append(summary, children);
+ return details;
+ }
+
+ function renderPageButton(page) {
+ const btn = document.createElement("button");
+ btn.type = "button";
+ btn.className = `page-item${page.path === state.currentPath ? " active" : ""}`;
+ btn.innerHTML = `${html(page.title)}${html(page.filename || page.path)}${html(page.path)}${page.tags.length ? `${page.tags.map((tag) => `${html(tag)}`).join("")}` : ""}`;
+ btn.onclick = () => loadPage(page.path);
+ return btn;
+ }
+
+ function countPages(node) {
+ let count = node.pages.length;
+ node.dirs.forEach((child) => { count += countPages(child); });
+ return count;
+ }
+
+ function containsCurrentPath(node) {
+ return Boolean(state.currentPath && (state.currentPath.startsWith(`${node.path}/`) || node.pages.some((page) => page.path === state.currentPath)));
+ }
+
+ function newPageInDirectory(dirPath) {
+ const pageType = pageTypeForDirectory(dirPath);
+ setForm({ pageType, date: currentOrgDate(), comments: true });
+ editor.section.value = pageType === "post" ? postSectionForDirectory(dirPath) : "";
+ editor.targetPath.value = `${dirPath}/`;
+ state.pathManual = true;
+ updateEditorMode();
+ saveMessage.textContent = `New file will be created in ${dirPath}.`;
+ editor.title.focus();
+ }
+
+ function pageTypeForDirectory(dirPath) {
+ if (dirPath === "lima" || dirPath.startsWith("lima/")) return "lima";
+ if (dirPath === "posts" || dirPath.startsWith("posts/")) return "post";
+ if (dirPath === "blogs" || dirPath.startsWith("blogs/")) return "blog";
+ return "page";
+ }
+
+ function postSectionForDirectory(dirPath) {
+ if (!dirPath.startsWith("posts/")) return "";
+ return dirPath.slice("posts/".length);
+ }
+
function html(value) {
return String(value).replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[char]));
}
@@ -1034,6 +1139,7 @@ APP_HTML = r"""
updateEditorMode();
updateSuggestedPath();
updateMarkdownPreview();
+ if (state.pages.length) renderPages();
saveMessage.textContent = "";
}
diff --git a/posts/posts-list.org b/posts/posts-list.org
index c2a545b..1456f82 100755
--- a/posts/posts-list.org
+++ b/posts/posts-list.org
@@ -4,7 +4,7 @@
See the categories: @@html:Categories@@
* Posts:
-- [[file:career/career-list.org][Career List]] @@html:08-05-2026 12:09@@
+- [[file:career/career-list.org][Career List]] @@html:08-05-2026 12:27@@
- [[file:posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]] @@html:14-04-2026 16:36@@
- [[file:career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:11-03-2026 17:18@@ @@html:learning@@ @@html:notes@@
- [[file:career/javascript.org][Understands the Javascript language]] @@html:11-03-2026 16:52@@ @@html:learning@@ @@html:notes@@