├── .gitignore
├── LICENSE
├── README.md
├── _quarto.yml
├── custom.scss
├── data
├── country_rank.rds
├── goalscorers.csv
├── preprocessing.R
├── results.csv
├── shootouts.csv
├── soccer_matches.rds
└── soccer_scorers.rds
├── docs
├── images
│ └── logo.png
├── index.html
├── search.json
├── shiny101-modular-app-blueprint.html
└── site_libs
│ ├── clipboard
│ └── clipboard.min.js
│ ├── quarto-html
│ ├── light-border.css
│ ├── popper.min.js
│ ├── quarto-html.min.css
│ ├── quarto-syntax-highlighting.css
│ ├── tabby.min.js
│ ├── tippy.css
│ └── tippy.umd.min.js
│ └── revealjs
│ ├── dist
│ ├── reset.css
│ ├── reveal.css
│ ├── reveal.esm.js
│ ├── reveal.esm.js.map
│ ├── reveal.js
│ ├── reveal.js.map
│ └── theme
│ │ ├── fonts
│ │ ├── league-gothic
│ │ │ ├── LICENSE
│ │ │ ├── league-gothic.css
│ │ │ ├── league-gothic.eot
│ │ │ ├── league-gothic.ttf
│ │ │ └── league-gothic.woff
│ │ └── source-sans-pro
│ │ │ ├── LICENSE
│ │ │ ├── source-sans-pro-italic.eot
│ │ │ ├── source-sans-pro-italic.ttf
│ │ │ ├── source-sans-pro-italic.woff
│ │ │ ├── source-sans-pro-regular.eot
│ │ │ ├── source-sans-pro-regular.ttf
│ │ │ ├── source-sans-pro-regular.woff
│ │ │ ├── source-sans-pro-semibold.eot
│ │ │ ├── source-sans-pro-semibold.ttf
│ │ │ ├── source-sans-pro-semibold.woff
│ │ │ ├── source-sans-pro-semibolditalic.eot
│ │ │ ├── source-sans-pro-semibolditalic.ttf
│ │ │ ├── source-sans-pro-semibolditalic.woff
│ │ │ └── source-sans-pro.css
│ │ └── quarto.css
│ └── plugin
│ ├── highlight
│ ├── highlight.esm.js
│ ├── highlight.js
│ ├── monokai.css
│ ├── plugin.js
│ └── zenburn.css
│ ├── markdown
│ ├── markdown.esm.js
│ ├── markdown.js
│ └── plugin.js
│ ├── math
│ ├── katex.js
│ ├── math.esm.js
│ ├── math.js
│ ├── mathjax2.js
│ ├── mathjax3.js
│ └── plugin.js
│ ├── notes
│ ├── notes.esm.js
│ ├── notes.js
│ ├── plugin.js
│ └── speaker-view.html
│ ├── pdf-export
│ ├── pdfexport.js
│ └── plugin.yml
│ ├── quarto-line-highlight
│ ├── line-highlight.css
│ ├── line-highlight.js
│ └── plugin.yml
│ ├── quarto-support
│ ├── footer.css
│ ├── plugin.yml
│ └── support.js
│ ├── reveal-menu
│ ├── menu.css
│ ├── menu.js
│ ├── plugin.yml
│ ├── quarto-menu.css
│ └── quarto-menu.js
│ ├── search
│ ├── plugin.js
│ ├── search.esm.js
│ └── search.js
│ └── zoom
│ ├── plugin.js
│ ├── zoom.esm.js
│ └── zoom.js
├── images
├── ProductioniZingShiny.png
├── athlyticz.png
├── background.png
└── logo.png
├── shescores
└── app.R
├── shiny101-modular-app-blueprint.qmd
├── shinyconf2024-shiny101.Rproj
└── templates
├── 00_base.R
├── 01_start.R
├── 02_bslib.R
├── 03_modules.R
├── 04_nested_modules.R
└── 05_sharing_data.R
/.gitignore:
--------------------------------------------------------------------------------
1 | # History files
2 | .Rhistory
3 | .Rapp.history
4 |
5 | # Session Data files
6 | .RData
7 | .RDataTmp
8 |
9 | # User-specific files
10 | .Ruserdata
11 |
12 | # Example code in package build process
13 | *-Ex.R
14 |
15 | # Output files from R CMD build
16 | /*.tar.gz
17 |
18 | # Output files from R CMD check
19 | /*.Rcheck/
20 |
21 | # RStudio files
22 | .Rproj.user/
23 |
24 | # produced vignettes
25 | vignettes/*.html
26 | vignettes/*.pdf
27 |
28 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3
29 | .httr-oauth
30 |
31 | # knitr and R markdown default cache directories
32 | *_cache/
33 | /cache/
34 |
35 | # Temporary files created by R markdown
36 | *.utf8.md
37 | *.knit.md
38 |
39 | # R Environment Variables
40 | .Renviron
41 |
42 | # translation temp files
43 | po/*~
44 |
45 | # RStudio Connect folder
46 | rsconnect/
47 |
48 | /.quarto/
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Veerle van Leemput
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShinyConf2024 workshop: Modular App Blue print
2 |
3 | This repository contains the code for the ShinyConf2024 workshop on building modular Shiny apps. The workshop will be held on the 17th of April, 2024 📅.
4 |
5 | ## About the data
6 |
7 | In this workshop, we will be using data related to Women's International Football results ⚽️. The data is available in the `data` folder and it contains some pre-processing. The data is sourced from [Kaggle](https://www.kaggle.com/datasets/martj42/womens-international-football-results?resource=download).
8 |
9 | ## Shiny courses
10 |
11 | Want to keep learning? We are launching two online courses that are all about Shiny ✨:
12 |
13 | - The [ProductioniZing Shiny Course](https://athlyticz.com/shiny-ii), during which you will learn everything about Shiny: from building your first app to testing and deployment 🚀.
14 | - The [CustomiZing WidgetZ Course](https://athlyticz.com/shiny-iii), which is all about creating outstanding user interfaces with Shiny. During this course you will build your own Shiny package and some very cool widgets.
15 |
16 | [](https://athlyticz.com/shiny-ii)
17 |
18 |
19 | ## Other resources
20 |
21 | Some of my favourite resources to learn more about Shiny:
22 |
23 | - [Outstanding User Interfaces with Shiny](https://unleash-shiny.rinterface.com) by David Granjon
24 | - [Mastering Shiny](https://mastering-shiny.org)
25 | - [Engineering Production-Grade Shiny Applications](https://engineering-shiny.org)
26 | - [Official Shiny Documentation](https://shiny.posit.co/r/getstarted/shiny-basics/lesson1/index.html)
27 |
--------------------------------------------------------------------------------
/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | title: "Shiny 101: The Modular App Blueprint"
3 | type: website
4 | output-dir: docs
5 |
--------------------------------------------------------------------------------
/custom.scss:
--------------------------------------------------------------------------------
1 | /*-- scss:defaults --*/
2 | $theme-darkblue: #085088;
3 | $theme-blue: #3c8fbc;
4 | $theme-white: #F2F2F2;
5 | $theme-pink: #C567A3;
6 | $theme-gray: #9BAEBC;
7 | $theme-oldpink: #d1bcc8;
8 | $theme-darkgrey: #20313a;
9 |
10 | $body-bg: $theme-white;
11 | $link-color: $theme-blue;
12 | $body-color: $theme-darkgrey;
13 | $presentation-heading-color: $theme-darkgrey;
14 |
15 | @import url('https://fonts.googleapis.com/css?family=Lato:400,700');
16 |
17 | $presentation-heading-font: 'Lato', serif;
18 | $font-family-sans-serif: 'Lato', sans-serif;
19 |
20 | .reveal .slide code {
21 | color: $theme-darkblue;
22 | }
23 |
24 | .reveal h2 {
25 | padding-bottom: 40px;
26 | }
27 |
28 | .reveal .slide ul {
29 | list-style-type: square;
30 | }
31 | .reveal .slide ul li::marker {
32 | color: $theme-blue;
33 | }
34 |
35 | .reveal .footer {
36 | height: 10%;
37 | max-height: 50px;
38 | min-height: 20px;
39 | bottom: 0px !important;
40 | background-color: $theme-oldpink;
41 | z-index: 1 !important;
42 | }
43 |
44 | .reveal .footer a {
45 | color: $theme-white !important;
46 | }
47 |
48 | .reveal .footer p {
49 | margin-top: 0.5em;
50 | }
51 |
--------------------------------------------------------------------------------
/data/country_rank.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/data/country_rank.rds
--------------------------------------------------------------------------------
/data/preprocessing.R:
--------------------------------------------------------------------------------
1 | library(dplyr)
2 | library(countrycode)
3 |
4 | # Read the csv files
5 | results <- read.csv("data/results.csv")
6 | goalscorers <- read.csv("data/goalscorers.csv")
7 |
8 | # Merge the two dataframes on date, home_team, away_team
9 | # Remove any games with no known outcome
10 | data_scorers <- results |>
11 | left_join(goalscorers,
12 | by = c("date", "home_team", "away_team")) |>
13 | filter(!is.na(home_score) | !is.na(away_score))
14 |
15 | # function to get the unicode for the country flag
16 | get_flag <- function(country_codes) {
17 | sapply(country_codes, function(country_code) {
18 | # question mark emoji
19 | if (is.null(country_code) || is.na(country_code)) {
20 | return(intToUtf8(10067))
21 | } else {
22 | intToUtf8(127397 + strtoi(charToRaw(toupper(country_code)), 16L))
23 | }
24 | }) |>
25 | as.vector()
26 | }
27 |
28 | data_scorers <-
29 | data_scorers |>
30 | left_join(select(countrycode::codelist, c(country.name.en, iso2c)),
31 | by = c("home_team" = "country.name.en")) |>
32 | mutate(country_flag_home = get_flag(iso2c)) |>
33 | select(-iso2c) |>
34 | left_join(select(countrycode::codelist, c(country.name.en, iso2c)),
35 | by = c("away_team" = "country.name.en")) |>
36 | mutate(country_flag_away = get_flag(iso2c)) |>
37 | select(-iso2c) |>
38 | group_by(scorer, team) |>
39 | summarise(goals = sum(ifelse(team == home_team, home_score, away_score)),
40 | penalties = sum(penalty),
41 | country_flag = first(ifelse(team == home_team, country_flag_home, country_flag_away))) |>
42 | arrange(desc(goals))
43 |
44 |
45 | data_matches <- results |>
46 | filter(!is.na(home_score) | !is.na(away_score)) |>
47 | left_join(select(countrycode::codelist, c(country.name.en, iso2c)),
48 | by = c("home_team" = "country.name.en")) |>
49 | mutate(country_flag_home = get_flag(iso2c)) |>
50 | select(-iso2c) |>
51 | left_join(select(countrycode::codelist, c(country.name.en, iso2c)),
52 | by = c("away_team" = "country.name.en")) |>
53 | mutate(country_flag_away = get_flag(iso2c)) |>
54 | select(-iso2c)
55 |
56 | # construct country rank
57 | # Get the number of matches played by each country
58 | # Get the number of goals scored by each country
59 | # Both when the country when was in the home_team and away_team
60 | country_rank <- data_matches |>
61 | group_by(home_team) |>
62 | summarise(
63 | country = first(home_team),
64 | matches = n(),
65 | goals = sum(home_score),
66 | country_flag = first(country_flag_home)
67 | ) |>
68 | bind_rows(
69 | data_matches |>
70 | group_by(away_team) |>
71 | summarise(
72 | country = first(away_team),
73 | matches = n(),
74 | goals = sum(away_score),
75 | country_flag = first(country_flag_away)
76 | )
77 | ) |>
78 | group_by(country) |>
79 | summarise(
80 | matches = sum(matches),
81 | goals = sum(goals),
82 | country_flag = first(country_flag)
83 | ) |>
84 | arrange(desc(goals))
85 |
86 | saveRDS(data_scorers, "data/soccer_scorers.rds")
87 | saveRDS(data_matches, "data/soccer_matches.rds")
88 | saveRDS(country_rank, "data/country_rank.rds")
89 |
--------------------------------------------------------------------------------
/data/shootouts.csv:
--------------------------------------------------------------------------------
1 | date,home_team,away_team,winner
2 | 1995-06-13,Sweden,China PR,China PR
3 | 1998-12-15,North Korea,Taiwan,North Korea
4 | 1999-07-10,Brazil,Norway,Brazil
5 | 1999-07-10,United States,China PR,United States
6 | 1999-08-02,Canada,Mexico,Mexico
7 | 1999-08-04,Canada,Costa Rica,Costa Rica
8 | 2001-09-12,Vietnam,Myanmar,Vietnam
9 | 2006-12-13,Japan,North Korea,North Korea
10 | 2009-03-12,France,New Zealand,France
11 | 2009-12-16,Thailand,Vietnam,Vietnam
12 | 2011-06-15,Colombia,Denmark,Colombia
13 | 2011-07-09,England,France,France
14 | 2011-07-10,Brazil,United States,United States
15 | 2011-07-17,Japan,United States,Japan
16 | 2011-10-27,Brazil,Canada,Canada
17 | 2012-03-06,New Zealand,Netherlands,Netherlands
18 | 2012-03-06,South Korea,Finland,South Korea
19 | 2013-03-11,Slovenia,Bosnia and Herzegovina,Slovenia
20 | 2013-03-11,Russia,Czech Republic,Russia
21 | 2013-03-13,South Africa,Northern Ireland,South Africa
22 | 2013-12-18,Myanmar,Thailand,Thailand
23 | 2014-03-12,Scotland,South Korea,South Korea
24 | 2015-03-11,Northern Ireland,Bosnia and Herzegovina,Bosnia and Herzegovina
25 | 2015-03-11,Croatia,Wales,Wales
26 | 2015-03-11,South Korea,Belgium,South Korea
27 | 2015-06-26,Germany,France,Germany
28 | 2017-03-08,Belgium,Austria,Belgium
29 | 2017-03-08,Scotland,Wales,Scotland
30 | 2018-03-06,Poland,Ukraine,Ukraine
31 | 2018-03-07,Wales,Austria,Austria
32 | 2018-08-24,Taiwan,Vietnam,Taiwan
33 | 2019-03-04,Croatia,Montenegro,Montenegro
34 | 2019-03-05,India,Kazakhstan,Kazakhstan
35 | 2019-03-06,Austria,Belgium,Belgium
36 | 2019-03-06,North Korea,Italy,North Korea
37 | 2019-06-22,Norway,Australia,Norway
38 | 2019-08-09,Argentina,Colombia,Colombia
39 | 2019-09-01,Brazil,Chile,Chile
40 | 2019-11-10,China PR,Brazil,China PR
41 | 2021-04-13,Switzerland,Czech Republic,Switzerland
42 | 2021-06-10,Estonia,Faroe Islands,Faroe Islands
43 | 2021-07-30,Canada,Brazil,Canada
44 | 2021-07-30,Netherlands,United States,United States
45 | 2021-08-06,Sweden,Canada,Canada
46 | 2021-09-25,Jordan,Iran,Iran
47 | 2021-10-26,Ethiopia,Uganda,Uganda
48 | 2022-01-30,Taiwan,Philippines,Philippines
49 | 2022-02-03,China PR,Japan,China PR
50 | 2022-02-16,Hungary,Russia,Russia
51 | 2022-02-19,Wales,Belgium,Belgium
52 | 2022-02-22,Scotland,Hungary,Scotland
53 | 2022-02-22,Belgium,Russia,Belgium
54 | 2022-02-22,Mali,Senegal,Senegal
55 | 2022-02-23,Sweden,Italy,Sweden
56 | 2022-07-13,Zambia,Senegal,Zambia
57 | 2022-07-17,Senegal,Tunisia,Senegal
58 | 2022-07-18,Morocco,Nigeria,Morocco
59 | 2022-07-23,Papua New Guinea,Tonga,Papua New Guinea
60 | 2022-07-24,Chile,Venezuela,Chile
61 | 2022-07-30,Samoa,Solomon Islands,Solomon Islands
62 | 2023-02-19,Taiwan,Paraguay,Paraguay
63 | 2023-04-06,England,Brazil,England
64 | 2023-08-06,Sweden,United States,Sweden
65 | 2023-08-07,England,Nigeria,England
66 | 2023-08-12,Australia,France,Australia
--------------------------------------------------------------------------------
/data/soccer_matches.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/data/soccer_matches.rds
--------------------------------------------------------------------------------
/data/soccer_scorers.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/data/soccer_scorers.rds
--------------------------------------------------------------------------------
/docs/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/docs/images/logo.png
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
node has the
91 | * 'data-line-numbers' attribute we also generate slide
92 | * numbers.
93 | *
94 | * If the block contains multiple line highlight steps,
95 | * we clone the block and create a fragment for each step.
96 | */
97 | highlightBlock: function( block ) {
98 |
99 | hljs.highlightElement( block );
100 |
101 | // Don't generate line numbers for empty code blocks
102 | if( block.innerHTML.trim().length === 0 ) return;
103 |
104 | if( block.hasAttribute( 'data-line-numbers' ) ) {
105 | hljs.lineNumbersBlock( block, { singleLine: true } );
106 |
107 | var scrollState = { currentBlock: block };
108 |
109 | // If there is more than one highlight step, generate
110 | // fragments
111 | var highlightSteps = Plugin.deserializeHighlightSteps( block.getAttribute( 'data-line-numbers' ) );
112 | if( highlightSteps.length > 1 ) {
113 |
114 | // If the original code block has a fragment-index,
115 | // each clone should follow in an incremental sequence
116 | var fragmentIndex = parseInt( block.getAttribute( 'data-fragment-index' ), 10 );
117 |
118 | if( typeof fragmentIndex !== 'number' || isNaN( fragmentIndex ) ) {
119 | fragmentIndex = null;
120 | }
121 |
122 | // Generate fragments for all steps except the original block
123 | highlightSteps.slice(1).forEach( function( highlight ) {
124 |
125 | var fragmentBlock = block.cloneNode( true );
126 | fragmentBlock.setAttribute( 'data-line-numbers', Plugin.serializeHighlightSteps( [ highlight ] ) );
127 | fragmentBlock.classList.add( 'fragment' );
128 | block.parentNode.appendChild( fragmentBlock );
129 | Plugin.highlightLines( fragmentBlock );
130 |
131 | if( typeof fragmentIndex === 'number' ) {
132 | fragmentBlock.setAttribute( 'data-fragment-index', fragmentIndex );
133 | fragmentIndex += 1;
134 | }
135 | else {
136 | fragmentBlock.removeAttribute( 'data-fragment-index' );
137 | }
138 |
139 | // Scroll highlights into view as we step through them
140 | fragmentBlock.addEventListener( 'visible', Plugin.scrollHighlightedLineIntoView.bind( Plugin, fragmentBlock, scrollState ) );
141 | fragmentBlock.addEventListener( 'hidden', Plugin.scrollHighlightedLineIntoView.bind( Plugin, fragmentBlock.previousSibling, scrollState ) );
142 |
143 | } );
144 |
145 | block.removeAttribute( 'data-fragment-index' );
146 | block.setAttribute( 'data-line-numbers', Plugin.serializeHighlightSteps( [ highlightSteps[0] ] ) );
147 |
148 | }
149 |
150 | // Scroll the first highlight into view when the slide
151 | // becomes visible. Note supported in IE11 since it lacks
152 | // support for Element.closest.
153 | var slide = typeof block.closest === 'function' ? block.closest( 'section:not(.stack)' ) : null;
154 | if( slide ) {
155 | var scrollFirstHighlightIntoView = function() {
156 | Plugin.scrollHighlightedLineIntoView( block, scrollState, true );
157 | slide.removeEventListener( 'visible', scrollFirstHighlightIntoView );
158 | }
159 | slide.addEventListener( 'visible', scrollFirstHighlightIntoView );
160 | }
161 |
162 | Plugin.highlightLines( block );
163 |
164 | }
165 |
166 | },
167 |
168 | /**
169 | * Animates scrolling to the first highlighted line
170 | * in the given code block.
171 | */
172 | scrollHighlightedLineIntoView: function( block, scrollState, skipAnimation ) {
173 |
174 | cancelAnimationFrame( scrollState.animationFrameID );
175 |
176 | // Match the scroll position of the currently visible
177 | // code block
178 | if( scrollState.currentBlock ) {
179 | block.scrollTop = scrollState.currentBlock.scrollTop;
180 | }
181 |
182 | // Remember the current code block so that we can match
183 | // its scroll position when showing/hiding fragments
184 | scrollState.currentBlock = block;
185 |
186 | var highlightBounds = this.getHighlightedLineBounds( block )
187 | var viewportHeight = block.offsetHeight;
188 |
189 | // Subtract padding from the viewport height
190 | var blockStyles = getComputedStyle( block );
191 | viewportHeight -= parseInt( blockStyles.paddingTop ) + parseInt( blockStyles.paddingBottom );
192 |
193 | // Scroll position which centers all highlights
194 | var startTop = block.scrollTop;
195 | var targetTop = highlightBounds.top + ( Math.min( highlightBounds.bottom - highlightBounds.top, viewportHeight ) - viewportHeight ) / 2;
196 |
197 | // Account for offsets in position applied to the
198 | // that holds our lines of code
199 | var lineTable = block.querySelector( '.hljs-ln' );
200 | if( lineTable ) targetTop += lineTable.offsetTop - parseInt( blockStyles.paddingTop );
201 |
202 | // Make sure the scroll target is within bounds
203 | targetTop = Math.max( Math.min( targetTop, block.scrollHeight - viewportHeight ), 0 );
204 |
205 | if( skipAnimation === true || startTop === targetTop ) {
206 | block.scrollTop = targetTop;
207 | }
208 | else {
209 |
210 | // Don't attempt to scroll if there is no overflow
211 | if( block.scrollHeight <= viewportHeight ) return;
212 |
213 | var time = 0;
214 | var animate = function() {
215 | time = Math.min( time + 0.02, 1 );
216 |
217 | // Update our eased scroll position
218 | block.scrollTop = startTop + ( targetTop - startTop ) * Plugin.easeInOutQuart( time );
219 |
220 | // Keep animating unless we've reached the end
221 | if( time < 1 ) {
222 | scrollState.animationFrameID = requestAnimationFrame( animate );
223 | }
224 | };
225 |
226 | animate();
227 |
228 | }
229 |
230 | },
231 |
232 | /**
233 | * The easing function used when scrolling.
234 | */
235 | easeInOutQuart: function( t ) {
236 |
237 | // easeInOutQuart
238 | return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t;
239 |
240 | },
241 |
242 | getHighlightedLineBounds: function( block ) {
243 |
244 | var highlightedLines = block.querySelectorAll( '.highlight-line' );
245 | if( highlightedLines.length === 0 ) {
246 | return { top: 0, bottom: 0 };
247 | }
248 | else {
249 | var firstHighlight = highlightedLines[0];
250 | var lastHighlight = highlightedLines[ highlightedLines.length -1 ];
251 |
252 | return {
253 | top: firstHighlight.offsetTop,
254 | bottom: lastHighlight.offsetTop + lastHighlight.offsetHeight
255 | }
256 | }
257 |
258 | },
259 |
260 | /**
261 | * Visually emphasize specific lines within a code block.
262 | * This only works on blocks with line numbering turned on.
263 | *
264 | * @param {HTMLElement} block a block
265 | * @param {String} [linesToHighlight] The lines that should be
266 | * highlighted in this format:
267 | * "1" = highlights line 1
268 | * "2,5" = highlights lines 2 & 5
269 | * "2,5-7" = highlights lines 2, 5, 6 & 7
270 | */
271 | highlightLines: function( block, linesToHighlight ) {
272 |
273 | var highlightSteps = Plugin.deserializeHighlightSteps( linesToHighlight || block.getAttribute( 'data-line-numbers' ) );
274 |
275 | if( highlightSteps.length ) {
276 |
277 | highlightSteps[0].forEach( function( highlight ) {
278 |
279 | var elementsToHighlight = [];
280 |
281 | // Highlight a range
282 | if( typeof highlight.end === 'number' ) {
283 | elementsToHighlight = [].slice.call( block.querySelectorAll( 'table tr:nth-child(n+'+highlight.start+'):nth-child(-n+'+highlight.end+')' ) );
284 | }
285 | // Highlight a single line
286 | else if( typeof highlight.start === 'number' ) {
287 | elementsToHighlight = [].slice.call( block.querySelectorAll( 'table tr:nth-child('+highlight.start+')' ) );
288 | }
289 |
290 | if( elementsToHighlight.length ) {
291 | elementsToHighlight.forEach( function( lineElement ) {
292 | lineElement.classList.add( 'highlight-line' );
293 | } );
294 |
295 | block.classList.add( 'has-highlights' );
296 | }
297 |
298 | } );
299 |
300 | }
301 |
302 | },
303 |
304 | /**
305 | * Parses and formats a user-defined string of line
306 | * numbers to highlight.
307 | *
308 | * @example
309 | * Plugin.deserializeHighlightSteps( '1,2|3,5-10' )
310 | * // [
311 | * // [ { start: 1 }, { start: 2 } ],
312 | * // [ { start: 3 }, { start: 5, end: 10 } ]
313 | * // ]
314 | */
315 | deserializeHighlightSteps: function( highlightSteps ) {
316 |
317 | // Remove whitespace
318 | highlightSteps = highlightSteps.replace( /\s/g, '' );
319 |
320 | // Divide up our line number groups
321 | highlightSteps = highlightSteps.split( Plugin.HIGHLIGHT_STEP_DELIMITER );
322 |
323 | return highlightSteps.map( function( highlights ) {
324 |
325 | return highlights.split( Plugin.HIGHLIGHT_LINE_DELIMITER ).map( function( highlight ) {
326 |
327 | // Parse valid line numbers
328 | if( /^[\d-]+$/.test( highlight ) ) {
329 |
330 | highlight = highlight.split( Plugin.HIGHLIGHT_LINE_RANGE_DELIMITER );
331 |
332 | var lineStart = parseInt( highlight[0], 10 ),
333 | lineEnd = parseInt( highlight[1], 10 );
334 |
335 | if( isNaN( lineEnd ) ) {
336 | return {
337 | start: lineStart
338 | };
339 | }
340 | else {
341 | return {
342 | start: lineStart,
343 | end: lineEnd
344 | };
345 | }
346 |
347 | }
348 | // If no line numbers are provided, no code will be highlighted
349 | else {
350 |
351 | return {};
352 |
353 | }
354 |
355 | } );
356 |
357 | } );
358 |
359 | },
360 |
361 | /**
362 | * Serializes parsed line number data into a string so
363 | * that we can store it in the DOM.
364 | */
365 | serializeHighlightSteps: function( highlightSteps ) {
366 |
367 | return highlightSteps.map( function( highlights ) {
368 |
369 | return highlights.map( function( highlight ) {
370 |
371 | // Line range
372 | if( typeof highlight.end === 'number' ) {
373 | return highlight.start + Plugin.HIGHLIGHT_LINE_RANGE_DELIMITER + highlight.end;
374 | }
375 | // Single line
376 | else if( typeof highlight.start === 'number' ) {
377 | return highlight.start;
378 | }
379 | // All lines
380 | else {
381 | return '';
382 | }
383 |
384 | } ).join( Plugin.HIGHLIGHT_LINE_DELIMITER );
385 |
386 | } ).join( Plugin.HIGHLIGHT_STEP_DELIMITER );
387 |
388 | }
389 |
390 | }
391 |
392 | // Function to perform a better "data-trim" on code snippets
393 | // Will slice an indentation amount on each line of the snippet (amount based on the line having the lowest indentation length)
394 | function betterTrim(snippetEl) {
395 | // Helper functions
396 | function trimLeft(val) {
397 | // Adapted from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill
398 | return val.replace(/^[\s\uFEFF\xA0]+/g, '');
399 | }
400 | function trimLineBreaks(input) {
401 | var lines = input.split('\n');
402 |
403 | // Trim line-breaks from the beginning
404 | for (var i = 0; i < lines.length; i++) {
405 | if (lines[i].trim() === '') {
406 | lines.splice(i--, 1);
407 | } else break;
408 | }
409 |
410 | // Trim line-breaks from the end
411 | for (var i = lines.length-1; i >= 0; i--) {
412 | if (lines[i].trim() === '') {
413 | lines.splice(i, 1);
414 | } else break;
415 | }
416 |
417 | return lines.join('\n');
418 | }
419 |
420 | // Main function for betterTrim()
421 | return (function(snippetEl) {
422 | var content = trimLineBreaks(snippetEl.innerHTML);
423 | var lines = content.split('\n');
424 | // Calculate the minimum amount to remove on each line start of the snippet (can be 0)
425 | var pad = lines.reduce(function(acc, line) {
426 | if (line.length > 0 && trimLeft(line).length > 0 && acc > line.length - trimLeft(line).length) {
427 | return line.length - trimLeft(line).length;
428 | }
429 | return acc;
430 | }, Number.POSITIVE_INFINITY);
431 | // Slice each line with this amount
432 | return lines.map(function(line, index) {
433 | return line.slice(pad);
434 | })
435 | .join('\n');
436 | })(snippetEl);
437 | }
438 |
439 | export default () => Plugin;
440 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/highlight/zenburn.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov
4 | based on dark.css by Ivan Sagalaev
5 |
6 | */
7 |
8 | .hljs {
9 | display: block;
10 | overflow-x: auto;
11 | padding: 0.5em;
12 | background: #3f3f3f;
13 | color: #dcdcdc;
14 | }
15 |
16 | .hljs-keyword,
17 | .hljs-selector-tag,
18 | .hljs-tag {
19 | color: #e3ceab;
20 | }
21 |
22 | .hljs-template-tag {
23 | color: #dcdcdc;
24 | }
25 |
26 | .hljs-number {
27 | color: #8cd0d3;
28 | }
29 |
30 | .hljs-variable,
31 | .hljs-template-variable,
32 | .hljs-attribute {
33 | color: #efdcbc;
34 | }
35 |
36 | .hljs-literal {
37 | color: #efefaf;
38 | }
39 |
40 | .hljs-subst {
41 | color: #8f8f8f;
42 | }
43 |
44 | .hljs-title,
45 | .hljs-name,
46 | .hljs-selector-id,
47 | .hljs-selector-class,
48 | .hljs-section,
49 | .hljs-type {
50 | color: #efef8f;
51 | }
52 |
53 | .hljs-symbol,
54 | .hljs-bullet,
55 | .hljs-link {
56 | color: #dca3a3;
57 | }
58 |
59 | .hljs-deletion,
60 | .hljs-string,
61 | .hljs-built_in,
62 | .hljs-builtin-name {
63 | color: #cc9393;
64 | }
65 |
66 | .hljs-addition,
67 | .hljs-comment,
68 | .hljs-quote,
69 | .hljs-meta {
70 | color: #7f9f7f;
71 | }
72 |
73 |
74 | .hljs-emphasis {
75 | font-style: italic;
76 | }
77 |
78 | .hljs-strong {
79 | font-weight: bold;
80 | }
81 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/markdown/plugin.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * The reveal.js markdown plugin. Handles parsing of
3 | * markdown inside of presentations as well as loading
4 | * of external markdown documents.
5 | */
6 |
7 | import { marked } from 'marked';
8 |
9 | const DEFAULT_SLIDE_SEPARATOR = '\r?\n---\r?\n',
10 | DEFAULT_NOTES_SEPARATOR = 'notes?:',
11 | DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR = '\\\.element\\\s*?(.+?)$',
12 | DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR = '\\\.slide:\\\s*?(\\\S.+?)$';
13 |
14 | const SCRIPT_END_PLACEHOLDER = '__SCRIPT_END__';
15 |
16 | const CODE_LINE_NUMBER_REGEX = /\[([\s\d,|-]*)\]/;
17 |
18 | const HTML_ESCAPE_MAP = {
19 | '&': '&',
20 | '<': '<',
21 | '>': '>',
22 | '"': '"',
23 | "'": '''
24 | };
25 |
26 | const Plugin = () => {
27 |
28 | // The reveal.js instance this plugin is attached to
29 | let deck;
30 |
31 | /**
32 | * Retrieves the markdown contents of a slide section
33 | * element. Normalizes leading tabs/whitespace.
34 | */
35 | function getMarkdownFromSlide( section ) {
36 |
37 | // look for a ' );
45 |
46 | var leadingWs = text.match( /^\n?(\s*)/ )[1].length,
47 | leadingTabs = text.match( /^\n?(\t*)/ )[1].length;
48 |
49 | if( leadingTabs > 0 ) {
50 | text = text.replace( new RegExp('\\n?\\t{' + leadingTabs + '}','g'), '\n' );
51 | }
52 | else if( leadingWs > 1 ) {
53 | text = text.replace( new RegExp('\\n? {' + leadingWs + '}', 'g'), '\n' );
54 | }
55 |
56 | return text;
57 |
58 | }
59 |
60 | /**
61 | * Given a markdown slide section element, this will
62 | * return all arguments that aren't related to markdown
63 | * parsing. Used to forward any other user-defined arguments
64 | * to the output markdown slide.
65 | */
66 | function getForwardedAttributes( section ) {
67 |
68 | var attributes = section.attributes;
69 | var result = [];
70 |
71 | for( var i = 0, len = attributes.length; i < len; i++ ) {
72 | var name = attributes[i].name,
73 | value = attributes[i].value;
74 |
75 | // disregard attributes that are used for markdown loading/parsing
76 | if( /data\-(markdown|separator|vertical|notes)/gi.test( name ) ) continue;
77 |
78 | if( value ) {
79 | result.push( name + '="' + value + '"' );
80 | }
81 | else {
82 | result.push( name );
83 | }
84 | }
85 |
86 | return result.join( ' ' );
87 |
88 | }
89 |
90 | /**
91 | * Inspects the given options and fills out default
92 | * values for what's not defined.
93 | */
94 | function getSlidifyOptions( options ) {
95 |
96 | options = options || {};
97 | options.separator = options.separator || DEFAULT_SLIDE_SEPARATOR;
98 | options.notesSeparator = options.notesSeparator || DEFAULT_NOTES_SEPARATOR;
99 | options.attributes = options.attributes || '';
100 |
101 | return options;
102 |
103 | }
104 |
105 | /**
106 | * Helper function for constructing a markdown slide.
107 | */
108 | function createMarkdownSlide( content, options ) {
109 |
110 | options = getSlidifyOptions( options );
111 |
112 | var notesMatch = content.split( new RegExp( options.notesSeparator, 'mgi' ) );
113 |
114 | if( notesMatch.length === 2 ) {
115 | content = notesMatch[0] + '';
116 | }
117 |
118 | // prevent script end tags in the content from interfering
119 | // with parsing
120 | content = content.replace( /<\/script>/g, SCRIPT_END_PLACEHOLDER );
121 |
122 | return '';
123 |
124 | }
125 |
126 | /**
127 | * Parses a data string into multiple slides based
128 | * on the passed in separator arguments.
129 | */
130 | function slidify( markdown, options ) {
131 |
132 | options = getSlidifyOptions( options );
133 |
134 | var separatorRegex = new RegExp( options.separator + ( options.verticalSeparator ? '|' + options.verticalSeparator : '' ), 'mg' ),
135 | horizontalSeparatorRegex = new RegExp( options.separator );
136 |
137 | var matches,
138 | lastIndex = 0,
139 | isHorizontal,
140 | wasHorizontal = true,
141 | content,
142 | sectionStack = [];
143 |
144 | // iterate until all blocks between separators are stacked up
145 | while( matches = separatorRegex.exec( markdown ) ) {
146 | var notes = null;
147 |
148 | // determine direction (horizontal by default)
149 | isHorizontal = horizontalSeparatorRegex.test( matches[0] );
150 |
151 | if( !isHorizontal && wasHorizontal ) {
152 | // create vertical stack
153 | sectionStack.push( [] );
154 | }
155 |
156 | // pluck slide content from markdown input
157 | content = markdown.substring( lastIndex, matches.index );
158 |
159 | if( isHorizontal && wasHorizontal ) {
160 | // add to horizontal stack
161 | sectionStack.push( content );
162 | }
163 | else {
164 | // add to vertical stack
165 | sectionStack[sectionStack.length-1].push( content );
166 | }
167 |
168 | lastIndex = separatorRegex.lastIndex;
169 | wasHorizontal = isHorizontal;
170 | }
171 |
172 | // add the remaining slide
173 | ( wasHorizontal ? sectionStack : sectionStack[sectionStack.length-1] ).push( markdown.substring( lastIndex ) );
174 |
175 | var markdownSections = '';
176 |
177 | // flatten the hierarchical stack, and insert tags
178 | for( var i = 0, len = sectionStack.length; i < len; i++ ) {
179 | // vertical
180 | if( sectionStack[i] instanceof Array ) {
181 | markdownSections += '';
182 |
183 | sectionStack[i].forEach( function( child ) {
184 | markdownSections += '' + createMarkdownSlide( child, options ) + ' ';
185 | } );
186 |
187 | markdownSections += ' ';
188 | }
189 | else {
190 | markdownSections += '' + createMarkdownSlide( sectionStack[i], options ) + ' ';
191 | }
192 | }
193 |
194 | return markdownSections;
195 |
196 | }
197 |
198 | /**
199 | * Parses any current data-markdown slides, splits
200 | * multi-slide markdown into separate sections and
201 | * handles loading of external markdown.
202 | */
203 | function processSlides( scope ) {
204 |
205 | return new Promise( function( resolve ) {
206 |
207 | var externalPromises = [];
208 |
209 | [].slice.call( scope.querySelectorAll( 'section[data-markdown]:not([data-markdown-parsed])') ).forEach( function( section, i ) {
210 |
211 | if( section.getAttribute( 'data-markdown' ).length ) {
212 |
213 | externalPromises.push( loadExternalMarkdown( section ).then(
214 |
215 | // Finished loading external file
216 | function( xhr, url ) {
217 | section.outerHTML = slidify( xhr.responseText, {
218 | separator: section.getAttribute( 'data-separator' ),
219 | verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
220 | notesSeparator: section.getAttribute( 'data-separator-notes' ),
221 | attributes: getForwardedAttributes( section )
222 | });
223 | },
224 |
225 | // Failed to load markdown
226 | function( xhr, url ) {
227 | section.outerHTML = '' +
228 | 'ERROR: The attempt to fetch ' + url + ' failed with HTTP status ' + xhr.status + '.' +
229 | 'Check your browser\'s JavaScript console for more details.' +
230 | 'Remember that you need to serve the presentation HTML from a HTTP server.
' +
231 | ' ';
232 | }
233 |
234 | ) );
235 |
236 | }
237 | else {
238 |
239 | section.outerHTML = slidify( getMarkdownFromSlide( section ), {
240 | separator: section.getAttribute( 'data-separator' ),
241 | verticalSeparator: section.getAttribute( 'data-separator-vertical' ),
242 | notesSeparator: section.getAttribute( 'data-separator-notes' ),
243 | attributes: getForwardedAttributes( section )
244 | });
245 |
246 | }
247 |
248 | });
249 |
250 | Promise.all( externalPromises ).then( resolve );
251 |
252 | } );
253 |
254 | }
255 |
256 | function loadExternalMarkdown( section ) {
257 |
258 | return new Promise( function( resolve, reject ) {
259 |
260 | var xhr = new XMLHttpRequest(),
261 | url = section.getAttribute( 'data-markdown' );
262 |
263 | var datacharset = section.getAttribute( 'data-charset' );
264 |
265 | // see https://developer.mozilla.org/en-US/docs/Web/API/element.getAttribute#Notes
266 | if( datacharset != null && datacharset != '' ) {
267 | xhr.overrideMimeType( 'text/html; charset=' + datacharset );
268 | }
269 |
270 | xhr.onreadystatechange = function( section, xhr ) {
271 | if( xhr.readyState === 4 ) {
272 | // file protocol yields status code 0 (useful for local debug, mobile applications etc.)
273 | if ( ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status === 0 ) {
274 |
275 | resolve( xhr, url );
276 |
277 | }
278 | else {
279 |
280 | reject( xhr, url );
281 |
282 | }
283 | }
284 | }.bind( this, section, xhr );
285 |
286 | xhr.open( 'GET', url, true );
287 |
288 | try {
289 | xhr.send();
290 | }
291 | catch ( e ) {
292 | console.warn( 'Failed to get the Markdown file ' + url + '. Make sure that the presentation and the file are served by a HTTP server and the file can be found there. ' + e );
293 | resolve( xhr, url );
294 | }
295 |
296 | } );
297 |
298 | }
299 |
300 | /**
301 | * Check if a node value has the attributes pattern.
302 | * If yes, extract it and add that value as one or several attributes
303 | * to the target element.
304 | *
305 | * You need Cache Killer on Chrome to see the effect on any FOM transformation
306 | * directly on refresh (F5)
307 | * http://stackoverflow.com/questions/5690269/disabling-chrome-cache-for-website-development/7000899#answer-11786277
308 | */
309 | function addAttributeInElement( node, elementTarget, separator ) {
310 |
311 | var mardownClassesInElementsRegex = new RegExp( separator, 'mg' );
312 | var mardownClassRegex = new RegExp( "([^\"= ]+?)=\"([^\"]+?)\"|(data-[^\"= ]+?)(?=[\" ])", 'mg' );
313 | var nodeValue = node.nodeValue;
314 | var matches,
315 | matchesClass;
316 | if( matches = mardownClassesInElementsRegex.exec( nodeValue ) ) {
317 |
318 | var classes = matches[1];
319 | nodeValue = nodeValue.substring( 0, matches.index ) + nodeValue.substring( mardownClassesInElementsRegex.lastIndex );
320 | node.nodeValue = nodeValue;
321 | while( matchesClass = mardownClassRegex.exec( classes ) ) {
322 | if( matchesClass[2] ) {
323 | elementTarget.setAttribute( matchesClass[1], matchesClass[2] );
324 | } else {
325 | elementTarget.setAttribute( matchesClass[3], "" );
326 | }
327 | }
328 | return true;
329 | }
330 | return false;
331 | }
332 |
333 | /**
334 | * Add attributes to the parent element of a text node,
335 | * or the element of an attribute node.
336 | */
337 | function addAttributes( section, element, previousElement, separatorElementAttributes, separatorSectionAttributes ) {
338 |
339 | if ( element != null && element.childNodes != undefined && element.childNodes.length > 0 ) {
340 | var previousParentElement = element;
341 | for( var i = 0; i < element.childNodes.length; i++ ) {
342 | var childElement = element.childNodes[i];
343 | if ( i > 0 ) {
344 | var j = i - 1;
345 | while ( j >= 0 ) {
346 | var aPreviousChildElement = element.childNodes[j];
347 | if ( typeof aPreviousChildElement.setAttribute == 'function' && aPreviousChildElement.tagName != "BR" ) {
348 | previousParentElement = aPreviousChildElement;
349 | break;
350 | }
351 | j = j - 1;
352 | }
353 | }
354 | var parentSection = section;
355 | if( childElement.nodeName == "section" ) {
356 | parentSection = childElement ;
357 | previousParentElement = childElement ;
358 | }
359 | if ( typeof childElement.setAttribute == 'function' || childElement.nodeType == Node.COMMENT_NODE ) {
360 | addAttributes( parentSection, childElement, previousParentElement, separatorElementAttributes, separatorSectionAttributes );
361 | }
362 | }
363 | }
364 |
365 | if ( element.nodeType == Node.COMMENT_NODE ) {
366 | if ( addAttributeInElement( element, previousElement, separatorElementAttributes ) == false ) {
367 | addAttributeInElement( element, section, separatorSectionAttributes );
368 | }
369 | }
370 | }
371 |
372 | /**
373 | * Converts any current data-markdown slides in the
374 | * DOM to HTML.
375 | */
376 | function convertSlides() {
377 |
378 | var sections = deck.getRevealElement().querySelectorAll( '[data-markdown]:not([data-markdown-parsed])');
379 |
380 | [].slice.call( sections ).forEach( function( section ) {
381 |
382 | section.setAttribute( 'data-markdown-parsed', true )
383 |
384 | var notes = section.querySelector( 'aside.notes' );
385 | var markdown = getMarkdownFromSlide( section );
386 |
387 | section.innerHTML = marked( markdown );
388 | addAttributes( section, section, null, section.getAttribute( 'data-element-attributes' ) ||
389 | section.parentNode.getAttribute( 'data-element-attributes' ) ||
390 | DEFAULT_ELEMENT_ATTRIBUTES_SEPARATOR,
391 | section.getAttribute( 'data-attributes' ) ||
392 | section.parentNode.getAttribute( 'data-attributes' ) ||
393 | DEFAULT_SLIDE_ATTRIBUTES_SEPARATOR);
394 |
395 | // If there were notes, we need to re-add them after
396 | // having overwritten the section's HTML
397 | if( notes ) {
398 | section.appendChild( notes );
399 | }
400 |
401 | } );
402 |
403 | return Promise.resolve();
404 |
405 | }
406 |
407 | function escapeForHTML( input ) {
408 |
409 | return input.replace( /([&<>'"])/g, char => HTML_ESCAPE_MAP[char] );
410 |
411 | }
412 |
413 | return {
414 | id: 'markdown',
415 |
416 | /**
417 | * Starts processing and converting Markdown within the
418 | * current reveal.js deck.
419 | */
420 | init: function( reveal ) {
421 |
422 | deck = reveal;
423 |
424 | let { renderer, animateLists, ...markedOptions } = deck.getConfig().markdown || {};
425 |
426 | if( !renderer ) {
427 | renderer = new marked.Renderer();
428 |
429 | renderer.code = ( code, language ) => {
430 |
431 | // Off by default
432 | let lineNumbers = '';
433 |
434 | // Users can opt in to show line numbers and highlight
435 | // specific lines.
436 | // ```javascript [] show line numbers
437 | // ```javascript [1,4-8] highlights lines 1 and 4-8
438 | if( CODE_LINE_NUMBER_REGEX.test( language ) ) {
439 | lineNumbers = language.match( CODE_LINE_NUMBER_REGEX )[1].trim();
440 | lineNumbers = `data-line-numbers="${lineNumbers}"`;
441 | language = language.replace( CODE_LINE_NUMBER_REGEX, '' ).trim();
442 | }
443 |
444 | // Escape before this gets injected into the DOM to
445 | // avoid having the HTML parser alter our code before
446 | // highlight.js is able to read it
447 | code = escapeForHTML( code );
448 |
449 | return `${code}
`;
450 | };
451 | }
452 |
453 | if( animateLists === true ) {
454 | renderer.listitem = text => `${text} `;
455 | }
456 |
457 | marked.setOptions( {
458 | renderer,
459 | ...markedOptions
460 | } );
461 |
462 | return processSlides( deck.getRevealElement() ).then( convertSlides );
463 |
464 | },
465 |
466 | // TODO: Do these belong in the API?
467 | processSlides: processSlides,
468 | convertSlides: convertSlides,
469 | slidify: slidify,
470 | marked: marked
471 | }
472 |
473 | };
474 |
475 | export default Plugin;
476 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/math/katex.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A plugin which enables rendering of math equations inside
3 | * of reveal.js slides. Essentially a thin wrapper for KaTeX.
4 | *
5 | * @author Hakim El Hattab
6 | * @author Gerhard Burger
7 | */
8 | export const KaTeX = () => {
9 | let deck;
10 |
11 | let defaultOptions = {
12 | version: 'latest',
13 | delimiters: [
14 | {left: '$$', right: '$$', display: true}, // Note: $$ has to come before $
15 | {left: '$', right: '$', display: false},
16 | {left: '\\(', right: '\\)', display: false},
17 | {left: '\\[', right: '\\]', display: true}
18 | ],
19 | ignoredTags: ['script', 'noscript', 'style', 'textarea', 'pre']
20 | }
21 |
22 | const loadCss = src => {
23 | let link = document.createElement('link');
24 | link.rel = 'stylesheet';
25 | link.href = src;
26 | document.head.appendChild(link);
27 | };
28 |
29 | /**
30 | * Loads a JavaScript file and returns a Promise for when it is loaded
31 | * Credits: https://aaronsmith.online/easily-load-an-external-script-using-javascript/
32 | */
33 | const loadScript = src => {
34 | return new Promise((resolve, reject) => {
35 | const script = document.createElement('script')
36 | script.type = 'text/javascript'
37 | script.onload = resolve
38 | script.onerror = reject
39 | script.src = src
40 | document.head.append(script)
41 | })
42 | };
43 |
44 | async function loadScripts(urls) {
45 | for(const url of urls) {
46 | await loadScript(url);
47 | }
48 | }
49 |
50 | return {
51 | id: 'katex',
52 |
53 | init: function (reveal) {
54 |
55 | deck = reveal;
56 |
57 | let revealOptions = deck.getConfig().katex || {};
58 |
59 | let options = {...defaultOptions, ...revealOptions};
60 | const {local, version, extensions, ...katexOptions} = options;
61 |
62 | let baseUrl = options.local || 'https://cdn.jsdelivr.net/npm/katex';
63 | let versionString = options.local ? '' : '@' + options.version;
64 |
65 | let cssUrl = baseUrl + versionString + '/dist/katex.min.css';
66 | let katexUrl = baseUrl + versionString + '/dist/katex.min.js';
67 | let mhchemUrl = baseUrl + versionString + '/dist/contrib/mhchem.min.js'
68 | let karUrl = baseUrl + versionString + '/dist/contrib/auto-render.min.js';
69 |
70 | let katexScripts = [katexUrl];
71 | if(options.extensions && options.extensions.includes("mhchem")) {
72 | katexScripts.push(mhchemUrl);
73 | }
74 | katexScripts.push(karUrl);
75 |
76 | const renderMath = () => {
77 | renderMathInElement(reveal.getSlidesElement(), katexOptions);
78 | deck.layout();
79 | }
80 |
81 | loadCss(cssUrl);
82 |
83 | // For some reason dynamically loading with defer attribute doesn't result in the expected behavior, the below code does
84 | loadScripts(katexScripts).then(() => {
85 | if( deck.isReady() ) {
86 | renderMath();
87 | }
88 | else {
89 | deck.on( 'ready', renderMath.bind( this ) );
90 | }
91 | });
92 |
93 | }
94 | }
95 |
96 | };
97 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/math/mathjax2.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A plugin which enables rendering of math equations inside
3 | * of reveal.js slides. Essentially a thin wrapper for MathJax.
4 | *
5 | * @author Hakim El Hattab
6 | */
7 | export const MathJax2 = () => {
8 |
9 | // The reveal.js instance this plugin is attached to
10 | let deck;
11 |
12 | let defaultOptions = {
13 | messageStyle: 'none',
14 | tex2jax: {
15 | inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ],
16 | skipTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ]
17 | },
18 | skipStartupTypeset: true
19 | };
20 |
21 | function loadScript( url, callback ) {
22 |
23 | let head = document.querySelector( 'head' );
24 | let script = document.createElement( 'script' );
25 | script.type = 'text/javascript';
26 | script.src = url;
27 |
28 | // Wrapper for callback to make sure it only fires once
29 | let finish = () => {
30 | if( typeof callback === 'function' ) {
31 | callback.call();
32 | callback = null;
33 | }
34 | }
35 |
36 | script.onload = finish;
37 |
38 | // IE
39 | script.onreadystatechange = () => {
40 | if ( this.readyState === 'loaded' ) {
41 | finish();
42 | }
43 | }
44 |
45 | // Normal browsers
46 | head.appendChild( script );
47 |
48 | }
49 |
50 | return {
51 | id: 'mathjax2',
52 |
53 | init: function( reveal ) {
54 |
55 | deck = reveal;
56 |
57 | let revealOptions = deck.getConfig().mathjax2 || deck.getConfig().math || {};
58 |
59 | let options = { ...defaultOptions, ...revealOptions };
60 | let mathjax = options.mathjax || 'https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js';
61 | let config = options.config || 'TeX-AMS_HTML-full';
62 | let url = mathjax + '?config=' + config;
63 |
64 | options.tex2jax = { ...defaultOptions.tex2jax, ...revealOptions.tex2jax };
65 |
66 | options.mathjax = options.config = null;
67 |
68 | loadScript( url, function() {
69 |
70 | MathJax.Hub.Config( options );
71 |
72 | // Typeset followed by an immediate reveal.js layout since
73 | // the typesetting process could affect slide height
74 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, deck.getRevealElement() ] );
75 | MathJax.Hub.Queue( deck.layout );
76 |
77 | // Reprocess equations in slides when they turn visible
78 | deck.on( 'slidechanged', function( event ) {
79 |
80 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] );
81 |
82 | } );
83 |
84 | } );
85 |
86 | }
87 | }
88 |
89 | };
90 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/math/mathjax3.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A plugin which enables rendering of math equations inside
3 | * of reveal.js slides. Essentially a thin wrapper for MathJax 3
4 | *
5 | * @author Hakim El Hattab
6 | * @author Gerhard Burger
7 | */
8 | export const MathJax3 = () => {
9 |
10 | // The reveal.js instance this plugin is attached to
11 | let deck;
12 |
13 | let defaultOptions = {
14 | tex: {
15 | inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ]
16 | },
17 | options: {
18 | skipHtmlTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ]
19 | },
20 | startup: {
21 | ready: () => {
22 | MathJax.startup.defaultReady();
23 | MathJax.startup.promise.then(() => {
24 | Reveal.layout();
25 | });
26 | }
27 | }
28 | };
29 |
30 | function loadScript( url, callback ) {
31 |
32 | let script = document.createElement( 'script' );
33 | script.type = "text/javascript"
34 | script.id = "MathJax-script"
35 | script.src = url;
36 | script.async = true
37 |
38 | // Wrapper for callback to make sure it only fires once
39 | script.onload = () => {
40 | if (typeof callback === 'function') {
41 | callback.call();
42 | callback = null;
43 | }
44 | };
45 |
46 | document.head.appendChild( script );
47 |
48 | }
49 |
50 | return {
51 | id: 'mathjax3',
52 | init: function(reveal) {
53 |
54 | deck = reveal;
55 |
56 | let revealOptions = deck.getConfig().mathjax3 || {};
57 | let options = {...defaultOptions, ...revealOptions};
58 | options.tex = {...defaultOptions.tex, ...revealOptions.tex}
59 | options.options = {...defaultOptions.options, ...revealOptions.options}
60 | options.startup = {...defaultOptions.startup, ...revealOptions.startup}
61 |
62 | let url = options.mathjax || 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js';
63 | options.mathjax = null;
64 |
65 | window.MathJax = options;
66 |
67 | loadScript( url, function() {
68 | // Reprocess equations in slides when they turn visible
69 | Reveal.addEventListener( 'slidechanged', function( event ) {
70 | MathJax.typeset();
71 | } );
72 | } );
73 |
74 | }
75 | }
76 |
77 | };
78 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/math/plugin.js:
--------------------------------------------------------------------------------
1 | import {KaTeX} from "./katex";
2 | import {MathJax2} from "./mathjax2";
3 | import {MathJax3} from "./mathjax3";
4 |
5 | const defaultTypesetter = MathJax2;
6 |
7 | /*!
8 | * This plugin is a wrapper for the MathJax2,
9 | * MathJax3 and KaTeX typesetter plugins.
10 | */
11 | export default Plugin = Object.assign( defaultTypesetter(), {
12 | KaTeX,
13 | MathJax2,
14 | MathJax3
15 | } );
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/notes/plugin.js:
--------------------------------------------------------------------------------
1 | import speakerViewHTML from './speaker-view.html';
2 |
3 | import { marked } from 'marked';
4 |
5 | /**
6 | * Handles opening of and synchronization with the reveal.js
7 | * notes window.
8 | *
9 | * Handshake process:
10 | * 1. This window posts 'connect' to notes window
11 | * - Includes URL of presentation to show
12 | * 2. Notes window responds with 'connected' when it is available
13 | * 3. This window proceeds to send the current presentation state
14 | * to the notes window
15 | */
16 | const Plugin = () => {
17 |
18 | let connectInterval;
19 | let speakerWindow = null;
20 | let deck;
21 |
22 | /**
23 | * Opens a new speaker view window.
24 | */
25 | function openSpeakerWindow() {
26 |
27 | // If a window is already open, focus it
28 | if( speakerWindow && !speakerWindow.closed ) {
29 | speakerWindow.focus();
30 | }
31 | else {
32 | speakerWindow = window.open( 'about:blank', 'reveal.js - Notes', 'width=1100,height=700' );
33 | speakerWindow.marked = marked;
34 | speakerWindow.document.write( speakerViewHTML );
35 |
36 | if( !speakerWindow ) {
37 | alert( 'Speaker view popup failed to open. Please make sure popups are allowed and reopen the speaker view.' );
38 | return;
39 | }
40 |
41 | connect();
42 | }
43 |
44 | }
45 |
46 | /**
47 | * Reconnect with an existing speaker view window.
48 | */
49 | function reconnectSpeakerWindow( reconnectWindow ) {
50 |
51 | if( speakerWindow && !speakerWindow.closed ) {
52 | speakerWindow.focus();
53 | }
54 | else {
55 | speakerWindow = reconnectWindow;
56 | window.addEventListener( 'message', onPostMessage );
57 | onConnected();
58 | }
59 |
60 | }
61 |
62 | /**
63 | * Connect to the notes window through a postmessage handshake.
64 | * Using postmessage enables us to work in situations where the
65 | * origins differ, such as a presentation being opened from the
66 | * file system.
67 | */
68 | function connect() {
69 |
70 | const presentationURL = deck.getConfig().url;
71 |
72 | const url = typeof presentationURL === 'string' ? presentationURL :
73 | window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search;
74 |
75 | // Keep trying to connect until we get a 'connected' message back
76 | connectInterval = setInterval( function() {
77 | speakerWindow.postMessage( JSON.stringify( {
78 | namespace: 'reveal-notes',
79 | type: 'connect',
80 | state: deck.getState(),
81 | url
82 | } ), '*' );
83 | }, 500 );
84 |
85 | window.addEventListener( 'message', onPostMessage );
86 |
87 | }
88 |
89 | /**
90 | * Calls the specified Reveal.js method with the provided argument
91 | * and then pushes the result to the notes frame.
92 | */
93 | function callRevealApi( methodName, methodArguments, callId ) {
94 |
95 | let result = deck[methodName].apply( deck, methodArguments );
96 | speakerWindow.postMessage( JSON.stringify( {
97 | namespace: 'reveal-notes',
98 | type: 'return',
99 | result,
100 | callId
101 | } ), '*' );
102 |
103 | }
104 |
105 | /**
106 | * Posts the current slide data to the notes window.
107 | */
108 | function post( event ) {
109 |
110 | let slideElement = deck.getCurrentSlide(),
111 | notesElement = slideElement.querySelector( 'aside.notes' ),
112 | fragmentElement = slideElement.querySelector( '.current-fragment' );
113 |
114 | let messageData = {
115 | namespace: 'reveal-notes',
116 | type: 'state',
117 | notes: '',
118 | markdown: false,
119 | whitespace: 'normal',
120 | state: deck.getState()
121 | };
122 |
123 | // Look for notes defined in a slide attribute
124 | if( slideElement.hasAttribute( 'data-notes' ) ) {
125 | messageData.notes = slideElement.getAttribute( 'data-notes' );
126 | messageData.whitespace = 'pre-wrap';
127 | }
128 |
129 | // Look for notes defined in a fragment
130 | if( fragmentElement ) {
131 | let fragmentNotes = fragmentElement.querySelector( 'aside.notes' );
132 | if( fragmentNotes ) {
133 | notesElement = fragmentNotes;
134 | }
135 | else if( fragmentElement.hasAttribute( 'data-notes' ) ) {
136 | messageData.notes = fragmentElement.getAttribute( 'data-notes' );
137 | messageData.whitespace = 'pre-wrap';
138 |
139 | // In case there are slide notes
140 | notesElement = null;
141 | }
142 | }
143 |
144 | // Look for notes defined in an aside element
145 | if( notesElement ) {
146 | messageData.notes = notesElement.innerHTML;
147 | messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string';
148 | }
149 |
150 | speakerWindow.postMessage( JSON.stringify( messageData ), '*' );
151 |
152 | }
153 |
154 | function onPostMessage( event ) {
155 |
156 | let data = JSON.parse( event.data );
157 | if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) {
158 | clearInterval( connectInterval );
159 | onConnected();
160 | }
161 | else if( data && data.namespace === 'reveal-notes' && data.type === 'call' ) {
162 | callRevealApi( data.methodName, data.arguments, data.callId );
163 | }
164 |
165 | }
166 |
167 | /**
168 | * Called once we have established a connection to the notes
169 | * window.
170 | */
171 | function onConnected() {
172 |
173 | // Monitor events that trigger a change in state
174 | deck.on( 'slidechanged', post );
175 | deck.on( 'fragmentshown', post );
176 | deck.on( 'fragmenthidden', post );
177 | deck.on( 'overviewhidden', post );
178 | deck.on( 'overviewshown', post );
179 | deck.on( 'paused', post );
180 | deck.on( 'resumed', post );
181 |
182 | // Post the initial state
183 | post();
184 |
185 | }
186 |
187 | return {
188 | id: 'notes',
189 |
190 | init: function( reveal ) {
191 |
192 | deck = reveal;
193 |
194 | if( !/receiver/i.test( window.location.search ) ) {
195 |
196 | // If the there's a 'notes' query set, open directly
197 | if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) {
198 | openSpeakerWindow();
199 | }
200 | else {
201 | // Keep listening for speaker view hearbeats. If we receive a
202 | // heartbeat from an orphaned window, reconnect it. This ensures
203 | // that we remain connected to the notes even if the presentation
204 | // is reloaded.
205 | window.addEventListener( 'message', event => {
206 |
207 | if( !speakerWindow && typeof event.data === 'string' ) {
208 | let data;
209 |
210 | try {
211 | data = JSON.parse( event.data );
212 | }
213 | catch( error ) {}
214 |
215 | if( data && data.namespace === 'reveal-notes' && data.type === 'heartbeat' ) {
216 | reconnectSpeakerWindow( event.source );
217 | }
218 | }
219 | });
220 | }
221 |
222 | // Open the notes when the 's' key is hit
223 | deck.addKeyBinding({keyCode: 83, key: 'S', description: 'Speaker notes view'}, function() {
224 | openSpeakerWindow();
225 | } );
226 |
227 | }
228 |
229 | },
230 |
231 | open: openSpeakerWindow
232 | };
233 |
234 | };
235 |
236 | export default Plugin;
237 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/pdf-export/pdfexport.js:
--------------------------------------------------------------------------------
1 | var PdfExport = ( function( _Reveal ){
2 |
3 | var Reveal = _Reveal;
4 | var setStylesheet = null;
5 | var installAltKeyBindings = null;
6 |
7 | function getRevealJsPath(){
8 | var regex = /\b[^/]+\/reveal.css$/i;
9 | var script = Array.from( document.querySelectorAll( 'link' ) ).find( function( e ){
10 | return e.attributes.href && e.attributes.href.value.search( regex ) >= 0;
11 | });
12 | if( !script ){
13 | console.error( 'reveal.css could not be found in included elements. Did you rename this file?' );
14 | return '';
15 | }
16 | return script.attributes.href.value.replace( regex, '' );
17 | }
18 |
19 | function setStylesheet3( pdfExport ){
20 | var link = document.querySelector( '#print' );
21 | if( !link ){
22 | link = document.createElement( 'link' );
23 | link.rel = 'stylesheet';
24 | link.id = 'print';
25 | document.querySelector( 'head' ).appendChild( link );
26 | }
27 | var style = 'paper';
28 | if( pdfExport ){
29 | style = 'pdf';
30 | }
31 | link.href = getRevealJsPath() + 'css/print/' + style + '.css';
32 | }
33 |
34 | function setStylesheet4( pdfExport ){
35 | }
36 |
37 | function installAltKeyBindings3(){
38 | }
39 |
40 | function installAltKeyBindings4(){
41 | if( isPrintingPDF() ){
42 | var config = Reveal.getConfig();
43 | var shortcut = config.pdfExportShortcut || 'E';
44 | window.addEventListener( 'keydown', function( e ){
45 | if( e.target.nodeName.toUpperCase() == 'BODY'
46 | && ( e.key.toUpperCase() == shortcut.toUpperCase() || e.keyCode == shortcut.toUpperCase().charCodeAt( 0 ) ) ){
47 | e.preventDefault();
48 | togglePdfExport();
49 | return false;
50 | }
51 | }, true );
52 | }
53 | }
54 |
55 | function isPrintingPDF(){
56 | return ( /print-pdf/gi ).test( window.location.search );
57 | }
58 |
59 | function togglePdfExport(){
60 | var url_doc = new URL( document.URL );
61 | var query_doc = new URLSearchParams( url_doc.searchParams );
62 | if( isPrintingPDF() ){
63 | query_doc.delete( 'print-pdf' );
64 | }else{
65 | query_doc.set( 'print-pdf', '' );
66 | }
67 | url_doc.search = ( query_doc.toString() ? '?' + query_doc.toString() : '' );
68 | window.location.href = url_doc.toString();
69 | }
70 |
71 | function installKeyBindings(){
72 | var config = Reveal.getConfig();
73 | var shortcut = config.pdfExportShortcut || 'E';
74 | Reveal.addKeyBinding({
75 | keyCode: shortcut.toUpperCase().charCodeAt( 0 ),
76 | key: shortcut.toUpperCase(),
77 | description: 'PDF export mode'
78 | }, togglePdfExport );
79 | installAltKeyBindings();
80 | }
81 |
82 | function install(){
83 | installKeyBindings();
84 | setStylesheet( isPrintingPDF() );
85 | }
86 |
87 | var Plugin = {
88 | }
89 |
90 | if( Reveal && Reveal.VERSION && Reveal.VERSION.length && Reveal.VERSION[ 0 ] == '3' ){
91 | // reveal 3.x
92 | setStylesheet = setStylesheet3;
93 | installAltKeyBindings = installAltKeyBindings3;
94 | install();
95 | }else{
96 | // must be reveal 4.x
97 | setStylesheet = setStylesheet4;
98 | installAltKeyBindings = installAltKeyBindings4;
99 | Plugin.id = 'pdf-export';
100 | Plugin.init = function( _Reveal ){
101 | Reveal = _Reveal;
102 | install();
103 | };
104 | Plugin.togglePdfExport = function () {
105 | togglePdfExport();
106 | };
107 | }
108 |
109 | return Plugin;
110 |
111 | })( Reveal );
112 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/pdf-export/plugin.yml:
--------------------------------------------------------------------------------
1 | name: PdfExport
2 | script: pdfexport.js
3 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/quarto-line-highlight/line-highlight.css:
--------------------------------------------------------------------------------
1 | .reveal
2 | div.sourceCode
3 | pre
4 | code.has-line-highlights
5 | > span:not(.highlight-line) {
6 | opacity: 0.4;
7 | }
8 |
9 | .reveal pre.numberSource {
10 | padding-left: 0;
11 | }
12 |
13 | .reveal pre.numberSource code > span {
14 | left: -2.1em;
15 | }
16 |
17 | pre.numberSource code > span > a:first-child::before {
18 | left: -0.7em;
19 | }
20 |
21 | .reveal pre > code:not(:first-child).fragment {
22 | position: absolute;
23 | top: 0;
24 | left: 0;
25 | width: 100%;
26 | box-sizing: border-box;
27 | }
28 |
29 | .reveal div.sourceCode pre code {
30 | min-height: 100%;
31 | }
32 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/quarto-line-highlight/line-highlight.js:
--------------------------------------------------------------------------------
1 | window.QuartoLineHighlight = function () {
2 | function isPrintView() {
3 | return /print-pdf/gi.test(window.location.search);
4 | }
5 |
6 | const delimiters = {
7 | step: "|",
8 | line: ",",
9 | lineRange: "-",
10 | };
11 |
12 | const regex = new RegExp(
13 | "^[\\d" + Object.values(delimiters).join("") + "]+$"
14 | );
15 |
16 | function handleLinesSelector(deck, attr) {
17 | // if we are in printview with pdfSeparateFragments: false
18 | // then we'll also want to supress
19 | if (regex.test(attr)) {
20 | if (isPrintView() && deck.getConfig().pdfSeparateFragments !== true) {
21 | return false;
22 | } else {
23 | return true;
24 | }
25 | } else {
26 | return false;
27 | }
28 | }
29 |
30 | const kCodeLineNumbersAttr = "data-code-line-numbers";
31 | const kFragmentIndex = "data-fragment-index";
32 |
33 | function initQuartoLineHighlight(deck) {
34 | const divSourceCode = deck
35 | .getRevealElement()
36 | .querySelectorAll("div.sourceCode");
37 | // Process each div created by Pandoc highlighting - numbered line are already included.
38 | divSourceCode.forEach((el) => {
39 | if (el.hasAttribute(kCodeLineNumbersAttr)) {
40 | const codeLineAttr = el.getAttribute(kCodeLineNumbersAttr);
41 | el.removeAttribute("data-code-line-numbers");
42 | if (handleLinesSelector(deck, codeLineAttr)) {
43 | // Only process if attr is a string to select lines to highlights
44 | // e.g "1|3,6|8-11"
45 | const codeBlock = el.querySelectorAll("pre code");
46 | codeBlock.forEach((code) => {
47 | // move attributes on code block
48 | code.setAttribute(kCodeLineNumbersAttr, codeLineAttr);
49 |
50 | const scrollState = { currentBlock: code };
51 |
52 | // Check if there are steps and duplicate code block accordingly
53 | const highlightSteps = splitLineNumbers(codeLineAttr);
54 | if (highlightSteps.length > 1) {
55 | // If the original code block has a fragment-index,
56 | // each clone should follow in an incremental sequence
57 | let fragmentIndex = parseInt(
58 | code.getAttribute(kFragmentIndex),
59 | 10
60 | );
61 | fragmentIndex =
62 | typeof fragmentIndex !== "number" || isNaN(fragmentIndex)
63 | ? null
64 | : fragmentIndex;
65 |
66 | let stepN = 1;
67 | highlightSteps.slice(1).forEach(
68 | // Generate fragments for all steps except the original block
69 | (step) => {
70 | var fragmentBlock = code.cloneNode(true);
71 | fragmentBlock.setAttribute(
72 | "data-code-line-numbers",
73 | joinLineNumbers([step])
74 | );
75 | fragmentBlock.classList.add("fragment");
76 |
77 | // Pandoc sets id on spans we need to keep unique
78 | fragmentBlock
79 | .querySelectorAll(":scope > span")
80 | .forEach((span) => {
81 | if (span.hasAttribute("id")) {
82 | span.setAttribute(
83 | "id",
84 | span.getAttribute("id").concat("-" + stepN)
85 | );
86 | }
87 | });
88 | stepN = ++stepN;
89 |
90 | // Add duplicated element after existing one
91 | code.parentNode.appendChild(fragmentBlock);
92 |
93 | // Each new element is highlighted based on the new attributes value
94 | highlightCodeBlock(fragmentBlock);
95 |
96 | if (typeof fragmentIndex === "number") {
97 | fragmentBlock.setAttribute(kFragmentIndex, fragmentIndex);
98 | fragmentIndex += 1;
99 | } else {
100 | fragmentBlock.removeAttribute(kFragmentIndex);
101 | }
102 |
103 | // Scroll highlights into view as we step through them
104 | fragmentBlock.addEventListener(
105 | "visible",
106 | scrollHighlightedLineIntoView.bind(
107 | this,
108 | fragmentBlock,
109 | scrollState
110 | )
111 | );
112 | fragmentBlock.addEventListener(
113 | "hidden",
114 | scrollHighlightedLineIntoView.bind(
115 | this,
116 | fragmentBlock.previousSibling,
117 | scrollState
118 | )
119 | );
120 | }
121 | );
122 | code.removeAttribute(kFragmentIndex);
123 | code.setAttribute(
124 | kCodeLineNumbersAttr,
125 | joinLineNumbers([highlightSteps[0]])
126 | );
127 | }
128 |
129 | // Scroll the first highlight into view when the slide becomes visible.
130 | const slide =
131 | typeof code.closest === "function"
132 | ? code.closest("section:not(.stack)")
133 | : null;
134 | if (slide) {
135 | const scrollFirstHighlightIntoView = function () {
136 | scrollHighlightedLineIntoView(code, scrollState, true);
137 | slide.removeEventListener(
138 | "visible",
139 | scrollFirstHighlightIntoView
140 | );
141 | };
142 | slide.addEventListener("visible", scrollFirstHighlightIntoView);
143 | }
144 |
145 | highlightCodeBlock(code);
146 | });
147 | }
148 | }
149 | });
150 | }
151 |
152 | function highlightCodeBlock(codeBlock) {
153 | const highlightSteps = splitLineNumbers(
154 | codeBlock.getAttribute(kCodeLineNumbersAttr)
155 | );
156 |
157 | if (highlightSteps.length) {
158 | // If we have at least one step, we generate fragments
159 | highlightSteps[0].forEach((highlight) => {
160 | // Add expected class on for reveal CSS
161 | codeBlock.parentNode.classList.add("code-wrapper");
162 |
163 | // Select lines to highlight
164 | spanToHighlight = [];
165 | if (typeof highlight.last === "number") {
166 | spanToHighlight = [].slice.call(
167 | codeBlock.querySelectorAll(
168 | ":scope > span:nth-child(n+" +
169 | highlight.first +
170 | "):nth-child(-n+" +
171 | highlight.last +
172 | ")"
173 | )
174 | );
175 | } else if (typeof highlight.first === "number") {
176 | spanToHighlight = [].slice.call(
177 | codeBlock.querySelectorAll(
178 | ":scope > span:nth-child(" + highlight.first + ")"
179 | )
180 | );
181 | }
182 | if (spanToHighlight.length) {
183 | // Add a class on and to select line to highlight
184 | spanToHighlight.forEach((span) =>
185 | span.classList.add("highlight-line")
186 | );
187 | codeBlock.classList.add("has-line-highlights");
188 | }
189 | });
190 | }
191 | }
192 |
193 | /**
194 | * Animates scrolling to the first highlighted line
195 | * in the given code block.
196 | */
197 | function scrollHighlightedLineIntoView(block, scrollState, skipAnimation) {
198 | window.cancelAnimationFrame(scrollState.animationFrameID);
199 |
200 | // Match the scroll position of the currently visible
201 | // code block
202 | if (scrollState.currentBlock) {
203 | block.scrollTop = scrollState.currentBlock.scrollTop;
204 | }
205 |
206 | // Remember the current code block so that we can match
207 | // its scroll position when showing/hiding fragments
208 | scrollState.currentBlock = block;
209 |
210 | const highlightBounds = getHighlightedLineBounds(block);
211 | let viewportHeight = block.offsetHeight;
212 |
213 | // Subtract padding from the viewport height
214 | const blockStyles = window.getComputedStyle(block);
215 | viewportHeight -=
216 | parseInt(blockStyles.paddingTop) + parseInt(blockStyles.paddingBottom);
217 |
218 | // Scroll position which centers all highlights
219 | const startTop = block.scrollTop;
220 | let targetTop =
221 | highlightBounds.top +
222 | (Math.min(highlightBounds.bottom - highlightBounds.top, viewportHeight) -
223 | viewportHeight) /
224 | 2;
225 |
226 | // Make sure the scroll target is within bounds
227 | targetTop = Math.max(
228 | Math.min(targetTop, block.scrollHeight - viewportHeight),
229 | 0
230 | );
231 |
232 | if (skipAnimation === true || startTop === targetTop) {
233 | block.scrollTop = targetTop;
234 | } else {
235 | // Don't attempt to scroll if there is no overflow
236 | if (block.scrollHeight <= viewportHeight) return;
237 |
238 | let time = 0;
239 |
240 | const animate = function () {
241 | time = Math.min(time + 0.02, 1);
242 |
243 | // Update our eased scroll position
244 | block.scrollTop =
245 | startTop + (targetTop - startTop) * easeInOutQuart(time);
246 |
247 | // Keep animating unless we've reached the end
248 | if (time < 1) {
249 | scrollState.animationFrameID = requestAnimationFrame(animate);
250 | }
251 | };
252 |
253 | animate();
254 | }
255 | }
256 |
257 | function getHighlightedLineBounds(block) {
258 | const highlightedLines = block.querySelectorAll(".highlight-line");
259 | if (highlightedLines.length === 0) {
260 | return { top: 0, bottom: 0 };
261 | } else {
262 | const firstHighlight = highlightedLines[0];
263 | const lastHighlight = highlightedLines[highlightedLines.length - 1];
264 |
265 | return {
266 | top: firstHighlight.offsetTop,
267 | bottom: lastHighlight.offsetTop + lastHighlight.offsetHeight,
268 | };
269 | }
270 | }
271 |
272 | /**
273 | * The easing function used when scrolling.
274 | */
275 | function easeInOutQuart(t) {
276 | // easeInOutQuart
277 | return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t;
278 | }
279 |
280 | function splitLineNumbers(lineNumbersAttr) {
281 | // remove space
282 | lineNumbersAttr = lineNumbersAttr.replace("/s/g", "");
283 | // seperate steps (for fragment)
284 | lineNumbersAttr = lineNumbersAttr.split(delimiters.step);
285 |
286 | // for each step, calculate first and last line, if any
287 | return lineNumbersAttr.map((highlights) => {
288 | // detect lines
289 | const lines = highlights.split(delimiters.line);
290 | return lines.map((range) => {
291 | if (/^[\d-]+$/.test(range)) {
292 | range = range.split(delimiters.lineRange);
293 | const firstLine = parseInt(range[0], 10);
294 | const lastLine = range[1] ? parseInt(range[1], 10) : undefined;
295 | return {
296 | first: firstLine,
297 | last: lastLine,
298 | };
299 | } else {
300 | return {};
301 | }
302 | });
303 | });
304 | }
305 |
306 | function joinLineNumbers(splittedLineNumbers) {
307 | return splittedLineNumbers
308 | .map(function (highlights) {
309 | return highlights
310 | .map(function (highlight) {
311 | // Line range
312 | if (typeof highlight.last === "number") {
313 | return highlight.first + delimiters.lineRange + highlight.last;
314 | }
315 | // Single line
316 | else if (typeof highlight.first === "number") {
317 | return highlight.first;
318 | }
319 | // All lines
320 | else {
321 | return "";
322 | }
323 | })
324 | .join(delimiters.line);
325 | })
326 | .join(delimiters.step);
327 | }
328 |
329 | return {
330 | id: "quarto-line-highlight",
331 | init: function (deck) {
332 | initQuartoLineHighlight(deck);
333 |
334 | // If we're printing to PDF, scroll the code highlights of
335 | // all blocks in the deck into view at once
336 | deck.on("pdf-ready", function () {
337 | [].slice
338 | .call(
339 | deck
340 | .getRevealElement()
341 | .querySelectorAll(
342 | "pre code[data-code-line-numbers].current-fragment"
343 | )
344 | )
345 | .forEach(function (block) {
346 | scrollHighlightedLineIntoView(block, {}, true);
347 | });
348 | });
349 | },
350 | };
351 | };
352 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/quarto-line-highlight/plugin.yml:
--------------------------------------------------------------------------------
1 | # adapted from https://github.com/hakimel/reveal.js/tree/master/plugin/highlight
2 | name: QuartoLineHighlight
3 | script: line-highlight.js
4 | stylesheet: line-highlight.css
5 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/quarto-support/footer.css:
--------------------------------------------------------------------------------
1 | .reveal .slide-logo {
2 | display: block;
3 | position: fixed;
4 | bottom: 0;
5 | right: 12px;
6 | max-height: 2.2rem;
7 | height: 100%;
8 | width: auto;
9 | z-index: 2;
10 | }
11 |
12 | .reveal .footer {
13 | display: block;
14 | position: fixed;
15 | bottom: 18px;
16 | width: 100%;
17 | margin: 0 auto;
18 | text-align: center;
19 | font-size: 18px;
20 | z-index: 2;
21 | }
22 |
23 | .reveal .footer > * {
24 | margin-top: 0;
25 | margin-bottom: 0;
26 | }
27 |
28 | .reveal .slide .footer {
29 | display: none;
30 | }
31 |
32 | .reveal .slide-number {
33 | bottom: 10px;
34 | right: 10px;
35 | font-size: 16px;
36 | background-color: transparent;
37 | }
38 |
39 | .reveal.has-logo .slide-number {
40 | bottom: initial;
41 | top: 8px;
42 | right: 8px;
43 | }
44 |
45 | .reveal .slide-number .slide-number-delimiter {
46 | margin: 0;
47 | }
48 |
49 | .reveal .slide-menu-button {
50 | left: 8px;
51 | bottom: 8px;
52 | }
53 |
54 | .reveal .slide-chalkboard-buttons {
55 | position: fixed;
56 | left: 12px;
57 | bottom: 8px;
58 | z-index: 30;
59 | font-size: 24px;
60 | }
61 |
62 | .reveal .slide-chalkboard-buttons.slide-menu-offset {
63 | left: 54px;
64 | }
65 |
66 | .reveal .slide-chalkboard-buttons > span {
67 | margin-right: 14px;
68 | cursor: pointer;
69 | }
70 |
71 | @media screen and (max-width: 800px) {
72 | .reveal .slide-logo {
73 | max-height: 1.1rem;
74 | bottom: -2px;
75 | right: 10px;
76 | }
77 | .reveal .footer {
78 | font-size: 14px;
79 | bottom: 12px;
80 | }
81 | .reveal .slide-number {
82 | font-size: 12px;
83 | bottom: 7px;
84 | }
85 | .reveal .slide-menu-button .fas::before {
86 | height: 1.3rem;
87 | width: 1.3rem;
88 | vertical-align: -0.125em;
89 | background-size: 1.3rem 1.3rem;
90 | }
91 |
92 | .reveal .slide-chalkboard-buttons .fas::before {
93 | height: 0.95rem;
94 | width: 0.95rem;
95 | background-size: 0.95rem 0.95rem;
96 | vertical-align: -0em;
97 | }
98 |
99 | .reveal .slide-chalkboard-buttons.slide-menu-offset {
100 | left: 36px;
101 | }
102 | .reveal .slide-chalkboard-buttons > span {
103 | margin-right: 9px;
104 | }
105 | }
106 |
107 | html.print-pdf .reveal .slide-menu-button,
108 | html.print-pdf .reveal .slide-chalkboard-buttons {
109 | display: none;
110 | }
111 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/quarto-support/plugin.yml:
--------------------------------------------------------------------------------
1 | name: QuartoSupport
2 | script: support.js
3 | stylesheet: footer.css
4 | config:
5 | smaller: false
6 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/quarto-support/support.js:
--------------------------------------------------------------------------------
1 | // catch all plugin for various quarto features
2 | window.QuartoSupport = function () {
3 | function isPrintView() {
4 | return /print-pdf/gi.test(window.location.search);
5 | }
6 |
7 | // implement controlsAudo
8 | function controlsAuto(deck) {
9 | const config = deck.getConfig();
10 | if (config.controlsAuto === true) {
11 | const iframe = window.location !== window.parent.location;
12 | const localhost =
13 | window.location.hostname === "localhost" ||
14 | window.location.hostname === "127.0.0.1";
15 | deck.configure({
16 | controls:
17 | (iframe && !localhost) ||
18 | (deck.hasVerticalSlides() && config.navigationMode !== "linear"),
19 | });
20 | }
21 | }
22 |
23 | // helper to provide event handlers for all links in a container
24 | function handleLinkClickEvents(deck, container) {
25 | Array.from(container.querySelectorAll("a")).forEach((el) => {
26 | const url = el.getAttribute("href");
27 | if (/^(http|www)/gi.test(url)) {
28 | el.addEventListener(
29 | "click",
30 | (ev) => {
31 | const fullscreen = !!window.document.fullscreen;
32 | const dataPreviewLink = el.getAttribute("data-preview-link");
33 |
34 | // if there is a local specifcation then use that
35 | if (dataPreviewLink) {
36 | if (
37 | dataPreviewLink === "true" ||
38 | (dataPreviewLink === "auto" && fullscreen)
39 | ) {
40 | ev.preventDefault();
41 | deck.showPreview(url);
42 | return false;
43 | }
44 | } else {
45 | const previewLinks = !!deck.getConfig().previewLinks;
46 | const previewLinksAuto =
47 | deck.getConfig().previewLinksAuto === true;
48 | if (previewLinks == true || (previewLinksAuto && fullscreen)) {
49 | ev.preventDefault();
50 | deck.showPreview(url);
51 | return false;
52 | }
53 | }
54 |
55 | // if the deck is in an iframe we want to open it externally
56 | // (don't do this when in vscode though as it has its own
57 | // handler for opening links externally that will be play)
58 | const iframe = window.location !== window.parent.location;
59 | if (
60 | iframe &&
61 | !window.location.search.includes("quartoPreviewReqId=")
62 | ) {
63 | ev.preventDefault();
64 | ev.stopImmediatePropagation();
65 | window.open(url, "_blank");
66 | return false;
67 | }
68 |
69 | // if the user has set data-preview-link to "auto" we need to handle the event
70 | // (because reveal will interpret "auto" as true)
71 | if (dataPreviewLink === "auto") {
72 | ev.preventDefault();
73 | ev.stopImmediatePropagation();
74 | const target =
75 | el.getAttribute("target") ||
76 | (ev.ctrlKey || ev.metaKey ? "_blank" : "");
77 | if (target) {
78 | window.open(url, target);
79 | } else {
80 | window.location.href = url;
81 | }
82 | return false;
83 | }
84 | },
85 | false
86 | );
87 | }
88 | });
89 | }
90 |
91 | // implement previewLinksAuto
92 | function previewLinksAuto(deck) {
93 | handleLinkClickEvents(deck, deck.getRevealElement());
94 | }
95 |
96 | // apply styles
97 | function applyGlobalStyles(deck) {
98 | if (deck.getConfig()["smaller"] === true) {
99 | const revealParent = deck.getRevealElement();
100 | revealParent.classList.add("smaller");
101 | }
102 | }
103 |
104 | // add logo image
105 | function addLogoImage(deck) {
106 | const revealParent = deck.getRevealElement();
107 | const logoImg = document.querySelector(".slide-logo");
108 | if (logoImg) {
109 | revealParent.appendChild(logoImg);
110 | revealParent.classList.add("has-logo");
111 | }
112 | }
113 |
114 | // add footer text
115 | function addFooter(deck) {
116 | const revealParent = deck.getRevealElement();
117 | const defaultFooterDiv = document.querySelector(".footer-default");
118 | if (defaultFooterDiv) {
119 | revealParent.appendChild(defaultFooterDiv);
120 | handleLinkClickEvents(deck, defaultFooterDiv);
121 | if (!isPrintView()) {
122 | deck.on("slidechanged", function (ev) {
123 | const prevSlideFooter = document.querySelector(
124 | ".reveal > .footer:not(.footer-default)"
125 | );
126 | if (prevSlideFooter) {
127 | prevSlideFooter.remove();
128 | }
129 | const currentSlideFooter = ev.currentSlide.querySelector(".footer");
130 | if (currentSlideFooter) {
131 | defaultFooterDiv.style.display = "none";
132 | const slideFooter = currentSlideFooter.cloneNode(true);
133 | handleLinkClickEvents(deck, slideFooter);
134 | deck.getRevealElement().appendChild(slideFooter);
135 | } else {
136 | defaultFooterDiv.style.display = "block";
137 | }
138 | });
139 | }
140 | }
141 | }
142 |
143 | // add chalkboard buttons
144 | function addChalkboardButtons(deck) {
145 | const chalkboard = deck.getPlugin("RevealChalkboard");
146 | if (chalkboard && !isPrintView()) {
147 | const revealParent = deck.getRevealElement();
148 | const chalkboardDiv = document.createElement("div");
149 | chalkboardDiv.classList.add("slide-chalkboard-buttons");
150 | if (document.querySelector(".slide-menu-button")) {
151 | chalkboardDiv.classList.add("slide-menu-offset");
152 | }
153 | // add buttons
154 | const buttons = [
155 | {
156 | icon: "easel2",
157 | title: "Toggle Chalkboard (b)",
158 | onclick: chalkboard.toggleChalkboard,
159 | },
160 | {
161 | icon: "brush",
162 | title: "Toggle Notes Canvas (c)",
163 | onclick: chalkboard.toggleNotesCanvas,
164 | },
165 | ];
166 | buttons.forEach(function (button) {
167 | const span = document.createElement("span");
168 | span.title = button.title;
169 | const icon = document.createElement("i");
170 | icon.classList.add("fas");
171 | icon.classList.add("fa-" + button.icon);
172 | span.appendChild(icon);
173 | span.onclick = function (event) {
174 | event.preventDefault();
175 | button.onclick();
176 | };
177 | chalkboardDiv.appendChild(span);
178 | });
179 | revealParent.appendChild(chalkboardDiv);
180 | const config = deck.getConfig();
181 | if (!config.chalkboard.buttons) {
182 | chalkboardDiv.classList.add("hidden");
183 | }
184 |
185 | // show and hide chalkboard buttons on slidechange
186 | deck.on("slidechanged", function (ev) {
187 | const config = deck.getConfig();
188 | let buttons = !!config.chalkboard.buttons;
189 | const slideButtons = ev.currentSlide.getAttribute(
190 | "data-chalkboard-buttons"
191 | );
192 | if (slideButtons) {
193 | if (slideButtons === "true" || slideButtons === "1") {
194 | buttons = true;
195 | } else if (slideButtons === "false" || slideButtons === "0") {
196 | buttons = false;
197 | }
198 | }
199 | if (buttons) {
200 | chalkboardDiv.classList.remove("hidden");
201 | } else {
202 | chalkboardDiv.classList.add("hidden");
203 | }
204 | });
205 | }
206 | }
207 |
208 | function handleTabbyClicks() {
209 | const tabs = document.querySelectorAll(".panel-tabset-tabby > li > a");
210 | for (let i = 0; i < tabs.length; i++) {
211 | const tab = tabs[i];
212 | tab.onclick = function (ev) {
213 | ev.preventDefault();
214 | ev.stopPropagation();
215 | return false;
216 | };
217 | }
218 | }
219 |
220 | function fixupForPrint(deck) {
221 | if (isPrintView()) {
222 | const slides = deck.getSlides();
223 | slides.forEach(function (slide) {
224 | slide.removeAttribute("data-auto-animate");
225 | });
226 | window.document.querySelectorAll(".hljs").forEach(function (el) {
227 | el.classList.remove("hljs");
228 | });
229 | window.document.querySelectorAll(".hljs-ln-code").forEach(function (el) {
230 | el.classList.remove("hljs-ln-code");
231 | });
232 | }
233 | }
234 |
235 | function handleSlideChanges(deck) {
236 | // dispatch for htmlwidgets
237 | const fireSlideEnter = () => {
238 | const event = window.document.createEvent("Event");
239 | event.initEvent("slideenter", true, true);
240 | window.document.dispatchEvent(event);
241 | };
242 |
243 | const fireSlideChanged = (previousSlide, currentSlide) => {
244 | fireSlideEnter();
245 |
246 | // dispatch for shiny
247 | if (window.jQuery) {
248 | if (previousSlide) {
249 | window.jQuery(previousSlide).trigger("hidden");
250 | }
251 | if (currentSlide) {
252 | window.jQuery(currentSlide).trigger("shown");
253 | }
254 | }
255 | };
256 |
257 | // fire slideEnter for tabby tab activations (for htmlwidget resize behavior)
258 | document.addEventListener("tabby", fireSlideEnter, false);
259 |
260 | deck.on("slidechanged", function (event) {
261 | fireSlideChanged(event.previousSlide, event.currentSlide);
262 | });
263 | }
264 |
265 | function workaroundMermaidDistance(deck) {
266 | if (window.document.querySelector("pre.mermaid-js")) {
267 | const slideCount = deck.getTotalSlides();
268 | deck.configure({
269 | mobileViewDistance: slideCount,
270 | viewDistance: slideCount,
271 | });
272 | }
273 | }
274 |
275 | return {
276 | id: "quarto-support",
277 | init: function (deck) {
278 | controlsAuto(deck);
279 | previewLinksAuto(deck);
280 | fixupForPrint(deck);
281 | applyGlobalStyles(deck);
282 | addLogoImage(deck);
283 | addFooter(deck);
284 | addChalkboardButtons(deck);
285 | handleTabbyClicks();
286 | handleSlideChanges(deck);
287 | workaroundMermaidDistance(deck);
288 | },
289 | };
290 | };
291 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/reveal-menu/menu.css:
--------------------------------------------------------------------------------
1 | .slide-menu-wrapper {
2 | font-family: 'Source Sans Pro', Helvetica, sans-serif;
3 | }
4 |
5 | .slide-menu-wrapper .slide-menu {
6 | background-color: #333;
7 | z-index: 200;
8 | position: fixed;
9 | top: 0;
10 | width: 300px;
11 | height: 100%;
12 | /*overflow-y: scroll;*/
13 | transition: transform 0.3s;
14 | font-size: 16px;
15 | font-weight: normal;
16 | }
17 |
18 | .slide-menu-wrapper .slide-menu.slide-menu--wide {
19 | width: 500px;
20 | }
21 |
22 | .slide-menu-wrapper .slide-menu.slide-menu--third {
23 | width: 33%;
24 | }
25 |
26 | .slide-menu-wrapper .slide-menu.slide-menu--half {
27 | width: 50%;
28 | }
29 |
30 | .slide-menu-wrapper .slide-menu.slide-menu--full {
31 | width: 95%;
32 | }
33 |
34 | /*
35 | * Slides menu
36 | */
37 |
38 | .slide-menu-wrapper .slide-menu-items {
39 | margin: 0;
40 | padding: 0;
41 | width: 100%;
42 | border-bottom: solid 1px #555;
43 | }
44 |
45 | .slide-menu-wrapper .slide-menu-item,
46 | .slide-menu-wrapper .slide-menu-item-vertical {
47 | display: block;
48 | text-align: left;
49 | padding: 10px 18px;
50 | color: #aaa;
51 | cursor: pointer;
52 | }
53 |
54 | .slide-menu-wrapper .slide-menu-item-vertical {
55 | padding-left: 30px;
56 | }
57 |
58 | .slide-menu-wrapper .slide-menu--wide .slide-menu-item-vertical,
59 | .slide-menu-wrapper .slide-menu--third .slide-menu-item-vertical,
60 | .slide-menu-wrapper .slide-menu--half .slide-menu-item-vertical,
61 | .slide-menu-wrapper .slide-menu--full .slide-menu-item-vertical,
62 | .slide-menu-wrapper .slide-menu--custom .slide-menu-item-vertical {
63 | padding-left: 50px;
64 | }
65 |
66 | .slide-menu-wrapper .slide-menu-item {
67 | border-top: solid 1px #555;
68 | }
69 |
70 | .slide-menu-wrapper .active-menu-panel li.selected {
71 | background-color: #222;
72 | color: white;
73 | }
74 |
75 | .slide-menu-wrapper .active-menu-panel li.active {
76 | color: #eee;
77 | }
78 |
79 | .slide-menu-wrapper .slide-menu-item.no-title .slide-menu-item-title,
80 | .slide-menu-wrapper .slide-menu-item-vertical.no-title .slide-menu-item-title {
81 | font-style: italic;
82 | }
83 |
84 | .slide-menu-wrapper .slide-menu-item-number {
85 | color: #999;
86 | padding-right: 6px;
87 | }
88 |
89 | .slide-menu-wrapper .slide-menu-item i.far,
90 | .slide-menu-wrapper .slide-menu-item i.fas,
91 | .slide-menu-wrapper .slide-menu-item-vertical i.far,
92 | .slide-menu-wrapper .slide-menu-item-vertical i.fas,
93 | .slide-menu-wrapper .slide-menu-item svg.svg-inline--fa,
94 | .slide-menu-wrapper .slide-menu-item-vertical svg.svg-inline--fa {
95 | padding-right: 12px;
96 | display: none;
97 | }
98 |
99 | .slide-menu-wrapper .slide-menu-item.past i.fas.past,
100 | .slide-menu-wrapper .slide-menu-item-vertical.past i.fas.past,
101 | .slide-menu-wrapper .slide-menu-item.active i.fas.active,
102 | .slide-menu-wrapper .slide-menu-item-vertical.active i.fas.active,
103 | .slide-menu-wrapper .slide-menu-item.future i.far.future,
104 | .slide-menu-wrapper .slide-menu-item-vertical.future i.far.future,
105 | .slide-menu-wrapper .slide-menu-item.past svg.svg-inline--fa.past,
106 | .slide-menu-wrapper .slide-menu-item-vertical.past svg.svg-inline--fa.past,
107 | .slide-menu-wrapper .slide-menu-item.active svg.svg-inline--fa.active,
108 | .slide-menu-wrapper .slide-menu-item-vertical.active svg.svg-inline--fa.active,
109 | .slide-menu-wrapper .slide-menu-item.future svg.svg-inline--fa.future,
110 | .slide-menu-wrapper .slide-menu-item-vertical.future svg.svg-inline--fa.future {
111 | display: inline-block;
112 | }
113 |
114 | .slide-menu-wrapper .slide-menu-item.past i.fas.past,
115 | .slide-menu-wrapper .slide-menu-item-vertical.past i.fas.past,
116 | .slide-menu-wrapper .slide-menu-item.future i.far.future,
117 | .slide-menu-wrapper .slide-menu-item-vertical.future i.far.future,
118 | .slide-menu-wrapper .slide-menu-item.past svg.svg-inline--fa.past,
119 | .slide-menu-wrapper .slide-menu-item-vertical.past svg.svg-inline--fa.past,
120 | .slide-menu-wrapper .slide-menu-item.future svg.svg-inline--fa.future,
121 | .slide-menu-wrapper .slide-menu-item-vertical.future svg.svg-inline--fa.future {
122 | opacity: 0.4;
123 | }
124 |
125 | .slide-menu-wrapper .slide-menu-item.active i.fas.active,
126 | .slide-menu-wrapper .slide-menu-item-vertical.active i.fas.active,
127 | .slide-menu-wrapper .slide-menu-item.active svg.svg-inline--fa.active,
128 | .slide-menu-wrapper .slide-menu-item-vertical.active svg.svg-inline--fa.active {
129 | opacity: 0.8;
130 | }
131 |
132 | .slide-menu-wrapper .slide-menu--left {
133 | left: 0;
134 | -webkit-transform: translateX(-100%);
135 | -ms-transform: translateX(-100%);
136 | transform: translateX(-100%);
137 | }
138 |
139 | .slide-menu-wrapper .slide-menu--left.active {
140 | -webkit-transform: translateX(0);
141 | -ms-transform: translateX(0);
142 | transform: translateX(0);
143 | }
144 |
145 | .slide-menu-wrapper .slide-menu--right {
146 | right: 0;
147 | -webkit-transform: translateX(100%);
148 | -ms-transform: translateX(100%);
149 | transform: translateX(100%);
150 | }
151 |
152 | .slide-menu-wrapper .slide-menu--right.active {
153 | -webkit-transform: translateX(0);
154 | -ms-transform: translateX(0);
155 | transform: translateX(0);
156 | }
157 |
158 | .slide-menu-wrapper {
159 | transition: transform 0.3s;
160 | }
161 |
162 | /*
163 | * Toolbar
164 | */
165 | .slide-menu-wrapper .slide-menu-toolbar {
166 | height: 60px;
167 | width: 100%;
168 | font-size: 12px;
169 | display: table;
170 | table-layout: fixed; /* ensures equal width */
171 | margin: 0;
172 | padding: 0;
173 | border-bottom: solid 2px #666;
174 | }
175 |
176 | .slide-menu-wrapper .slide-menu-toolbar > li {
177 | display: table-cell;
178 | line-height: 150%;
179 | text-align: center;
180 | vertical-align: middle;
181 | cursor: pointer;
182 | color: #aaa;
183 | border-radius: 3px;
184 | }
185 |
186 | .slide-menu-wrapper .slide-menu-toolbar > li.toolbar-panel-button i,
187 | .slide-menu-wrapper
188 | .slide-menu-toolbar
189 | > li.toolbar-panel-button
190 | svg.svg-inline--fa {
191 | font-size: 1.7em;
192 | }
193 |
194 | .slide-menu-wrapper .slide-menu-toolbar > li.active-toolbar-button {
195 | color: white;
196 | text-shadow: 0 1px black;
197 | text-decoration: underline;
198 | }
199 |
200 | .slide-menu-toolbar > li.toolbar-panel-button:hover {
201 | color: white;
202 | }
203 |
204 | .slide-menu-toolbar
205 | > li.toolbar-panel-button:hover
206 | span.slide-menu-toolbar-label,
207 | .slide-menu-wrapper
208 | .slide-menu-toolbar
209 | > li.active-toolbar-button
210 | span.slide-menu-toolbar-label {
211 | visibility: visible;
212 | }
213 |
214 | /*
215 | * Panels
216 | */
217 | .slide-menu-wrapper .slide-menu-panel {
218 | position: absolute;
219 | width: 100%;
220 | visibility: hidden;
221 | height: calc(100% - 60px);
222 | overflow-x: hidden;
223 | overflow-y: auto;
224 | color: #aaa;
225 | }
226 |
227 | .slide-menu-wrapper .slide-menu-panel.active-menu-panel {
228 | visibility: visible;
229 | }
230 |
231 | .slide-menu-wrapper .slide-menu-panel h1,
232 | .slide-menu-wrapper .slide-menu-panel h2,
233 | .slide-menu-wrapper .slide-menu-panel h3,
234 | .slide-menu-wrapper .slide-menu-panel h4,
235 | .slide-menu-wrapper .slide-menu-panel h5,
236 | .slide-menu-wrapper .slide-menu-panel h6 {
237 | margin: 20px 0 10px 0;
238 | color: #fff;
239 | line-height: 1.2;
240 | letter-spacing: normal;
241 | text-shadow: none;
242 | }
243 |
244 | .slide-menu-wrapper .slide-menu-panel h1 {
245 | font-size: 1.6em;
246 | }
247 | .slide-menu-wrapper .slide-menu-panel h2 {
248 | font-size: 1.4em;
249 | }
250 | .slide-menu-wrapper .slide-menu-panel h3 {
251 | font-size: 1.3em;
252 | }
253 | .slide-menu-wrapper .slide-menu-panel h4 {
254 | font-size: 1.1em;
255 | }
256 | .slide-menu-wrapper .slide-menu-panel h5 {
257 | font-size: 1em;
258 | }
259 | .slide-menu-wrapper .slide-menu-panel h6 {
260 | font-size: 0.9em;
261 | }
262 |
263 | .slide-menu-wrapper .slide-menu-panel p {
264 | margin: 10px 0 5px 0;
265 | }
266 |
267 | .slide-menu-wrapper .slide-menu-panel a {
268 | color: #ccc;
269 | text-decoration: underline;
270 | }
271 |
272 | .slide-menu-wrapper .slide-menu-panel a:hover {
273 | color: white;
274 | }
275 |
276 | .slide-menu-wrapper .slide-menu-item a {
277 | text-decoration: none;
278 | }
279 |
280 | .slide-menu-wrapper .slide-menu-custom-panel {
281 | width: calc(100% - 20px);
282 | padding-left: 10px;
283 | padding-right: 10px;
284 | }
285 |
286 | .slide-menu-wrapper .slide-menu-custom-panel .slide-menu-items {
287 | width: calc(100% + 20px);
288 | margin-left: -10px;
289 | margin-right: 10px;
290 | }
291 |
292 | /*
293 | * Theme and Transitions buttons
294 | */
295 |
296 | .slide-menu-wrapper div[data-panel='Themes'] li,
297 | .slide-menu-wrapper div[data-panel='Transitions'] li {
298 | display: block;
299 | text-align: left;
300 | cursor: pointer;
301 | color: #848484;
302 | }
303 |
304 | /*
305 | * Menu controls
306 | */
307 | .reveal .slide-menu-button {
308 | position: fixed;
309 | left: 30px;
310 | bottom: 30px;
311 | z-index: 30;
312 | font-size: 24px;
313 | }
314 |
315 | /*
316 | * Menu overlay
317 | */
318 |
319 | .slide-menu-wrapper .slide-menu-overlay {
320 | position: fixed;
321 | z-index: 199;
322 | top: 0;
323 | left: 0;
324 | overflow: hidden;
325 | width: 0;
326 | height: 0;
327 | background-color: #000;
328 | opacity: 0;
329 | transition: opacity 0.3s, width 0s 0.3s, height 0s 0.3s;
330 | }
331 |
332 | .slide-menu-wrapper .slide-menu-overlay.active {
333 | width: 100%;
334 | height: 100%;
335 | opacity: 0.7;
336 | transition: opacity 0.3s;
337 | }
338 |
339 | /*
340 | * Hide menu for pdf printing
341 | */
342 | body.print-pdf .slide-menu-wrapper .slide-menu,
343 | body.print-pdf .reveal .slide-menu-button,
344 | body.print-pdf .slide-menu-wrapper .slide-menu-overlay {
345 | display: none;
346 | }
347 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/reveal-menu/plugin.yml:
--------------------------------------------------------------------------------
1 | name: RevealMenu
2 | script: [menu.js, quarto-menu.js]
3 | stylesheet: [menu.css, quarto-menu.css]
4 | config:
5 | menu:
6 | side: "left"
7 | useTextContentForMissingTitles: true
8 | markers: false
9 | loadIcons: false
10 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/reveal-menu/quarto-menu.css:
--------------------------------------------------------------------------------
1 | .slide-menu-wrapper .slide-tool-item {
2 | display: block;
3 | text-align: left;
4 | padding: 10px 18px;
5 | color: #aaa;
6 | cursor: pointer;
7 | border-top: solid 1px #555;
8 | }
9 |
10 | .slide-menu-wrapper .slide-tool-item a {
11 | text-decoration: none;
12 | }
13 |
14 | .slide-menu-wrapper .slide-tool-item kbd {
15 | font-family: monospace;
16 | margin-right: 10px;
17 | padding: 3px 8px;
18 | color: inherit;
19 | border: 1px solid;
20 | border-radius: 5px;
21 | border-color: #555;
22 | }
23 |
24 | .slide-menu-wrapper .slide-menu-toolbar > li.active-toolbar-button {
25 | text-decoration: none;
26 | }
27 |
28 | .reveal .slide-menu-button {
29 | left: 8px;
30 | bottom: 8px;
31 | }
32 |
33 | .reveal .slide-menu-button .fas::before,
34 | .reveal .slide-chalkboard-buttons .fas::before,
35 | .slide-menu-wrapper .slide-menu-toolbar .fas::before {
36 | display: inline-block;
37 | height: 2.2rem;
38 | width: 2.2rem;
39 | content: "";
40 | vertical-align: -0.125em;
41 | background-repeat: no-repeat;
42 | background-size: 2.2rem 2.2rem;
43 | }
44 |
45 | .reveal .slide-chalkboard-buttons .fas::before {
46 | height: 1.45rem;
47 | width: 1.45rem;
48 | background-size: 1.45rem 1.45rem;
49 | vertical-align: 0.1em;
50 | }
51 |
52 | .slide-menu-wrapper .slide-menu-toolbar .fas::before {
53 | height: 1.8rem;
54 | width: 1.8rem;
55 | background-size: 1.8rem 1.8rem;
56 | }
57 |
58 | .slide-menu-wrapper .slide-menu-toolbar .fa-images::before {
59 | background-image: url('data:image/svg+xml,');
60 | }
61 |
62 | .slide-menu-wrapper .slide-menu-toolbar .fa-gear::before {
63 | background-image: url('data:image/svg+xml,');
64 | }
65 |
66 | .slide-menu-wrapper .slide-menu-toolbar .fa-times::before {
67 | background-image: url('data:image/svg+xml,');
68 | }
69 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/reveal-menu/quarto-menu.js:
--------------------------------------------------------------------------------
1 | window.revealMenuToolHandler = function (handler) {
2 | return function (event) {
3 | event.preventDefault();
4 | handler();
5 | Reveal.getPlugin("menu").closeMenu();
6 | };
7 | };
8 |
9 | window.RevealMenuToolHandlers = {
10 | fullscreen: revealMenuToolHandler(function () {
11 | const element = document.documentElement;
12 | const requestMethod =
13 | element.requestFullscreen ||
14 | element.webkitRequestFullscreen ||
15 | element.webkitRequestFullScreen ||
16 | element.mozRequestFullScreen ||
17 | element.msRequestFullscreen;
18 | if (requestMethod) {
19 | requestMethod.apply(element);
20 | }
21 | }),
22 | speakerMode: revealMenuToolHandler(function () {
23 | Reveal.getPlugin("notes").open();
24 | }),
25 | keyboardHelp: revealMenuToolHandler(function () {
26 | Reveal.toggleHelp(true);
27 | }),
28 | overview: revealMenuToolHandler(function () {
29 | Reveal.toggleOverview(true);
30 | }),
31 | toggleChalkboard: revealMenuToolHandler(function () {
32 | RevealChalkboard.toggleChalkboard();
33 | }),
34 | toggleNotesCanvas: revealMenuToolHandler(function () {
35 | RevealChalkboard.toggleNotesCanvas();
36 | }),
37 | downloadDrawings: revealMenuToolHandler(function () {
38 | RevealChalkboard.download();
39 | }),
40 | togglePdfExport: revealMenuToolHandler(function () {
41 | PdfExport.togglePdfExport();
42 | }),
43 | };
44 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/search/plugin.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Handles finding a text string anywhere in the slides and showing the next occurrence to the user
3 | * by navigatating to that slide and highlighting it.
4 | *
5 | * @author Jon Snyder , February 2013
6 | */
7 |
8 | const Plugin = () => {
9 |
10 | // The reveal.js instance this plugin is attached to
11 | let deck;
12 |
13 | let searchElement;
14 | let searchButton;
15 | let searchInput;
16 |
17 | let matchedSlides;
18 | let currentMatchedIndex;
19 | let searchboxDirty;
20 | let hilitor;
21 |
22 | function render() {
23 |
24 | searchElement = document.createElement( 'div' );
25 | searchElement.classList.add( 'searchbox' );
26 | searchElement.style.position = 'absolute';
27 | searchElement.style.top = '10px';
28 | searchElement.style.right = '10px';
29 | searchElement.style.zIndex = 10;
30 |
31 | //embedded base64 search icon Designed by Sketchdock - http://www.sketchdock.com/:
32 | searchElement.innerHTML = `
33 | `;
34 |
35 | searchInput = searchElement.querySelector( '.searchinput' );
36 | searchInput.style.width = '240px';
37 | searchInput.style.fontSize = '14px';
38 | searchInput.style.padding = '4px 6px';
39 | searchInput.style.color = '#000';
40 | searchInput.style.background = '#fff';
41 | searchInput.style.borderRadius = '2px';
42 | searchInput.style.border = '0';
43 | searchInput.style.outline = '0';
44 | searchInput.style.boxShadow = '0 2px 18px rgba(0, 0, 0, 0.2)';
45 | searchInput.style['-webkit-appearance'] = 'none';
46 |
47 | deck.getRevealElement().appendChild( searchElement );
48 |
49 | // searchButton.addEventListener( 'click', function(event) {
50 | // doSearch();
51 | // }, false );
52 |
53 | searchInput.addEventListener( 'keyup', function( event ) {
54 | switch (event.keyCode) {
55 | case 13:
56 | event.preventDefault();
57 | doSearch();
58 | searchboxDirty = false;
59 | break;
60 | default:
61 | searchboxDirty = true;
62 | }
63 | }, false );
64 |
65 | closeSearch();
66 |
67 | }
68 |
69 | function openSearch() {
70 | if( !searchElement ) render();
71 |
72 | searchElement.style.display = 'inline';
73 | searchInput.focus();
74 | searchInput.select();
75 | }
76 |
77 | function closeSearch() {
78 | if( !searchElement ) render();
79 |
80 | searchElement.style.display = 'none';
81 | if(hilitor) hilitor.remove();
82 | }
83 |
84 | function toggleSearch() {
85 | if( !searchElement ) render();
86 |
87 | if (searchElement.style.display !== 'inline') {
88 | openSearch();
89 | }
90 | else {
91 | closeSearch();
92 | }
93 | }
94 |
95 | function doSearch() {
96 | //if there's been a change in the search term, perform a new search:
97 | if (searchboxDirty) {
98 | var searchstring = searchInput.value;
99 |
100 | if (searchstring === '') {
101 | if(hilitor) hilitor.remove();
102 | matchedSlides = null;
103 | }
104 | else {
105 | //find the keyword amongst the slides
106 | hilitor = new Hilitor("slidecontent");
107 | matchedSlides = hilitor.apply(searchstring);
108 | currentMatchedIndex = 0;
109 | }
110 | }
111 |
112 | if (matchedSlides) {
113 | //navigate to the next slide that has the keyword, wrapping to the first if necessary
114 | if (matchedSlides.length && (matchedSlides.length <= currentMatchedIndex)) {
115 | currentMatchedIndex = 0;
116 | }
117 | if (matchedSlides.length > currentMatchedIndex) {
118 | deck.slide(matchedSlides[currentMatchedIndex].h, matchedSlides[currentMatchedIndex].v);
119 | currentMatchedIndex++;
120 | }
121 | }
122 | }
123 |
124 | // Original JavaScript code by Chirp Internet: www.chirp.com.au
125 | // Please acknowledge use of this code by including this header.
126 | // 2/2013 jon: modified regex to display any match, not restricted to word boundaries.
127 | function Hilitor(id, tag) {
128 |
129 | var targetNode = document.getElementById(id) || document.body;
130 | var hiliteTag = tag || "EM";
131 | var skipTags = new RegExp("^(?:" + hiliteTag + "|SCRIPT|FORM)$");
132 | var colors = ["#ff6", "#a0ffff", "#9f9", "#f99", "#f6f"];
133 | var wordColor = [];
134 | var colorIdx = 0;
135 | var matchRegex = "";
136 | var matchingSlides = [];
137 |
138 | this.setRegex = function(input)
139 | {
140 | input = input.replace(/^[^\w]+|[^\w]+$/g, "").replace(/[^\w'-]+/g, "|");
141 | matchRegex = new RegExp("(" + input + ")","i");
142 | }
143 |
144 | this.getRegex = function()
145 | {
146 | return matchRegex.toString().replace(/^\/\\b\(|\)\\b\/i$/g, "").replace(/\|/g, " ");
147 | }
148 |
149 | // recursively apply word highlighting
150 | this.hiliteWords = function(node)
151 | {
152 | if(node == undefined || !node) return;
153 | if(!matchRegex) return;
154 | if(skipTags.test(node.nodeName)) return;
155 |
156 | if(node.hasChildNodes()) {
157 | for(var i=0; i < node.childNodes.length; i++)
158 | this.hiliteWords(node.childNodes[i]);
159 | }
160 | if(node.nodeType == 3) { // NODE_TEXT
161 | var nv, regs;
162 | if((nv = node.nodeValue) && (regs = matchRegex.exec(nv))) {
163 | //find the slide's section element and save it in our list of matching slides
164 | var secnode = node;
165 | while (secnode != null && secnode.nodeName != 'SECTION') {
166 | secnode = secnode.parentNode;
167 | }
168 |
169 | var slideIndex = deck.getIndices(secnode);
170 | var slidelen = matchingSlides.length;
171 | var alreadyAdded = false;
172 | for (var i=0; i < slidelen; i++) {
173 | if ( (matchingSlides[i].h === slideIndex.h) && (matchingSlides[i].v === slideIndex.v) ) {
174 | alreadyAdded = true;
175 | }
176 | }
177 | if (! alreadyAdded) {
178 | matchingSlides.push(slideIndex);
179 | }
180 |
181 | if(!wordColor[regs[0].toLowerCase()]) {
182 | wordColor[regs[0].toLowerCase()] = colors[colorIdx++ % colors.length];
183 | }
184 |
185 | var match = document.createElement(hiliteTag);
186 | match.appendChild(document.createTextNode(regs[0]));
187 | match.style.backgroundColor = wordColor[regs[0].toLowerCase()];
188 | match.style.fontStyle = "inherit";
189 | match.style.color = "#000";
190 |
191 | var after = node.splitText(regs.index);
192 | after.nodeValue = after.nodeValue.substring(regs[0].length);
193 | node.parentNode.insertBefore(match, after);
194 | }
195 | }
196 | };
197 |
198 | // remove highlighting
199 | this.remove = function()
200 | {
201 | var arr = document.getElementsByTagName(hiliteTag);
202 | var el;
203 | while(arr.length && (el = arr[0])) {
204 | el.parentNode.replaceChild(el.firstChild, el);
205 | }
206 | };
207 |
208 | // start highlighting at target node
209 | this.apply = function(input)
210 | {
211 | if(input == undefined || !input) return;
212 | this.remove();
213 | this.setRegex(input);
214 | this.hiliteWords(targetNode);
215 | return matchingSlides;
216 | };
217 |
218 | }
219 |
220 | return {
221 |
222 | id: 'search',
223 |
224 | init: reveal => {
225 |
226 | deck = reveal;
227 | deck.registerKeyboardShortcut( 'CTRL + Shift + F', 'Search' );
228 |
229 | document.addEventListener( 'keydown', function( event ) {
230 | if( event.key == "F" && (event.ctrlKey || event.metaKey) ) { //Control+Shift+f
231 | event.preventDefault();
232 | toggleSearch();
233 | }
234 | }, false );
235 |
236 | },
237 |
238 | open: openSearch
239 |
240 | }
241 | };
242 |
243 | export default Plugin;
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/search/search.esm.js:
--------------------------------------------------------------------------------
1 | var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},t=function(e){try{return!!e()}catch(e){return!0}},n=!t((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})),r=function(e){return e&&e.Math==Math&&e},o=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof e&&e)||function(){return this}()||Function("return this")(),i=t,c=/#|\.prototype\./,a=function(e,t){var n=l[u(e)];return n==s||n!=f&&("function"==typeof t?i(t):!!t)},u=a.normalize=function(e){return String(e).replace(c,".").toLowerCase()},l=a.data={},f=a.NATIVE="N",s=a.POLYFILL="P",p=a,g=function(e){return"object"==typeof e?null!==e:"function"==typeof e},d=g,h=function(e){if(!d(e))throw TypeError(String(e)+" is not an object");return e},y=g,v=h,x=function(e){if(!y(e)&&null!==e)throw TypeError("Can't set "+String(e)+" as a prototype");return e},b=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{(e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(n,[]),t=n instanceof Array}catch(e){}return function(n,r){return v(n),x(r),t?e.call(n,r):n.__proto__=r,n}}():void 0),E=g,m=b,S={},w=g,O=o.document,R=w(O)&&w(O.createElement),T=function(e){return R?O.createElement(e):{}},_=!n&&!t((function(){return 7!=Object.defineProperty(T("div"),"a",{get:function(){return 7}}).a})),j=g,P=function(e,t){if(!j(e))return e;var n,r;if(t&&"function"==typeof(n=e.toString)&&!j(r=n.call(e)))return r;if("function"==typeof(n=e.valueOf)&&!j(r=n.call(e)))return r;if(!t&&"function"==typeof(n=e.toString)&&!j(r=n.call(e)))return r;throw TypeError("Can't convert object to primitive value")},I=n,C=_,N=h,A=P,k=Object.defineProperty;S.f=I?k:function(e,t,n){if(N(e),t=A(t,!0),N(n),C)try{return k(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e};var $={},L=function(e){if(null==e)throw TypeError("Can't call method on "+e);return e},M=L,U=function(e){return Object(M(e))},D=U,F={}.hasOwnProperty,z=function(e,t){return F.call(D(e),t)},K={}.toString,B=function(e){return K.call(e).slice(8,-1)},W=B,G="".split,V=t((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==W(e)?G.call(e,""):Object(e)}:Object,Y=L,q=function(e){return V(Y(e))},X=Math.ceil,H=Math.floor,J=function(e){return isNaN(e=+e)?0:(e>0?H:X)(e)},Q=J,Z=Math.min,ee=function(e){return e>0?Z(Q(e),9007199254740991):0},te=J,ne=Math.max,re=Math.min,oe=q,ie=ee,ce=function(e,t){var n=te(e);return n<0?ne(n+t,0):re(n,t)},ae=function(e){return function(t,n,r){var o,i=oe(t),c=ie(i.length),a=ce(r,c);if(e&&n!=n){for(;c>a;)if((o=i[a++])!=o)return!0}else for(;c>a;a++)if((e||a in i)&&i[a]===n)return e||a||0;return!e&&-1}},ue={includes:ae(!0),indexOf:ae(!1)},le={},fe=z,se=q,pe=ue.indexOf,ge=le,de=function(e,t){var n,r=se(e),o=0,i=[];for(n in r)!fe(ge,n)&&fe(r,n)&&i.push(n);for(;t.length>o;)fe(r,n=t[o++])&&(~pe(i,n)||i.push(n));return i},he=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"].concat("length","prototype");$.f=Object.getOwnPropertyNames||function(e){return de(e,he)};var ye={exports:{}},ve=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}},xe=S,be=ve,Ee=n?function(e,t,n){return xe.f(e,t,be(1,n))}:function(e,t,n){return e[t]=n,e},me=o,Se=Ee,we=function(e,t){try{Se(me,e,t)}catch(n){me[e]=t}return t},Oe=we,Re=o["__core-js_shared__"]||Oe("__core-js_shared__",{}),Te=Re;(ye.exports=function(e,t){return Te[e]||(Te[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.12.1",mode:"global",copyright:"© 2021 Denis Pushkarev (zloirock.ru)"});var _e,je,Pe=0,Ie=Math.random(),Ce=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++Pe+Ie).toString(36)},Ne=o,Ae=o,ke=function(e){return"function"==typeof e?e:void 0},$e=function(e,t){return arguments.length<2?ke(Ne[e])||ke(Ae[e]):Ne[e]&&Ne[e][t]||Ae[e]&&Ae[e][t]},Le=$e("navigator","userAgent")||"",Me=o.process,Ue=Me&&Me.versions,De=Ue&&Ue.v8;De?je=(_e=De.split("."))[0]<4?1:_e[0]+_e[1]:Le&&(!(_e=Le.match(/Edge\/(\d+)/))||_e[1]>=74)&&(_e=Le.match(/Chrome\/(\d+)/))&&(je=_e[1]);var Fe=je&&+je,ze=t,Ke=!!Object.getOwnPropertySymbols&&!ze((function(){return!String(Symbol())||!Symbol.sham&&Fe&&Fe<41})),Be=Ke&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,We=o,Ge=ye.exports,Ve=z,Ye=Ce,qe=Ke,Xe=Be,He=Ge("wks"),Je=We.Symbol,Qe=Xe?Je:Je&&Je.withoutSetter||Ye,Ze=function(e){return Ve(He,e)&&(qe||"string"==typeof He[e])||(qe&&Ve(Je,e)?He[e]=Je[e]:He[e]=Qe("Symbol."+e)),He[e]},et=g,tt=B,nt=Ze("match"),rt=h,ot=function(){var e=rt(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.dotAll&&(t+="s"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t},it={},ct=t;function at(e,t){return RegExp(e,t)}it.UNSUPPORTED_Y=ct((function(){var e=at("a","y");return e.lastIndex=2,null!=e.exec("abcd")})),it.BROKEN_CARET=ct((function(){var e=at("^r","gy");return e.lastIndex=2,null!=e.exec("str")}));var ut={exports:{}},lt=Re,ft=Function.toString;"function"!=typeof lt.inspectSource&&(lt.inspectSource=function(e){return ft.call(e)});var st,pt,gt,dt=lt.inspectSource,ht=dt,yt=o.WeakMap,vt="function"==typeof yt&&/native code/.test(ht(yt)),xt=ye.exports,bt=Ce,Et=xt("keys"),mt=vt,St=g,wt=Ee,Ot=z,Rt=Re,Tt=function(e){return Et[e]||(Et[e]=bt(e))},_t=le,jt=o.WeakMap;if(mt||Rt.state){var Pt=Rt.state||(Rt.state=new jt),It=Pt.get,Ct=Pt.has,Nt=Pt.set;st=function(e,t){if(Ct.call(Pt,e))throw new TypeError("Object already initialized");return t.facade=e,Nt.call(Pt,e,t),t},pt=function(e){return It.call(Pt,e)||{}},gt=function(e){return Ct.call(Pt,e)}}else{var At=Tt("state");_t[At]=!0,st=function(e,t){if(Ot(e,At))throw new TypeError("Object already initialized");return t.facade=e,wt(e,At,t),t},pt=function(e){return Ot(e,At)?e[At]:{}},gt=function(e){return Ot(e,At)}}var kt={set:st,get:pt,has:gt,enforce:function(e){return gt(e)?pt(e):st(e,{})},getterFor:function(e){return function(t){var n;if(!St(t)||(n=pt(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}},$t=o,Lt=Ee,Mt=z,Ut=we,Dt=dt,Ft=kt.get,zt=kt.enforce,Kt=String(String).split("String");(ut.exports=function(e,t,n,r){var o,i=!!r&&!!r.unsafe,c=!!r&&!!r.enumerable,a=!!r&&!!r.noTargetGet;"function"==typeof n&&("string"!=typeof t||Mt(n,"name")||Lt(n,"name",t),(o=zt(n)).source||(o.source=Kt.join("string"==typeof t?t:""))),e!==$t?(i?!a&&e[t]&&(c=!0):delete e[t],c?e[t]=n:Lt(e,t,n)):c?e[t]=n:Ut(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&Ft(this).source||Dt(this)}));var Bt=$e,Wt=S,Gt=n,Vt=Ze("species"),Yt=n,qt=o,Xt=p,Ht=function(e,t,n){var r,o;return m&&"function"==typeof(r=t.constructor)&&r!==n&&E(o=r.prototype)&&o!==n.prototype&&m(e,o),e},Jt=S.f,Qt=$.f,Zt=function(e){var t;return et(e)&&(void 0!==(t=e[nt])?!!t:"RegExp"==tt(e))},en=ot,tn=it,nn=ut.exports,rn=t,on=kt.enforce,cn=function(e){var t=Bt(e),n=Wt.f;Gt&&t&&!t[Vt]&&n(t,Vt,{configurable:!0,get:function(){return this}})},an=Ze("match"),un=qt.RegExp,ln=un.prototype,fn=/a/g,sn=/a/g,pn=new un(fn)!==fn,gn=tn.UNSUPPORTED_Y;if(Yt&&Xt("RegExp",!pn||gn||rn((function(){return sn[an]=!1,un(fn)!=fn||un(sn)==sn||"/a/i"!=un(fn,"i")})))){for(var dn=function(e,t){var n,r=this instanceof dn,o=Zt(e),i=void 0===t;if(!r&&o&&e.constructor===dn&&i)return e;pn?o&&!i&&(e=e.source):e instanceof dn&&(i&&(t=en.call(e)),e=e.source),gn&&(n=!!t&&t.indexOf("y")>-1)&&(t=t.replace(/y/g,""));var c=Ht(pn?new un(e,t):un(e,t),r?this:ln,dn);gn&&n&&(on(c).sticky=!0);return c},hn=function(e){e in dn||Jt(dn,e,{configurable:!0,get:function(){return un[e]},set:function(t){un[e]=t}})},yn=Qt(un),vn=0;yn.length>vn;)hn(yn[vn++]);ln.constructor=dn,dn.prototype=ln,nn(qt,"RegExp",dn)}cn("RegExp");var xn={},bn={},En={}.propertyIsEnumerable,mn=Object.getOwnPropertyDescriptor,Sn=mn&&!En.call({1:2},1);bn.f=Sn?function(e){var t=mn(this,e);return!!t&&t.enumerable}:En;var wn=n,On=bn,Rn=ve,Tn=q,_n=P,jn=z,Pn=_,In=Object.getOwnPropertyDescriptor;xn.f=wn?In:function(e,t){if(e=Tn(e),t=_n(t,!0),Pn)try{return In(e,t)}catch(e){}if(jn(e,t))return Rn(!On.f.call(e,t),e[t])};var Cn={};Cn.f=Object.getOwnPropertySymbols;var Nn=$,An=Cn,kn=h,$n=$e("Reflect","ownKeys")||function(e){var t=Nn.f(kn(e)),n=An.f;return n?t.concat(n(e)):t},Ln=z,Mn=$n,Un=xn,Dn=S,Fn=o,zn=xn.f,Kn=Ee,Bn=ut.exports,Wn=we,Gn=function(e,t){for(var n=Mn(t),r=Dn.f,o=Un.f,i=0;i0&&(!i.multiline||i.multiline&&"\n"!==e[i.lastIndex-1])&&(u="(?: "+u+")",f=" "+f,l++),n=new RegExp("^(?:"+u+")",a)),tr&&(n=new RegExp("^"+u+"$(?!\\s)",a)),Zn&&(t=i.lastIndex),r=Hn.call(c?n:i,f),c?r?(r.input=r.input.slice(l),r[0]=r[0].slice(l),r.index=i.lastIndex,i.lastIndex+=r[0].length):i.lastIndex=0:Zn&&r&&(i.lastIndex=i.global?r.index+r[0].length:t),tr&&r&&r.length>1&&Jn.call(r[0],n,(function(){for(o=1;o")})),br="$0"==="a".replace(/./,"$0"),Er=dr("replace"),mr=!!/./[Er]&&""===/./[Er]("a","$0"),Sr=!gr((function(){var e=/(?:)/,t=e.exec;e.exec=function(){return t.apply(this,arguments)};var n="ab".split(e);return 2!==n.length||"a"!==n[0]||"b"!==n[1]})),wr=J,Or=L,Rr=function(e){return function(t,n){var r,o,i=String(Or(t)),c=wr(n),a=i.length;return c<0||c>=a?e?"":void 0:(r=i.charCodeAt(c))<55296||r>56319||c+1===a||(o=i.charCodeAt(c+1))<56320||o>57343?e?i.charAt(c):r:e?i.slice(c,c+2):o-56320+(r-55296<<10)+65536}},Tr={codeAt:Rr(!1),charAt:Rr(!0)}.charAt,_r=U,jr=Math.floor,Pr="".replace,Ir=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,Cr=/\$([$&'`]|\d{1,2})/g,Nr=B,Ar=nr,kr=function(e,t,n,r){var o=dr(e),i=!gr((function(){var t={};return t[o]=function(){return 7},7!=""[e](t)})),c=i&&!gr((function(){var t=!1,n=/a/;return"split"===e&&((n={}).constructor={},n.constructor[yr]=function(){return n},n.flags="",n[o]=/./[o]),n.exec=function(){return t=!0,null},n[o](""),!t}));if(!i||!c||"replace"===e&&(!xr||!br||mr)||"split"===e&&!Sr){var a=/./[o],u=n(o,""[e],(function(e,t,n,r,o){var c=t.exec;return c===pr||c===vr.exec?i&&!o?{done:!0,value:a.call(t,n,r)}:{done:!0,value:e.call(n,t,r)}:{done:!1}}),{REPLACE_KEEPS_$0:br,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:mr}),l=u[0],f=u[1];sr(String.prototype,e,l),sr(vr,o,2==t?function(e,t){return f.call(e,this,t)}:function(e){return f.call(e,this)})}r&&hr(vr[o],"sham",!0)},$r=h,Lr=ee,Mr=J,Ur=L,Dr=function(e,t,n){return t+(n?Tr(e,t).length:1)},Fr=function(e,t,n,r,o,i){var c=n+e.length,a=r.length,u=Cr;return void 0!==o&&(o=_r(o),u=Ir),Pr.call(i,u,(function(i,u){var l;switch(u.charAt(0)){case"$":return"$";case"&":return e;case"`":return t.slice(0,n);case"'":return t.slice(c);case"<":l=o[u.slice(1,-1)];break;default:var f=+u;if(0===f)return i;if(f>a){var s=jr(f/10);return 0===s?i:s<=a?void 0===r[s-1]?u.charAt(1):r[s-1]+u.charAt(1):i}l=r[f-1]}return void 0===l?"":l}))},zr=function(e,t){var n=e.exec;if("function"==typeof n){var r=n.call(e,t);if("object"!=typeof r)throw TypeError("RegExp exec method returned something other than an Object or null");return r}if("RegExp"!==Nr(e))throw TypeError("RegExp#exec called on incompatible receiver");return Ar.call(e,t)},Kr=Math.max,Br=Math.min;kr("replace",2,(function(e,t,n,r){var o=r.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE,i=r.REPLACE_KEEPS_$0,c=o?"$":"$0";return[function(n,r){var o=Ur(this),i=null==n?void 0:n[e];return void 0!==i?i.call(n,o,r):t.call(String(o),n,r)},function(e,r){if(!o&&i||"string"==typeof r&&-1===r.indexOf(c)){var a=n(t,e,this,r);if(a.done)return a.value}var u=$r(e),l=String(this),f="function"==typeof r;f||(r=String(r));var s=u.global;if(s){var p=u.unicode;u.lastIndex=0}for(var g=[];;){var d=zr(u,l);if(null===d)break;if(g.push(d),!s)break;""===String(d[0])&&(u.lastIndex=Dr(l,Lr(u.lastIndex),p))}for(var h,y="",v=0,x=0;x=v&&(y+=l.slice(v,E)+R,v=E+b.length)}return y+l.slice(v)}]}));var Wr={};Wr[Ze("toStringTag")]="z";var Gr="[object z]"===String(Wr),Vr=Gr,Yr=B,qr=Ze("toStringTag"),Xr="Arguments"==Yr(function(){return arguments}()),Hr=Vr?Yr:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),qr))?n:Xr?Yr(t):"Object"==(r=Yr(t))&&"function"==typeof t.callee?"Arguments":r},Jr=Gr?{}.toString:function(){return"[object "+Hr(this)+"]"},Qr=Gr,Zr=ut.exports,eo=Jr;Qr||Zr(Object.prototype,"toString",eo,{unsafe:!0})
2 | /*!
3 | * Handles finding a text string anywhere in the slides and showing the next occurrence to the user
4 | * by navigatating to that slide and highlighting it.
5 | *
6 | * @author Jon Snyder , February 2013
7 | */;export default function(){var e,t,n,r,o,i,c;function a(){(t=document.createElement("div")).classList.add("searchbox"),t.style.position="absolute",t.style.top="10px",t.style.right="10px",t.style.zIndex=10,t.innerHTML='\n\t\t',(n=t.querySelector(".searchinput")).style.width="240px",n.style.fontSize="14px",n.style.padding="4px 6px",n.style.color="#000",n.style.background="#fff",n.style.borderRadius="2px",n.style.border="0",n.style.outline="0",n.style.boxShadow="0 2px 18px rgba(0, 0, 0, 0.2)",n.style["-webkit-appearance"]="none",e.getRevealElement().appendChild(t),n.addEventListener("keyup",(function(t){switch(t.keyCode){case 13:t.preventDefault(),function(){if(i){var t=n.value;""===t?(c&&c.remove(),r=null):(c=new f("slidecontent"),r=c.apply(t),o=0)}r&&(r.length&&r.length<=o&&(o=0),r.length>o&&(e.slide(r[o].h,r[o].v),o++))}(),i=!1;break;default:i=!0}}),!1),l()}function u(){t||a(),t.style.display="inline",n.focus(),n.select()}function l(){t||a(),t.style.display="none",c&&c.remove()}function f(t,n){var r=document.getElementById(t)||document.body,o=n||"EM",i=new RegExp("^(?:"+o+"|SCRIPT|FORM)$"),c=["#ff6","#a0ffff","#9f9","#f99","#f6f"],a=[],u=0,l="",f=[];this.setRegex=function(e){e=e.replace(/^[^\w]+|[^\w]+$/g,"").replace(/[^\w'-]+/g,"|"),l=new RegExp("("+e+")","i")},this.getRegex=function(){return l.toString().replace(/^\/\\b\(|\)\\b\/i$/g,"").replace(/\|/g," ")},this.hiliteWords=function(t){if(null!=t&&t&&l&&!i.test(t.nodeName)){if(t.hasChildNodes())for(var n=0;n0?H:X)(e)},Q=J,Z=Math.min,ee=function(e){return e>0?Z(Q(e),9007199254740991):0},te=J,ne=Math.max,re=Math.min,oe=q,ie=ee,ce=function(e,t){var n=te(e);return n<0?ne(n+t,0):re(n,t)},ae=function(e){return function(t,n,r){var o,i=oe(t),c=ie(i.length),a=ce(r,c);if(e&&n!=n){for(;c>a;)if((o=i[a++])!=o)return!0}else for(;c>a;a++)if((e||a in i)&&i[a]===n)return e||a||0;return!e&&-1}},ue={includes:ae(!0),indexOf:ae(!1)},le={},fe=z,se=q,pe=ue.indexOf,de=le,ge=function(e,t){var n,r=se(e),o=0,i=[];for(n in r)!fe(de,n)&&fe(r,n)&&i.push(n);for(;t.length>o;)fe(r,n=t[o++])&&(~pe(i,n)||i.push(n));return i},he=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"].concat("length","prototype");$.f=Object.getOwnPropertyNames||function(e){return ge(e,he)};var ye={exports:{}},ve=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}},xe=S,be=ve,me=n?function(e,t,n){return xe.f(e,t,be(1,n))}:function(e,t,n){return e[t]=n,e},Ee=o,Se=me,we=function(e,t){try{Se(Ee,e,t)}catch(n){Ee[e]=t}return t},Oe=we,Re="__core-js_shared__",Te=o[Re]||Oe(Re,{}),_e=Te;(ye.exports=function(e,t){return _e[e]||(_e[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.12.1",mode:"global",copyright:"© 2021 Denis Pushkarev (zloirock.ru)"});var je,Pe,Ie=0,Ce=Math.random(),Ne=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++Ie+Ce).toString(36)},Ae=o,ke=o,$e=function(e){return"function"==typeof e?e:void 0},Le=function(e,t){return arguments.length<2?$e(Ae[e])||$e(ke[e]):Ae[e]&&Ae[e][t]||ke[e]&&ke[e][t]},Me=Le("navigator","userAgent")||"",Ue=o.process,De=Ue&&Ue.versions,Fe=De&&De.v8;Fe?Pe=(je=Fe.split("."))[0]<4?1:je[0]+je[1]:Me&&(!(je=Me.match(/Edge\/(\d+)/))||je[1]>=74)&&(je=Me.match(/Chrome\/(\d+)/))&&(Pe=je[1]);var ze=Pe&&+Pe,Ke=t,Be=!!Object.getOwnPropertySymbols&&!Ke((function(){return!String(Symbol())||!Symbol.sham&&ze&&ze<41})),We=Be&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,Ge=o,Ve=ye.exports,Ye=z,qe=Ne,Xe=Be,He=We,Je=Ve("wks"),Qe=Ge.Symbol,Ze=He?Qe:Qe&&Qe.withoutSetter||qe,et=function(e){return Ye(Je,e)&&(Xe||"string"==typeof Je[e])||(Xe&&Ye(Qe,e)?Je[e]=Qe[e]:Je[e]=Ze("Symbol."+e)),Je[e]},tt=d,nt=B,rt=et("match"),ot=h,it=function(){var e=ot(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.dotAll&&(t+="s"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t},ct={},at=t;function ut(e,t){return RegExp(e,t)}ct.UNSUPPORTED_Y=at((function(){var e=ut("a","y");return e.lastIndex=2,null!=e.exec("abcd")})),ct.BROKEN_CARET=at((function(){var e=ut("^r","gy");return e.lastIndex=2,null!=e.exec("str")}));var lt={exports:{}},ft=Te,st=Function.toString;"function"!=typeof ft.inspectSource&&(ft.inspectSource=function(e){return st.call(e)});var pt,dt,gt,ht=ft.inspectSource,yt=ht,vt=o.WeakMap,xt="function"==typeof vt&&/native code/.test(yt(vt)),bt=ye.exports,mt=Ne,Et=bt("keys"),St=xt,wt=d,Ot=me,Rt=z,Tt=Te,_t=function(e){return Et[e]||(Et[e]=mt(e))},jt=le,Pt="Object already initialized",It=o.WeakMap;if(St||Tt.state){var Ct=Tt.state||(Tt.state=new It),Nt=Ct.get,At=Ct.has,kt=Ct.set;pt=function(e,t){if(At.call(Ct,e))throw new TypeError(Pt);return t.facade=e,kt.call(Ct,e,t),t},dt=function(e){return Nt.call(Ct,e)||{}},gt=function(e){return At.call(Ct,e)}}else{var $t=_t("state");jt[$t]=!0,pt=function(e,t){if(Rt(e,$t))throw new TypeError(Pt);return t.facade=e,Ot(e,$t,t),t},dt=function(e){return Rt(e,$t)?e[$t]:{}},gt=function(e){return Rt(e,$t)}}var Lt={set:pt,get:dt,has:gt,enforce:function(e){return gt(e)?dt(e):pt(e,{})},getterFor:function(e){return function(t){var n;if(!wt(t)||(n=dt(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}},Mt=o,Ut=me,Dt=z,Ft=we,zt=ht,Kt=Lt.get,Bt=Lt.enforce,Wt=String(String).split("String");(lt.exports=function(e,t,n,r){var o,i=!!r&&!!r.unsafe,c=!!r&&!!r.enumerable,a=!!r&&!!r.noTargetGet;"function"==typeof n&&("string"!=typeof t||Dt(n,"name")||Ut(n,"name",t),(o=Bt(n)).source||(o.source=Wt.join("string"==typeof t?t:""))),e!==Mt?(i?!a&&e[t]&&(c=!0):delete e[t],c?e[t]=n:Ut(e,t,n)):c?e[t]=n:Ft(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&Kt(this).source||zt(this)}));var Gt=Le,Vt=S,Yt=n,qt=et("species"),Xt=n,Ht=o,Jt=p,Qt=function(e,t,n){var r,o;return E&&"function"==typeof(r=t.constructor)&&r!==n&&m(o=r.prototype)&&o!==n.prototype&&E(e,o),e},Zt=S.f,en=$.f,tn=function(e){var t;return tt(e)&&(void 0!==(t=e[rt])?!!t:"RegExp"==nt(e))},nn=it,rn=ct,on=lt.exports,cn=t,an=Lt.enforce,un=function(e){var t=Gt(e),n=Vt.f;Yt&&t&&!t[qt]&&n(t,qt,{configurable:!0,get:function(){return this}})},ln=et("match"),fn=Ht.RegExp,sn=fn.prototype,pn=/a/g,dn=/a/g,gn=new fn(pn)!==pn,hn=rn.UNSUPPORTED_Y;if(Xt&&Jt("RegExp",!gn||hn||cn((function(){return dn[ln]=!1,fn(pn)!=pn||fn(dn)==dn||"/a/i"!=fn(pn,"i")})))){for(var yn=function(e,t){var n,r=this instanceof yn,o=tn(e),i=void 0===t;if(!r&&o&&e.constructor===yn&&i)return e;gn?o&&!i&&(e=e.source):e instanceof yn&&(i&&(t=nn.call(e)),e=e.source),hn&&(n=!!t&&t.indexOf("y")>-1)&&(t=t.replace(/y/g,""));var c=Qt(gn?new fn(e,t):fn(e,t),r?this:sn,yn);hn&&n&&(an(c).sticky=!0);return c},vn=function(e){e in yn||Zt(yn,e,{configurable:!0,get:function(){return fn[e]},set:function(t){fn[e]=t}})},xn=en(fn),bn=0;xn.length>bn;)vn(xn[bn++]);sn.constructor=yn,yn.prototype=sn,on(Ht,"RegExp",yn)}un("RegExp");var mn={},En={},Sn={}.propertyIsEnumerable,wn=Object.getOwnPropertyDescriptor,On=wn&&!Sn.call({1:2},1);En.f=On?function(e){var t=wn(this,e);return!!t&&t.enumerable}:Sn;var Rn=n,Tn=En,_n=ve,jn=q,Pn=P,In=z,Cn=_,Nn=Object.getOwnPropertyDescriptor;mn.f=Rn?Nn:function(e,t){if(e=jn(e),t=Pn(t,!0),Cn)try{return Nn(e,t)}catch(e){}if(In(e,t))return _n(!Tn.f.call(e,t),e[t])};var An={};An.f=Object.getOwnPropertySymbols;var kn=$,$n=An,Ln=h,Mn=Le("Reflect","ownKeys")||function(e){var t=kn.f(Ln(e)),n=$n.f;return n?t.concat(n(e)):t},Un=z,Dn=Mn,Fn=mn,zn=S,Kn=o,Bn=mn.f,Wn=me,Gn=lt.exports,Vn=we,Yn=function(e,t){for(var n=Dn(t),r=zn.f,o=Fn.f,i=0;i0&&(!i.multiline||i.multiline&&"\n"!==e[i.lastIndex-1])&&(u="(?: "+u+")",f=" "+f,l++),n=new RegExp("^(?:"+u+")",a)),rr&&(n=new RegExp("^"+u+"$(?!\\s)",a)),tr&&(t=i.lastIndex),r=Qn.call(c?n:i,f),c?r?(r.input=r.input.slice(l),r[0]=r[0].slice(l),r.index=i.lastIndex,i.lastIndex+=r[0].length):i.lastIndex=0:tr&&r&&(i.lastIndex=i.global?r.index+r[0].length:t),rr&&r&&r.length>1&&Zn.call(r[0],n,(function(){for(o=1;o")})),Sr="$0"==="a".replace(/./,"$0"),wr=vr("replace"),Or=!!/./[wr]&&""===/./[wr]("a","$0"),Rr=!yr((function(){var e=/(?:)/,t=e.exec;e.exec=function(){return t.apply(this,arguments)};var n="ab".split(e);return 2!==n.length||"a"!==n[0]||"b"!==n[1]})),Tr=J,_r=L,jr=function(e){return function(t,n){var r,o,i=String(_r(t)),c=Tr(n),a=i.length;return c<0||c>=a?e?"":void 0:(r=i.charCodeAt(c))<55296||r>56319||c+1===a||(o=i.charCodeAt(c+1))<56320||o>57343?e?i.charAt(c):r:e?i.slice(c,c+2):o-56320+(r-55296<<10)+65536}},Pr={codeAt:jr(!1),charAt:jr(!0)}.charAt,Ir=U,Cr=Math.floor,Nr="".replace,Ar=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,kr=/\$([$&'`]|\d{1,2})/g,$r=B,Lr=or,Mr=function(e,t,n,r){var o=vr(e),i=!yr((function(){var t={};return t[o]=function(){return 7},7!=""[e](t)})),c=i&&!yr((function(){var t=!1,n=/a/;return"split"===e&&((n={}).constructor={},n.constructor[br]=function(){return n},n.flags="",n[o]=/./[o]),n.exec=function(){return t=!0,null},n[o](""),!t}));if(!i||!c||"replace"===e&&(!Er||!Sr||Or)||"split"===e&&!Rr){var a=/./[o],u=n(o,""[e],(function(e,t,n,r,o){var c=t.exec;return c===hr||c===mr.exec?i&&!o?{done:!0,value:a.call(t,n,r)}:{done:!0,value:e.call(n,t,r)}:{done:!1}}),{REPLACE_KEEPS_$0:Sr,REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE:Or}),l=u[0],f=u[1];gr(String.prototype,e,l),gr(mr,o,2==t?function(e,t){return f.call(e,this,t)}:function(e){return f.call(e,this)})}r&&xr(mr[o],"sham",!0)},Ur=h,Dr=ee,Fr=J,zr=L,Kr=function(e,t,n){return t+(n?Pr(e,t).length:1)},Br=function(e,t,n,r,o,i){var c=n+e.length,a=r.length,u=kr;return void 0!==o&&(o=Ir(o),u=Ar),Nr.call(i,u,(function(i,u){var l;switch(u.charAt(0)){case"$":return"$";case"&":return e;case"`":return t.slice(0,n);case"'":return t.slice(c);case"<":l=o[u.slice(1,-1)];break;default:var f=+u;if(0===f)return i;if(f>a){var s=Cr(f/10);return 0===s?i:s<=a?void 0===r[s-1]?u.charAt(1):r[s-1]+u.charAt(1):i}l=r[f-1]}return void 0===l?"":l}))},Wr=function(e,t){var n=e.exec;if("function"==typeof n){var r=n.call(e,t);if("object"!=typeof r)throw TypeError("RegExp exec method returned something other than an Object or null");return r}if("RegExp"!==$r(e))throw TypeError("RegExp#exec called on incompatible receiver");return Lr.call(e,t)},Gr=Math.max,Vr=Math.min;Mr("replace",2,(function(e,t,n,r){var o=r.REGEXP_REPLACE_SUBSTITUTES_UNDEFINED_CAPTURE,i=r.REPLACE_KEEPS_$0,c=o?"$":"$0";return[function(n,r){var o=zr(this),i=null==n?void 0:n[e];return void 0!==i?i.call(n,o,r):t.call(String(o),n,r)},function(e,r){if(!o&&i||"string"==typeof r&&-1===r.indexOf(c)){var a=n(t,e,this,r);if(a.done)return a.value}var u=Ur(e),l=String(this),f="function"==typeof r;f||(r=String(r));var s=u.global;if(s){var p=u.unicode;u.lastIndex=0}for(var d=[];;){var g=Wr(u,l);if(null===g)break;if(d.push(g),!s)break;""===String(g[0])&&(u.lastIndex=Kr(l,Dr(u.lastIndex),p))}for(var h,y="",v=0,x=0;x=v&&(y+=l.slice(v,m)+R,v=m+b.length)}return y+l.slice(v)}]}));var Yr={};Yr[et("toStringTag")]="z";var qr="[object z]"===String(Yr),Xr=qr,Hr=B,Jr=et("toStringTag"),Qr="Arguments"==Hr(function(){return arguments}()),Zr=Xr?Hr:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),Jr))?n:Qr?Hr(t):"Object"==(r=Hr(t))&&"function"==typeof t.callee?"Arguments":r},eo=qr?{}.toString:function(){return"[object "+Zr(this)+"]"},to=qr,no=lt.exports,ro=eo;to||no(Object.prototype,"toString",ro,{unsafe:!0})
2 | /*!
3 | * Handles finding a text string anywhere in the slides and showing the next occurrence to the user
4 | * by navigatating to that slide and highlighting it.
5 | *
6 | * @author Jon Snyder , February 2013
7 | */;return function(){var e,t,n,r,o,i,c;function a(){(t=document.createElement("div")).classList.add("searchbox"),t.style.position="absolute",t.style.top="10px",t.style.right="10px",t.style.zIndex=10,t.innerHTML='\n\t\t',(n=t.querySelector(".searchinput")).style.width="240px",n.style.fontSize="14px",n.style.padding="4px 6px",n.style.color="#000",n.style.background="#fff",n.style.borderRadius="2px",n.style.border="0",n.style.outline="0",n.style.boxShadow="0 2px 18px rgba(0, 0, 0, 0.2)",n.style["-webkit-appearance"]="none",e.getRevealElement().appendChild(t),n.addEventListener("keyup",(function(t){switch(t.keyCode){case 13:t.preventDefault(),function(){if(i){var t=n.value;""===t?(c&&c.remove(),r=null):(c=new f("slidecontent"),r=c.apply(t),o=0)}r&&(r.length&&r.length<=o&&(o=0),r.length>o&&(e.slide(r[o].h,r[o].v),o++))}(),i=!1;break;default:i=!0}}),!1),l()}function u(){t||a(),t.style.display="inline",n.focus(),n.select()}function l(){t||a(),t.style.display="none",c&&c.remove()}function f(t,n){var r=document.getElementById(t)||document.body,o=n||"EM",i=new RegExp("^(?:"+o+"|SCRIPT|FORM)$"),c=["#ff6","#a0ffff","#9f9","#f99","#f6f"],a=[],u=0,l="",f=[];this.setRegex=function(e){e=e.replace(/^[^\w]+|[^\w]+$/g,"").replace(/[^\w'-]+/g,"|"),l=new RegExp("("+e+")","i")},this.getRegex=function(){return l.toString().replace(/^\/\\b\(|\)\\b\/i$/g,"").replace(/\|/g," ")},this.hiliteWords=function(t){if(null!=t&&t&&l&&!i.test(t.nodeName)){if(t.hasChildNodes())for(var n=0;n {
31 |
32 | zoom.reset();
33 |
34 | }
35 |
36 | };
37 |
38 | export default () => Plugin;
39 |
40 | /*!
41 | * zoom.js 0.3 (modified for use with reveal.js)
42 | * http://lab.hakim.se/zoom-js
43 | * MIT licensed
44 | *
45 | * Copyright (C) 2011-2014 Hakim El Hattab, http://hakim.se
46 | */
47 | var zoom = (function(){
48 |
49 | // The current zoom level (scale)
50 | var level = 1;
51 |
52 | // The current mouse position, used for panning
53 | var mouseX = 0,
54 | mouseY = 0;
55 |
56 | // Timeout before pan is activated
57 | var panEngageTimeout = -1,
58 | panUpdateInterval = -1;
59 |
60 | // Check for transform support so that we can fallback otherwise
61 | var supportsTransforms = 'transform' in document.body.style;
62 |
63 | if( supportsTransforms ) {
64 | // The easing that will be applied when we zoom in/out
65 | document.body.style.transition = 'transform 0.8s ease';
66 | }
67 |
68 | // Zoom out if the user hits escape
69 | document.addEventListener( 'keyup', function( event ) {
70 | if( level !== 1 && event.keyCode === 27 ) {
71 | zoom.out();
72 | }
73 | } );
74 |
75 | // Monitor mouse movement for panning
76 | document.addEventListener( 'mousemove', function( event ) {
77 | if( level !== 1 ) {
78 | mouseX = event.clientX;
79 | mouseY = event.clientY;
80 | }
81 | } );
82 |
83 | /**
84 | * Applies the CSS required to zoom in, prefers the use of CSS3
85 | * transforms but falls back on zoom for IE.
86 | *
87 | * @param {Object} rect
88 | * @param {Number} scale
89 | */
90 | function magnify( rect, scale ) {
91 |
92 | var scrollOffset = getScrollOffset();
93 |
94 | // Ensure a width/height is set
95 | rect.width = rect.width || 1;
96 | rect.height = rect.height || 1;
97 |
98 | // Center the rect within the zoomed viewport
99 | rect.x -= ( window.innerWidth - ( rect.width * scale ) ) / 2;
100 | rect.y -= ( window.innerHeight - ( rect.height * scale ) ) / 2;
101 |
102 | if( supportsTransforms ) {
103 | // Reset
104 | if( scale === 1 ) {
105 | document.body.style.transform = '';
106 | }
107 | // Scale
108 | else {
109 | var origin = scrollOffset.x +'px '+ scrollOffset.y +'px',
110 | transform = 'translate('+ -rect.x +'px,'+ -rect.y +'px) scale('+ scale +')';
111 |
112 | document.body.style.transformOrigin = origin;
113 | document.body.style.transform = transform;
114 | }
115 | }
116 | else {
117 | // Reset
118 | if( scale === 1 ) {
119 | document.body.style.position = '';
120 | document.body.style.left = '';
121 | document.body.style.top = '';
122 | document.body.style.width = '';
123 | document.body.style.height = '';
124 | document.body.style.zoom = '';
125 | }
126 | // Scale
127 | else {
128 | document.body.style.position = 'relative';
129 | document.body.style.left = ( - ( scrollOffset.x + rect.x ) / scale ) + 'px';
130 | document.body.style.top = ( - ( scrollOffset.y + rect.y ) / scale ) + 'px';
131 | document.body.style.width = ( scale * 100 ) + '%';
132 | document.body.style.height = ( scale * 100 ) + '%';
133 | document.body.style.zoom = scale;
134 | }
135 | }
136 |
137 | level = scale;
138 |
139 | if( document.documentElement.classList ) {
140 | if( level !== 1 ) {
141 | document.documentElement.classList.add( 'zoomed' );
142 | }
143 | else {
144 | document.documentElement.classList.remove( 'zoomed' );
145 | }
146 | }
147 | }
148 |
149 | /**
150 | * Pan the document when the mosue cursor approaches the edges
151 | * of the window.
152 | */
153 | function pan() {
154 | var range = 0.12,
155 | rangeX = window.innerWidth * range,
156 | rangeY = window.innerHeight * range,
157 | scrollOffset = getScrollOffset();
158 |
159 | // Up
160 | if( mouseY < rangeY ) {
161 | window.scroll( scrollOffset.x, scrollOffset.y - ( 1 - ( mouseY / rangeY ) ) * ( 14 / level ) );
162 | }
163 | // Down
164 | else if( mouseY > window.innerHeight - rangeY ) {
165 | window.scroll( scrollOffset.x, scrollOffset.y + ( 1 - ( window.innerHeight - mouseY ) / rangeY ) * ( 14 / level ) );
166 | }
167 |
168 | // Left
169 | if( mouseX < rangeX ) {
170 | window.scroll( scrollOffset.x - ( 1 - ( mouseX / rangeX ) ) * ( 14 / level ), scrollOffset.y );
171 | }
172 | // Right
173 | else if( mouseX > window.innerWidth - rangeX ) {
174 | window.scroll( scrollOffset.x + ( 1 - ( window.innerWidth - mouseX ) / rangeX ) * ( 14 / level ), scrollOffset.y );
175 | }
176 | }
177 |
178 | function getScrollOffset() {
179 | return {
180 | x: window.scrollX !== undefined ? window.scrollX : window.pageXOffset,
181 | y: window.scrollY !== undefined ? window.scrollY : window.pageYOffset
182 | }
183 | }
184 |
185 | return {
186 | /**
187 | * Zooms in on either a rectangle or HTML element.
188 | *
189 | * @param {Object} options
190 | * - element: HTML element to zoom in on
191 | * OR
192 | * - x/y: coordinates in non-transformed space to zoom in on
193 | * - width/height: the portion of the screen to zoom in on
194 | * - scale: can be used instead of width/height to explicitly set scale
195 | */
196 | to: function( options ) {
197 |
198 | // Due to an implementation limitation we can't zoom in
199 | // to another element without zooming out first
200 | if( level !== 1 ) {
201 | zoom.out();
202 | }
203 | else {
204 | options.x = options.x || 0;
205 | options.y = options.y || 0;
206 |
207 | // If an element is set, that takes precedence
208 | if( !!options.element ) {
209 | // Space around the zoomed in element to leave on screen
210 | var padding = 20;
211 | var bounds = options.element.getBoundingClientRect();
212 |
213 | options.x = bounds.left - padding;
214 | options.y = bounds.top - padding;
215 | options.width = bounds.width + ( padding * 2 );
216 | options.height = bounds.height + ( padding * 2 );
217 | }
218 |
219 | // If width/height values are set, calculate scale from those values
220 | if( options.width !== undefined && options.height !== undefined ) {
221 | options.scale = Math.max( Math.min( window.innerWidth / options.width, window.innerHeight / options.height ), 1 );
222 | }
223 |
224 | if( options.scale > 1 ) {
225 | options.x *= options.scale;
226 | options.y *= options.scale;
227 |
228 | magnify( options, options.scale );
229 |
230 | if( options.pan !== false ) {
231 |
232 | // Wait with engaging panning as it may conflict with the
233 | // zoom transition
234 | panEngageTimeout = setTimeout( function() {
235 | panUpdateInterval = setInterval( pan, 1000 / 60 );
236 | }, 800 );
237 |
238 | }
239 | }
240 | }
241 | },
242 |
243 | /**
244 | * Resets the document zoom state to its default.
245 | */
246 | out: function() {
247 | clearTimeout( panEngageTimeout );
248 | clearInterval( panUpdateInterval );
249 |
250 | magnify( { x: 0, y: 0 }, 1 );
251 |
252 | level = 1;
253 | },
254 |
255 | // Alias
256 | magnify: function( options ) { this.to( options ) },
257 | reset: function() { this.out() },
258 |
259 | zoomLevel: function() {
260 | return level;
261 | }
262 | }
263 |
264 | })();
265 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/zoom/zoom.esm.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * reveal.js Zoom plugin
3 | */
4 | var e={id:"zoom",init:function(e){e.getRevealElement().addEventListener("mousedown",(function(n){var o=/Linux/.test(window.navigator.platform)?"ctrl":"alt",i=(e.getConfig().zoomKey?e.getConfig().zoomKey:o)+"Key",d=e.getConfig().zoomLevel?e.getConfig().zoomLevel:2;n[i]&&!e.isOverview()&&(n.preventDefault(),t.to({x:n.clientX,y:n.clientY,scale:d,pan:!1}))}))},destroy:function(){t.reset()}},t=function(){var e=1,n=0,o=0,i=-1,d=-1,l="transform"in document.body.style;function s(t,n){var o=r();if(t.width=t.width||1,t.height=t.height||1,t.x-=(window.innerWidth-t.width*n)/2,t.y-=(window.innerHeight-t.height*n)/2,l)if(1===n)document.body.style.transform="";else{var i=o.x+"px "+o.y+"px",d="translate("+-t.x+"px,"+-t.y+"px) scale("+n+")";document.body.style.transformOrigin=i,document.body.style.transform=d}else 1===n?(document.body.style.position="",document.body.style.left="",document.body.style.top="",document.body.style.width="",document.body.style.height="",document.body.style.zoom=""):(document.body.style.position="relative",document.body.style.left=-(o.x+t.x)/n+"px",document.body.style.top=-(o.y+t.y)/n+"px",document.body.style.width=100*n+"%",document.body.style.height=100*n+"%",document.body.style.zoom=n);e=n,document.documentElement.classList&&(1!==e?document.documentElement.classList.add("zoomed"):document.documentElement.classList.remove("zoomed"))}function c(){var t=.12*window.innerWidth,i=.12*window.innerHeight,d=r();owindow.innerHeight-i&&window.scroll(d.x,d.y+(1-(window.innerHeight-o)/i)*(14/e)),nwindow.innerWidth-t&&window.scroll(d.x+(1-(window.innerWidth-n)/t)*(14/e),d.y)}function r(){return{x:void 0!==window.scrollX?window.scrollX:window.pageXOffset,y:void 0!==window.scrollY?window.scrollY:window.pageYOffset}}return l&&(document.body.style.transition="transform 0.8s ease"),document.addEventListener("keyup",(function(n){1!==e&&27===n.keyCode&&t.out()})),document.addEventListener("mousemove",(function(t){1!==e&&(n=t.clientX,o=t.clientY)})),{to:function(n){if(1!==e)t.out();else{if(n.x=n.x||0,n.y=n.y||0,n.element){var o=n.element.getBoundingClientRect();n.x=o.left-20,n.y=o.top-20,n.width=o.width+40,n.height=o.height+40}void 0!==n.width&&void 0!==n.height&&(n.scale=Math.max(Math.min(window.innerWidth/n.width,window.innerHeight/n.height),1)),n.scale>1&&(n.x*=n.scale,n.y*=n.scale,s(n,n.scale),!1!==n.pan&&(i=setTimeout((function(){d=setInterval(c,1e3/60)}),800)))}},out:function(){clearTimeout(i),clearInterval(d),s({x:0,y:0},1),e=1},magnify:function(e){this.to(e)},reset:function(){this.out()},zoomLevel:function(){return e}}}();export default function(){return e}
5 |
--------------------------------------------------------------------------------
/docs/site_libs/revealjs/plugin/zoom/zoom.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).RevealZoom=t()}(this,(function(){"use strict";
2 | /*!
3 | * reveal.js Zoom plugin
4 | */var e={id:"zoom",init:function(e){e.getRevealElement().addEventListener("mousedown",(function(o){var n=/Linux/.test(window.navigator.platform)?"ctrl":"alt",i=(e.getConfig().zoomKey?e.getConfig().zoomKey:n)+"Key",d=e.getConfig().zoomLevel?e.getConfig().zoomLevel:2;o[i]&&!e.isOverview()&&(o.preventDefault(),t.to({x:o.clientX,y:o.clientY,scale:d,pan:!1}))}))},destroy:function(){t.reset()}},t=function(){var e=1,o=0,n=0,i=-1,d=-1,l="transform"in document.body.style;function s(t,o){var n=r();if(t.width=t.width||1,t.height=t.height||1,t.x-=(window.innerWidth-t.width*o)/2,t.y-=(window.innerHeight-t.height*o)/2,l)if(1===o)document.body.style.transform="";else{var i=n.x+"px "+n.y+"px",d="translate("+-t.x+"px,"+-t.y+"px) scale("+o+")";document.body.style.transformOrigin=i,document.body.style.transform=d}else 1===o?(document.body.style.position="",document.body.style.left="",document.body.style.top="",document.body.style.width="",document.body.style.height="",document.body.style.zoom=""):(document.body.style.position="relative",document.body.style.left=-(n.x+t.x)/o+"px",document.body.style.top=-(n.y+t.y)/o+"px",document.body.style.width=100*o+"%",document.body.style.height=100*o+"%",document.body.style.zoom=o);e=o,document.documentElement.classList&&(1!==e?document.documentElement.classList.add("zoomed"):document.documentElement.classList.remove("zoomed"))}function c(){var t=.12*window.innerWidth,i=.12*window.innerHeight,d=r();nwindow.innerHeight-i&&window.scroll(d.x,d.y+(1-(window.innerHeight-n)/i)*(14/e)),owindow.innerWidth-t&&window.scroll(d.x+(1-(window.innerWidth-o)/t)*(14/e),d.y)}function r(){return{x:void 0!==window.scrollX?window.scrollX:window.pageXOffset,y:void 0!==window.scrollY?window.scrollY:window.pageYOffset}}return l&&(document.body.style.transition="transform 0.8s ease"),document.addEventListener("keyup",(function(o){1!==e&&27===o.keyCode&&t.out()})),document.addEventListener("mousemove",(function(t){1!==e&&(o=t.clientX,n=t.clientY)})),{to:function(o){if(1!==e)t.out();else{if(o.x=o.x||0,o.y=o.y||0,o.element){var n=o.element.getBoundingClientRect();o.x=n.left-20,o.y=n.top-20,o.width=n.width+40,o.height=n.height+40}void 0!==o.width&&void 0!==o.height&&(o.scale=Math.max(Math.min(window.innerWidth/o.width,window.innerHeight/o.height),1)),o.scale>1&&(o.x*=o.scale,o.y*=o.scale,s(o,o.scale),!1!==o.pan&&(i=setTimeout((function(){d=setInterval(c,1e3/60)}),800)))}},out:function(){clearTimeout(i),clearInterval(d),s({x:0,y:0},1),e=1},magnify:function(e){this.to(e)},reset:function(){this.out()},zoomLevel:function(){return e}}}();return function(){return e}}));
5 |
--------------------------------------------------------------------------------
/images/ProductioniZingShiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/images/ProductioniZingShiny.png
--------------------------------------------------------------------------------
/images/athlyticz.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/images/athlyticz.png
--------------------------------------------------------------------------------
/images/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/images/background.png
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hypebright/shinyconf2024-shiny101/00dc48a1fef26a450012a3138cd691862fd7149b/images/logo.png
--------------------------------------------------------------------------------
/shescores/app.R:
--------------------------------------------------------------------------------
1 | # global ------------------------------------------------
2 | library(shiny)
3 | library(bslib)
4 | library(shinyWidgets)
5 | library(lubridate)
6 | library(dplyr)
7 | library(echarts4r)
8 | library(DT)
9 |
10 | custom_theme <- bs_theme(
11 | version = 5,
12 | # or any other theme that you want
13 | bootswatch = "quartz",
14 | base_font = font_google("PT Sans")
15 | )
16 |
17 | # set font for echarts
18 | e_common(
19 | font_family = "PT Sans",
20 | theme = NULL
21 | )
22 |
23 | # read in data
24 | # note that for demo purposes this is in a top-level folder
25 | # normally, you would have this inside the folder that contains app.R
26 | soccer_scorers <- readRDS("../data/soccer_scorers.rds")
27 | soccer_matches <- readRDS("../data/soccer_matches.rds")
28 | soccer_rank <- readRDS("../data/country_rank.rds")
29 |
30 | # options for in pickerInput
31 | available_countries <- sort(unique(soccer_matches$home_team))
32 |
33 | available_countries <- setNames(available_countries,
34 | sort(unique(paste(soccer_matches$home_team, soccer_matches$country_flag_home))))
35 |
36 | # country page module ------------------------------------
37 | countryPageUI <- function(id, page_name) {
38 | ns <- NS(id)
39 | tagList(
40 | h2(page_name),
41 | prettyCheckbox(
42 | inputId = ns("favourite"),
43 | label = "This is my favourite",
44 | value = FALSE,
45 | status = "warning",
46 | icon = icon("star"),
47 | plain = TRUE,
48 | outline = TRUE
49 | ),
50 | card(
51 | min_height = "700px",
52 | card_header("General Info"),
53 | card_body(
54 | textOutput(ns("general_info")),
55 | DTOutput(ns("matches_table"))
56 | )
57 | )
58 | )
59 | }
60 |
61 | countryPageServer <- function(id, chosen_country, r) {
62 | moduleServer(id, function(input, output, session) {
63 |
64 | this_soccer_matches <- reactive({
65 | soccer_matches |>
66 | filter(home_team == chosen_country | away_team == chosen_country) |>
67 | arrange(desc(date))
68 | })
69 |
70 | output$general_info <- renderText({
71 | sprintf("%s has played %s matches in total. The first match was on %s and the last match was on %s.",
72 | chosen_country,
73 | nrow(this_soccer_matches()),
74 | min(this_soccer_matches()$date),
75 | max(this_soccer_matches()$date)
76 | )
77 | })
78 |
79 | output$matches_table <- renderDT({
80 | this_soccer_matches() |>
81 | select(date, home_team, away_team, home_score, away_score, tournament) |>
82 | datatable()
83 | })
84 |
85 | observe({
86 |
87 | req(!is.null(input$favourite))
88 |
89 | current_favourites <- isolate(r$favourites)
90 |
91 | if (input$favourite) {
92 | r$favourites <- c(current_favourites, chosen_country)
93 | } else {
94 | r$favourites <- current_favourites[current_favourites != chosen_country]
95 | }
96 | })
97 |
98 | })
99 | }
100 |
101 | # main app -----------------------------------------------
102 | ui <- page_navbar(
103 | theme = custom_theme,
104 | title = "She Scores ⚽️: Women's International Soccer Matches",
105 | id = "navbar_id",
106 | nav_panel(
107 | title = "Overview",
108 | fluidRow(
109 | column(4,
110 | value_box(
111 | title = "Top scoring country",
112 | value = paste(
113 | head(soccer_rank$country, 1),
114 | head(soccer_rank$country_flag, 1)
115 | )
116 | )),
117 | column(4,
118 | value_box(
119 | title = "Top scorer",
120 | value = paste(
121 | head(soccer_scorers$scorer, 1),
122 | head(soccer_scorers$country_flag, 1)
123 | )
124 | )),
125 | column(4,
126 | value_box(
127 | title = "Total countries",
128 | value = length(unique(soccer_matches$home_team))
129 | ))
130 | ),
131 | card(
132 | min_height = "600px",
133 | echarts4rOutput("overview")
134 | )
135 | ),
136 | nav_menu(title = "Countries",
137 | nav_panel(
138 | title = "Set-up",
139 | pickerInput(
140 | inputId = "countries",
141 | label = "Select a country",
142 | multiple = TRUE,
143 | choices = available_countries,
144 | selected = NULL,
145 | options = pickerOptions(
146 | actionsBox = TRUE,
147 | liveSearch = TRUE,
148 | liveSearchPlaceholder = "Search for a country",
149 | selectedTextFormat = "count > 1",
150 | countSelectedText = "{0} countries selected"
151 | )
152 | ),
153 | actionButton(
154 | inputId = "pages",
155 | label = "Generate pages",
156 | icon = icon("plus-circle"),
157 | width = "250px"
158 | ),
159 | textOutput("favourites")
160 | )
161 | )
162 | )
163 |
164 | server <- function(input, output, session) {
165 |
166 | r <- reactiveValues(active_pages = NULL,
167 | favourites = NULL)
168 |
169 | output$overview <- renderEcharts4r({
170 | # get the number of matches over time
171 | soccer_matches |>
172 | mutate(date = as.Date(date)) |>
173 | group_by(date = lubridate::floor_date(date, "year")) |>
174 | summarise(matches = n()) |>
175 | e_charts(date) |>
176 | e_line(matches,
177 | lineStyle = list(
178 | width = 3,
179 | color = "#0f0437"
180 | )) |>
181 | e_title("Soccer matches over time",
182 | left = "40%", # somehow textAlign does not work
183 | textStyle = list(
184 | color = "white",
185 | fontSize = 26
186 | )) |>
187 | e_tooltip() |>
188 | e_legend(show = FALSE) |>
189 | e_x_axis(
190 | axisLabel = list(
191 | color = "white"
192 | )
193 | ) |>
194 | e_y_axis(
195 | axisLabel = list(
196 | color = "white"
197 | )
198 | )
199 | })
200 |
201 | observe({
202 |
203 | chosen_countries <- input$countries
204 |
205 | lapply(chosen_countries, function(this_country) {
206 |
207 | if (this_country %in% r$active_pages) {
208 | # the country already has a page, and is chosen by the user, so don't do anything
209 | return()
210 | }
211 |
212 | nav_insert(id = "navbar_id",
213 | target = "Set-up",
214 | nav = nav_panel(
215 | title = this_country,
216 | countryPageUI(id = this_country, page_name = this_country)
217 | ),
218 | position = "after")
219 |
220 | countryPageServer(id = this_country,
221 | chosen_country = this_country,
222 | r = r)
223 |
224 | })
225 |
226 | if (setdiff(r$active_pages, chosen_countries) |> length() > 0) {
227 |
228 | remove_countries <- setdiff(r$active_pages, chosen_countries)
229 |
230 | for (country in remove_countries) {
231 | nav_remove(id = "navbar_id",
232 | target = country)
233 |
234 | }
235 |
236 | }
237 |
238 | r$active_pages <- chosen_countries
239 |
240 | }) |> bindEvent(input$pages)
241 |
242 | output$favourites <- renderText({
243 | if (is.null(r$favourites)) {
244 | "You have not selected any favourites yet."
245 | } else {
246 | sprintf("Your favourite countries are: %s",
247 | paste(r$favourites, collapse = ", "))
248 | }
249 | })
250 |
251 | }
252 |
253 | shinyApp(ui, server)
254 |
--------------------------------------------------------------------------------
/shinyconf2024-shiny101.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 |
3 | RestoreWorkspace: Default
4 | SaveWorkspace: Default
5 | AlwaysSaveHistory: Default
6 |
7 | EnableCodeIndexing: Yes
8 | UseSpacesForTab: Yes
9 | NumSpacesForTab: 2
10 | Encoding: UTF-8
11 |
12 | RnwWeave: Sweave
13 | LaTeX: pdfLaTeX
14 |
15 | AutoAppendNewline: Yes
16 | StripTrailingWhitespace: Yes
17 |
--------------------------------------------------------------------------------
/templates/00_base.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | ui <- fluidPage(
4 | numericInput(inputId = "number",
5 | label = "Enter a number",
6 | value = 0),
7 |
8 | textOutput(outputId = "text")
9 | )
10 |
11 | server <- function(input, output, session) {
12 | output$text <- renderText({
13 | input$number^2
14 | })
15 | }
16 |
17 | shinyApp(ui, server)
18 |
--------------------------------------------------------------------------------
/templates/01_start.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 |
3 | ui <- fluidPage(
4 | numericInput(inputId = "number",
5 | label = "Enter a number",
6 | value = 0),
7 |
8 | actionButton(inputId = "button",
9 | label = "Calculate"),
10 |
11 | textOutput(outputId = "text")
12 | )
13 |
14 | server <- function(input, output, session) {
15 | output$text <- renderText({
16 | input$number^2
17 | }) |> bindEvent(input$button)
18 | }
19 |
20 | shinyApp(ui, server)
21 |
--------------------------------------------------------------------------------
/templates/02_bslib.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(bslib)
3 |
4 | custom_theme <- bs_theme(
5 | version = 5,
6 | # for themes see: https://bootswatch.com
7 | preset = "quartz",
8 | base_font = font_google("PT Sans"),
9 | bg = NULL,
10 | fg = NULL,
11 | primary = NULL,
12 | secondary = NULL,
13 | success = NULL,
14 | info = NULL,
15 | warning = NULL,
16 | danger = NULL,
17 | code_font = NULL,
18 | heading_font = NULL,
19 | font_scale = NULL
20 | )
21 |
22 | ui <- page_navbar(
23 | theme = custom_theme,
24 | title = "Modular App Blueprint",
25 | nav_panel(
26 | title = "Numbers",
27 | numericInput(inputId = "number",
28 | label = "Enter a number",
29 | value = 0),
30 | actionButton(inputId = "button",
31 | label = "Calculate"),
32 | textOutput(outputId = "text")
33 | )
34 | )
35 |
36 | server <- function(input, output, session) {
37 | output$text <- renderText({
38 | input$number^2
39 | }) |> bindEvent(input$button)
40 | }
41 |
42 | shinyApp(ui, server)
43 |
--------------------------------------------------------------------------------
/templates/03_modules.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(bslib)
3 |
4 | custom_theme <- bs_theme(
5 | version = 5,
6 | # for themes see: https://bootswatch.com
7 | preset = "quartz",
8 | base_font = font_google("PT Sans"),
9 | bg = NULL,
10 | fg = NULL,
11 | primary = NULL,
12 | secondary = NULL,
13 | success = NULL,
14 | info = NULL,
15 | warning = NULL,
16 | danger = NULL,
17 | code_font = NULL,
18 | heading_font = NULL,
19 | font_scale = NULL
20 | )
21 |
22 | numberModUI <- function(id) {
23 | ns <- NS(id)
24 | tagList(
25 | numericInput(inputId = ns("number"),
26 | label = "Enter a number",
27 | value = 0),
28 | actionButton(inputId = ns("button"),
29 | label = "Calculate"),
30 | textOutput(outputId = ns("text"))
31 | )
32 | }
33 |
34 | numberModServer <- function(id) {
35 | moduleServer(id, function(input, output, session) {
36 | output$text <- renderText({
37 | input$number^2
38 | }) |> bindEvent(input$button)
39 | })
40 | }
41 |
42 | ui <- page_navbar(
43 | theme = custom_theme,
44 | title = "Modular App Blueprint",
45 | nav_panel(
46 | title = "Numbers",
47 | numberModUI("numbers")
48 | )
49 | )
50 |
51 | server <- function(input, output, session) {
52 | numberModServer("numbers")
53 | }
54 |
55 | shinyApp(ui, server)
56 |
--------------------------------------------------------------------------------
/templates/04_nested_modules.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(bslib)
3 | library(DT)
4 |
5 | custom_theme <- bs_theme(
6 | version = 5,
7 | # for themes see: https://bootswatch.com
8 | preset = "quartz",
9 | base_font = font_google("PT Sans"),
10 | bg = NULL,
11 | fg = NULL,
12 | primary = NULL,
13 | secondary = NULL,
14 | success = NULL,
15 | info = NULL,
16 | warning = NULL,
17 | danger = NULL,
18 | code_font = NULL,
19 | heading_font = NULL,
20 | font_scale = NULL
21 | )
22 |
23 | # module 1 -------------------------------------------------------
24 | numberModUI <- function(id) {
25 | ns <- NS(id)
26 | tagList(
27 | numericInput(inputId = ns("number"),
28 | label = "Enter a number",
29 | value = 0),
30 | actionButton(inputId = ns("button"),
31 | label = "Calculate"),
32 | textOutput(outputId = ns("text")),
33 | numberAnalysisModUI(ns("analysis"))
34 | )
35 | }
36 |
37 | numberModServer <- function(id) {
38 | moduleServer(id, function(input, output, session) {
39 |
40 | output$text <- renderText({
41 | input$number^2
42 | }) |> bindEvent(input$button)
43 |
44 | numberAnalysisServer("analysis")
45 |
46 | })
47 | }
48 |
49 | # module 2 -------------------------------------------------------
50 | numberAnalysisModUI <- function(id) {
51 | ns <- NS(id)
52 | dataTableOutput(outputId = ns("table"))
53 | }
54 |
55 | numberAnalysisServer <- function(id) {
56 | moduleServer(id, function(input, output, session) {
57 | # display table of squares and highlight the number
58 | output$table <- renderDataTable({
59 | squares <- 1:10
60 | squares <- data.frame(number = squares, square = squares^2)
61 | datatable(squares, rownames = FALSE)
62 | })
63 | })
64 | }
65 |
66 | # app ------------------------------------------------------------
67 | ui <- page_navbar(
68 | theme = custom_theme,
69 | title = "Modular App Blueprint",
70 | nav_panel(
71 | title = "Numbers",
72 | numberModUI("numbers")
73 | )
74 | )
75 |
76 | server <- function(input, output, session) {
77 | numberModServer("numbers")
78 | }
79 |
80 | shinyApp(ui, server)
81 |
--------------------------------------------------------------------------------
/templates/05_sharing_data.R:
--------------------------------------------------------------------------------
1 | library(shiny)
2 | library(bslib)
3 | library(DT)
4 |
5 | custom_theme <- bs_theme(
6 | version = 5,
7 | # for themes see: https://bootswatch.com
8 | preset = "quartz",
9 | base_font = font_google("PT Sans"),
10 | bg = NULL,
11 | fg = NULL,
12 | primary = NULL,
13 | secondary = NULL,
14 | success = NULL,
15 | info = NULL,
16 | warning = NULL,
17 | danger = NULL,
18 | code_font = NULL,
19 | heading_font = NULL,
20 | font_scale = NULL
21 | )
22 |
23 | # module 1 -------------------------------------------------------
24 | numberModUI <- function(id) {
25 | ns <- NS(id)
26 | tagList(
27 | numericInput(inputId = ns("number"),
28 | label = "Enter a number",
29 | value = 0),
30 | actionButton(inputId = ns("button"),
31 | label = "Calculate"),
32 | textOutput(outputId = ns("text")),
33 | numberAnalysisModUI(ns("analysis"))
34 | )
35 | }
36 |
37 | numberModServer <- function(id, r) {
38 | moduleServer(id, function(input, output, session) {
39 |
40 | output$text <- renderText({
41 | input$number^2
42 | }) |> bindEvent(input$button)
43 |
44 | observe({
45 | r$number <- input$number
46 | r$button <- input$button
47 | })
48 |
49 | numberAnalysisServer("analysis", r = r)
50 |
51 | })
52 | }
53 |
54 | # module 2 -------------------------------------------------------
55 | numberAnalysisModUI <- function(id) {
56 | ns <- NS(id)
57 | dataTableOutput(outputId = ns("table"))
58 | }
59 |
60 | numberAnalysisServer <- function(id, r) {
61 | moduleServer(id, function(input, output, session) {
62 | # display table of squares and highlight the number
63 | output$table <- renderDataTable({
64 | req(r$number > 0)
65 | squares <- 1:(r$number + 5)
66 | squares <- data.frame(number = squares, square = squares^2)
67 | datatable(squares, rownames = FALSE, selection = "none") |>
68 | formatStyle(columns = "number",
69 | target = "row",
70 | border = styleEqual(r$number, "3px"))
71 | }) |> bindEvent(r$button)
72 | })
73 | }
74 |
75 | # app ------------------------------------------------------------
76 | ui <- page_navbar(
77 | theme = custom_theme,
78 | title = "Modular App Blueprint",
79 | nav_panel(
80 | title = "Numbers",
81 | numberModUI("numbers")
82 | )
83 | )
84 |
85 | server <- function(input, output, session) {
86 |
87 | r <- reactiveValues(number = NULL,
88 | button = NULL)
89 |
90 | numberModServer("numbers", r = r)
91 | }
92 |
93 | shinyApp(ui, server)
94 |
--------------------------------------------------------------------------------