diff --git a/l4d2web/static/css/components.css b/l4d2web/static/css/components.css
index 625e278..e8e4b3f 100644
--- a/l4d2web/static/css/components.css
+++ b/l4d2web/static/css/components.css
@@ -517,21 +517,29 @@ button.danger-outline:hover {
floating Uploads panel.
============================================================ */
-.files-manager {
- display: grid;
- gap: var(--space-m);
+/* The display: flex / grid declarations on the elements below have the
+ same specificity as the UA's `[hidden]{display:none}` rule and come
+ later in the cascade, so without this they'd win and elements would
+ stay visible after JS sets `el.hidden = true`. Targeted rather than
+ a global `[hidden]!important` so we don't fight unknown UA defaults. */
+.files-uploads[hidden],
+.files-editor-binary[hidden],
+.files-editor-text[hidden],
+.files-editor-replace-idle[hidden],
+.files-editor-replace-queued[hidden],
+.files-editor-rename-hint[hidden],
+.files-uploads-clear[hidden] {
+ display: none !important;
}
-.files-manager-toolbar {
- display: flex;
- align-items: center;
- gap: var(--space-m);
- flex-wrap: wrap;
+.files-manager {
+ display: grid;
+ gap: var(--space-s);
}
.files-manager-hint {
- flex: 1;
font-size: 0.85em;
+ margin: 0;
}
.files-tree-root {
@@ -551,6 +559,16 @@ button.danger-outline:hover {
background: color-mix(in srgb, var(--color-success) 10%, transparent);
}
+.files-row-root > .files-row-root-label {
+ font-style: italic;
+ color: var(--color-muted);
+}
+
+.files-root-children {
+ flex-basis: 100%;
+ margin-top: var(--space-xs);
+}
+
.files-empty {
margin: var(--space-s) var(--space-xs);
}
@@ -613,13 +631,6 @@ button.danger-outline:hover {
border-color: var(--color-danger);
}
-.files-root-actions {
- margin-left: auto;
- display: inline-flex;
- align-items: center;
- gap: var(--space-xs);
-}
-
.files-row.is-drag-source {
opacity: 0.5;
}
diff --git a/l4d2web/static/js/file-tree.js b/l4d2web/static/js/file-tree.js
index 5cfd006..f9ade86 100644
--- a/l4d2web/static/js/file-tree.js
+++ b/l4d2web/static/js/file-tree.js
@@ -4,13 +4,18 @@
// carries `data-files-url`. First expand fires a fetch and innerHTMLs the
// returned partial into the next `.file-tree-children`; subsequent clicks
// just toggle visibility — no re-fetch.
+//
+// Children-div lookup goes through the row's
rather than the button's
+// nextElementSibling so the files-overlay variant — where a per-row action
+// column sits between the toggle button and the children div — works too.
(function () {
document.addEventListener("click", function (event) {
const button = event.target.closest(".file-tree-toggle");
if (!button) return;
- const children = button.nextElementSibling;
- if (!children || !children.classList.contains("file-tree-children")) return;
+ const row = button.closest(".file-tree-row");
+ const children = row ? row.querySelector(":scope > .file-tree-children") : null;
+ if (!children) return;
const wasExpanded = button.getAttribute("aria-expanded") === "true";
button.setAttribute("aria-expanded", wasExpanded ? "false" : "true");
diff --git a/l4d2web/static/js/files-overlay.js b/l4d2web/static/js/files-overlay.js
index d3c500a..0654d4f 100644
--- a/l4d2web/static/js/files-overlay.js
+++ b/l4d2web/static/js/files-overlay.js
@@ -136,20 +136,22 @@
}
if (!path) {
- // Overlay root — replace the tree-root container's children.
- const empty = treeRoot.querySelector(".files-empty");
+ // Overlay root — swap into the synthetic root row's children div.
+ const target = manager.querySelector(".files-root-children");
+ if (!target) return;
+ const empty = target.querySelector(".files-empty");
if (empty) empty.remove();
- const existingUl = treeRoot.querySelector(":scope > ul.file-tree");
+ const existingUl = target.querySelector(":scope > ul.file-tree");
if (existingUl) existingUl.remove();
- treeRoot.insertAdjacentHTML("beforeend", html);
+ target.insertAdjacentHTML("beforeend", html);
// If the new content is also empty, restore the placeholder.
- const newUl = treeRoot.querySelector(":scope > ul.file-tree");
+ const newUl = target.querySelector(":scope > ul.file-tree");
if (newUl && newUl.children.length === 0) {
newUl.remove();
const p = document.createElement("p");
p.className = "muted files-empty";
- p.textContent = 'Empty — drop files here, or click "+ new file" above.';
- treeRoot.appendChild(p);
+ p.textContent = 'Empty — drop files here, or click "+ new file" on this row.';
+ target.appendChild(p);
}
return;
}
@@ -602,21 +604,22 @@
}
});
- input.addEventListener(
- "keydown",
- (event) => {
- if (event.key === "Enter") {
- event.preventDefault();
- fresh.click();
- }
- },
- { once: true }
- );
-
newFolderDialog.showModal();
setTimeout(() => input.focus(), 0);
}
+ // Enter on the new-folder input submits — bound once at module init so
+ // it survives multiple openings of the dialog. (A previous version used
+ // `{once: true}` inside openNewFolder, which was consumed by the first
+ // letter the user typed and never saw Enter.)
+ newFolderDialog
+ .querySelector(".files-new-folder-name")
+ .addEventListener("keydown", (event) => {
+ if (event.key !== "Enter") return;
+ event.preventDefault();
+ newFolderDialog.querySelector(".files-new-folder-create")?.click();
+ });
+
// ---------- upload queue + progress panel -------------------------------
const uploadQueue = [];
diff --git a/l4d2web/templates/overlay_detail.html b/l4d2web/templates/overlay_detail.html
index e927e68..d2d9add 100644
--- a/l4d2web/templates/overlay_detail.html
+++ b/l4d2web/templates/overlay_detail.html
@@ -65,27 +65,32 @@
-
- Drop files / folders onto a folder row to upload, drag rows to move
-
-
-
-
-
-
-
- {% if not file_tree_root_entries %}
-
Empty — drop files here, or click "+ new file" above.
- {% else %}
- {% set entries = file_tree_root_entries %}
- {% set truncated = file_tree_truncated %}
- {% set truncated_count = file_tree_truncated_count %}
- {% set files_base_url = "/overlays/" ~ overlay.id %}
- {% set download_supported = True %}
- {% set files_overlay = True %}
- {% include "_overlay_file_tree.html" %}
- {% endif %}
-
+
Drop files or folders onto a folder row to upload. Drag rows inside the tree to move them.
+
+ -
+ (overlay root)
+
+
+
+
+
+
+ {% if not file_tree_root_entries %}
+
Empty — drop files here, or click "+ new file" on this row.
+ {% else %}
+ {% set entries = file_tree_root_entries %}
+ {% set truncated = file_tree_truncated %}
+ {% set truncated_count = file_tree_truncated_count %}
+ {% set files_base_url = "/overlays/" ~ overlay.id %}
+ {% set download_supported = True %}
+ {% set files_overlay = True %}
+ {% include "_overlay_file_tree.html" %}
+ {% endif %}
+
+
+
{% else %}
{% if not file_tree_root_entries %}