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/NEWS.md b/NEWS.md
index 731195a..ac0533a 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,7 @@
# revealjs.ids (development version)
+- Slides titled "Overview" or "Überblick" are now automatically turned into a table of contents (ported from the old Google Docs slide workflow): their body content is replaced at presentation time with a hierarchical, clickable list of all `#`/`##` slide headings.
+
- Speaker notes (`::: notes`) and timing comments placed before the first heading are now attached to the title slide instead of ending up on a separate empty phantom slide after it, so the title itself can have a time budget and a welcome note (shown in the speaker view and the mobile notes overlay, and included in `slide_timing()` and the pacing timer).
- Disable reveal.js' automatic scroll view on narrow screens (`scrollActivationWidth: 0`): when a phone activated scroll view during load, it restructured the slides DOM and the slides menu degenerated to italic "Slide 1..N" fallback entries. Phones now get the regular swipeable slide view, where menu, speaker notes overlay, and pacing timer work as usual. Re-enable with `reveal_options: list(scrollActivationWidth = 600)` or force scroll view with `view: "scroll"`.
diff --git a/README.Rmd b/README.Rmd
index b8e1d9d..3a34e5a 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -154,6 +154,18 @@
To compute the time budget without rendering the presentation, you can call `slide_timing()` directly on the `.Rmd` source file.
+## Overview Slides
+
+Like in the old Google-Docs-based slide workflow, a slide titled "Overview" or "Überblick" is automatically turned into a table of contents: its body content is replaced with a hierarchical list of all `#` and `##` slide headings, and each entry links to the corresponding slide.
+
+``` markdown
+## Overview
+
+(any placeholder content -- replaced automatically)
+```
+
+The TOC is generated when the presentation is opened, so it always reflects the deck it is part of. Slides without a heading and the title slide are left out.
+
## Incremental Bullets
You can render bullets incrementally by adding the `incremental` option:
diff --git a/README.md b/README.md
index ca5994c..e7b2515 100644
--- a/README.md
+++ b/README.md
@@ -195,6 +195,24 @@
To compute the time budget without rendering the presentation, you can
call `slide_timing()` directly on the `.Rmd` source file.
+## Overview Slides
+
+Like in the old Google-Docs-based slide workflow, a slide titled
+"Overview" or "Überblick" is automatically turned into a table of
+contents: its body content is replaced with a hierarchical list of all
+`#` and `##` slide headings, and each entry links to the corresponding
+slide.
+
+``` markdown
+## Overview
+
+(any placeholder content -- replaced automatically)
+```
+
+The TOC is generated when the presentation is opened, so it always
+reflects the deck it is part of. Slides without a heading and the
+title slide are left out.
+
## Incremental Bullets
You can render bullets incrementally by adding the `incremental` option:
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>
diff --git a/man/revealjs_presentation.Rd b/man/revealjs_presentation.Rd
index 3912575..d9fefed 100644
--- a/man/revealjs_presentation.Rd
+++ b/man/revealjs_presentation.Rd
@@ -226,6 +226,14 @@
The function \code{\link[=slide_timing]{slide_timing()}} computes these totals directly from an
\code{.Rmd} source file without rendering it.
+}
+
+\subsection{Overview slides}{
+
+Like in the original Google Docs slide workflow, a slide titled
+"Overview" or "Überblick" is automatically turned into a table of
+contents: its body content is replaced at presentation time with a
+hierarchical, clickable list of all \verb{#}/\verb{##} slide headings.
\subsection{Search}{
When opt-in, it is possible to show a search box when pressing \code{CTRL + SHIFT + F}. It will seach in the whole presentation, and highlight matched words. The
diff --git a/tests/testthat/test-slide_timing.R b/tests/testthat/test-slide_timing.R
index 48bc3d1..219a60d 100644
--- a/tests/testthat/test-slide_timing.R
+++ b/tests/testthat/test-slide_timing.R
@@ -212,6 +212,29 @@
)
})
+test_that("overview slides get the TOC script", {
+ skip_if_not_pandoc()
+ rmd <- local_temp_rmd_file(
+ "---",
+ "title: Overview test",
+ "output: revealjs.ids::revealjs_presentation",
+ "---",
+ "",
+ "## Overview",
+ "",
+ "Placeholder",
+ "",
+ "# Chapter One",
+ "",
+ "## Slide",
+ "",
+ "Content"
+ )
+ html <- .render_and_read(rmd, xml = FALSE)
+ expect_true(any(grepl("ids-overview-toc", html)))
+ expect_true(any(grepl("Überblick", html, fixed = TRUE)))
+})
+
test_that("a user-supplied totalTime is respected", {
skip_if_not_pandoc()
rmd <- local_temp_rmd_file(