Mylloon
9dfcc1101d
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
feat: Basic support for new `/cours` endpoint (not ready for release yet), see commit description for more - Basic /cours support - Fix LaTeX support (see #47 / cours+blog) - Better detection of when there is LaTeX in document - Don't shuffle markdown and LaTeX processing (thanks to comrak) - Macros on release - Local image support (cours+blog) - PDF support - Support of markdown files integration in other markdown files - Very basic exclusion support in toc (need a lot of improvement!!) - Update multiple dependencies (actix-web, ramhorns, comrak, reqwest, hljs) - Reformat some code - ToC in /cours support (very basic, works via building it in rust and processing it in js) - Remove very old assets (font + jspdf) - Hide navbar when printing the website - New tag in index page - Fix OCaml support for HLJS + add "pseudocode" derived from Julia Reviewed-on: #44 Co-authored-by: Mylloon <kennel.anri@tutanota.com> Co-committed-by: Mylloon <kennel.anri@tutanota.com>
167 lines
4.5 KiB
JavaScript
167 lines
4.5 KiB
JavaScript
/**
|
|
* 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<ChildNode>} options Options we have, init with list root
|
|
* @returns
|
|
*/
|
|
const deepestNodeOpened = (path, options) => {
|
|
// 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 svgDarkTheme = () => {
|
|
for (const item of document.getElementsByTagName("img")) {
|
|
if (!item.src.startsWith("data:image/svg+xml;base64,")) {
|
|
// Exclude image who aren't SVG and base64 encoded
|
|
break;
|
|
}
|
|
|
|
/** Convert to grayscale */
|
|
const colorToGrayscale = (color) => {
|
|
return 0.3 * color.r + 0.59 * color.g + 0.11 * color.b;
|
|
};
|
|
|
|
/** Extract color using canvas2d */
|
|
const extractColors = (image) => {
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = image.width;
|
|
canvas.height = image.height;
|
|
const ctx = canvas.getContext("2d");
|
|
ctx.drawImage(image, 0, 0);
|
|
const imageData = ctx.getImageData(
|
|
0,
|
|
0,
|
|
Math.max(1, canvas.width),
|
|
Math.max(1, canvas.height)
|
|
);
|
|
const pixelData = imageData.data;
|
|
|
|
const colors = [];
|
|
for (let i = 0; i < pixelData.length; i += 4) {
|
|
if (pixelData[i + 3] > 0) {
|
|
colors.push({
|
|
r: pixelData[i],
|
|
g: pixelData[i + 1],
|
|
b: pixelData[i + 2],
|
|
});
|
|
}
|
|
}
|
|
|
|
return colors;
|
|
};
|
|
|
|
// Extract colors
|
|
const colors = extractColors(item);
|
|
|
|
// Calculate the average grayscale value
|
|
const grayscaleValues = colors.map(colorToGrayscale);
|
|
const totalGrayscale = grayscaleValues.reduce((acc, val) => acc + val, 0);
|
|
const averageGrayscale = totalGrayscale / grayscaleValues.length;
|
|
|
|
if (averageGrayscale < 128) {
|
|
item.style = "filter: invert(1);";
|
|
}
|
|
}
|
|
};
|
|
|
|
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 last_openeded = deepestNodeOpened(
|
|
path.split("/"),
|
|
fileTreeElement.querySelector("ul").childNodes
|
|
);
|
|
|
|
uncollapse(last_openeded);
|
|
}
|
|
|
|
// Fix SVG images in dark mode
|
|
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
svgDarkTheme();
|
|
}
|
|
});
|