2024-12-13 20:02:39 +01:00
|
|
|
/**
|
|
|
|
* Clean URLs from anchors
|
|
|
|
* @param {string} url Link URL
|
|
|
|
* @returns Link URL cleaned
|
|
|
|
*/
|
|
|
|
const cleanURL = (url) => url.split("#")[0];
|
|
|
|
|
2024-12-14 10:59:56 +01:00
|
|
|
/**
|
|
|
|
* Capitalize a text
|
|
|
|
* @param {string} text Input text
|
|
|
|
* @returns Capitalize text
|
|
|
|
*/
|
|
|
|
const capitalize = (text) =>
|
|
|
|
text.length === 0 ? text : text[0].toUpperCase() + text.substring(1);
|
|
|
|
|
2024-04-01 18:11:46 +02:00
|
|
|
/**
|
|
|
|
* Build the filetree
|
|
|
|
* @param {HTMLElement} parent Root element of the filetree
|
2024-12-14 10:59:56 +01:00
|
|
|
* @param {{name: string, is_dir: boolean, children: any[]}[]} data FileNode
|
2024-04-01 18:11:46 +02:00
|
|
|
* @param {string} location Current location, used for links creation
|
|
|
|
*/
|
|
|
|
const buildFileTree = (parent, data, location) => {
|
2024-12-12 16:25:43 +01:00
|
|
|
const fragment = document.createDocumentFragment();
|
2024-04-01 18:11:46 +02:00
|
|
|
const ul = document.createElement("ul");
|
2024-12-12 16:25:43 +01:00
|
|
|
fragment.appendChild(ul);
|
|
|
|
|
2024-04-01 18:11:46 +02:00
|
|
|
data.forEach((item) => {
|
|
|
|
const li = document.createElement("li");
|
|
|
|
li.classList.add(item.is_dir ? "directory" : "file");
|
|
|
|
|
|
|
|
if (item.is_dir) {
|
|
|
|
// Directory
|
2024-12-14 11:00:38 +01:00
|
|
|
li.textContent = capitalize(item.name);
|
2024-04-01 18:11:46 +02:00
|
|
|
li.classList.add("collapsed");
|
|
|
|
|
|
|
|
// Toggle collapsing on click
|
2024-12-12 16:41:28 +01:00
|
|
|
li.addEventListener(
|
|
|
|
"click",
|
|
|
|
(e) => {
|
|
|
|
if (e.target === li) {
|
|
|
|
li.classList.toggle("collapsed");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ passive: true }
|
|
|
|
);
|
2024-04-01 18:11:46 +02:00
|
|
|
} else {
|
|
|
|
// File
|
2024-12-13 20:02:39 +01:00
|
|
|
const url = cleanURL(window.location.href).split("?")[0];
|
2024-04-01 18:11:46 +02:00
|
|
|
const a = document.createElement("a");
|
2024-12-14 10:59:56 +01:00
|
|
|
a.text = capitalize(
|
|
|
|
item.name.endsWith(".md") ? item.name.slice(0, -3) : item.name
|
|
|
|
);
|
2024-04-01 18:11:46 +02:00
|
|
|
a.href = `${url}?q=${location}${item.name}`;
|
|
|
|
li.appendChild(a);
|
|
|
|
}
|
|
|
|
|
|
|
|
ul.appendChild(li);
|
|
|
|
|
2024-12-12 16:25:43 +01:00
|
|
|
if (item.children?.length) {
|
2024-04-01 18:11:46 +02:00
|
|
|
buildFileTree(
|
|
|
|
li,
|
|
|
|
item.children,
|
2024-12-12 16:25:43 +01:00
|
|
|
item.is_dir ? `${location}${item.name}/` : location
|
2024-04-01 18:11:46 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-12-12 16:25:43 +01:00
|
|
|
parent.appendChild(fragment);
|
2024-04-01 18:11:46 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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<ChildNode>} options Options we have, init with list root
|
2024-12-13 20:02:39 +01:00
|
|
|
* @returns The deepest node
|
2024-04-01 18:11:46 +02:00
|
|
|
*/
|
|
|
|
const deepestNodeOpened = (path, options) => {
|
2024-11-28 17:11:40 +01:00
|
|
|
if (path[0] === "") {
|
|
|
|
return options[0].parentNode.parentNode;
|
|
|
|
}
|
|
|
|
|
2024-04-01 18:11:46 +02:00
|
|
|
// Iterate over possible options
|
|
|
|
for (let i = 0; i < options.length; ++i) {
|
|
|
|
// If the directory and the current path match
|
2024-12-15 16:50:56 +01:00
|
|
|
if (
|
|
|
|
decodeURI(path[0]).toLowerCase() ===
|
|
|
|
options[i].firstChild.nodeValue.toLowerCase()
|
|
|
|
) {
|
2024-04-01 18:11:46 +02:00
|
|
|
if (path.length === 1) {
|
|
|
|
// We found it
|
|
|
|
return options[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Continue the search
|
|
|
|
return deepestNodeOpened(
|
|
|
|
path.slice(1),
|
|
|
|
options[i].querySelector("ul").childNodes
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-12-13 20:02:39 +01:00
|
|
|
/**
|
|
|
|
* Search in the filetree, when nothing is aske, returns to initial state
|
|
|
|
* @param {string} query Query
|
|
|
|
* @param {HTMLElement} parent Filetree
|
|
|
|
* @param {HTMLLIElement} currentFile Current file opened
|
|
|
|
*/
|
2024-11-27 20:49:45 +01:00
|
|
|
const searchFiles = (query, parent, currentFile) => {
|
2024-12-12 17:14:39 +01:00
|
|
|
// Prevent blocking the main thread
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
const children = parent.querySelectorAll("li");
|
|
|
|
|
|
|
|
const normalizedQuery = query.toLowerCase().trim();
|
|
|
|
if (normalizedQuery === "") {
|
|
|
|
children.forEach((item) => {
|
|
|
|
item.style.display = "";
|
|
|
|
if (
|
|
|
|
item.classList.contains("directory") &&
|
|
|
|
!item.classList.contains("collapsed")
|
|
|
|
) {
|
|
|
|
item.classList.add("collapsed");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
uncollapse(currentFile);
|
|
|
|
return;
|
|
|
|
}
|
2024-11-27 20:49:45 +01:00
|
|
|
|
2024-12-12 17:14:39 +01:00
|
|
|
for (const item of children) {
|
|
|
|
const matches = item.innerText.toLowerCase().includes(normalizedQuery);
|
|
|
|
if (matches) {
|
|
|
|
item.style.display = "";
|
|
|
|
uncollapse(item);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
item.style.display = "none";
|
2024-11-27 20:49:45 +01:00
|
|
|
}
|
2024-12-12 17:14:39 +01:00
|
|
|
});
|
2024-11-27 20:49:45 +01:00
|
|
|
};
|
|
|
|
|
2024-04-01 18:11:46 +02:00
|
|
|
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
|
2024-12-13 20:02:39 +01:00
|
|
|
const infoURL = cleanURL(window.location.href).split("?");
|
2024-12-13 15:16:07 +01:00
|
|
|
const fullpath = infoURL.length > 1 ? infoURL[1].substring(2) : "index.md";
|
|
|
|
const path = fullpath.substring(0, fullpath.lastIndexOf("/"));
|
2024-11-27 18:27:54 +01:00
|
|
|
|
2024-12-13 15:16:07 +01:00
|
|
|
const currentlyOpen = deepestNodeOpened(
|
|
|
|
path.split("/"),
|
|
|
|
fileTreeElement.querySelector("ul").childNodes
|
|
|
|
);
|
|
|
|
uncollapse(currentlyOpen);
|
|
|
|
|
|
|
|
// Bold opened file
|
|
|
|
const openedFile = decodeURI(fullpath.split("/").at(-1));
|
|
|
|
currentlyOpen.querySelector("ul").childNodes.forEach((el) => {
|
2024-12-15 16:50:56 +01:00
|
|
|
const elementToCompare = decodeURI(
|
|
|
|
el.firstChild.search
|
|
|
|
? el.firstChild.search.substring(3).split("/").at(-1)
|
|
|
|
: el.firstChild.data
|
|
|
|
);
|
|
|
|
|
|
|
|
if (elementToCompare === openedFile) {
|
2024-12-13 15:16:07 +01:00
|
|
|
el.style.fontWeight = "bold";
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Search bar hook
|
|
|
|
document.getElementById("searchBar").addEventListener("input", (e) => {
|
|
|
|
searchFiles(e.target.value, fileTreeElement, currentlyOpen);
|
|
|
|
});
|
2024-11-27 20:49:45 +01:00
|
|
|
|
2024-11-27 19:56:37 +01:00
|
|
|
// Responsive menu
|
2024-11-27 20:08:48 +01:00
|
|
|
let menuOpen = false;
|
2024-11-28 00:00:48 +01:00
|
|
|
const button = document.getElementById("menu");
|
2024-11-27 19:56:37 +01:00
|
|
|
const content = document.getElementsByTagName("main")[0];
|
|
|
|
const initialButtonTextContent = button.textContent;
|
2024-11-27 20:08:48 +01:00
|
|
|
const resetPage = () => {
|
2024-11-27 19:56:37 +01:00
|
|
|
menuOpen = !menuOpen;
|
|
|
|
if (menuOpen) {
|
|
|
|
fileTreeElement.style.display = "block";
|
|
|
|
content.style.display = "none";
|
|
|
|
button.textContent = "Fermer le menu";
|
2024-11-28 14:54:04 +01:00
|
|
|
return;
|
2024-11-27 19:56:37 +01:00
|
|
|
}
|
2024-11-28 14:54:04 +01:00
|
|
|
|
|
|
|
fileTreeElement.style.display = "";
|
|
|
|
content.style.display = "";
|
|
|
|
button.textContent = initialButtonTextContent;
|
2024-11-27 20:08:48 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
button.addEventListener("click", resetPage);
|
2024-11-27 19:56:37 +01:00
|
|
|
window.addEventListener("resize", () => {
|
|
|
|
if (menuOpen && window.innerWidth > 640) {
|
2024-11-27 20:08:48 +01:00
|
|
|
resetPage();
|
2024-11-27 19:56:37 +01:00
|
|
|
}
|
|
|
|
});
|
2024-04-01 18:11:46 +02:00
|
|
|
});
|