Bonjour,

Je tente de faire un mini éditeur wysiwyg très simple avec seulement les fonctionnalités Bold et Italic. Le tout dans une div contenteditable.
Au départ, tout va bien.

Ce que je fais

Je récupère la selection via un :

window.getSelection()

Ensuite je fais un simple replace de ma selection par :

<span style="font-weight: bold;">Ma selection</span>

Tout ça c'est bon tant qu'on a pas plusieurs occurences du mot que l'on souhaite styliser. Car en fonction de la regex on ne peut pas vraiment savoir quel occurence on a selectionner. Je me suis donc dis que j'allais récupérer aussi la position du curseur et je fais un slice au bon endroit, j'insert ma span, etc... Jusque là tout va bien encore.
Si ensuite je sélectionne des mots avant celui que je viens de mettre en gras, ça marche. Mais si je selectionne des mots après... c'est la cata. En fait la position du curseur ne prend pas en compte les caractères utilisés pour les élements HTML.

Donc si je met en gras un mot à la position X et qu'ensuite je veux en sélectionner un autre à la position X + Y ma methode pour récupérer la selection me retourne X + Y

Ce que je veux

Hors ce que j'aimerai c'est avoir X + (les caractères ajoutés avec la span et le style) + Y

Voici un petit codepen avec un bout du code utilisé (ne fonctionne que sur chrome il me semble)

Si vous avez des pites/idées, je suis preneur :)

Merci à vous

6 réponses


SwithFr
Auteur
Réponse acceptée

Bon finalement.... j'ai vu qu'il existait une API magique : ici
Du coup ça fait largement le taff !!

Merci tout de même pour l'effort et l'aide apportée.

Yop.
Il faut pour ça utiliser la fonction(/méthode) .cloneContents du prototype Range.
Celle-ci va te retourner un fragment de document, qui peut contenir des nœuds et des parties de nœuds texte dans un document donné (DataFragment), ici, de la séléction dans ce cas présent (selection.getRange(0) as Range).
Il te suffira tout simplement de créer un élément HTML temporairement, d'ajouter ce DataFragment à cet élément et de retourner son contenu HTML et le tour est joué.

SwithFr
Auteur

Salut PhiSyX, merci pour ton aide, cependant, j'ai du mal à suivre le raisonement. Est-ce que sur mon codepend, tu peux me dire quelles sont les modifications que tu penses en fonction de tes éléments de réponses ?
Car j'ai tenter d'utiliser .cloneContents mais j'ai toujours le mot selectionné et non la span

En soi, à partir de ce que tu as fait, il faut juste rajouter la partie .cloneContents, à un élément HTML temporaire et d'y retourner son contenu HTML.

const fragment = range.cloneContents() // DataFragment
const $temp = document.createElement('div') // Création d'un élément html temporaire.
$temp.appendChild(fragment) // Ajout du DataFragment à l'élément HTML.
console.log('selected', $temp.innerHTML)
SwithFr
Auteur

Je dois être fatigué, mais je ne vois pas comment celà résoud le problème de la position. Récupérer la selection n'est pas le souci, c'est de savoir la position de la selection qui me fait bloquer.

Dans mon exemple si je selectionne le mot après celui en gras, il me retourne en position 190 (index du dernier caractère) or si on regarde la chaine de caractères avec le span etc, il devrait me retourner 223. (car la span avec le style font 33 caractères)

Beh, pour le coup alors l'idée c'est de ne récupérer que les caractères qui précèdent ta séléction et de rajouter la "lenght" du contenu HTML de l'élément HTML temporaire.

const getSelection = () => {
  const selection = window.getSelection()
  const range = selection.getRangeAt(0)
  const fragment = range.cloneContents()

  const $temp = document.createElement('div')
  $temp.appendChild(fragment)

  const preCaretRange = range.cloneRange()
  const postCaretRange = range.cloneRange()

  preCaretRange.selectNodeContents($div)
  preCaretRange.setEnd(range.startContainer, range.startOffset) // a été changé

  postCaretRange.selectNodeContents($div) 
  postCaretRange.setEnd(range.endContainer, range.endOffset)

  const strStart = preCaretRange.toString()
  const endStart = range.toString()

  const cursorPositionText = strStart.length + endStart.length
  const cursorPositionHTML = strStart.length + $temp.innerHTML.length

  return {
    selectionText: selection.toString(),
    cursorPositionText,

    selectionHTML: $temp.innerHTML,
    cursorPositionHTML
  }
}