From d38a04d72cb488fc0dea67a22a12c9a3438b4559 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Sat, 18 May 2024 08:58:55 -0400 Subject: [PATCH] Improve panelset code chunk behavior in Quarto (#196) --- .Rbuildignore | 1 + DESCRIPTION | 2 +- NEWS.md | 6 + docs/NEWS.md | 5 + docs/panelset/libs/panelset/panelset.js | 13 ++- docs/panelset/rmarkdown.html | 13 ++- docs/tile-view/index.html | 13 +-- inst/panelset/panelset.js | 13 ++- make.R | 2 + tests/testthat/_snaps/panelset.md | 69 ++++++++++++ tests/testthat/test-panelset.R | 141 ++++++++---------------- 11 files changed, 166 insertions(+), 112 deletions(-) mode change 100644 => 100755 make.R diff --git a/.Rbuildignore b/.Rbuildignore index 618e6c7..38f1ac0 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -16,3 +16,4 @@ ^cran-comments\.md$ ^CRAN-SUBMISSION$ ^_extensions$ +^\.luarc\.json$ diff --git a/DESCRIPTION b/DESCRIPTION index 896f76d..aef7a51 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -61,7 +61,7 @@ Suggests: Config/testthat/edition: 3 Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.1 Collate: 'animate.R' 'banner.R' diff --git a/NEWS.md b/NEWS.md index 9c09653..d6ed84c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -28,6 +28,12 @@ ``` ```` + Unlike in R Markdown, where you always need to place panelset chunks in a + `::: {.panelset}` div, in Quarto, panelset code chunks automatically create + their own panelsets with two tabs (code and output). Use the + `::: {.panelset}` syntax to add more than one panelset code chunk to the same + panelset (#196). + * Nested **panelsets** are now supported (#194)! In xaringan slides and when using the hand-rolled panelset syntax, you can now nest panelsets within panelsets. In R Markdown or Quarto documents, panelsets chunks can be nested diff --git a/docs/NEWS.md b/docs/NEWS.md index 83faece..f91a0e6 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -28,6 +28,11 @@ ``` ```` + Unlike in R Markdown, where you always need to place panelset chunks in a + `::: {.panelset}` div, in Quarto panelset code chunks automatically create + their own panelsets. You can still use the `::: {.panelset}` syntax to add + additional panels to the same panelset ([#196](https://github.com/gadenbuie/xaringanExtra/issues/196)). + * Nested **panelsets** are now supported ([#194](https://github.com/gadenbuie/xaringanExtra/issues/194))! In xaringan slides and when using the hand-rolled panelset syntax, you can now nest panelsets within panelsets. In R Markdown or Quarto documents, panelsets chunks can be nested diff --git a/docs/panelset/libs/panelset/panelset.js b/docs/panelset/libs/panelset/panelset.js index 2d40867..31b8c29 100644 --- a/docs/panelset/libs/panelset/panelset.js +++ b/docs/panelset/libs/panelset/panelset.js @@ -552,7 +552,7 @@ * @returns {HTMLElement} - The new panelset element. */ const initPanelSet = panelset => { - let panels = Array.from(panelset.querySelectorAll(':scope > .panel')) + let panels = Array.from(panelset.querySelectorAll(':scope > .panel, :scope > .cell > .panel')) const pandocSectionSelector = ':is(section, .section)[class*="level"]' if (!panels.length) { @@ -693,7 +693,16 @@ // initialize panels document .querySelectorAll('[data-panelset="true"]') - .forEach(el => el.classList.add('panelset')) + .forEach(el => { + const isCell = el.classList.contains('cell') + const hasParentPanelset = el.parentElement.classList.contains('panelset') + if (!isCell || !hasParentPanelset) { + // We let `data-panelset="true"` create a new panelset, unless it's on + // a code cell that's already inside a panelset, in which case the + // panels will be folded into the parent panelset. + el.classList.add('panelset') + } + }) const panelsets = { atomic: [], nested: [] } Array.from(document.querySelectorAll('.panelset')).forEach(el => { diff --git a/docs/panelset/rmarkdown.html b/docs/panelset/rmarkdown.html index 5642754..30bd163 100644 --- a/docs/panelset/rmarkdown.html +++ b/docs/panelset/rmarkdown.html @@ -1017,7 +1017,7 @@ * @returns {HTMLElement} - The new panelset element. */ const initPanelSet = panelset => { - let panels = Array.from(panelset.querySelectorAll(':scope > .panel')) + let panels = Array.from(panelset.querySelectorAll(':scope > .panel, :scope > .cell > .panel')) const pandocSectionSelector = ':is(section, .section)[class*="level"]' if (!panels.length) { @@ -1158,7 +1158,16 @@ // initialize panels document .querySelectorAll('[data-panelset="true"]') - .forEach(el => el.classList.add('panelset')) + .forEach(el => { + const isCell = el.classList.contains('cell') + const hasParentPanelset = el.parentElement.classList.contains('panelset') + if (!isCell || !hasParentPanelset) { + // We let `data-panelset="true"` create a new panelset, unless it's on + // a code cell that's already inside a panelset, in which case the + // panels will be folded into the parent panelset. + el.classList.add('panelset') + } + }) const panelsets = { atomic: [], nested: [] } Array.from(document.querySelectorAll('.panelset')).forEach(el => { diff --git a/docs/tile-view/index.html b/docs/tile-view/index.html index 3c96530..193f3ff 100644 --- a/docs/tile-view/index.html +++ b/docs/tile-view/index.html @@ -547,13 +547,12 @@ ``` -*## mpg cyl disp hp drat wt qsec vs am gear carb -## Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4 -*## Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4 -## Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1 -## Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 -## Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2 -## Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1 +*## mpg cyl disp hp drat wt qsec vs am gear carb +## Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4 +*## Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4 +## Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1 +## Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 +## [ reached 'max' / getOption("max.print") -- omitted 2 rows ] ``` Question: what does `highlight.output = c(TRUE, FALSE)` mean? (Hint: think about R's recycling of vectors) diff --git a/inst/panelset/panelset.js b/inst/panelset/panelset.js index 2d40867..31b8c29 100644 --- a/inst/panelset/panelset.js +++ b/inst/panelset/panelset.js @@ -552,7 +552,7 @@ * @returns {HTMLElement} - The new panelset element. */ const initPanelSet = panelset => { - let panels = Array.from(panelset.querySelectorAll(':scope > .panel')) + let panels = Array.from(panelset.querySelectorAll(':scope > .panel, :scope > .cell > .panel')) const pandocSectionSelector = ':is(section, .section)[class*="level"]' if (!panels.length) { @@ -693,7 +693,16 @@ // initialize panels document .querySelectorAll('[data-panelset="true"]') - .forEach(el => el.classList.add('panelset')) + .forEach(el => { + const isCell = el.classList.contains('cell') + const hasParentPanelset = el.parentElement.classList.contains('panelset') + if (!isCell || !hasParentPanelset) { + // We let `data-panelset="true"` create a new panelset, unless it's on + // a code cell that's already inside a panelset, in which case the + // panels will be folded into the parent panelset. + el.classList.add('panelset') + } + }) const panelsets = { atomic: [], nested: [] } Array.from(document.querySelectorAll('.panelset')).forEach(el => { diff --git a/make.R b/make.R old mode 100644 new mode 100755 index 07cabfc..e557df4 --- a/make.R +++ b/make.R @@ -1,3 +1,5 @@ +#! /usr/bin/env Rscript + devtools::document() devtools::install(dependencies = FALSE) diff --git a/tests/testthat/_snaps/panelset.md b/tests/testthat/_snaps/panelset.md index 06bb2cf..1d65fa7 100644 --- a/tests/testthat/_snaps/panelset.md +++ b/tests/testthat/_snaps/panelset.md @@ -20,3 +20,72 @@ Output --panel-tab-foreground:var(--text-mild);--panel-tab-background:unset;--panel-tab-active-foreground:var(--text-dark);--panel-tab-active-background:var(--text-lightest);--panel-tab-active-border-color:var(--purple);--panel-tab-hover-background:#fafafa;--panel-tab-hover-border-color:var(--text-lightest);--panel-tabs-separator-color:var(--text-mild);--panel-tab-inactive-opacity:1; +# panelset knitr chunks with plots + + Code + cat(render_slide_text(paste("```{r echo=FALSE}", + "xaringanExtra::use_panelset(in_xaringan = TRUE)", "```", "", + "```{r plot, panelset = TRUE}", "hist(precip)", "```", sep = "\n"))) + Output + .panel[.panel-name[Code] + + ``` r + hist(precip) + ``` + + ] + + .panel[.panel-name[Output] + + ![](slides_files/figure-html/plot-1.png)<!-- --> + + ] + +# panelset knitr chunks with custom tab names + + Code + cat(render_slide_text(paste("```{r echo=FALSE}", + "xaringanExtra::use_panelset(in_xaringan = TRUE)", "```", "", + "```{r plot, panelset = c(source = 'Hist', output = 'Plot')}", "hist(precip)", + "```", sep = "\n"))) + Output + .panel[.panel-name[Hist] + + ``` r + hist(precip) + ``` + + ] + + .panel[.panel-name[Plot] + + ![](slides_files/figure-html/plot-1.png)<!-- --> + + ] + +# panelset knitr chunks with mutiple outputs + + Code + cat(render_slide_text(paste("```{r echo=FALSE}", + "xaringanExtra::use_panelset(in_xaringan = TRUE)", "```", "", + "```{r panelset = TRUE}", "print(\"Oak is strong and also gives shade.\")", + "print(\"The lake sparkled in the red hot sun.\")", "```", sep = "\n"))) + Output + .panel[.panel-name[Code] + + ``` r + print("Oak is strong and also gives shade.") + print("The lake sparkled in the red hot sun.") + ``` + + ] + + .panel[.panel-name[Output] + + ``` + ## [1] "Oak is strong and also gives shade." + ## [1] "The lake sparkled in the red hot sun." + ``` + + ] + diff --git a/tests/testthat/test-panelset.R b/tests/testthat/test-panelset.R index bcefcf2..7f8e591 100644 --- a/tests/testthat/test-panelset.R +++ b/tests/testthat/test-panelset.R @@ -110,114 +110,59 @@ extract_slides_text <- function(path) { } test_that("panelset knitr chunks with plots", { - rmd <- paste( - "```{r echo=FALSE}", - "xaringanExtra::use_panelset(in_xaringan = TRUE)", - "```", - "", - "```{r plot, panelset = TRUE}", - "hist(precip)", - "```", - sep = "\n" - ) - out <- render_slide_text(rmd) - - expect_equal( - out, - paste( - c( - ".panel[.panel-name[Code]", - "", - "```r", - "hist(precip)", - "```", - "", - "]", - "", - ".panel[.panel-name[Output]", - "", - "![](slides_files/figure-html/plot-1.png)<!-- -->", - "", - "]" - ), - collapse = "\n" + expect_snapshot( + cat( + render_slide_text( + paste( + "```{r echo=FALSE}", + "xaringanExtra::use_panelset(in_xaringan = TRUE)", + "```", + "", + "```{r plot, panelset = TRUE}", + "hist(precip)", + "```", + sep = "\n" + ) + ) ) ) }) test_that("panelset knitr chunks with custom tab names", { - rmd <- paste( - "```{r echo=FALSE}", - "xaringanExtra::use_panelset(in_xaringan = TRUE)", - "```", - "", - "```{r plot, panelset = c(source = 'Hist', output = 'Plot')}", - "hist(precip)", - "```", - sep = "\n" - ) - out <- render_slide_text(rmd) - - expect_equal( - out, - paste( - c( - ".panel[.panel-name[Hist]", - "", - "```r", - "hist(precip)", - "```", - "", - "]", - "", - ".panel[.panel-name[Plot]", - "", - "![](slides_files/figure-html/plot-1.png)<!-- -->", - "", - "]" - ), - collapse = "\n" + expect_snapshot( + cat( + render_slide_text( + paste( + "```{r echo=FALSE}", + "xaringanExtra::use_panelset(in_xaringan = TRUE)", + "```", + "", + "```{r plot, panelset = c(source = 'Hist', output = 'Plot')}", + "hist(precip)", + "```", + sep = "\n" + ) + ) ) ) }) test_that("panelset knitr chunks with mutiple outputs", { - rmd <- paste( - "```{r echo=FALSE}", - "xaringanExtra::use_panelset(in_xaringan = TRUE)", - "```", - "", - "```{r panelset = TRUE}", - 'print("Oak is strong and also gives shade.")', - 'print("The lake sparkled in the red hot sun.")', - "```", - sep = "\n" - ) - out <- render_slide_text(rmd) - - expect_equal( - out, - paste( - c( - ".panel[.panel-name[Code]", - "", - "```r", - "print(\"Oak is strong and also gives shade.\")", - "print(\"The lake sparkled in the red hot sun.\")", - "```", - "", - "]", - "", - ".panel[.panel-name[Output]", - "", - "```", - "## [1] \"Oak is strong and also gives shade.\"", - "## [1] \"The lake sparkled in the red hot sun.\"", - "```", - "", - "]" - ), - collapse = "\n" + expect_snapshot( + cat( + render_slide_text( + paste( + "```{r echo=FALSE}", + "xaringanExtra::use_panelset(in_xaringan = TRUE)", + "```", + "", + "```{r panelset = TRUE}", + 'print("Oak is strong and also gives shade.")', + 'print("The lake sparkled in the red hot sun.")', + "```", + sep = "\n" + ) + ) ) ) })