Afficher / cacher les zones de code avec bookdown et blogdown

Pliage de code dans bookdown et blogdown

L’option code_folding: true, comme dans les documents rmarkdown classiques, ne fonctionne pas avec bookdown ou blogdown. Il est quand même possible de l’activer avec quelques astuces. Tous les fichiers présentés ici, les fichiers javascript et Rmd nécessaires à bookdown et les fichiers html nécessaires à blogdown, pour activer le pliage de code, sont disponibles sur mon github (blog tips).
Comme pour l’article de blog que vous êtes en train de lire, ces codes permettent:

  • un bouton sur chaque bloc de code pour afficher/cacher
  • un bouton global avec un menu déroulant pour afficher/masquer tous les codes sur la page

Pliage du code dans bookdown

Il y a quelque temps, j’ai répondu à une question sur stackoverflow pour activer le pliage de code avec bookdown. Bien que cette réponse fonctionne, je n’étais pas totalement satisfait du comportement du bouton “Afficher/Masquer Global”. Aujourd’hui, je l’ai corrigé et il fonctionne proprement ! J’ai fait de “l’ingénierie inverse” pour trouver ce qui fonctionnait dans le rmarkdown classique et j’ai redéfini le css approprié pour bookdown.

Quelques codes javascript

La fonction principale de javascript qui sera appelée codefolding.js doit trouver le tag .sourceCode pour fonctionner avec bookdown. Cela nécessite également des fonctions javascript complémentaires de la librairie bootstrap, mais pas toutes.
Voici les étapes:

  1. Créez un dossier js dans le même répertoire que votre fichier Rmd.
  2. Téléchargez les fonctions javascript transition.js, collapse.js et dropdown.js ici par exemple: https://github.com/twbs/bootstrap/tree/v3.3.7/js et stockez-les dans votre dossier js.
  3. Créez un nouveau fichier dans le dossier js appelé codefolding.js avec le code suivant. C’est la même chose que pour l’option code_folding de rmarkdown mais avec pre.sourceCode ajouté pour que la fonction trouve les morceaux de code R. Vous pouvez ajouter d’autres langage de code avec pre.lang dans ce fichier.

Le fichier codefolding.js:

window.initializeCodeFolding = function(show) {

  // handlers for show-all and hide all
  $("#rmd-show-all-code").click(function() {
    $('div.r-code-collapse').each(function() {
      $(this).collapse('show');
    });
  });
  $("#rmd-hide-all-code").click(function() {
    $('div.r-code-collapse').each(function() {
      $(this).collapse('hide');
    });
  });

  // index for unique code element ids
  var currentIndex = 1;

  // select all R code blocks
  var rCodeBlocks = $('pre.sourceCode, pre.r, pre.python, pre.bash, pre.sql, pre.cpp, pre.stan, pre.js');
  rCodeBlocks.each(function() {

    // create a collapsable div to wrap the code in
    var div = $('<div class="collapse r-code-collapse"></div>');
    if (show)
      div.addClass('in');
    var id = 'rcode-643E0F36' + currentIndex++;
    div.attr('id', id);
    $(this).before(div);
    $(this).detach().appendTo(div);

    // add a show code button right above
    var showCodeText = $('<span>' + (show ? 'Hide' : 'Code') + '</span>');
    var showCodeButton = $('<button type="button" class="btn btn-default btn-xs code-folding-btn pull-right"></button>');
    showCodeButton.append(showCodeText);
    showCodeButton
        .attr('data-toggle', 'collapse')
        .attr('data-target', '#' + id)
        .attr('aria-expanded', show)
        .attr('aria-controls', id);

    var buttonRow = $('<div class="row"></div>');
    var buttonCol = $('<div class="col-md-12"></div>');

    buttonCol.append(showCodeButton);
    buttonRow.append(buttonCol);

    div.before(buttonRow);

    // update state of button on show/hide
    div.on('hidden.bs.collapse', function () {
      showCodeText.text('Code');
    });
    div.on('show.bs.collapse', function () {
      showCodeText.text('Hide');
    });
  });

}

Le fichier Rmd

Le script Rmd pour bookdown inclut toutes les fonctions js directement écrites dans l’en-tête, de sorte que le dossier js n’est pas utile pour le document final lui-même. Dans le bloc setup, j’ai défini l’option pour afficher tous les blocs de code par défaut dans le document, mais vous pouvez choisir de les masquer par défaut avec 'show' === 'hide'. Le fichier Rmd complet à utiliser comme base pour un document bookdown se trouve sur mon répertoire “blog tips” sur github.

  • L’en-tête YAML suivant lit un fichier header.html qui est construit en “knittant” le fichier Rmd.
---
title: "Toggle R code"
author: "StatnMap"
date: '20 mai, 2018'
output:
  bookdown::html_document2:
      includes:
        in_header: header.html
  bookdown::gitbook:
      includes:
        in_header: header.html
---
  • Le code R ci-dessous doit être inclus dans un bloc avec echo=FALSE. Ce code lit les fichiers js et les inclut dans un fichier header.html externe avec le css associé. Ce header.html est ensuite directement inclus dans les fichiers bookdown grâce à la configuration yaml ci-dessus.
    Vous verrez qu’il y a quelques css dans ce code pour gérer pour la position, les couleurs, … des boutons à cliquer.
codejs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/codefolding.js")
collapsejs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/collapse.js")
transitionjs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/transition.js")
dropdownjs <- readr::read_lines("/mnt/Data/autoentrepreneur/js/dropdown.js")

htmlhead <- c(
  paste('
<script>',
paste(transitionjs, collapse = "\n"),
'</script>
<script>',
paste(collapsejs, collapse = "\n"),
'</script>
<script>',
paste(codejs, collapse = "\n"),
'</script>
<script>',
paste(dropdownjs, collapse = "\n"),
'</script>
<style type="text/css">
.code-folding-btn { margin-bottom: 4px; }
.row { display: flex; }
.collapse { display: none; }
.in { display:block }
.pull-right > .dropdown-menu {
    right: 0;
    left: auto;
}
.open > .dropdown-menu {
    display: block;
}
.dropdown-menu {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 1000;
    display: none;
    float: left;
    min-width: 160px;
    padding: 5px 0;
    margin: 2px 0 0;
    font-size: 14px;
    text-align: left;
    list-style: none;
    background-color: #fff;
    -webkit-background-clip: padding-box;
    background-clip: padding-box;
    border: 1px solid #ccc;
    border: 1px solid rgba(0,0,0,.15);
    border-radius: 4px;
    -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
    box-shadow: 0 6px 12px rgba(0,0,0,.175);
}
</style>
<script>
$(document).ready(function () {
  window.initializeCodeFolding("show" === "show");
});
</script>
', sep = "\n"),
  paste0('
<script>
document.write(\'<div class="btn-group pull-right" style="position: absolute; top: 20%; right: 2%; z-index: 200"><button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" data-_extension-text-contrast=""><span>Code</span> <span class="caret"></span></button><ul class="dropdown-menu" style="min-width: 50px;"><li><a id="rmd-show-all-code" href="#">Show All Code</a></li><li><a id="rmd-hide-all-code" href="#">Hide All Code</a></li></ul></div>\')
</script>
')
)

readr::write_lines(htmlhead, path = "/mnt/Data/autoentrepreneur/header.html")

Pliage du code en blogdown

Comme j’ai récemment migré vers hugo, j’ai décidé que je pouvais inclure cette astuce de pliage de code avec blogdown. Comme vous pouvez le voir sur l’article que vous lisez actuellement, le pliage de code est activé. Vous pouvez récupérer tous les codes qui vont suivre dans mon dossier “blog tips” sur github. Si vous voulez voir toutes les modifications que j’ai ajoutées à mon template de site Internet “hugo-statnmap-template” pour cette question de pliage de code, vous pouvez allez voir le “pull request” associé sur le github de mon template. Notez que mon template est multilingue, donc le code associé au pliage de code l’est aussi.
Le fonctionnement dépend des mêmes codes que pour bookdown, sauf que nous pouvons le configurer une seule fois pour l’ensemble du site en utilisant le thème.

Quelques bibliothèques javascripts

Les mêmes bibliothèques javascript sont nécessaires.

  1. Créez un dossier js dans le répertoire static de votre thème hugo.
  2. Ajoutez les fonctions javascript complémentaires de bootstrap: transition.js, collapse.js et dropdown.js (ici par exemple) dans le répertoire js.
  3. Dans ce même répertoire js, créez un nouveau fichier appelé codefolding.js. Le code est exactement le même que pour bookdown.

Le fichier codefolding.js:

window.initializeCodeFolding = function(show) {

  // handlers for show-all and hide all
  $("#rmd-show-all-code").click(function() {
    $('div.r-code-collapse').each(function() {
      $(this).collapse('show');
    });
  });
  $("#rmd-hide-all-code").click(function() {
    $('div.r-code-collapse').each(function() {
      $(this).collapse('hide');
    });
  });

  // index for unique code element ids
  var currentIndex = 1;

  // select all R code blocks
  var rCodeBlocks = $('pre.sourceCode, pre.r, pre.python, pre.bash, pre.sql, pre.cpp, pre.stan, pre.js');
  rCodeBlocks.each(function() {

    // create a collapsable div to wrap the code in
    var div = $('<div class="collapse r-code-collapse"></div>');
    if (show)
      div.addClass('in');
    var id = 'rcode-643E0F36' + currentIndex++;
    div.attr('id', id);
    $(this).before(div);
    $(this).detach().appendTo(div);

    // add a show code button right above
    var showCodeText = $('<span>' + (show ? 'Hide' : 'Code') + '</span>');
    var showCodeButton = $('<button type="button" class="btn btn-default btn-xs code-folding-btn pull-right"></button>');
    showCodeButton.append(showCodeText);
    showCodeButton
        .attr('data-toggle', 'collapse')
        .attr('data-target', '#' + id)
        .attr('aria-expanded', show)
        .attr('aria-controls', id);

    var buttonRow = $('<div class="row"></div>');
    var buttonCol = $('<div class="col-md-12"></div>');

    buttonCol.append(showCodeButton);
    buttonRow.append(buttonCol);

    div.before(buttonRow);

    // update state of button on show/hide
    div.on('hidden.bs.collapse', function () {
      showCodeText.text('Code');
    });
    div.on('show.bs.collapse', function () {
      showCodeText.text('Hide');
    });
  });

}

Modifiez vos modèles de templates Hugo

  1. Dans votre fichier de configuration de thème, vous pouvez ajouter les paramètres suivants pour activer ou désactiver totalement le pliage de code dans le site Web et définir s’il est affiché ou non par défaut. Notez que ceci peut également être défini dans chaque article du blog (voir ci-dessous).
# Set to true to disable code folding
    disable_codefolding = false
# Set to "hide" or "show" all codes by default
    codefolding_show = "hide"
  1. Dans le pied de page principal (footer) ou l’en-tête (header) de votre thème, vous devez charger les libraries javascript. Vous pouvez utiliser le code suivant (footer_js.html).
{{ if not .Site.Params.disable_codefolding }}
  <script src="{{ "js/collapse.js" | relURL }}"></script>
  <script src="{{ "js/dropdown.js" | relURL }}"></script>
  <script src="{{ "js/transition.js" | relURL }}"></script>
{{ end }}
  1. Pour pouvoir activer ou non le pliage de code dans chaque article, vous devez charger codefolding.js dans le pied de page (ou en-tête) de l’article directement. Vous pouvez également laisser la possibilité de choisir pour chaque article si tous les codes sont affichés ou masqués lors du chargement de l’article. C’est la même partie de code que pour bookdown avec "show" === "show", mais ici on utilise .Site.Params ou .Params pour le définir. Par exemple, sur mon site, vous pouvez aller sur les autres articles du blog et vous verrez que tous les codes sont cachés par défaut ("show" === "hide"). En revanche, lorsque vous arrivez sur l’article actuel sur le pliage de code, tous les codes ont été ouverts. C’est parce que j’ai mis codefolding_show: 'show' dans l’en-tête yaml de mon fichier Rmd. De même pour le paramètre disable_codefolding: false que vous pouvez paramétrer dans votre fichier de configuration du site principal ainsi que dans chaque article du blog. Pour ça, vous devez ajouter cette partie de code directement dans votre modèle de pied de page (ou en-tête) d’article (article_footer_js.html).
{{ if and (not .Site.Params.disable_codefolding) (not .Params.disable_codefolding) (in (string .Content) "</pre>") }} 
  <script>
  $(document).ready(function () {
    window.initializeCodeFolding("show" === {{ if isset .Params "codefolding_show" }}{{ .Params.codefolding_show }}{{ else }}{{ default ("hide") .Site.Params.codefolding_show }}{{ end }});
  });
  </script>
  <script src="{{ "js/codefolding.js" | relURL }}"></script>
{{ end }}
  1. Vous devez trouver l’endroit approprié pour ajouter le code suivant dans le template de vos articles, dans l’en-tête de l’article par exemple. Ce code permet de créer un bouton principal pour afficher/masquer tous les blocs de code en même temps. Dans mon cas, j’ai stocké le code suivant dans header_maincodefolding.html dans le répertoire template/partials. J’ai ensuite ajouté {{ partial "header_maincodefolding" . }} dans l’en-tête de mon article.
{{ if and (not .Site.Params.disable_codefolding) (not .Params.disable_codefolding) (in (string .Content) "</pre>") }}
<div id="code-folding-buttons" class="btn-group pull-right">
  <button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" data-_extension-text-contrast="">
    <span>Show/Hide all code</span> 
    <span class="caret"></span>
  </button>
  <ul class="dropdown-menu" style="min-width: 50px;">
  <li><a id="rmd-show-all-code" href="#">Show All Code</a>
  </li><li><a id="rmd-hide-all-code" href="#">Hide All Code</a></li>
  </ul>
</div>
{{ end }}

Ajouter quelques css

Pour que tous les boutons apparaissent correctement sur votre page web, vous devez les placer avec un peu de css. Voici les codes css que j’ai utilisés.

  • Code pour le bouton principal “Afficher/masquer” tous les codes
#code-folding-buttons {float: right;}
#code-folding-buttons .pull-right > .dropdown-menu {
    right: 0;
    left: auto;
}
#code-folding-buttons .btn.btn-default.btn-xs.dropdown-toggle {
    float: right;
}
#code-folding-buttons.open > .dropdown-menu {
    display: block;
}
#code-folding-buttons .dropdown-menu {
    top: 100%;
    left: 0;
    z-index: 1000;
    display: none;
    min-width: 160px;
    padding: 5px 0;
    margin: 2px 0 0;
    font-size: 14px;
    text-align: left;
    list-style: none;
    background-color: #fff;
    -webkit-background-clip: padding-box;
    background-clip: padding-box;
    border: 1px solid #ccc;
    border: 1px solid rgba(0,0,0,.15);
    border-radius: 4px;
    -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
    box-shadow: 0 6px 12px rgba(0,0,0,.175);
}
#code-folding-buttons .dropdown-menu li {
    padding: 5px;
}
  • Code pour le bouton au-dessus de chaque bloc de code
.code-folding-btn { 
    margin-bottom: 4px; 
}
.row { 
    display: flex;
    border-bottom: solid 1px #d7d7d7;
}
.collapse {
    display: none; 
}
.in { 
    display: block ;
    border: solid 1px #d7d7d7;
    border-radius: 5px;
}
.col-md-12 {
    margin: 0 0 0 auto;
}

Une fois encore, je rappelle que tous ces scripts sont disponibles dans mon dossier “blog tips” sur github

comments powered by Disqus