Normalisation
7 min
les éditeurs slate peuvent modifier des structures de données complexes et imbriquées et dans la plupart des cas, cela fonctionne très bien mais dans certaines situations, des incohérences dans la structure de données peuvent être introduites — le plus souvent lorsqu’un utilisateur est autorisé à coller du contenu richtext arbitraire la « normalisation » est le moyen de garantir que le contenu de votre éditeur reste toujours d’une certaine forme cela ressemble à une « validation », sauf qu’au lieu de seulement déterminer si le contenu est valide ou non, son rôle est de corriger le contenu pour le rendre à nouveau valide contraintes intégrées les éditeurs slate sont fournis avec quelques contraintes intégrées ces contraintes sont là pour rendre le travail avec le contenu beaucoup plus prévisible que contenteditable toute la logique intégrée de slate dépend de ces contraintes, donc il est malheureusement impossible de les omettre elles sont… tous les nœuds élément doivent contenir au moins un descendant texte — même les éléments vides docid\ cvnf gy7od8ojhx0veaow si un nœud élément ne contient aucun enfant, un nœud texte vide sera ajouté comme seul enfant cette contrainte existe pour garantir que les points d’ancrage et de focus de la sélection (qui reposent sur la référence à des nœuds texte) puissent toujours être placés dans n’importe quel nœud ainsi, les éléments vides (ou les éléments void) ne seraient pas sélectionnables deux textes adjacents avec les mêmes propriétés personnalisées seront fusionnés si deux nœuds texte adjacents ont le même formatage, ils sont fusionnés en un seul nœud texte contenant la concaténation des deux chaînes de texte cela permet d’éviter que les nœuds texte ne fassent qu’augmenter en nombre dans le document, puisque l’ajout et la suppression de formatage entraînent tous deux une division des nœuds texte les nœuds bloc ne peuvent contenir que d’autres blocs, ou des éléments inline et des nœuds texte par exemple, un bloc de type paragraphe ne peut pas avoir à la fois un autre élément bloc paragraphe et un élément inline lien comme enfants le type d’enfants autorisés est déterminé par le premier enfant, et tout autre enfant non conforme est supprimé cela garantit que les comportements richtext courants comme « scinder un bloc en deux » fonctionnent de manière cohérente les nœuds inline ne peuvent pas être le premier ou le dernier enfant d’un bloc parent, ni être adjacents à un autre nœud inline dans le tableau des enfants si c’est le cas, un nœud texte vide sera ajouté pour corriger la situation conformément à la contrainte le nœud éditeur de niveau supérieur ne peut contenir que des nœuds bloc si l’un des enfants de niveau supérieur est un nœud inline ou texte, il sera supprimé cela garantit qu’il y a toujours des nœuds bloc dans l’éditeur afin que des comportements comme « scinder un bloc en deux » fonctionnent comme prévu les nœuds doivent être sérialisables en json par exemple, évitez d’utiliser undefined dans votre modèle de données cela garantit que les opérations docid\ ny1dqwlycay xn pd f5i sont également sérialisables en json, une propriété supposée par les bibliothèques de collaboration les valeurs de propriétés ne doivent pas être null à la place, vous devriez utiliser une propriété optionnelle, par exemple foo? string au lieu de foo string | null cette limitation est due au fait que null est utilisé dans les opérations docid\ ny1dqwlycay xn pd f5i pour représenter l’absence d’une propriété ces contraintes par défaut sont toutes obligatoires car elles rendent le travail avec les documents slate beaucoup plus prévisible 🤖 bien que ces contraintes soient les meilleures que nous ayons définies pour l’instant, nous cherchons toujours des moyens de rendre les contraintes intégrées de slate moins contraignantes si possible — tant que les comportements standard restent faciles à comprendre si vous trouvez un moyen de réduire ou de supprimer une contrainte intégrée avec une approche différente, nous sommes preneurs ! ajout de contraintes les contraintes intégrées sont assez génériques mais vous pouvez également ajouter vos propres contraintes par dessus celles intégrées, spécifiques à votre domaine pour cela, vous étendez la fonction normalizenode sur l’éditeur la fonction normalizenode est appelée chaque fois qu’une opération est appliquée pour insérer ou mettre à jour un nœud (ou ses descendants), ce qui vous donne la possibilité de garantir que les modifications ne l’ont pas laissé dans un état invalide, et de corriger le nœud si nécessaire par exemple, voici un plugin qui garantit que les blocs paragraphe n’ont que des éléments texte ou inline comme enfants import { transforms, element, node } from 'slate' const withparagraphs = editor => { const { normalizenode } = editor editor normalizenode = entry => { const \[node, path] = entry // if the element is a paragraph, ensure its children are valid if (element iselement(node) && node type === 'paragraph') { for (const \[child, childpath] of node children(editor, path)) { if (element iselement(child) && !editor isinline(child)) { transforms unwrapnodes(editor, { at childpath }) return } } } // fall back to the original `normalizenode` to enforce other constraints normalizenode(entry) } return editor } cet exemple est assez simple chaque fois que normalizenode est appelé sur un élément paragraphe, il parcourt chacun de ses enfants et s’assure qu’aucun d’eux n’est un élément bloc et si l’un est un élément bloc, il est désemboîté, de sorte que le bloc est supprimé et que ses enfants prennent sa place le nœud est « corrigé » mais que se passe t il si l’enfant contient des blocs imbriqués ? normalisation en plusieurs passes une chose à comprendre concernant les contraintes de normalizenode est qu’elles fonctionnent en mode multi‑passe si vous vérifiez à nouveau l’exemple ci dessus, vous remarquerez l’instruction retour if (element iselement(child) && !editor isinline(child)) { transforms unwrapnodes(editor, { at childpath }) return } vous pourriez d’abord trouver cela étrange, parce qu’avec le retour placé là, la version originale de normalizenodes ne sera jamais appelée, et les contraintes intégrées ne pourront pas exécuter leurs propres normalisations mais il existe une petite « astuce » liée à la normalisation lorsque vous appelez editor unwrapnodes , vous modifiez en réalité le contenu du nœud en cours de normalisation donc, même si vous terminez la passe actuelle, en modifiant le nœud vous déclenchez une nouvelle passe de normalisation cela entraîne une forme de normalisation récursive cette caractéristique multi pass rend l’écriture de normalisations beaucoup plus simple, parce que vous n’avez qu’à corriger un seul problème à la fois, et non tous les problèmes possibles pouvant rendre un nœud invalide pour voir comment cela fonctionne en pratique, commençons par ce document invalide \<editor> \<paragraph a> \<paragraph b> \<paragraph c>word\</paragraph> \</paragraph> \</paragraph> \</editor> l’éditeur commence par exécuter normalizenode sur \<paragraph c> et il est valide, car il ne contient que des nœuds texte en tant qu’enfants un écueil à éviter est de créer une boucle de normalisation infinie cela peut se produire si vous vérifiez une structure invalide spécifique, mais que vous ne corrigez pas réellement cette structure avec la modification que vous apportez au nœud cela entraîne une boucle infinie, car le nœud continue d’être signalé comme invalide, mais n’est jamais correctement corrigé par exemple, considérez une normalisation qui garantit que les éléments lien ont une propriété url valide // warning this is an example of incorrect behavior! const withlinks = editor => { const { normalizenode } = editor editor normalizenode = entry => { const \[node, path] = entry if ( element iselement(node) && node type === 'link' && typeof node url !== 'string' ) { // error null is not a valid value for a url transforms setnodes(editor, { url null }, { at path }) return } normalizenode(entry) } return editor } cette correction est mal écrite elle veut garantir que tous les éléments lien ont une propriété url sous forme de chaîne mais pour corriger les liens invalides, elle définit la propriété url à null , ce qui n’est toujours pas une chaîne ! dans ce cas, vous voudriez soit détacher le lien, en le supprimant entièrement ou étendre votre validation pour accepter également un url == null « vide » implications pour le reste du code des séquences de transforms peuvent devoir être encapsulées dans editor withoutnormalizing docid\ emmtio53 dod5rqvthcix si l’arbre de nœuds ne doit pas être normalisé entre les transforms c’est souvent le cas lorsque vous utilisez unwrapnodes suivi de wrapnodes par exemple, vous pourriez écrire une fonction pour changer le type d’un bloc comme suit const list types = \['numbered list', 'bulleted list'] function changeblocktype(editor, type) { editor withoutnormalizing(editor, () => { const isactive = isblockactive(editor, type) const islist = list types includes(type) transforms unwrapnodes(editor, { match n => list types includes( !editor iseditor(n) && slateelement iselement(n) && n type ), split true, }) const newproperties = { type isactive ? 'paragraph' islist ? 'list item' type, } transforms setnodes(editor, newproperties) if (!isactive && islist) { const block = { type type, children \[] } transforms wrapnodes(editor, block) } }) }