cours 16-10-26

This commit is contained in:
bach
2026-10-16 17:02:38 +02:00
commit 25fb3cee7f
6 changed files with 2394 additions and 0 deletions
+366
View File
@@ -0,0 +1,366 @@
/* =====================================================================
script.js — le "moteur" de la page
---------------------------------------------------------------------
Ce fichier fait fonctionner les éditeurs de code interactifs.
Il est volontairement TRÈS commenté : c'est lui-même un exemple
de vrai code JavaScript. Une fois que tu auras lu les chapitres,
reviens le lire : tu devrais tout comprendre !
Ce qu'il fait :
1. Trouver tous les éditeurs (.bac) de la page
2. Quand on clique « Exécuter », lancer le code écrit dedans
3. Intercepter les console.log pour les afficher DANS la page
4. Traduire les erreurs en français pour aider à comprendre
5. Surligner dans le menu le chapitre en cours de lecture
6. Gérer le bouton clair / sombre
===================================================================== */
/* ---------------------------------------------------------------------
"use strict" active le MODE STRICT : JavaScript devient plus sévère
et signale des erreurs qu'il ignorerait sinon (ex : utiliser une
variable jamais déclarée). C'est une bonne habitude.
--------------------------------------------------------------------- */
"use strict";
/* =====================================================================
1. OUTIL : transformer n'importe quelle valeur en texte lisible
=====================================================================
console.log("bonjour") est facile à afficher. Mais comment afficher
un tableau [1, 2, 3] ou un objet { nom: "Ada" } ? Cette fonction
regarde le TYPE de la valeur et fabrique un texte adapté.
Elle renvoie un élément HTML <span> coloré selon le type.
--------------------------------------------------------------------- */
function formaterValeur(valeur, profondeur = 0) {
// typeof renvoie le type de la valeur sous forme de texte :
// "string", "number", "boolean", "undefined", "object", "function"…
const type = typeof valeur;
// Un "texte" (string) : au premier niveau on l'affiche tel quel,
// à l'intérieur d'un tableau/objet on l'entoure de guillemets.
if (type === "string") {
return profondeur === 0 ? valeur : `"${valeur}"`;
}
if (type === "number" || type === "boolean" || type === "bigint") {
return String(valeur); // String(42) → "42"
}
if (valeur === undefined) return "undefined";
if (valeur === null) return "null";
if (type === "function") {
// valeur.name = le nom de la fonction (vide si anonyme)
return `ƒ ${valeur.name || "(anonyme)"}()`;
}
// On évite de descendre trop profond (objets imbriqués à l'infini)
if (profondeur > 3) return "…";
// Un tableau : Array.isArray est LA façon fiable de le reconnaître,
// car typeof [] renvoie "object" (une bizarrerie historique).
if (Array.isArray(valeur)) {
// .map transforme chaque élément, .join colle le tout avec ", "
const morceaux = valeur.map(v => formaterValeur(v, profondeur + 1));
return `[${morceaux.join(", ")}]`;
}
// Un élément HTML (ex : document.querySelector("h1"))
if (valeur instanceof Element) {
const classes = valeur.className ? "." + String(valeur.className).split(" ").join(".") : "";
return `<${valeur.tagName.toLowerCase()}${valeur.id ? "#" + valeur.id : ""}${classes}>`;
}
// Une Promise (chapitre asynchrone)
if (valeur instanceof Promise) return "Promise { … }";
// Une erreur
if (valeur instanceof Error) return `${valeur.name}: ${valeur.message}`;
// Un objet "classique" : { cle: valeur, ... }
if (type === "object") {
// Le nom du "constructeur" : utile pour les objets créés avec une classe
const nomClasse = valeur.constructor && valeur.constructor.name !== "Object"
? valeur.constructor.name + " "
: "";
// Object.entries({a:1, b:2}) → [["a",1], ["b",2]]
const paires = Object.entries(valeur).map(
([cle, v]) => `${cle}: ${formaterValeur(v, profondeur + 1)}`
);
return `${nomClasse}{ ${paires.join(", ")} }`;
}
return String(valeur);
}
/* Renvoie le nom d'une classe CSS selon le type, pour colorer la sortie */
function classeCouleur(valeur) {
if (typeof valeur === "number") return "t-nombre";
if (typeof valeur === "string") return "t-texte";
if (typeof valeur === "boolean") return "t-booleen";
if (valeur === null || valeur === undefined) return "t-vide";
return "";
}
/* =====================================================================
2. OUTIL : traduire les erreurs JavaScript en français
=====================================================================
Quand le code plante, JavaScript lance une "erreur" avec un nom
(ReferenceError, TypeError…) et un message en anglais.
On ajoute une petite explication pour débutant.
--------------------------------------------------------------------- */
const EXPLICATIONS_ERREURS = {
// Un objet utilisé comme un "dictionnaire" : nom de l'erreur → explication
ReferenceError: "Tu utilises un nom (variable ou fonction) qui n'existe pas. Faute de frappe ? Variable déclarée plus bas ou dans un autre bloc { } ?",
TypeError: "Tu fais une opération impossible sur ce type de valeur (ex : appeler quelque chose qui n'est pas une fonction, lire une propriété de undefined, modifier une const).",
SyntaxError: "Le code est mal écrit : il manque sans doute une parenthèse, une accolade, un guillemet ou une virgule.",
RangeError: "Une valeur est hors des limites autorisées (souvent : une fonction qui s'appelle elle-même à l'infini).",
};
function expliquerErreur(erreur) {
// L'opérateur ?? ("coalescence des nuls") donne la valeur de droite
// si celle de gauche est null ou undefined.
return EXPLICATIONS_ERREURS[erreur.name] ?? "";
}
/* =====================================================================
3. UNE "FAUSSE" CONSOLE pour chaque éditeur
=====================================================================
Le vrai console.log écrit dans les outils de développement (F12).
On fabrique un objet qui a les MÊMES méthodes (log, warn, error…)
mais qui écrit dans la zone .sortie de l'éditeur.
C'est une "fabrique" : une fonction qui renvoie un objet.
--------------------------------------------------------------------- */
function creerConsole(zoneSortie) {
// Fonction interne qui ajoute UNE ligne dans la zone de sortie.
// "...valeurs" (paramètre du reste) récupère TOUS les arguments
// dans un tableau : console.log("a", 1, true) → ["a", 1, true]
function ecrire(niveau, ...valeurs) {
const ligne = document.createElement("div"); // crée un <div> vide
ligne.className = "ligne " + niveau;
// Pour chaque valeur, on crée un <span> coloré, séparés par un espace
valeurs.forEach((valeur, index) => {
if (index > 0) ligne.append(" ");
const span = document.createElement("span");
span.className = classeCouleur(valeur);
// textContent (et pas innerHTML) : le texte est affiché tel quel,
// une balise <b> s'affichera littéralement au lieu d'être interprétée.
span.textContent = formaterValeur(valeur);
ligne.append(span);
});
zoneSortie.append(ligne); // ajoute dans la page
zoneSortie.scrollTop = zoneSortie.scrollHeight; // défile tout en bas
// On envoie AUSSI dans la vraie console du navigateur (F12)
console[niveau === "err" ? "error" : niveau === "warn" ? "warn" : "log"](...valeurs);
}
// L'objet renvoyé : mêmes noms de méthodes que la vraie console
return {
log: (...v) => ecrire("", ...v),
info: (...v) => ecrire("info", ...v),
warn: (...v) => ecrire("warn", "⚠", ...v),
error: (...v) => ecrire("err", "✖", ...v),
clear: () => { zoneSortie.innerHTML = ""; },
// console.table : affiche un tableau d'objets sous forme de lignes
table: (donnees) => {
if (!Array.isArray(donnees)) return ecrire("", donnees);
donnees.forEach((element, i) => ecrire("", `[${i}]`, element));
},
};
}
/* =====================================================================
4. EXÉCUTER LE CODE d'un éditeur
=====================================================================
Comment exécuter du texte comme si c'était du code ?
On crée une fonction à partir d'une chaîne de caractères.
AsyncFunction est le "constructeur" des fonctions async : il permet
d'utiliser "await" directement dans les exemples (chapitre 12).
On passe à cette fonction nos propres "console" et "scene", qui
REMPLACENT les vrais à l'intérieur du code exécuté.
⚠ Exécuter du texte comme du code est dangereux avec du texte venant
d'inconnus. Ici, c'est TOI qui écris le code, donc pas de souci.
--------------------------------------------------------------------- */
const AsyncFunction = (async function () {}).constructor;
async function executer(bac) {
// querySelector cherche le PREMIER élément correspondant au sélecteur CSS,
// mais seulement À L'INTÉRIEUR de "bac".
const editeur = bac.querySelector("textarea");
const sortie = bac.querySelector(".sortie");
const scene = bac.querySelector(".scene"); // peut être null s'il n'y en a pas
sortie.innerHTML = ""; // on efface l'ancienne sortie
// Si l'éditeur a une scène, on la remet dans son état de départ.
// (dataset.initial = l'attribut data-initial, rempli au chargement)
if (scene) scene.innerHTML = scene.dataset.initial;
const maConsole = creerConsole(sortie);
// try / catch : on "essaie" un bloc de code ; si une erreur survient,
// au lieu de tout planter, on "attrape" l'erreur dans catch.
try {
const fonction = new AsyncFunction("console", "scene", editeur.value);
await fonction(maConsole, scene);
} catch (erreur) {
maConsole.error(`${erreur.name} : ${erreur.message}`);
const aide = expliquerErreur(erreur);
if (aide) maConsole.info("💡 " + aide);
}
}
/* =====================================================================
5. BRANCHER chaque éditeur : boutons, raccourcis clavier
=====================================================================
document.querySelectorAll renvoie TOUS les éléments correspondants.
On boucle dessus avec forEach.
--------------------------------------------------------------------- */
function preparerEditeurs() {
document.querySelectorAll(".bac").forEach((bac) => {
const editeur = bac.querySelector("textarea");
const scene = bac.querySelector(".scene");
// On mémorise le code d'origine pour le bouton « Réinitialiser ».
// .trim() enlève les espaces / retours à la ligne au début et à la fin.
editeur.value = editeur.value.trim();
const codeOriginal = editeur.value;
// On mémorise aussi le contenu de départ de la scène
if (scene) scene.dataset.initial = scene.innerHTML;
// L'éditeur s'adapte à la hauteur de son code
ajusterHauteur(editeur);
editeur.addEventListener("input", () => ajusterHauteur(editeur));
// Désactive le correcteur orthographique (inutile pour du code)
editeur.spellcheck = false;
// --- Bouton « Exécuter » ---
bac.querySelector(".executer").addEventListener("click", () => executer(bac));
// --- Bouton « Réinitialiser » ---
bac.querySelector(".reinit").addEventListener("click", () => {
editeur.value = codeOriginal;
ajusterHauteur(editeur);
bac.querySelector(".sortie").innerHTML = "";
if (scene) scene.innerHTML = scene.dataset.initial;
});
// --- Raccourcis clavier dans l'éditeur ---
// L'événement "keydown" se déclenche quand on enfonce une touche.
// L'objet "evenement" décrit la touche (evenement.key) et les
// touches spéciales tenues (ctrlKey, metaKey = Cmd sur Mac…).
editeur.addEventListener("keydown", (evenement) => {
// Ctrl+Entrée (ou Cmd+Entrée) → exécuter
if (evenement.key === "Enter" && (evenement.ctrlKey || evenement.metaKey)) {
evenement.preventDefault(); // empêche le retour à la ligne
executer(bac);
}
// Tab → insérer 2 espaces au lieu de quitter le champ
// (Échap puis Tab permet quand même de sortir au clavier)
if (evenement.key === "Tab" && !evenement.shiftKey && !editeur.dataset.sortir) {
evenement.preventDefault();
const debut = editeur.selectionStart; // position du curseur
const fin = editeur.selectionEnd;
editeur.value = editeur.value.slice(0, debut) + " " + editeur.value.slice(fin);
editeur.selectionStart = editeur.selectionEnd = debut + 2;
}
// Échap : le prochain Tab sortira de l'éditeur (accessibilité clavier)
editeur.dataset.sortir = evenement.key === "Escape" ? "1" : "";
});
});
}
/* Fait grandir le <textarea> pour qu'il montre tout son code */
function ajusterHauteur(editeur) {
editeur.style.height = "auto"; // on remet à zéro…
editeur.style.height = editeur.scrollHeight + 4 + "px"; // …puis on mesure
}
/* =====================================================================
6. MENU : surligner le chapitre en cours
=====================================================================
IntersectionObserver "observe" des éléments et nous prévient quand
ils entrent ou sortent de l'écran. Bien plus efficace que de
recalculer à chaque mouvement de molette.
--------------------------------------------------------------------- */
function suivreLecture() {
const liens = document.querySelectorAll(".sommaire a");
const observateur = new IntersectionObserver((entrees) => {
entrees.forEach((entree) => {
if (!entree.isIntersecting) return; // pas visible → on ignore
const id = entree.target.id;
liens.forEach((lien) => {
// classList.toggle(nom, condition) : ajoute la classe si la
// condition est vraie, l'enlève sinon.
lien.classList.toggle("actif", lien.getAttribute("href") === "#" + id);
});
});
}, {
// On considère qu'une section est "lue" quand elle passe
// dans la bande située entre 20 % et 40 % du haut de l'écran.
rootMargin: "-20% 0px -60% 0px",
});
document.querySelectorAll("section.chapitre").forEach((s) => observateur.observe(s));
}
/* =====================================================================
7. THÈME clair / sombre
=====================================================================
localStorage permet de garder une petite info dans le navigateur,
même après fermeture de la page. On l'entoure de try/catch car il
peut être bloqué (navigation privée, réglages de confidentialité…).
--------------------------------------------------------------------- */
function gererTheme() {
const racine = document.documentElement; // la balise <html>
const bouton = document.querySelector("#bouton-theme");
// Relire le choix enregistré, s'il existe
try {
const sauvegarde = localStorage.getItem("theme");
if (sauvegarde) racine.dataset.theme = sauvegarde;
} catch (e) { /* pas grave : on garde le thème du système */ }
bouton.addEventListener("click", () => {
// Quel thème est affiché en ce moment ?
const sombreSysteme = window.matchMedia("(prefers-color-scheme: dark)").matches;
const actuel = racine.dataset.theme || (sombreSysteme ? "dark" : "light");
const nouveau = actuel === "dark" ? "light" : "dark";
racine.dataset.theme = nouveau; // → <html data-theme="dark">, lu par le CSS
try { localStorage.setItem("theme", nouveau); } catch (e) {}
});
}
/* =====================================================================
8. DÉMARRAGE
=====================================================================
La balise <script> est chargée avec l'attribut "defer" dans le HTML :
le script s'exécute seulement quand tout le HTML a été lu.
On peut donc chercher les éléments de la page sans risque.
--------------------------------------------------------------------- */
preparerEditeurs();
suivreLecture();
gererTheme();