auto-populate Overview/Überblick slides with a clickable TOC
Port the convention from the old Google Docs slide workflow: at
presentation time, any slide whose (only) heading is Overview or
Überblick gets its body content replaced with a hierarchical list of
the deck's h1/h2 slide headings, each entry linking to its slide.
Styled in the ids theme; slides without headings and the title slide
are left out.
Change-Id: I3bfd62d530f297a389030e431a6066b5adc99bb8
diff --git a/inst/reveal.js-5.2.1/dist/theme/ids.css b/inst/reveal.js-5.2.1/dist/theme/ids.css
index 8f95547..5ae8600 100644
--- a/inst/reveal.js-5.2.1/dist/theme/ids.css
+++ b/inst/reveal.js-5.2.1/dist/theme/ids.css
@@ -1054,4 +1054,34 @@
padding: 0;
}
+/*********************************************
+ * OVERVIEW / TOC SLIDES
+ *********************************************/
+.reveal .ids-overview-toc {
+ list-style: none;
+ margin: 0.2em 0 0 0;
+ padding: 0;
+ font-size: 0.72em;
+ line-height: 1.35;
+}
+.reveal .ids-overview-toc > li {
+ margin: 0.45em 0;
+ font-weight: bold;
+}
+.reveal .ids-overview-toc ul {
+ list-style: none;
+ margin: 0.1em 0 0.2em 0;
+ padding: 0 0 0 1.1em;
+ font-weight: normal;
+}
+.reveal .ids-overview-toc ul li {
+ margin: 0.18em 0;
+}
+.reveal .ids-overview-toc a {
+ color: inherit;
+}
+.reveal .ids-overview-toc a:hover {
+ color: var(--r-heading-color);
+}
+
/*# sourceMappingURL=ids.css.map */
diff --git a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
index 651b950..a557cf6 100644
--- a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
+++ b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
@@ -876,8 +876,7 @@
$if(theme-ids)$
<footer id="ids-footer"><span>
- $for(author)$$if(author.name)$$author.name$$else$$author$$endif$${ sep }, $endfor$ · $title$ · $date$</span></footer>
-<button id="ids-fullscreen" aria-label="Toggle fullscreen" title="Fullscreen"></button>
+ $for(author)$$if(author.name)$$author.name$$else$$author$$endif$${ sep }, $endfor$ · $title$ · $date$</span></footer><button id="ids-fullscreen" aria-label="Toggle fullscreen" title="Fullscreen"></button>
<button id="ids-notes" aria-label="Show speaker notes" title="Speaker notes"></button>
<div id="ids-notes-overlay" hidden>
<header id="ids-notes-header">
@@ -977,5 +976,61 @@
})();
</script>
$endif$
+<script>
+ // Slides whose (only) heading is "Overview" or "Überblick" are
+ // automatically populated with a table of contents built from the deck's
+ // h1/h2 slide headings, replacing whatever body content the slide had
+ // (ported from the Google Docs slide workflow). Styling in the ids theme.
+ (function () {
+ var TITLES = ['overview', 'überblick'];
+
+ function isOverviewSlide(slide) {
+ var h = slide.querySelector('h1,h2');
+ return !!h && TITLES.indexOf(h.textContent.trim().toLowerCase()) !== -1;
+ }
+
+ function build() {
+ if (typeof Reveal === 'undefined' || !Reveal.isReady()) return;
+ var slides = Reveal.getSlides().filter(function (slide) {
+ return !slide.classList.contains('title-frame');
+ });
+ slides.forEach(function (target) {
+ if (!isOverviewSlide(target) || target.dataset.tocDone) return;
+ target.dataset.tocDone = '1';
+ var heading = target.querySelector('h1,h2');
+ var list = document.createElement('ul');
+ list.className = 'ids-overview-toc';
+ var subList = null;
+ slides.forEach(function (slide) {
+ if (slide === target || !slide.id || isOverviewSlide(slide)) return;
+ var h = slide.querySelector('h1,h2');
+ if (!h) return;
+ var link = document.createElement('a');
+ link.href = '#/' + slide.id;
+ link.textContent = h.textContent;
+ var item = document.createElement('li');
+ item.appendChild(link);
+ if (h.tagName === 'H1') {
+ list.appendChild(item);
+ subList = null;
+ } else {
+ if (!subList && list.lastChild) {
+ subList = document.createElement('ul');
+ list.lastChild.appendChild(subList);
+ }
+ (subList || list).appendChild(item);
+ }
+ });
+ while (heading.nextSibling) target.removeChild(heading.nextSibling);
+ target.appendChild(list);
+ });
+ }
+
+ if (typeof Reveal !== 'undefined') {
+ if (Reveal.isReady()) build();
+ else Reveal.addEventListener('ready', build);
+ }
+ })();
+</script>
</body>
</html>