/** * Build the filetree * @param {HTMLElement} parent Root element of the filetree * @param {{name: string, is_dir: boolean, children: any[]}} data FileNode * @param {string} location Current location, used for links creation */ const buildFileTree = (parent, data, location) => { const ul = document.createElement("ul"); data.forEach((item) => { const li = document.createElement("li"); li.classList.add(item.is_dir ? "directory" : "file"); if (item.is_dir) { // Directory li.textContent = item.name; li.classList.add("collapsed"); // Toggle collapsing on click li.addEventListener("click", function (e) { if (e.target === li) { li.classList.toggle("collapsed"); } }); } else { // File const url = window.location.href.split("?")[0]; const a = document.createElement("a"); a.text = item.name; a.href = `${url}?q=${location}${item.name}`; li.appendChild(a); } ul.appendChild(li); if (item.children && item.children.length > 0) { buildFileTree( li, item.children, item.is_dir ? location + `${item.name}/` : location ); } }); parent.appendChild(ul); }; /** * Uncollapse elements from the deepest element * @param {HTMLLIElement} element Element to uncollapse */ const uncollapse = (element) => { if (element) { element.classList.remove("collapsed"); uncollapse(element.parentElement.closest("li")); } }; /** * Find the deepest opened directory * @param {string[]} path Current path we are looking at, init with fullpath * @param {NodeListOf} options Options we have, init with list root * @returns */ const deepestNodeOpened = (path, options) => { if (path[0] === "") { return options[0].parentNode.parentNode; } // Iterate over possible options for (let i = 0; i < options.length; ++i) { // If the directory and the current path match if (decodeURI(path[0]) === options[i].firstChild.nodeValue) { if (path.length === 1) { // We found it return options[i]; } // Continue the search return deepestNodeOpened( path.slice(1), options[i].querySelector("ul").childNodes ); } } }; const searchFiles = (query, parent, currentFile) => { const children = parent.querySelectorAll("li"); if (query === "") { children.forEach((item) => { item.style.display = ""; if ( item.classList.contains("directory") && !item.classList.contains("collapsed") ) { item.classList.add("collapsed"); } }); uncollapse(currentFile); return; } for (const item of children) { if (item.innerText.toLowerCase().includes(query.toLowerCase())) { item.style.display = ""; uncollapse(item); continue; } item.style.display = "none"; } }; window.addEventListener("load", () => { // Build the filetree const fileTreeElement = document.getElementsByTagName("aside")[0]; const dataElement = fileTreeElement.getElementsByTagName("span")[0]; buildFileTree( fileTreeElement, JSON.parse(dataElement.getAttribute("data-json")).children, "" ); dataElement.remove(); // Open nested openeded directories const infoURL = window.location.href.split("?"); if (infoURL.length > 1) { const fullpath = infoURL[1].substring(2); const path = fullpath.substring(0, fullpath.lastIndexOf("/")); const currentlyOpen = deepestNodeOpened( path.split("/"), fileTreeElement.querySelector("ul").childNodes ); uncollapse(currentlyOpen); // Bold opened file const openedFile = fullpath.split("/").at(-1); currentlyOpen.querySelector("ul").childNodes.forEach((el) => { if (el.innerText === openedFile) { el.style.fontWeight = "bold"; } }); // Search bar hook document.getElementById("searchBar").addEventListener("input", (e) => { searchFiles(e.target.value, fileTreeElement, currentlyOpen); }); } // Responsive menu let menuOpen = false; const button = document.getElementById("menu"); const content = document.getElementsByTagName("main")[0]; const initialButtonTextContent = button.textContent; const resetPage = () => { menuOpen = !menuOpen; if (menuOpen) { fileTreeElement.style.display = "block"; content.style.display = "none"; button.textContent = "Fermer le menu"; return; } fileTreeElement.style.display = ""; content.style.display = ""; button.textContent = initialButtonTextContent; }; button.addEventListener("click", resetPage); window.addEventListener("resize", () => { if (menuOpen && window.innerWidth > 640) { resetPage(); } }); });