mylloon.fr/static/js/cours.js
2024-12-15 16:50:56 +01:00

219 lines
5.9 KiB
JavaScript

/**
* Clean URLs from anchors
* @param {string} url Link URL
* @returns Link URL cleaned
*/
const cleanURL = (url) => url.split("#")[0];
/**
* Capitalize a text
* @param {string} text Input text
* @returns Capitalize text
*/
const capitalize = (text) =>
text.length === 0 ? text : text[0].toUpperCase() + text.substring(1);
/**
* 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 fragment = document.createDocumentFragment();
const ul = document.createElement("ul");
fragment.appendChild(ul);
data.forEach((item) => {
const li = document.createElement("li");
li.classList.add(item.is_dir ? "directory" : "file");
if (item.is_dir) {
// Directory
li.textContent = capitalize(item.name);
li.classList.add("collapsed");
// Toggle collapsing on click
li.addEventListener(
"click",
(e) => {
if (e.target === li) {
li.classList.toggle("collapsed");
}
},
{ passive: true }
);
} else {
// File
const url = cleanURL(window.location.href).split("?")[0];
const a = document.createElement("a");
a.text = capitalize(
item.name.endsWith(".md") ? item.name.slice(0, -3) : item.name
);
a.href = `${url}?q=${location}${item.name}`;
li.appendChild(a);
}
ul.appendChild(li);
if (item.children?.length) {
buildFileTree(
li,
item.children,
item.is_dir ? `${location}${item.name}/` : location
);
}
});
parent.appendChild(fragment);
};
/**
* 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
* @returns The deepest node
*/
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]).toLowerCase() ===
options[i].firstChild.nodeValue.toLowerCase()
) {
if (path.length === 1) {
// We found it
return options[i];
}
// Continue the search
return deepestNodeOpened(
path.slice(1),
options[i].querySelector("ul").childNodes
);
}
}
};
/**
* 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
*/
const searchFiles = (query, parent, currentFile) => {
// 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;
}
for (const item of children) {
const matches = item.innerText.toLowerCase().includes(normalizedQuery);
if (matches) {
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 = cleanURL(window.location.href).split("?");
const fullpath = infoURL.length > 1 ? infoURL[1].substring(2) : "index.md";
const path = fullpath.substring(0, fullpath.lastIndexOf("/"));
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) => {
const elementToCompare = decodeURI(
el.firstChild.search
? el.firstChild.search.substring(3).split("/").at(-1)
: el.firstChild.data
);
if (elementToCompare === 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();
}
});
});