Sous le capot
Comment le code fonctionne — Block_mng_interface, Block_manager, Redac
Block_mng_interface — l'interface simulée
JavaScript n'a pas de mot-clé interface (contrairement à TypeScript, PHP ou Java).
La technique classique pour pallier ça : créer une classe de base où chaque méthode
lance une erreur si elle n'est pas redéfinie par la sous-classe.
// Block_mng_interface.js
size() { throw new Error("Méthode 'size' non implémentée") }
rm(id) { throw new Error("Méthode 'rm' non implémentée") }
// ...
// Block_manager.js
export default class Block_manager extends Block_mng_interface {
size() { return this.blocks.length } // ← redéfinit → pas d'erreur
rm(id) { this.blocks = this.blocks.filter(b => b.id !== id) }
}
L'intérêt : si demain tu crées un Block_manager_v2 et que tu oublies d'implémenter
rm(), le crash est immédiat et le message d'erreur est clair.
Sans cette interface, le bug serait silencieux — la méthode hériterait de undefined
et planterait plus tard avec un message cryptique.
insert_after et get_next
existent dans Block_manager mais pas dans l'interface. Elle définit le contrat minimal,
pas la totalité de l'implémentation. C'est un choix pragmatique — on n'over-engineer pas pour l'instant.
Block_manager — le tableau commenté
Block_manager maintient un simple tableau this.blocks = [] et expose
des méthodes pour le lire et le modifier. C'est la source de vérité pour l'ordre des blocs
— le DOM est toujours synchronisé manuellement avec ce tableau par Redac.
Méthodes de lecture / navigation
Ces méthodes ne modifient jamais le tableau — elles répondent à des questions sur son état.
| Méthode | Ce qu'elle retourne | Qui l'appelle |
|---|---|---|
size() |
Nombre de blocs dans le tableau |
_refresh_states() — si size() === 1, le bouton
Supprimer est désactivé (on ne peut pas vider l'éditeur complètement)
|
has(id) |
true si un bloc avec cet id existe |
Non utilisé par Redac pour l'instant — utile pour des assertions ou de futurs gardes |
get(id) |
L'objet Block correspondant à cet id |
Non utilisé directement par Redac — get_previous() et get_next() l'appellent en interne |
get_index(id) |
Position 0-based du bloc dans le tableau | Utilisé en interne par up(), down(), is_first(), is_last() |
get_previous(id) |
L'objet bloc qui précède |
on_up dans _mount_tune() : pour savoir quel élément DOM
placer avant lequel dans insertBefore(block_el, prev_el)
|
get_next(id) |
L'objet bloc qui suit |
on_down : même logique mais dans l'autre sens —
insertBefore(next_el, block_el)
|
get_previous_id(id) |
L'id du bloc précédent (ou null) |
Pas utilisé par Redac directement — disponible pour les sous-classes de blocs si besoin |
get_next_id(id) |
L'id du bloc suivant (ou null) |
Idem |
is_first(id) |
true si le bloc est en position 0 |
_refresh_states() → tune.update_state(is_first, is_last, is_only)
→ désactive le bouton ↑ si premier
|
is_last(id) |
true si le bloc est en dernière position |
Idem — désactive le bouton ↓ si dernier |
Méthodes de mutation
Ces méthodes modifient le tableau. Elles sont toujours appelées en tandem avec une opération DOM dans Redac — le tableau et le DOM doivent rester synchronisés.
| Méthode | Ce qu'elle fait dans le tableau | Qui l'appelle + ce que Redac fait au DOM |
|---|---|---|
add(block) |
push en fin de tableau |
_init() et load() lors de la construction initiale.
DOM : wrapper.append(block.render())
|
insert_after(ref_id, block) |
Trouve l'index de ref_id, fait un splice(idx + 1, 0, block).
Si ref_id n'existe pas → push en fin.
|
_add_block(after_id) et _split_block().
DOM : wrapper.insertBefore(dom, ref_el.nextSibling)
|
rm(id) |
filter — réassigne this.blocks sans le bloc (ne retourne rien) |
on_delete dans _mount_tune().
DOM : document.getElementById(block.id).remove()
|
up(id) |
Swap le bloc avec son voisin à i - 1 via _swap() |
on_up.
DOM : wrapper.insertBefore(block_el, prev_el) — puis _refresh_states()
|
down(id) |
Swap le bloc avec son voisin à i + 1 |
on_down.
DOM : wrapper.insertBefore(next_el, block_el) — fait avancer le suivant d'un cran
|
Méthode debug
| Méthode | Description |
|---|---|
dump() |
console.log('Block_manager:', this.blocks) — à appeler depuis la console du navigateur pour inspecter l'état interne à n'importe quel moment |
Principe fondamental : DOM et tableau sont synchronisés à la main
Il n'y a pas de réactivité automatique (pas de Vue, React, ou autre). Chaque opération sur le tableau a son équivalent DOM appelé juste avant ou juste après, dans le même bloc de code.
// Dans _mount_tune(block), callback on_up :
t.on_up = () => {
const prev = this._blocks.get_previous(block.id) // ← lit le tableau
if (!prev) return // ← garde : déjà premier, rien à faire
const prev_el = document.getElementById(prev.id)
const block_el = document.getElementById(block.id)
this._wrapper.insertBefore(block_el, prev_el) // ← DOM : déplace l'élément
this._blocks.up(block.id) // ← tableau : swap i avec i-1
this._refresh_states() // ← met à jour les boutons ↑/↓
}
Si tu appelles this._blocks.up() sans faire le insertBefore,
le tableau et le DOM divergent — l'éditeur affiche les blocs dans un ordre,
save() en produit un autre. C'est le genre de bug silencieux à éviter.
_refresh_states() — la boucle de mise à jour des boutons
Après chaque opération qui modifie la liste (ajout, suppression, déplacement), Redac appelle
_refresh_states(). Cette méthode parcourt tous les blocs et appelle
tune.update_state(is_first, is_last, is_only) sur chacun.
_refresh_states() {
const n = this._blocks.size()
this._blocks.blocks.forEach((b, i) => {
b.tune?.update_state(i === 0, i === n - 1, n === 1)
})
}
Tune.update_state() active/désactive ensuite les boutons ↑ (disabled si premier),
↓ (disabled si dernier), et ✕ Supprimer (disabled si seul bloc).