;(function () { // This script owns the .tree-scroll child of the aside, not the aside itself. var container = document.querySelector("aside.tree-sidebar .tree-scroll") if (!container) return function joinPath(parent, name) { if (parent === "/" || parent === "") return "/" + name return parent.replace(/\/+$/, "") + "/" + name } function encodeSegments(p) { return p.replace(/^\/+/, "").split("/").map(encodeURIComponent).join("/") } function folderHref(p) { if (p === "/" || p === "") return "/" return "/" + encodeSegments(p) + "/" } function fetchFolder(path, expandTo) { var url = folderHref(path) + "?tree=1&foldersOnly=1" if (expandTo) url += "&expandTo=" + encodeURIComponent(expandTo) return fetch(url, { credentials: "same-origin" }).then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status) return r.json() }) } // Current page as a canonical, DECODED wiki path ("/" for root, no trailing // slash otherwise). pathname is percent-encoded; the tree's entry names (and // thus fullPath) are decoded, so we must decode here too or paths containing // spaces / non-ASCII never match a row and the chain never expands. var activePath = (function () { var p = window.location.pathname.replace(/\/+$/, "") if (p === "") return "/" try { return decodeURIComponent(p) } catch (e) { return p } })() var activeRow = null function disabledRow(text) { var row = document.createElement("div") row.className = "tree-row is-disabled" row.textContent = text return row } // renderInto fills containerEl with rows for `entries` (children of // parentPath). Folders carrying a `children` array (the pre-expanded // ancestor chain from ?expandTo) are opened immediately and recurse. function renderInto(containerEl, parentPath, entries) { entries.forEach(function (entry) { var built = buildRow(parentPath, entry) containerEl.appendChild(built.row) if (built.preExpand) built.open() }) } function buildRow(parentPath, entry) { var fullPath = joinPath(parentPath, entry.name) var row = document.createElement("div") row.className = "tree-row" var chevron = document.createElement("span") chevron.className = "tree-chevron" chevron.textContent = "▸" row.appendChild(chevron) var link = document.createElement("a") link.className = "tree-name tree-folder" link.textContent = entry.name link.href = folderHref(fullPath) row.appendChild(link) if (fullPath === activePath) { row.classList.add("is-active") activeRow = row } var preChildren = entry.children ? entry.children : null var childrenEl = null var loaded = false var isOpen = false var isLeaf = false function ensureChildrenEl() { if (!childrenEl) { childrenEl = document.createElement("div") childrenEl.className = "tree-children" } return childrenEl } function markLeaf() { isLeaf = true isOpen = false chevron.textContent = "▸" chevron.classList.add("is-leaf") if (childrenEl && childrenEl.parentNode) { childrenEl.parentNode.removeChild(childrenEl) } } function loadChildren() { var el = ensureChildrenEl() el.textContent = "" el.appendChild(disabledRow("…")) fetchFolder(fullPath) .then(function (resp) { el.textContent = "" loaded = true if (!resp.entries || resp.entries.length === 0) { markLeaf() return } renderInto(el, fullPath, resp.entries) }) .catch(function () { el.textContent = "" var err = document.createElement("div") err.className = "tree-row" err.textContent = "(failed — tap to retry)" err.addEventListener("click", function (e) { e.stopPropagation() loadChildren() }) el.appendChild(err) }) } function open() { if (isOpen || isLeaf) return var el = ensureChildrenEl() row.parentNode.insertBefore(el, row.nextSibling) chevron.textContent = "▾" // ▾ isOpen = true if (!loaded) { if (preChildren) { renderInto(el, fullPath, preChildren) loaded = true } else { loadChildren() } } } function close() { if (!isOpen) return if (childrenEl && childrenEl.parentNode) { childrenEl.parentNode.removeChild(childrenEl) } chevron.textContent = "▸" isOpen = false } // Clicking the name link navigates (folder) or opens the file; clicking // anywhere else on the row toggles expansion for folders. row.addEventListener("click", function (e) { if (e.target.closest("a.tree-name")) return if (isOpen) close() else open() }) return { row: row, open: open, preExpand: !!preChildren } } function render() { container.textContent = "" var listing = document.createElement("div") container.appendChild(listing) listing.appendChild(disabledRow("…")) fetchFolder("/", activePath === "/" ? "" : activePath) .then(function (resp) { listing.textContent = "" renderInto(listing, "/", resp.entries) if (activeRow) { try { activeRow.scrollIntoView({ block: "nearest" }) } catch (e) {} } }) .catch(function () { listing.textContent = "" var err = document.createElement("div") err.className = "tree-row" err.textContent = "(failed — tap to retry)" err.addEventListener("click", function () { render() }) listing.appendChild(err) }) } function setupMenuItem() { var specials = document.querySelector(".actions-special") if (!specials) return var item = document.createElement("button") item.type = "button" item.className = "btn btn-block" item.title = "Folders" item.textContent = "FOLDERS" item.addEventListener("click", function () { if (typeof openOverlay === "function") openOverlay(container) }) specials.appendChild(item) } render() setupMenuItem() })()