217 lines
6.3 KiB
JavaScript
217 lines
6.3 KiB
JavaScript
;(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 = !entry.hasChildren
|
|
if (isLeaf) chevron.classList.add("is-leaf")
|
|
|
|
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()
|
|
})()
|