Madino



Madino est une bibliothèque JavaScript permettant d'intégrer facilement des éditeurs de texte markdown dans un site web. La librairie est très flexible ce qui lui donne un grand pouvoir d’adaptation mais aussi un passage de configuration nécessaire.

Un exemple d'intégration de l'éditeur Madino.

Styles

La bibliothèque Madino propose actuellement 12 styles :

  • Gras
  • Italique
  • Barré
  • Citation
  • Code
  • Bloc de code
  • Lien
  • Image
  • Espace horizontal
  • Liste non ordonnée
  • Liste ordonnée
  • Titre

Configuration

Importez le script madino.min.js dans votre page HTML.

Votre page peut contenir plusieurs éditeurs markdown. Chaque éditeur à nécessairement besoin d'un espace d'écriture (textarea) et d'un espace d'affichage (div), et optionnellement d'un espace pour disposer les boutons d'actions (bold, italic, quoteblock...).

La configuration peut se faire dans un fichier JavaScript séparé ou dans une balise <script> à même le fichier HTML.

Configuration globale

La configuration globale s'adresse à tous les éditeurs Madino.

Parser

Afin de garantir une flexibilité vous devez définir la méthode de transformation du texte markdown en HTML par la fonction madino.parser :

Note : j'utilise dans cette exemple le parser showdown

let converter = new showdown.Converter()
// reçoit le markdown écrit et doit renvoyer sa conversion HTML
madino.parser = (markdown) => {
  return converter.makeHtml(markdown)
}

Langue & Placeholder

Deux langues sont proposées par défaut : Français ('fr') et Anglais ('en'). Vous pouvez remplacer les placeholders existants s'ils ne vous conviennent pas ou en définir pour une nouvelle langue :

// sélection de la langue
madino.lang = 'fr'
// rédifinition des placeholders
madino.placeholders = {
  fr: {
    bold: 'texte en gras',
    italic: 'texte en italique',
    strike: 'texte barré',
    quoteblock: 'citation',
    codeblock: 'code',
    code: 'texte',
    heading: 'Titre',
    image: 'titre de l\'image',
    link: 'titre du lien',
    ulitem: 'élément',
    olitem: 'élément'
  }
}

Configuration locale

La configuration locale s'adresse à une instance d'un éditeur Madino.

Création d'un éditeur Madino

Dans un premier temps il faut créer l'espace d'écriture et d'affichage et éventuellement l'espace d'actions.
Voici un exemple simplifié :

<div>
  <div id="madino-actions">
    <div onclick="Madino.apply('bold')">B</div>
    <div onclick="Madino.parse()">Parse</div>
  </div>
  <textarea id="madino-editor" rows="8" cols="80" onclick="Madino.check()" onkeyup="Madino.check()"></textarea>
  <div id="madino-output"></div>
</div>

Notez que le textarea doit appeler la fonction 'Madino.check()' lors d'un 'onclick' et 'onkeyup'

Créez une instance en précisant les composants soit par leur id prefixé par un # ou une référence direct vers le composant :

let Madino = new madino.Editor({
  editor: '#madino-editor',
  output: document.getElementById('madino-output')
})

Si vous n'indiquez pas les composants ils prendront les valeurs par défaut :

let Madino = new madino.Editor()
// editor : '#madino-editor'
// output : '#madino-output'

Détection de style

Deux fonctions permettent de détecter le style appliqué au texte sélectionné :

setStylesListener(callback)

Permet de définir un callback lorsque n'importe quel style est détecté sur le texte sélectionné.

  • style : le style selectionné (bold | italic | strike | quoteblock | code | codeblock | link | image | space | ulitem | olitem | heading ). Le style vaut null si la sélection n'est pas stylisée ou que la sélection est vide.
  • start : l'index de début de la sélection
  • end : l'index de fin de la sélection
  • selection : le texte sélectionné
Madino.setStylesListener((style, start, end, selection) => {
  if (style === null) console.log('selection unstylized')
  else console.log(style, 'style is selected @', start, end, 'selected text:', selection)
})
addStyleListener(style, callback)

Permet de définir un callback si le style est détecté sur le texte sélectionné. Ce style ne sera pas detecté par setStylesListener.

  • start : l'index de début de la sélection
  • end : l'index de fin de la sélection
  • selection : le texte sélectionné
Madino.addStyleListener('bold', (start, end, selection) => {
  console.log('style bold is selected @', start, end, 'selected text:', selection)
})

Application de style

Plusieurs fonctions permettent d'intéragir avec les styles de l'éditeur :

isStylized(style, [start], [end])

Retourne les données de style (styleData) si le texte sélectionné correspond au style, sinon retourne false.

// retourne si le texte sélectionné est en italique
Madino.isStylized('italic')
// retourne si le texte entre l'index 10 et 18 est en italique
Madino.isStylized('italic', 10, 18)
style(style, [params], [start], [end])

Permet d'appliquer le style au texte sélectionné. Les styles codeblock, link, image et heading peuvent prendre des paramètres optionnels afin d'altérer le style appliqué.

// applique le style gras, si aucune sélection insére '**texte gras**'
Madino.style('bold')
// applique le style gras entre l'index 10 et 16
Madino.style('bold', null, 10, 16)
// applique le style bloc de code avec le language 'javascript' dans l'en-tête
Madino.style('codeblock', { lang: 'javascript' })
// applique le style lien avec le titre et l'url prédéfini
Madino.style('link', { title: 'titre', url: 'url' })
// applique le style image avec le titre et l'url prédéfini
Madino.style('image', { title: 'titre', url: 'url' })
// applique le style titre de taille 2, la taille peut aller de 1 à 6
Madino.style('heading', { level: 2 })

Note : attention à bien respecter le nom des champs pour les paramètres.

unstyle(style, [styleData], [start], [end])

Permet d'enlever le style au texte sélectionné. Afin d'améliorer les performances si vous venez de calculer les données de style (styleData) avec isStyleSelected(..) vous pouvez les passer en paramètre afin d'éviter que unstyle(..) les recalculs.

// enlève le style 'lien' à la sélection
Madino.unstyle('link')
// enlève le style 'lien' entre l'index 36 et 62
Madino.unstyle('link', null, 36, 62)
// enlève le style 'lien' à la sélection avec les données de style pré-calculé
Madino.unstyle('link', styleData)
apply(style, [params])

Détecte avec isStylized(..) si le texte sélectionné est stylisé, si oui applique la fonction unstyle(..), sinon applique le style avec style(..). Son utilisation est similaire à celle de style(..).

// applique le style gras, si aucune sélection insére '**texte gras**'
Madino.apply('bold')
// applique le style bloc de code avec le language 'javascript' dans l'en-tête
Madino.apply('codeblock', { lang: 'javascript' })

Raccourcis

Madino intégre un système de raccourcis qui permet d'appliquer des styles ou d'exécuter des fonctions. Vous pouvez définir des raccourcis globaux pour tous les éditeurs, ou locaux pour des résultats différents entre chaque éditeurs. Un raccourcis est définit par son code et sa target.

Code
Le code correspond aux touches à appuyer pour exécuter la target. Les touches sont séparées par des '+' et peuvent être écrites sous leur charcode.
Exemples : ctrl+b, ctrl+shift+h, ctrl+66 (66 = b)

Target
La cible correspond au style à appliquer ou la fonction à exécuter. Cas particulier pour la cible parse qui exécute la fonction du même nom.

Globaux

Définition des raccourcis globaux :

madino.setShortcuts({
  'ctrl+66': 'bold',
  'crtl+a': () => { alert('madino') },
  'ctrl+shift+g': 'parse',
})

Locaux

Les raccourcis locaux sont prioritaires aux globaux.

Madino.setShortcuts({
  'ctrl+66': 'italic',
  'ctrl+k': () => { Madino.apply('codeblock', { lang: prompt("Language") }) },
})

Fonctions utiles

parse([markdown])

Parse le markdown de l'éditeur ou du paramètre markdown avec la fonction madino.parser(..) et l'affiche dans l'espace d'affichage.

// parse le texte de l'éditeur et l'affiche
Madino.parse()
// parse le markdown '**madino**' et l'affiche
Madino.parse('**madino**')
select(start, end)

Sélectionne le texte entre l'index start et end.

// sélectionne les deux premiers caractères
Madino.select(0, 2)
insert(value, [index])

Insére le texte value à la position du curseur ou à l'index spécifié.

// insére '**madino**' à la position du curseur dans l'éditeur
Madino.insert('**madino**')
// insére '**madino**' à l'index 0
Madino.insert('**madino**', 0)
// insére '**madino**' à la fin du texte
Madino.insert('**madino**', 'end')
replace(value, [start], [end])

Replace le texte sélectionné ou le texte entre l'index start et end par le texte value.

// remplace le texte sélectionné par '**madino**'
Madino.replace('**madino**')
// remplace le texte de l'index 0 à 50 par '**madino**'
Madino.replace('**madino**', 0, 50)
// remplace le texte de l'index 12 à 12 + taille de '**madino**' par '**madino**'
Madino.replace('**madino**', 12)
// remplace le texte de l'index 42 jusqu'à la fin du texte par '**madino**'
Madino.replace('**madino**', 42, 'end')
getSelectedText()

Retourne le texte sélectionné dans l'éditeur

alert(Madino.getSelectedText())

Exemple

Un exemple d'intégration de l'éditeur Madino est présent sur mon site.

Voici un petit exemple simple récapitulant l'ensemble des fonctionnalités

  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.0/showdown.min.js"></script>
  <script type="text/javascript" src="madino.js"></script>
  ...
  <div>
    <div id="madino-actions">
      <div onclick="Madino.apply('bold')">B</div>
      <div onclick="Madino.apply('italic')">I</div>
      <div onclick="Madino.apply('strike')">S</div>
      <div onclick="Madino.apply('quoteblock')">Q</div>
      <div onclick="Madino.apply('code')">C</div>
      <div onclick="Madino.apply('codeblock')">K</div>
      <div onclick="applyLink()">L</div>
      <div onclick="Madino.apply('image')">P</div>
      <div onclick="Madino.apply('space')">E</div>
      <div onclick="Madino.apply('ulitem')">U</div>
      <div onclick="Madino.apply('olitem')">O</div>
      <div onclick="Madino.apply('heading', { level: 2 })">T</div>
      <div onclick="Madino.parse()">Parse</div>
    </div>
    <textarea id="madino-editor" rows="8" cols="80" onclick="Madino.check()" onkeyup="Madino.check()"></textarea>
    <div id="madino-output"></div>
  <div>
  ...
  <script type="text/javascript">
    // configuration globale
    let converter = new showdown.Converter()
    madino.parser = (markdown) => {
      return converter.makeHtml(markdown)
    }
    madino.lang = 'fr'
    // rédéfinition d'un seul placeholder
    madino.placeholders = {
      fr: {
        bold: 'blablabla'
      }
    }
    madino.setShortcuts({
      'ctrl+b': 'bold',
      'ctrl+i': 'italic',
      // ...
      'ctrl+shift+p': 'parse',
    })

    // configuration locale
    let Madino = new madino.Editor()
    Madino.setShortcuts({
      'ctrl+l': () => { applyLink() },
      'ctrl+k': () => { Madino.apply('codeblock', { lang: prompt("Language") }) },
    })
    Madino.setStylesListener((style, start, end, selection) => {
      if (style === null) console.log('selection unstylized')
      else console.log(style, 'style is selected @', start, end, 'selection:', selection)
    })
    Madino.addStyleListener('bold', (start, end, selection) => {
      console.log('bold style is selected @', start, end, 'selection:', selection)
    })

    // applique un lien en demandant le titre et le lien
    function applyLink() {
      // réutilisez 'styleData' pour 'unstyle' pour éviter un recalcul
      let styleData = Madino.isStylized('link')
      if (styleData) Madino.unstyle('link', styleData)
      else {
        let selection = Madino.getSelectedText()
        let title = prompt("Titre du lien", selection)
        let url = prompt("URL du lien")
        // utilisez directement la fonction 'style' et non 'apply'
        // pour des gains de performances
        Madino.style('link', {
          title: title,
          url: url
        })
      }
    }

  </script>

TODO

  • Implémenter le undo / redo

Licence

MIT License

Copyright (c) 2019 Pierre-Loup Gosse

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.