mylloon.fr/static/js/markdown.js

164 lines
4.2 KiB
JavaScript

const Mode = {
Light: 1,
Dark: 2,
};
/**
* Convert to grayscale
* @param {{r: number, g: number, b: number}} color
* @returns Number between 0 and 255
*/
const colorToGrayscale = (color) => {
return 0.3 * color.r + 0.59 * color.g + 0.11 * color.b;
};
/**
* Extract colors from an image or SVG element using canvas
* @param {HTMLImageElement | SVGSVGElement} element Image or SVG source element
* @returns {Promise<Array>} Colors representing the element
*/
const extractColors = (element) => {
return new Promise((resolve, reject) => {
// Create a canvas with the same dimensions as the element
const canvas = document.createElement("canvas");
let imageSource;
if (element instanceof SVGSVGElement) {
// SVG element handling
canvas.width = element.viewBox.baseVal.width;
canvas.height = element.viewBox.baseVal.height;
const svgString = new XMLSerializer().serializeToString(element);
const svgBlob = new Blob([svgString], { type: "image/svg+xml" });
const svgUrl = URL.createObjectURL(svgBlob);
imageSource = new Image();
imageSource.src = svgUrl;
imageSource.onload = () => {
processImage(imageSource);
URL.revokeObjectURL(svgUrl);
};
imageSource.onerror = (error) => {
URL.revokeObjectURL(svgUrl);
reject(error);
};
} else {
// Regular image handling
canvas.width = element.naturalWidth || element.width;
canvas.height = element.naturalHeight || element.height;
processImage(element);
}
const processImage = (img) => {
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 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],
});
}
}
resolve(colors);
};
});
};
/**
* Change the color theme based on the mode
* @param {Mode} mode
*/
const changeTheme = (mode) => {
// Process SVG elements
for (const svgElement of document.getElementsByTagName("svg")) {
extractColors(svgElement)
.then((colors) => {
applyFilter(mode, svgElement, colors);
})
.catch((error) => {
console.error("Error extracting colors:", error);
});
}
// Process SVG images embedded as base64
for (const item of document.getElementsByTagName("img")) {
if (!item.src.startsWith("data:image/svg+xml;base64,")) {
// Exclude images that aren't SVG and base64 encoded
continue;
}
extractColors(item)
.then((colors) => {
applyFilter(mode, item, colors);
})
.catch((error) => {
console.error("Error extracting colors:", error);
});
}
};
/**
* Apply the filter based on the selected mode
* @param {Mode} mode Current theme
* @param {HTMLImageElement | SVGSVGElement} item Element
* @param {{r: number, g: number, b: number}} colors Array of the colors of the element
* @returns
*/
const applyFilter = (mode, item, colors) => {
// Calculate the average grayscale value
const grayscaleValues = colors.map(colorToGrayscale);
const totalGrayscale = grayscaleValues.reduce((acc, val) => acc + val, 0);
const averageGrayscale = totalGrayscale / grayscaleValues.length;
const treshold = 128;
const style = "filter: ";
const dim = "brightness(0.8) contrast(1.2)";
if (averageGrayscale < treshold && mode === Mode.Dark) {
item.style = style + dim + " invert(1);";
return;
}
if (averageGrayscale > treshold && mode === Mode.Light) {
item.style = style + "invert(1);";
return;
}
if (mode === Mode.Dark) {
item.style = style + `${dim};`;
return;
}
item.style = "";
};
window.addEventListener("load", () => {
// Fix SVG images
changeTheme(
window.matchMedia("(prefers-color-scheme: dark)").matches
? Mode.Dark
: Mode.Light
);
});
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", (event) =>
changeTheme(event.matches ? Mode.Dark : Mode.Light)
);