├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── _extensions ├── coatless │ └── webr │ │ ├── _extension.yml │ │ ├── qwebr-cell-elements.js │ │ ├── qwebr-cell-initialization.js │ │ ├── qwebr-compute-engine.js │ │ ├── qwebr-document-engine-initialization.js │ │ ├── qwebr-document-settings.js │ │ ├── qwebr-document-status.js │ │ ├── qwebr-monaco-editor-element.js │ │ ├── qwebr-monaco-editor-init.html │ │ ├── qwebr-styling.css │ │ ├── template.qmd │ │ ├── webr-serviceworker.js │ │ ├── webr-worker.js │ │ └── webr.lua └── nrennie │ └── PrettyPDF │ ├── PrettyPDF.tex │ ├── Ubuntu │ ├── UFL.txt │ ├── Ubuntu-Bold.ttf │ ├── Ubuntu-BoldItalic.ttf │ ├── Ubuntu-Italic.ttf │ ├── Ubuntu-Light.ttf │ ├── Ubuntu-LightItalic.ttf │ ├── Ubuntu-Medium.ttf │ ├── Ubuntu-MediumItalic.ttf │ └── Ubuntu-Regular.ttf │ ├── _extension.yml │ ├── logo.png │ └── pagestyle.tex ├── _quarto.yml ├── custom.scss ├── index.html ├── index.qmd ├── index_files ├── figure-revealjs │ ├── pop-people-1.png │ └── samp-people-1.png └── 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 ├── learning-statistics-with-webR.Rproj ├── webr-serviceworker.js └── webr-worker.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-detectable=false 2 | *.js linguist-detectable=false 3 | *.rmd linguist-detectable 4 | *.Rmd linguist-detectable 5 | *.qmd linguist-detectable 6 | *.lua linguist-detectable=false 7 | *.tex linguist-detectable=false 8 | *.scss linguist-detectable=false 9 | *.css linguist-detectable=false 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | drafts/ 6 | /.quarto/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learning statistics with webR 2 | 3 | Quarto revealjs slides to teach introductory statistics concepts using webR. Note that this project is currently a work-in-progress. 4 | 5 | Built using the Quarto webR extension: [github.com/coatless/quarto-webr](https://github.com/coatless/quarto-webr) 6 | -------------------------------------------------------------------------------- /_extensions/coatless/webr/_extension.yml: -------------------------------------------------------------------------------- 1 | name: webr 2 | title: Embedded webr code cells 3 | author: James Joseph Balamuta 4 | version: 0.4.1-dev.1 5 | quarto-required: ">=1.2.198" 6 | contributes: 7 | filters: 8 | - webr.lua 9 | -------------------------------------------------------------------------------- /_extensions/coatless/webr/qwebr-cell-elements.js: -------------------------------------------------------------------------------- 1 | // Supported Evaluation Types for Context 2 | globalThis.EvalTypes = Object.freeze({ 3 | Interactive: 'interactive', 4 | Setup: 'setup', 5 | Output: 'output', 6 | }); 7 | 8 | // Function that dispatches the creation request 9 | globalThis.qwebrCreateHTMLElement = function ( 10 | cellData 11 | ) { 12 | 13 | // Extract key components 14 | const evalType = cellData.options.context; 15 | const qwebrCounter = cellData.id; 16 | 17 | // We make an assumption that insertion points are defined by the Lua filter as: 18 | // qwebr-insertion-location-{qwebrCounter} 19 | const elementLocator = document.getElementById(`qwebr-insertion-location-${qwebrCounter}`); 20 | 21 | // Figure out the routine to use to insert the element. 22 | let qwebrElement; 23 | switch ( evalType ) { 24 | case EvalTypes.Interactive: 25 | qwebrElement = qwebrCreateInteractiveElement(qwebrCounter, cellData.options); 26 | break; 27 | case EvalTypes.Output: 28 | qwebrElement = qwebrCreateNonInteractiveOutputElement(qwebrCounter, cellData.options); 29 | break; 30 | case EvalTypes.Setup: 31 | qwebrElement = qwebrCreateNonInteractiveSetupElement(qwebrCounter, cellData.options); 32 | break; 33 | default: 34 | qwebrElement = document.createElement('div'); 35 | qwebrElement.textContent = 'Error creating `quarto-webr` element'; 36 | } 37 | 38 | // Insert the dynamically generated object at the document location. 39 | elementLocator.appendChild(qwebrElement); 40 | }; 41 | 42 | // Function that setups the interactive element creation 43 | globalThis.qwebrCreateInteractiveElement = function (qwebrCounter, qwebrOptions) { 44 | 45 | // Create main div element 46 | var mainDiv = document.createElement('div'); 47 | mainDiv.id = 'qwebr-interactive-area-' + qwebrCounter; 48 | mainDiv.className = `qwebr-interactive-area`; 49 | if (qwebrOptions.classes) { 50 | mainDiv.className += " " + qwebrOptions.classes 51 | } 52 | 53 | // Add a unique cell identifier that users can customize 54 | if (qwebrOptions.label) { 55 | mainDiv.setAttribute('data-id', qwebrOptions.label); 56 | } 57 | 58 | // Create toolbar div 59 | var toolbarDiv = document.createElement('div'); 60 | toolbarDiv.className = 'qwebr-editor-toolbar'; 61 | toolbarDiv.id = 'qwebr-editor-toolbar-' + qwebrCounter; 62 | 63 | // Create a div to hold the left buttons 64 | var leftButtonsDiv = document.createElement('div'); 65 | leftButtonsDiv.className = 'qwebr-editor-toolbar-left-buttons'; 66 | 67 | // Create a div to hold the right buttons 68 | var rightButtonsDiv = document.createElement('div'); 69 | rightButtonsDiv.className = 'qwebr-editor-toolbar-right-buttons'; 70 | 71 | // Create Run Code button 72 | var runCodeButton = document.createElement('button'); 73 | runCodeButton.className = 'btn btn-default qwebr-button qwebr-button-run'; 74 | runCodeButton.disabled = true; 75 | runCodeButton.type = 'button'; 76 | runCodeButton.id = 'qwebr-button-run-' + qwebrCounter; 77 | runCodeButton.textContent = '🟡 Loading webR...'; 78 | runCodeButton.title = `Run code (Shift + Enter)`; 79 | 80 | // Append buttons to the leftButtonsDiv 81 | leftButtonsDiv.appendChild(runCodeButton); 82 | 83 | // Create Reset button 84 | var resetButton = document.createElement('button'); 85 | resetButton.className = 'btn btn-light btn-xs qwebr-button qwebr-button-reset'; 86 | resetButton.type = 'button'; 87 | resetButton.id = 'qwebr-button-reset-' + qwebrCounter; 88 | resetButton.title = 'Start over'; 89 | resetButton.innerHTML = ''; 90 | 91 | // Create Copy button 92 | var copyButton = document.createElement('button'); 93 | copyButton.className = 'btn btn-light btn-xs qwebr-button qwebr-button-copy'; 94 | copyButton.type = 'button'; 95 | copyButton.id = 'qwebr-button-copy-' + qwebrCounter; 96 | copyButton.title = 'Copy code'; 97 | copyButton.innerHTML = ''; 98 | 99 | // Append buttons to the rightButtonsDiv 100 | rightButtonsDiv.appendChild(resetButton); 101 | rightButtonsDiv.appendChild(copyButton); 102 | 103 | // Create console area div 104 | var consoleAreaDiv = document.createElement('div'); 105 | consoleAreaDiv.id = 'qwebr-console-area-' + qwebrCounter; 106 | consoleAreaDiv.className = 'qwebr-console-area'; 107 | 108 | // Create editor div 109 | var editorDiv = document.createElement('div'); 110 | editorDiv.id = 'qwebr-editor-' + qwebrCounter; 111 | editorDiv.className = 'qwebr-editor'; 112 | 113 | // Create output code area div 114 | var outputCodeAreaDiv = document.createElement('div'); 115 | outputCodeAreaDiv.id = 'qwebr-output-code-area-' + qwebrCounter; 116 | outputCodeAreaDiv.className = 'qwebr-output-code-area'; 117 | outputCodeAreaDiv.setAttribute('aria-live', 'assertive'); 118 | 119 | // Create pre element inside output code area 120 | var preElement = document.createElement('pre'); 121 | preElement.style.visibility = 'hidden'; 122 | outputCodeAreaDiv.appendChild(preElement); 123 | 124 | // Create output graph area div 125 | var outputGraphAreaDiv = document.createElement('div'); 126 | outputGraphAreaDiv.id = 'qwebr-output-graph-area-' + qwebrCounter; 127 | outputGraphAreaDiv.className = 'qwebr-output-graph-area'; 128 | 129 | // Append buttons to the toolbar 130 | toolbarDiv.appendChild(leftButtonsDiv); 131 | toolbarDiv.appendChild(rightButtonsDiv); 132 | 133 | // Append all elements to the main div 134 | mainDiv.appendChild(toolbarDiv); 135 | consoleAreaDiv.appendChild(editorDiv); 136 | consoleAreaDiv.appendChild(outputCodeAreaDiv); 137 | mainDiv.appendChild(consoleAreaDiv); 138 | mainDiv.appendChild(outputGraphAreaDiv); 139 | 140 | return mainDiv; 141 | } 142 | 143 | // Function that adds output structure for non-interactive output 144 | globalThis.qwebrCreateNonInteractiveOutputElement = function(qwebrCounter, qwebrOptions) { 145 | // Create main div element 146 | var mainDiv = document.createElement('div'); 147 | mainDiv.id = 'qwebr-noninteractive-area-' + qwebrCounter; 148 | mainDiv.className = `qwebr-noninteractive-area`; 149 | if (qwebrOptions.classes) { 150 | mainDiv.className += " " + qwebrOptions.classes 151 | } 152 | 153 | // Add a unique cell identifier that users can customize 154 | if (qwebrOptions.label) { 155 | mainDiv.setAttribute('data-id', qwebrOptions.label); 156 | } 157 | 158 | // Create a status container div 159 | var statusContainer = createLoadingContainer(qwebrCounter); 160 | 161 | // Create output code area div 162 | var outputCodeAreaDiv = document.createElement('div'); 163 | outputCodeAreaDiv.id = 'qwebr-output-code-area-' + qwebrCounter; 164 | outputCodeAreaDiv.className = 'qwebr-output-code-area'; 165 | outputCodeAreaDiv.setAttribute('aria-live', 'assertive'); 166 | 167 | // Create pre element inside output code area 168 | var preElement = document.createElement('pre'); 169 | preElement.style.visibility = 'hidden'; 170 | outputCodeAreaDiv.appendChild(preElement); 171 | 172 | // Create output graph area div 173 | var outputGraphAreaDiv = document.createElement('div'); 174 | outputGraphAreaDiv.id = 'qwebr-output-graph-area-' + qwebrCounter; 175 | outputGraphAreaDiv.className = 'qwebr-output-graph-area'; 176 | 177 | // Append all elements to the main div 178 | mainDiv.appendChild(statusContainer); 179 | mainDiv.appendChild(outputCodeAreaDiv); 180 | mainDiv.appendChild(outputGraphAreaDiv); 181 | 182 | return mainDiv; 183 | }; 184 | 185 | // Function that adds a stub in the page to indicate a setup cell was used. 186 | globalThis.qwebrCreateNonInteractiveSetupElement = function(qwebrCounter, qwebrOptions) { 187 | // Create main div element 188 | var mainDiv = document.createElement('div'); 189 | mainDiv.id = `qwebr-noninteractive-setup-area-${qwebrCounter}`; 190 | mainDiv.className = `qwebr-noninteractive-setup-area`; 191 | if (qwebrOptions.classes) { 192 | mainDiv.className += " " + qwebrOptions.classes 193 | } 194 | 195 | 196 | // Add a unique cell identifier that users can customize 197 | if (qwebrOptions.label) { 198 | mainDiv.setAttribute('data-id', qwebrOptions.label); 199 | } 200 | 201 | // Create a status container div 202 | var statusContainer = createLoadingContainer(qwebrCounter); 203 | 204 | // Append status onto the main div 205 | mainDiv.appendChild(statusContainer); 206 | 207 | return mainDiv; 208 | } 209 | 210 | 211 | // Function to create loading container with specified ID 212 | globalThis.createLoadingContainer = function(qwebrCounter) { 213 | 214 | // Create a status container 215 | const container = document.createElement('div'); 216 | container.id = `qwebr-non-interactive-loading-container-${qwebrCounter}`; 217 | container.className = 'qwebr-non-interactive-loading-container qwebr-cell-needs-evaluation'; 218 | 219 | // Create an R project logo to indicate its a code space 220 | const rProjectIcon = document.createElement('i'); 221 | rProjectIcon.className = 'fa-brands fa-r-project fa-3x qwebr-r-project-logo'; 222 | 223 | // Setup a loading icon from font awesome 224 | const spinnerIcon = document.createElement('i'); 225 | spinnerIcon.className = 'fa-solid fa-spinner fa-spin fa-1x qwebr-icon-status-spinner'; 226 | 227 | // Add a section for status text 228 | const statusText = document.createElement('p'); 229 | statusText.id = `qwebr-status-text-${qwebrCounter}`; 230 | statusText.className = `qwebr-status-text qwebr-cell-needs-evaluation`; 231 | statusText.innerText = 'Loading webR...'; 232 | 233 | // Incorporate an inner container 234 | const innerContainer = document.createElement('div'); 235 | 236 | // Append elements to the inner container 237 | innerContainer.appendChild(spinnerIcon); 238 | innerContainer.appendChild(statusText); 239 | 240 | // Append elements to the main container 241 | container.appendChild(rProjectIcon); 242 | container.appendChild(innerContainer); 243 | 244 | return container; 245 | } -------------------------------------------------------------------------------- /_extensions/coatless/webr/qwebr-cell-initialization.js: -------------------------------------------------------------------------------- 1 | // Handle cell initialization initialization 2 | qwebrCellDetails.map( 3 | (entry) => { 4 | // Handle the creation of the element 5 | qwebrCreateHTMLElement(entry); 6 | // In the event of interactive, initialize the monaco editor 7 | if (entry.options.context == EvalTypes.Interactive) { 8 | qwebrCreateMonacoEditorInstance(entry); 9 | } 10 | } 11 | ); 12 | 13 | // Identify non-interactive cells (in order) 14 | const filteredEntries = qwebrCellDetails.filter(entry => { 15 | const contextOption = entry.options && entry.options.context; 16 | return ['output', 'setup'].includes(contextOption) || (contextOption == "interactive" && entry.options && entry.options.autorun === 'true'); 17 | }); 18 | 19 | // Condition non-interactive cells to only be run after webR finishes its initialization. 20 | qwebrInstance.then( 21 | async () => { 22 | const nHiddenCells = filteredEntries.length; 23 | var currentHiddenCell = 0; 24 | 25 | 26 | // Modify button state 27 | qwebrSetInteractiveButtonState(`🟡 Running hidden code cells ...`, false); 28 | 29 | // Begin processing non-interactive sections 30 | // Due to the iteration policy, we must use a for() loop. 31 | // Otherwise, we would need to switch to using reduce with an empty 32 | // starting promise 33 | for (const entry of filteredEntries) { 34 | 35 | // Determine cell being examined 36 | currentHiddenCell = currentHiddenCell + 1; 37 | const formattedMessage = `Evaluating hidden cell ${currentHiddenCell} out of ${nHiddenCells}`; 38 | 39 | // Update the document status header 40 | if (qwebrShowStartupMessage) { 41 | qwebrUpdateStatusHeader(formattedMessage); 42 | } 43 | 44 | // Display the update in non-active areas 45 | qwebrUpdateStatusMessage(formattedMessage); 46 | 47 | // Extract details on the active cell 48 | const evalType = entry.options.context; 49 | const cellCode = entry.code; 50 | const qwebrCounter = entry.id; 51 | 52 | if (['output', 'setup'].includes(evalType)) { 53 | // Disable further global status updates 54 | const activeContainer = document.getElementById(`qwebr-non-interactive-loading-container-${qwebrCounter}`); 55 | activeContainer.classList.remove('qwebr-cell-needs-evaluation'); 56 | activeContainer.classList.add('qwebr-cell-evaluated'); 57 | 58 | // Update status on the code cell 59 | const activeStatus = document.getElementById(`qwebr-status-text-${qwebrCounter}`); 60 | activeStatus.innerText = " Evaluating hidden code cell..."; 61 | activeStatus.classList.remove('qwebr-cell-needs-evaluation'); 62 | activeStatus.classList.add('qwebr-cell-evaluated'); 63 | } 64 | 65 | switch (evalType) { 66 | case 'interactive': 67 | // TODO: Make this more standardized. 68 | // At the moment, we're overriding the interactive status update by pretending its 69 | // output-like. 70 | const tempOptions = entry.options; 71 | tempOptions["context"] = "output" 72 | // Run the code in a non-interactive state that is geared to displaying output 73 | await qwebrExecuteCode(`${cellCode}`, qwebrCounter, tempOptions); 74 | break; 75 | case 'output': 76 | // Run the code in a non-interactive state that is geared to displaying output 77 | await qwebrExecuteCode(`${cellCode}`, qwebrCounter, entry.options); 78 | break; 79 | case 'setup': 80 | const activeDiv = document.getElementById(`qwebr-noninteractive-setup-area-${qwebrCounter}`); 81 | // Run the code in a non-interactive state with all output thrown away 82 | await mainWebR.evalRVoid(`${cellCode}`); 83 | break; 84 | default: 85 | break; 86 | } 87 | 88 | if (['output', 'setup'].includes(evalType)) { 89 | // Disable further global status updates 90 | const activeContainer = document.getElementById(`qwebr-non-interactive-loading-container-${qwebrCounter}`); 91 | // Disable visibility 92 | activeContainer.style.visibility = 'hidden'; 93 | activeContainer.style.display = 'none'; 94 | } 95 | } 96 | } 97 | ).then( 98 | () => { 99 | // Release document status as ready 100 | 101 | if (qwebrShowStartupMessage) { 102 | qwebrStartupMessage.innerText = "🟢 Ready!" 103 | } 104 | 105 | qwebrSetInteractiveButtonState( 106 | ` Run Code`, 107 | true 108 | ); 109 | } 110 | ); -------------------------------------------------------------------------------- /_extensions/coatless/webr/qwebr-compute-engine.js: -------------------------------------------------------------------------------- 1 | // Function to verify a given JavaScript Object is empty 2 | globalThis.qwebrIsObjectEmpty = function (arr) { 3 | return Object.keys(arr).length === 0; 4 | } 5 | 6 | // Global version of the Escape HTML function that converts HTML 7 | // characters to their HTML entities. 8 | globalThis.qwebrEscapeHTMLCharacters = function(unsafe) { 9 | return unsafe 10 | .replace(/&/g, "&") 11 | .replace(//g, ">") 13 | .replace(/"/g, """) 14 | .replace(/'/g, "'"); 15 | }; 16 | 17 | // Passthrough results 18 | globalThis.qwebrIdentity = function(x) { 19 | return x; 20 | }; 21 | 22 | // Append a comment 23 | globalThis.qwebrPrefixComment = function(x, comment) { 24 | return `${comment}${x}`; 25 | }; 26 | 27 | // Function to parse the pager results 28 | globalThis.qwebrParseTypePager = async function (msg) { 29 | 30 | // Split out the event data 31 | const { path, title, deleteFile } = msg.data; 32 | 33 | // Process the pager data by reading the information from disk 34 | const paged_data = await mainWebR.FS.readFile(path).then((data) => { 35 | // Obtain the file content 36 | let content = new TextDecoder().decode(data); 37 | 38 | // Remove excessive backspace characters until none remain 39 | while(content.match(/.[\b]/)){ 40 | content = content.replace(/.[\b]/g, ''); 41 | } 42 | 43 | // Returned cleaned data 44 | return content; 45 | }); 46 | 47 | // Unlink file if needed 48 | if (deleteFile) { 49 | await mainWebR.FS.unlink(path); 50 | } 51 | 52 | // Return extracted data with spaces 53 | return paged_data; 54 | } 55 | 56 | // Function to run the code using webR and parse the output 57 | globalThis.qwebrComputeEngine = async function( 58 | codeToRun, 59 | elements, 60 | options) { 61 | 62 | // Call into the R compute engine that persists within the document scope. 63 | // To be prepared for all scenarios, the following happens: 64 | // 1. We setup a canvas device to write to by making a namespace call into the {webr} package 65 | // 2. We use values inside of the options array to set the figure size. 66 | // 3. We capture the output stream information (STDOUT and STERR) 67 | // 4. While parsing the results, we disable image creation. 68 | 69 | // Create a canvas variable for graphics 70 | let canvas = undefined; 71 | 72 | // Create a pager variable for help/file contents 73 | let pager = []; 74 | 75 | // Handle how output is processed 76 | let showMarkup = options.results === "markup" && options.output !== "asis"; 77 | let processOutput; 78 | 79 | if (showMarkup) { 80 | processOutput = qwebrEscapeHTMLCharacters; 81 | } else { 82 | processOutput = qwebrIdentity; 83 | } 84 | 85 | // ---- 86 | // Convert from Inches to Pixels by using DPI (dots per inch) 87 | // for bitmap devices (dpi * inches = pixels) 88 | let fig_width = options["fig-width"] * options["dpi"] 89 | let fig_height = options["fig-height"] * options["dpi"] 90 | 91 | // Initialize webR 92 | await mainWebR.init(); 93 | 94 | // Setup a webR canvas by making a namespace call into the {webr} package 95 | await mainWebR.evalRVoid(`webr::canvas(width=${fig_width}, height=${fig_height})`); 96 | 97 | const result = await mainWebRCodeShelter.captureR(codeToRun, { 98 | withAutoprint: true, 99 | captureStreams: true, 100 | captureConditions: false//, 101 | // env: webR.objs.emptyEnv, // maintain a global environment for webR v0.2.0 102 | }); 103 | 104 | // ----- 105 | 106 | // Start attempting to parse the result data 107 | processResultOutput:try { 108 | 109 | // Stop creating images 110 | await mainWebR.evalRVoid("dev.off()"); 111 | 112 | // Avoid running through output processing 113 | if (options.results === "hide" || options.output === "false") { 114 | break processResultOutput; 115 | } 116 | 117 | // Merge output streams of STDOUT and STDErr (messages and errors are combined.) 118 | // Require both `warning` and `message` to be true to display `STDErr`. 119 | const out = result.output 120 | .filter( 121 | evt => evt.type === "stdout" || 122 | ( evt.type === "stderr" && (options.warning === "true" && options.message === "true")) 123 | ) 124 | .map((evt, index) => { 125 | const className = `qwebr-output-code-${evt.type}`; 126 | const outputResult = qwebrPrefixComment(processOutput(evt.data), options.comment); 127 | return `${outputResult}`; 128 | }) 129 | .join("\n"); 130 | 131 | 132 | // Clean the state 133 | // We're now able to process both graphics and pager events. 134 | // As a result, we cannot maintain a true 1-to-1 output order 135 | // without individually feeding each line 136 | const msgs = await mainWebR.flush(); 137 | 138 | // Output each image event stored 139 | msgs.forEach((msg) => { 140 | // Determine if old canvas can be used or a new canvas is required. 141 | if (msg.type === 'canvas'){ 142 | // Add image to the current canvas 143 | if (msg.data.event === 'canvasImage') { 144 | canvas.getContext('2d').drawImage(msg.data.image, 0, 0); 145 | } else if (msg.data.event === 'canvasNewPage') { 146 | 147 | // Generate a new canvas element 148 | canvas = document.createElement("canvas"); 149 | canvas.setAttribute("width", 2 * fig_width); 150 | canvas.setAttribute("height", 2 * fig_height); 151 | canvas.style.width = options["out-width"] ? options["out-width"] : `${fig_width}px`; 152 | if (options["out-height"]) { 153 | canvas.style.height = options["out-height"]; 154 | } 155 | canvas.style.display = "block"; 156 | canvas.style.margin = "auto"; 157 | } 158 | } 159 | }); 160 | 161 | // Use `map` to process the filtered "pager" events asynchronously 162 | const pager = await Promise.all( 163 | msgs.filter(msg => msg.type === 'pager').map( 164 | async (msg) => { 165 | return await qwebrParseTypePager(msg); 166 | } 167 | ) 168 | ); 169 | 170 | // Nullify the output area of content 171 | elements.outputCodeDiv.innerHTML = ""; 172 | elements.outputGraphDiv.innerHTML = ""; 173 | 174 | // Design an output object for messages 175 | const pre = document.createElement("pre"); 176 | if (/\S/.test(out)) { 177 | // Display results as HTML elements to retain output styling 178 | const div = document.createElement("div"); 179 | div.innerHTML = out; 180 | pre.appendChild(div); 181 | } else { 182 | // If nothing is present, hide the element. 183 | pre.style.visibility = "hidden"; 184 | } 185 | 186 | elements.outputCodeDiv.appendChild(pre); 187 | 188 | // Place the graphics on the canvas 189 | if (canvas) { 190 | // Create figure element 191 | const figureElement = document.createElement('figure'); 192 | 193 | // Append canvas to figure 194 | figureElement.appendChild(canvas); 195 | 196 | if (options['fig-cap']) { 197 | // Create figcaption element 198 | const figcaptionElement = document.createElement('figcaption'); 199 | figcaptionElement.innerText = options['fig-cap']; 200 | // Append figcaption to figure 201 | figureElement.appendChild(figcaptionElement); 202 | } 203 | 204 | elements.outputGraphDiv.appendChild(figureElement); 205 | } 206 | 207 | // Display the pager data 208 | if (pager) { 209 | // Use the `pre` element to preserve whitespace. 210 | pager.forEach((paged_data, index) => { 211 | let pre_pager = document.createElement("pre"); 212 | pre_pager.innerText = paged_data; 213 | pre_pager.classList.add("qwebr-output-code-pager"); 214 | pre_pager.setAttribute("id", `qwebr-output-code-pager-editor-${elements.id}-result-${index + 1}`); 215 | elements.outputCodeDiv.appendChild(pre_pager); 216 | }); 217 | } 218 | } finally { 219 | // Clean up the remaining code 220 | mainWebRCodeShelter.purge(); 221 | } 222 | } 223 | 224 | // Function to execute the code (accepts code as an argument) 225 | globalThis.qwebrExecuteCode = async function ( 226 | codeToRun, 227 | id, 228 | options = {}) { 229 | 230 | // If options are not passed, we fall back on the bare minimum to handle the computation 231 | if (qwebrIsObjectEmpty(options)) { 232 | options = { 233 | "context": "interactive", 234 | "fig-width": 7, "fig-height": 5, 235 | "out-width": "700px", "out-height": "", 236 | "dpi": 72, 237 | "results": "markup", 238 | "warning": "true", "message": "true", 239 | }; 240 | } 241 | 242 | // Next, we access the compute areas values 243 | const elements = { 244 | runButton: document.getElementById(`qwebr-button-run-${id}`), 245 | outputCodeDiv: document.getElementById(`qwebr-output-code-area-${id}`), 246 | outputGraphDiv: document.getElementById(`qwebr-output-graph-area-${id}`), 247 | id: id, 248 | } 249 | 250 | // Disallowing execution of other code cells 251 | document.querySelectorAll(".qwebr-button-run").forEach((btn) => { 252 | btn.disabled = true; 253 | }); 254 | 255 | if (options.context == EvalTypes.Interactive) { 256 | // Emphasize the active code cell 257 | elements.runButton.innerHTML = ' Run Code'; 258 | } 259 | 260 | // Evaluate the code and parse the output into the document 261 | await qwebrComputeEngine(codeToRun, elements, options); 262 | 263 | // Switch to allowing execution of code 264 | document.querySelectorAll(".qwebr-button-run").forEach((btn) => { 265 | btn.disabled = false; 266 | }); 267 | 268 | if (options.context == EvalTypes.Interactive) { 269 | // Revert to the initial code cell state 270 | elements.runButton.innerHTML = ' Run Code'; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /_extensions/coatless/webr/qwebr-document-engine-initialization.js: -------------------------------------------------------------------------------- 1 | // Function to install a single package 2 | async function qwebrInstallRPackage(packageName) { 3 | await mainWebR.evalRVoid(`webr::install('${packageName}');`); 4 | } 5 | 6 | // Function to load a single package 7 | async function qwebrLoadRPackage(packageName) { 8 | await mainWebR.evalRVoid(`require('${packageName}', quietly = TRUE)`); 9 | } 10 | 11 | // Generic function to process R packages 12 | async function qwebrProcessRPackagesWithStatus(packages, processType, displayStatusMessageUpdate = true) { 13 | // Switch between contexts 14 | const messagePrefix = processType === 'install' ? 'Installing' : 'Loading'; 15 | 16 | // Modify button state 17 | qwebrSetInteractiveButtonState(`🟡 ${messagePrefix} package ...`, false); 18 | 19 | // Iterate over packages 20 | for (let i = 0; i < packages.length; i++) { 21 | const activePackage = packages[i]; 22 | const formattedMessage = `${messagePrefix} package ${i + 1} out of ${packages.length}: ${activePackage}`; 23 | 24 | // Display the update in header 25 | if (displayStatusMessageUpdate) { 26 | qwebrUpdateStatusHeader(formattedMessage); 27 | } 28 | 29 | // Display the update in non-active areas 30 | qwebrUpdateStatusMessage(formattedMessage); 31 | 32 | // Run package installation 33 | if (processType === 'install') { 34 | await qwebrInstallRPackage(activePackage); 35 | } else { 36 | await qwebrLoadRPackage(activePackage); 37 | } 38 | } 39 | 40 | // Clean slate 41 | if (processType === 'load') { 42 | await mainWebR.flush(); 43 | } 44 | } 45 | 46 | // Start a timer 47 | const initializeWebRTimerStart = performance.now(); 48 | 49 | // Encase with a dynamic import statement 50 | globalThis.qwebrInstance = import(qwebrCustomizedWebROptions.baseURL + "webr.mjs").then( 51 | async ({ WebR, ChannelType }) => { 52 | // Populate WebR options with defaults or new values based on `webr` meta 53 | globalThis.mainWebR = new WebR(qwebrCustomizedWebROptions); 54 | 55 | // Initialization WebR 56 | await mainWebR.init(); 57 | 58 | // Setup a shelter 59 | globalThis.mainWebRCodeShelter = await new mainWebR.Shelter(); 60 | 61 | // Setup a pager to allow processing help documentation 62 | await mainWebR.evalRVoid('webr::pager_install()'); 63 | 64 | // Override the existing install.packages() to use webr::install() 65 | await mainWebR.evalRVoid('webr::shim_install()'); 66 | 67 | // Specify the repositories to pull from 68 | // Note: webR does not use the `repos` option, but instead uses `webr_pkg_repos` 69 | // inside of `install()`. However, other R functions still pull from `repos`. 70 | await mainWebR.evalRVoid(` 71 | options( 72 | webr_pkg_repos = c(${qwebrPackageRepoURLS.map(repoURL => `'${repoURL}'`).join(',')}), 73 | repos = c(${qwebrPackageRepoURLS.map(repoURL => `'${repoURL}'`).join(',')}) 74 | ) 75 | `); 76 | 77 | // Check to see if any packages need to be installed 78 | if (qwebrSetupRPackages) { 79 | // Obtain only a unique list of packages 80 | const uniqueRPackageList = Array.from(new Set(qwebrInstallRPackagesList)); 81 | 82 | // Install R packages one at a time (either silently or with a status update) 83 | await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'install', qwebrShowStartupMessage); 84 | 85 | if (qwebrAutoloadRPackages) { 86 | // Load R packages one at a time (either silently or with a status update) 87 | await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'load', qwebrShowStartupMessage); 88 | } 89 | } 90 | } 91 | ); 92 | 93 | // Stop timer 94 | const initializeWebRTimerEnd = performance.now(); 95 | -------------------------------------------------------------------------------- /_extensions/coatless/webr/qwebr-document-settings.js: -------------------------------------------------------------------------------- 1 | // Document level settings ---- 2 | 3 | // Determine if we need to install R packages 4 | globalThis.qwebrInstallRPackagesList = [{{INSTALLRPACKAGESLIST}}]; 5 | 6 | // Specify possible locations to search for the repository 7 | globalThis.qwebrPackageRepoURLS = [{{RPACKAGEREPOURLS}}]; 8 | 9 | // Check to see if we have an empty array, if we do set to skip the installation. 10 | globalThis.qwebrSetupRPackages = !(qwebrInstallRPackagesList.indexOf("") !== -1); 11 | globalThis.qwebrAutoloadRPackages = {{AUTOLOADRPACKAGES}}; 12 | 13 | // Display a startup message? 14 | globalThis.qwebrShowStartupMessage = {{SHOWSTARTUPMESSAGE}}; 15 | globalThis.qwebrShowHeaderMessage = {{SHOWHEADERMESSAGE}}; 16 | 17 | // Describe the webR settings that should be used 18 | globalThis.qwebrCustomizedWebROptions = { 19 | "baseURL": "{{BASEURL}}", 20 | "serviceWorkerUrl": "{{SERVICEWORKERURL}}", 21 | "homedir": "{{HOMEDIR}}", 22 | "channelType": "{{CHANNELTYPE}}" 23 | }; 24 | 25 | // Store cell data 26 | globalThis.qwebrCellDetails = {{QWEBRCELLDETAILS}}; 27 | -------------------------------------------------------------------------------- /_extensions/coatless/webr/qwebr-document-status.js: -------------------------------------------------------------------------------- 1 | // Declare startupMessageQWebR globally 2 | globalThis.qwebrStartupMessage = document.createElement("p"); 3 | 4 | 5 | // Function to set the button text 6 | globalThis.qwebrSetInteractiveButtonState = function(buttonText, enableCodeButton = true) { 7 | document.querySelectorAll(".qwebr-button-run").forEach((btn) => { 8 | btn.innerHTML = buttonText; 9 | btn.disabled = !enableCodeButton; 10 | }); 11 | } 12 | 13 | // Function to update the status message in non-interactive cells 14 | globalThis.qwebrUpdateStatusMessage = function(message) { 15 | document.querySelectorAll(".qwebr-status-text.qwebr-cell-needs-evaluation").forEach((elem) => { 16 | elem.innerText = message; 17 | }); 18 | } 19 | 20 | // Function to update the status message 21 | globalThis.qwebrUpdateStatusHeader = function(message) { 22 | qwebrStartupMessage.innerHTML = ` 23 | 24 | ${message}`; 25 | } 26 | 27 | // Function that attaches the document status message and diagnostics 28 | function displayStartupMessage(showStartupMessage, showHeaderMessage) { 29 | if (!showStartupMessage) { 30 | return; 31 | } 32 | 33 | // Get references to header elements 34 | const headerHTML = document.getElementById("title-block-header"); 35 | const headerRevealJS = document.getElementById("title-slide"); 36 | 37 | // Create the outermost div element for metadata 38 | const quartoTitleMeta = document.createElement("div"); 39 | quartoTitleMeta.classList.add("quarto-title-meta"); 40 | 41 | // Create the first inner div element 42 | const firstInnerDiv = document.createElement("div"); 43 | firstInnerDiv.setAttribute("id", "qwebr-status-message-area"); 44 | 45 | // Create the second inner div element for "WebR Status" heading and contents 46 | const secondInnerDiv = document.createElement("div"); 47 | secondInnerDiv.setAttribute("id", "qwebr-status-message-title"); 48 | secondInnerDiv.classList.add("quarto-title-meta-heading"); 49 | secondInnerDiv.innerText = "WebR Status"; 50 | 51 | // Create another inner div for contents 52 | const secondInnerDivContents = document.createElement("div"); 53 | secondInnerDivContents.setAttribute("id", "qwebr-status-message-body"); 54 | secondInnerDivContents.classList.add("quarto-title-meta-contents"); 55 | 56 | // Describe the WebR state 57 | qwebrStartupMessage.innerText = "🟡 Loading..."; 58 | qwebrStartupMessage.setAttribute("id", "qwebr-status-message-text"); 59 | // Add `aria-live` to auto-announce the startup status to screen readers 60 | qwebrStartupMessage.setAttribute("aria-live", "assertive"); 61 | 62 | // Append the startup message to the contents 63 | secondInnerDivContents.appendChild(qwebrStartupMessage); 64 | 65 | // Add a status indicator for COOP and COEP Headers if needed 66 | if (showHeaderMessage) { 67 | const crossOriginMessage = document.createElement("p"); 68 | crossOriginMessage.innerText = `${crossOriginIsolated ? '🟢' : '🟡'} COOP & COEP Headers`; 69 | crossOriginMessage.setAttribute("id", "qwebr-coop-coep-header"); 70 | secondInnerDivContents.appendChild(crossOriginMessage); 71 | } 72 | 73 | // Combine the inner divs and contents 74 | firstInnerDiv.appendChild(secondInnerDiv); 75 | firstInnerDiv.appendChild(secondInnerDivContents); 76 | quartoTitleMeta.appendChild(firstInnerDiv); 77 | 78 | // Determine where to insert the quartoTitleMeta element 79 | if (headerHTML || headerRevealJS) { 80 | // Append to the existing "title-block-header" element or "title-slide" div 81 | (headerHTML || headerRevealJS).appendChild(quartoTitleMeta); 82 | } else { 83 | // If neither headerHTML nor headerRevealJS is found, insert after "webr-monaco-editor-init" script 84 | const monacoScript = document.getElementById("qwebr-monaco-editor-init"); 85 | const header = document.createElement("header"); 86 | header.setAttribute("id", "title-block-header"); 87 | header.appendChild(quartoTitleMeta); 88 | monacoScript.after(header); 89 | } 90 | } 91 | 92 | displayStartupMessage(qwebrShowStartupMessage, qwebrShowHeaderMessage); -------------------------------------------------------------------------------- /_extensions/coatless/webr/qwebr-monaco-editor-element.js: -------------------------------------------------------------------------------- 1 | // Global dictionary to store Monaco Editor instances 2 | globalThis.qwebrEditorInstances = {}; 3 | 4 | // Function that builds and registers a Monaco Editor instance 5 | globalThis.qwebrCreateMonacoEditorInstance = function (cellData) { 6 | 7 | const initialCode = cellData.code; 8 | const qwebrCounter = cellData.id; 9 | const qwebrOptions = cellData.options; 10 | 11 | // Retrieve the previously created document elements 12 | let runButton = document.getElementById(`qwebr-button-run-${qwebrCounter}`); 13 | let resetButton = document.getElementById(`qwebr-button-reset-${qwebrCounter}`); 14 | let copyButton = document.getElementById(`qwebr-button-copy-${qwebrCounter}`); 15 | let editorDiv = document.getElementById(`qwebr-editor-${qwebrCounter}`); 16 | 17 | // Load the Monaco Editor and create an instance 18 | let editor; 19 | require(['vs/editor/editor.main'], function () { 20 | editor = monaco.editor.create(editorDiv, { 21 | value: initialCode, 22 | language: 'r', 23 | theme: 'vs-light', 24 | automaticLayout: true, // Works wonderfully with RevealJS 25 | scrollBeyondLastLine: false, 26 | minimap: { 27 | enabled: false 28 | }, 29 | fontSize: '17.5pt', // Bootstrap is 1 rem 30 | renderLineHighlight: "none", // Disable current line highlighting 31 | hideCursorInOverviewRuler: true, // Remove cursor indictor in right hand side scroll bar 32 | readOnly: qwebrOptions['read-only'] ?? false 33 | }); 34 | 35 | // Store the official counter ID to be used in keyboard shortcuts 36 | editor.__qwebrCounter = qwebrCounter; 37 | 38 | // Store the official div container ID 39 | editor.__qwebrEditorId = `qwebr-editor-${qwebrCounter}`; 40 | 41 | // Store the initial code value and options 42 | editor.__qwebrinitialCode = initialCode; 43 | editor.__qwebrOptions = qwebrOptions; 44 | 45 | // Set at the model level the preferred end of line (EOL) character to LF. 46 | // This prevent `\r\n` from being given to the webR engine if the user is on Windows. 47 | // See details in: https://github.com/coatless/quarto-webr/issues/94 48 | // Associated error text: 49 | // Error: :1:7 unexpected input 50 | 51 | // Retrieve the underlying model 52 | const model = editor.getModel(); 53 | // Set EOL for the model 54 | model.setEOL(monaco.editor.EndOfLineSequence.LF); 55 | 56 | // Dynamically modify the height of the editor window if new lines are added. 57 | let ignoreEvent = false; 58 | const updateHeight = () => { 59 | const contentHeight = editor.getContentHeight(); 60 | // We're avoiding a width change 61 | //editorDiv.style.width = `${width}px`; 62 | editorDiv.style.height = `${contentHeight}px`; 63 | try { 64 | ignoreEvent = true; 65 | 66 | // The key to resizing is this call 67 | editor.layout(); 68 | } finally { 69 | ignoreEvent = false; 70 | } 71 | }; 72 | 73 | // Helper function to check if selected text is empty 74 | function isEmptyCodeText(selectedCodeText) { 75 | return (selectedCodeText === null || selectedCodeText === undefined || selectedCodeText === ""); 76 | } 77 | 78 | // Registry of keyboard shortcuts that should be re-added to each editor window 79 | // when focus changes. 80 | const addWebRKeyboardShortCutCommands = () => { 81 | // Add a keydown event listener for Shift+Enter to run all code in cell 82 | editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => { 83 | 84 | // Retrieve all text inside the editor 85 | qwebrExecuteCode(editor.getValue(), editor.__qwebrCounter, editor.__qwebrOptions); 86 | }); 87 | 88 | // Add a keydown event listener for CMD/Ctrl+Enter to run selected code 89 | editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => { 90 | 91 | // Get the selected text from the editor 92 | const selectedText = editor.getModel().getValueInRange(editor.getSelection()); 93 | // Check if no code is selected 94 | if (isEmptyCodeText(selectedText)) { 95 | // Obtain the current cursor position 96 | let currentPosition = editor.getPosition(); 97 | // Retrieve the current line content 98 | let currentLine = editor.getModel().getLineContent(currentPosition.lineNumber); 99 | 100 | // Propose a new position to move the cursor to 101 | let newPosition = new monaco.Position(currentPosition.lineNumber + 1, 1); 102 | 103 | // Check if the new position is beyond the last line of the editor 104 | if (newPosition.lineNumber > editor.getModel().getLineCount()) { 105 | // Add a new line at the end of the editor 106 | editor.executeEdits("addNewLine", [{ 107 | range: new monaco.Range(newPosition.lineNumber, 1, newPosition.lineNumber, 1), 108 | text: "\n", 109 | forceMoveMarkers: true, 110 | }]); 111 | } 112 | 113 | // Run the entire line of code. 114 | qwebrExecuteCode(currentLine, editor.__qwebrCounter, editor.__qwebrOptions); 115 | 116 | // Move cursor to new position 117 | editor.setPosition(newPosition); 118 | } else { 119 | // Code to run when Ctrl+Enter is pressed with selected code 120 | qwebrExecuteCode(selectedText, editor.__qwebrCounter, editor.__qwebrOptions); 121 | } 122 | }); 123 | } 124 | 125 | // Register an on focus event handler for when a code cell is selected to update 126 | // what keyboard shortcut commands should work. 127 | // This is a workaround to fix a regression that happened with multiple 128 | // editor windows since Monaco 0.32.0 129 | // https://github.com/microsoft/monaco-editor/issues/2947 130 | editor.onDidFocusEditorText(addWebRKeyboardShortCutCommands); 131 | 132 | // Register an on change event for when new code is added to the editor window 133 | editor.onDidContentSizeChange(updateHeight); 134 | 135 | // Manually re-update height to account for the content we inserted into the call 136 | updateHeight(); 137 | 138 | // Store the editor instance in the global dictionary 139 | qwebrEditorInstances[editor.__qwebrCounter] = editor; 140 | 141 | }); 142 | 143 | // Add a click event listener to the run button 144 | runButton.onclick = function () { 145 | qwebrExecuteCode(editor.getValue(), editor.__qwebrCounter, editor.__qwebrOptions); 146 | }; 147 | 148 | // Add a click event listener to the reset button 149 | copyButton.onclick = function () { 150 | // Retrieve current code data 151 | const data = editor.getValue(); 152 | 153 | // Write code data onto the clipboard. 154 | navigator.clipboard.writeText(data || ""); 155 | }; 156 | 157 | // Add a click event listener to the copy button 158 | resetButton.onclick = function () { 159 | editor.setValue(editor.__qwebrinitialCode); 160 | }; 161 | 162 | } -------------------------------------------------------------------------------- /_extensions/coatless/webr/qwebr-monaco-editor-init.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /_extensions/coatless/webr/qwebr-styling.css: -------------------------------------------------------------------------------- 1 | .monaco-editor pre { 2 | background-color: unset !important; 3 | } 4 | 5 | .qwebr-editor-toolbar { 6 | width: 100%; 7 | display: flex; 8 | justify-content: space-between; 9 | box-sizing: border-box; 10 | } 11 | 12 | .qwebr-editor-toolbar-left-buttons, .qwebr-editor-toolbar-right-buttons { 13 | display: flex; 14 | } 15 | 16 | .qwebr-non-interactive-loading-container.qwebr-cell-needs-evaluation, .qwebr-non-interactive-loading-container.qwebr-cell-evaluated { 17 | justify-content: center; 18 | display: flex; 19 | background-color: rgba(250, 250, 250, 0.65); 20 | border: 1px solid rgba(233, 236, 239, 0.65); 21 | border-radius: 0.5rem; 22 | margin-top: 15px; 23 | margin-bottom: 15px; 24 | } 25 | 26 | .qwebr-r-project-logo { 27 | color: #2767B0; /* R Project's blue color */ 28 | } 29 | 30 | .qwebr-icon-status-spinner { 31 | color: #7894c4; 32 | } 33 | 34 | .qwebr-icon-run-code { 35 | color: #0d9c29 36 | } 37 | 38 | .qwebr-output-code-stdout { 39 | color: #111; 40 | } 41 | 42 | .qwebr-output-code-stderr { 43 | color: #db4133; 44 | } 45 | 46 | .qwebr-editor { 47 | border: 1px solid #EEEEEE; 48 | } 49 | 50 | .qwebr-editor-toolbar { 51 | background-color: #EEEEEE; 52 | padding: 0.2rem 0.5rem; 53 | } 54 | 55 | .qwebr-button { 56 | background-color: #EEEEEE; 57 | display: inline-block; 58 | font-weight: 400; 59 | line-height: 1; 60 | text-decoration: none; 61 | text-align: center; 62 | color: #000; 63 | border-color: #dee2e6; 64 | border: 1px solid rgba(0,0,0,0); 65 | padding: 0.375rem 0.75rem; 66 | font-size: .9rem; 67 | border-radius: 0.25rem; 68 | transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; 69 | } 70 | 71 | .qwebr-button:hover { 72 | color: #000; 73 | background-color: #d9dce0; 74 | border-color: #c8ccd0; 75 | } 76 | 77 | .qwebr-button:disabled,.qwebr-button.disabled,fieldset:disabled .qwebr-button { 78 | pointer-events: none; 79 | opacity: .65 80 | } 81 | 82 | .qwebr-button-reset { 83 | color: #696969; /*#4682b4;*/ 84 | } 85 | 86 | .qwebr-button-copy { 87 | color: #696969; 88 | } 89 | 90 | 91 | /* Custom styling for RevealJS Presentations*/ 92 | 93 | /* Reset the style of the interactive area */ 94 | .reveal div.qwebr-interactive-area { 95 | display: block; 96 | box-shadow: none; 97 | max-width: 100%; 98 | max-height: 100%; 99 | margin: 0; 100 | padding: 0; 101 | } 102 | 103 | /* Provide space to entries */ 104 | .reveal div.qwebr-output-code-area pre div { 105 | margin: 1px 2px 1px 10px; 106 | } 107 | 108 | /* Collapse the inside code tags to avoid extra space between line outputs */ 109 | .reveal pre div code.qwebr-output-code-stdout, .reveal pre div code.qwebr-output-code-stderr { 110 | padding: 0; 111 | display: contents; 112 | } 113 | 114 | .reveal pre div code.qwebr-output-code-stdout { 115 | color: #111; 116 | } 117 | 118 | .reveal pre div code.qwebr-output-code-stderr { 119 | color: #db4133; 120 | } 121 | 122 | 123 | /* Create a border around console and output (does not effect graphs) */ 124 | .reveal div.qwebr-console-area { 125 | border: 1px solid #EEEEEE; 126 | box-shadow: 2px 2px 10px #EEEEEE; 127 | } 128 | 129 | /* Cap output height and allow text to scroll */ 130 | /* TODO: Is there a better way to fit contents/max it parallel to the monaco editor size? */ 131 | .reveal div.qwebr-output-code-area pre { 132 | max-height: 400px; 133 | overflow: scroll; 134 | } 135 | -------------------------------------------------------------------------------- /_extensions/coatless/webr/template.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "WebR-enabled code cell" 3 | format: html 4 | engine: knitr 5 | #webr: 6 | # show-startup-message: false # Disable display of webR initialization state 7 | # show-header-message: true # Display whether COOP&COEP headers are set for speed. 8 | # packages: ['ggplot2', 'dplyr'] # Pre-install dependencies 9 | # autoload-packages: false # Disable automatic library calls on R packages specified in packages. 10 | # repos: # Specify repositories to check for custom packages 11 | # - https://github-username.github.io/reponame 12 | # - https://username.r-universe.dev 13 | # channel-type: 'post-message' # Specify a specific communication channel type. 14 | # home-dir: "/home/rstudio" # Customize where the working directory is 15 | # base-url: '' # Base URL used for downloading R WebAssembly binaries 16 | # service-worker-url: '' # URL from where to load JavaScript worker scripts when loading webR with the ServiceWorker communication channel. 17 | filters: 18 | - webr 19 | --- 20 | 21 | ## Demo 22 | 23 | This is a webr-enabled code cell in a Quarto HTML document. 24 | 25 | ```{webr-r} 26 | 1 + 1 27 | ``` 28 | 29 | ```{webr-r} 30 | fit = lm(mpg ~ am, data = mtcars) 31 | summary(fit) 32 | ``` 33 | 34 | ```{webr-r} 35 | plot(pressure) 36 | ``` 37 | -------------------------------------------------------------------------------- /_extensions/coatless/webr/webr-serviceworker.js: -------------------------------------------------------------------------------- 1 | importScripts('https://webr.r-wasm.org/v0.2.2/webr-serviceworker.js'); 2 | -------------------------------------------------------------------------------- /_extensions/coatless/webr/webr-worker.js: -------------------------------------------------------------------------------- 1 | importScripts('https://webr.r-wasm.org/v0.2.2/webr-worker.js'); 2 | -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/PrettyPDF.tex: -------------------------------------------------------------------------------- 1 | % load packages 2 | \usepackage{geometry} 3 | \usepackage{xcolor} 4 | \usepackage{eso-pic} 5 | \usepackage{fancyhdr} 6 | \usepackage{sectsty} 7 | \usepackage{fontspec} 8 | \usepackage{titlesec} 9 | 10 | %% Set page size with a wider right margin 11 | \geometry{a4paper, total={170mm,257mm}, left=20mm, top=20mm, bottom=20mm, right=50mm} 12 | 13 | %% Let's define some colours 14 | \definecolor{light}{HTML}{E6E6FA} 15 | \definecolor{highlight}{HTML}{800080} 16 | \definecolor{dark}{HTML}{330033} 17 | 18 | %% Let's add the border on the right hand side 19 | \AddToShipoutPicture{% 20 | \AtPageLowerLeft{% 21 | \put(\LenToUnit{\dimexpr\paperwidth-3cm},0){% 22 | \color{light}\rule{3cm}{\LenToUnit\paperheight}% 23 | }% 24 | }% 25 | % logo 26 | \AtPageLowerLeft{% start the bar at the bottom right of the page 27 | \put(\LenToUnit{\dimexpr\paperwidth-2.25cm},27.2cm){% move it to the top right 28 | \color{light}\includegraphics[width=1.5cm]{_extensions/nrennie/PrettyPDF/logo.png} 29 | }% 30 | }% 31 | } 32 | 33 | %% Style the page number 34 | \fancypagestyle{mystyle}{ 35 | \fancyhf{} 36 | \renewcommand\headrulewidth{0pt} 37 | \fancyfoot[R]{\thepage} 38 | \fancyfootoffset{3.5cm} 39 | } 40 | \setlength{\footskip}{20pt} 41 | 42 | %% style the chapter/section fonts 43 | \chapterfont{\color{dark}\fontsize{20}{16.8}\selectfont} 44 | \sectionfont{\color{dark}\fontsize{20}{16.8}\selectfont} 45 | \subsectionfont{\color{dark}\fontsize{14}{16.8}\selectfont} 46 | \titleformat{\subsection} 47 | {\sffamily\Large\bfseries}{\thesection}{1em}{}[{\titlerule[0.8pt]}] 48 | 49 | % left align title 50 | \makeatletter 51 | \renewcommand{\maketitle}{\bgroup\setlength{\parindent}{0pt} 52 | \begin{flushleft} 53 | {\sffamily\huge\textbf{\MakeUppercase{\@title}}} \vspace{0.3cm} \newline 54 | {\Large {\@subtitle}} \newline 55 | \@author 56 | \end{flushleft}\egroup 57 | } 58 | \makeatother 59 | 60 | %% Use some custom fonts 61 | \setsansfont{Ubuntu}[ 62 | Path=_extensions/nrennie/PrettyPDF/Ubuntu/, 63 | Scale=0.9, 64 | Extension = .ttf, 65 | UprightFont=*-Regular, 66 | BoldFont=*-Bold, 67 | ItalicFont=*-Italic, 68 | ] 69 | 70 | \setmainfont{Ubuntu}[ 71 | Path=_extensions/nrennie/PrettyPDF/Ubuntu/, 72 | Scale=0.9, 73 | Extension = .ttf, 74 | UprightFont=*-Regular, 75 | BoldFont=*-Bold, 76 | ItalicFont=*-Italic, 77 | ] 78 | -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/Ubuntu/UFL.txt: -------------------------------------------------------------------------------- 1 | ------------------------------- 2 | UBUNTU FONT LICENCE Version 1.0 3 | ------------------------------- 4 | 5 | PREAMBLE 6 | This licence allows the licensed fonts to be used, studied, modified and 7 | redistributed freely. The fonts, including any derivative works, can be 8 | bundled, embedded, and redistributed provided the terms of this licence 9 | are met. The fonts and derivatives, however, cannot be released under 10 | any other licence. The requirement for fonts to remain under this 11 | licence does not require any document created using the fonts or their 12 | derivatives to be published under this licence, as long as the primary 13 | purpose of the document is not to be a vehicle for the distribution of 14 | the fonts. 15 | 16 | DEFINITIONS 17 | "Font Software" refers to the set of files released by the Copyright 18 | Holder(s) under this licence and clearly marked as such. This may 19 | include source files, build scripts and documentation. 20 | 21 | "Original Version" refers to the collection of Font Software components 22 | as received under this licence. 23 | 24 | "Modified Version" refers to any derivative made by adding to, deleting, 25 | or substituting -- in part or in whole -- any of the components of the 26 | Original Version, by changing formats or by porting the Font Software to 27 | a new environment. 28 | 29 | "Copyright Holder(s)" refers to all individuals and companies who have a 30 | copyright ownership of the Font Software. 31 | 32 | "Substantially Changed" refers to Modified Versions which can be easily 33 | identified as dissimilar to the Font Software by users of the Font 34 | Software comparing the Original Version with the Modified Version. 35 | 36 | To "Propagate" a work means to do anything with it that, without 37 | permission, would make you directly or secondarily liable for 38 | infringement under applicable copyright law, except executing it on a 39 | computer or modifying a private copy. Propagation includes copying, 40 | distribution (with or without modification and with or without charging 41 | a redistribution fee), making available to the public, and in some 42 | countries other activities as well. 43 | 44 | PERMISSION & CONDITIONS 45 | This licence does not grant any rights under trademark law and all such 46 | rights are reserved. 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a 49 | copy of the Font Software, to propagate the Font Software, subject to 50 | the below conditions: 51 | 52 | 1) Each copy of the Font Software must contain the above copyright 53 | notice and this licence. These can be included either as stand-alone 54 | text files, human-readable headers or in the appropriate machine- 55 | readable metadata fields within text or binary files as long as those 56 | fields can be easily viewed by the user. 57 | 58 | 2) The font name complies with the following: 59 | (a) The Original Version must retain its name, unmodified. 60 | (b) Modified Versions which are Substantially Changed must be renamed to 61 | avoid use of the name of the Original Version or similar names entirely. 62 | (c) Modified Versions which are not Substantially Changed must be 63 | renamed to both (i) retain the name of the Original Version and (ii) add 64 | additional naming elements to distinguish the Modified Version from the 65 | Original Version. The name of such Modified Versions must be the name of 66 | the Original Version, with "derivative X" where X represents the name of 67 | the new work, appended to that name. 68 | 69 | 3) The name(s) of the Copyright Holder(s) and any contributor to the 70 | Font Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except (i) as required by this licence, (ii) to 72 | acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with 73 | their explicit written permission. 74 | 75 | 4) The Font Software, modified or unmodified, in part or in whole, must 76 | be distributed entirely under this licence, and must not be distributed 77 | under any other licence. The requirement for fonts to remain under this 78 | licence does not affect any document created using the Font Software, 79 | except any version of the Font Software extracted from a document 80 | created using the Font Software may only be distributed under this 81 | licence. 82 | 83 | TERMINATION 84 | This licence becomes null and void if any of the above conditions are 85 | not met. 86 | 87 | DISCLAIMER 88 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF 91 | COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 92 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 93 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 94 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 95 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER 96 | DEALINGS IN THE FONT SOFTWARE. 97 | -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-Bold.ttf -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-BoldItalic.ttf -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-Italic.ttf -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-Light.ttf -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-LightItalic.ttf -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-Medium.ttf -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-MediumItalic.ttf -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/_extensions/nrennie/PrettyPDF/Ubuntu/Ubuntu-Regular.ttf -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/_extension.yml: -------------------------------------------------------------------------------- 1 | title: PrettyPDF 2 | author: Nicola Rennie 3 | version: 0.0.1 4 | contributes: 5 | formats: 6 | pdf: 7 | include-in-header: 8 | - "PrettyPDF.tex" 9 | include-before-body: 10 | - "pagestyle.tex" 11 | toc: false 12 | code-block-bg: light 13 | linkcolor: highlight 14 | urlcolor: highlight -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/_extensions/nrennie/PrettyPDF/logo.png -------------------------------------------------------------------------------- /_extensions/nrennie/PrettyPDF/pagestyle.tex: -------------------------------------------------------------------------------- 1 | \pagestyle{mystyle} -------------------------------------------------------------------------------- /_quarto.yml: -------------------------------------------------------------------------------- 1 | project: 2 | title: "Learning statistics with webR" -------------------------------------------------------------------------------- /custom.scss: -------------------------------------------------------------------------------- 1 | /*-- scss:defaults --*/ 2 | 3 | // fonts 4 | @import url('https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,400;0,700;1,400;1,700&display=swap'); 5 | 6 | // font family 7 | $font-family-sans-serif: 'Ubuntu', Arial, sans-serif !default; 8 | $presentation-font-size-root: 36px; 9 | 10 | // colours 11 | $code-block-bg: #fafafa; 12 | $link-color: #1A936F; 13 | $code-color: #1A936F !default; 14 | 15 | /*-- scss:rules --*/ 16 | 17 | .btn-webr { 18 | background-color: $code-block-bg !important; 19 | border-color: $link-color; 20 | border-width: 3px; 21 | border-radius: 15px !important; 22 | padding: 10px; 23 | font-family: $font-family-sans-serif; 24 | margin-bottom: 10px; 25 | font-size: 20px; 26 | cursor:pointer; 27 | } 28 | 29 | .reveal .slides { 30 | padding: 30px; 31 | width: 60% !important; 32 | } 33 | 34 | .reveal .slide-menu-button { 35 | left: 5px; 36 | top: 15px; 37 | padding-left: 10px; 38 | color: #baded3; 39 | } 40 | 41 | .reveal .progress { 42 | background: rgba(0, 0, 0, 0.2); 43 | color: #1A936F; 44 | height: 2%; 45 | } 46 | 47 | 48 | h1 { 49 | padding-bottom: 30px; 50 | padding-top: 60px; 51 | } 52 | 53 | h2 { 54 | padding-bottom: 35px; 55 | padding-top: 20px; 56 | } -------------------------------------------------------------------------------- /index.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction to Statistics" 3 | author: "Nicola Rennie" 4 | format: 5 | revealjs: 6 | theme: [custom.scss] 7 | auto-stretch: false 8 | filters: 9 | - webr 10 | --- 11 | 12 | # Descriptive statistics {background-color="#1A936F"} 13 | 14 | Descriptive statistics provide a summary that quantitatively describes a sample of data. 15 | 16 | ```{r} 17 | #| label: setup 18 | #| echo: false 19 | #| eval: true 20 | #| message: false 21 | library(tidyverse) 22 | library(emojifont) 23 | library(showtext) 24 | library(reactable) 25 | library(kableExtra) 26 | font_add_google("Ubuntu", "Ubuntu") 27 | showtext_auto() 28 | set.seed(1234) 29 | population_df = tibble(ID = 1:200, 30 | x = rep(1:20, times = 10), 31 | y = rep(1:10, each = 20), 32 | Value = rpois(200, 250)) 33 | sample_size = 10 34 | sample_ids = sample(1:200, size = sample_size, replace = FALSE) 35 | sample_df = filter(population_df, ID %in% sample_ids) 36 | ``` 37 | 38 | ## Population 39 | 40 | **Population** refers to the entire group of individuals that we want to draw conclusions about. 41 | 42 | ```{r} 43 | #| label: pop-people 44 | #| eval: true 45 | #| echo: false 46 | #| fig-align: center 47 | #| fig-height: 4.16 48 | ggplot() + 49 | geom_text(data = population_df, 50 | mapping = aes(x = x, 51 | y = y, 52 | label = fontawesome('fa-user'), 53 | colour = Value), 54 | family='fontawesome-webfont', size = 20) + 55 | scale_colour_gradient(low = "#baded3", high = "#12664d") + 56 | labs(title = "Population: 200 people") + 57 | theme_void() + 58 | theme(legend.position = "none", 59 | legend.title = element_blank(), 60 | plot.margin = margin(10, 10, 10, 10), 61 | plot.title = element_text(face = "bold", 62 | hjust = 0.5, 63 | family = "Ubuntu", 64 | size = 36, 65 | margin = margin(b = 10))) 66 | 67 | ``` 68 | 69 | ## Sample 70 | 71 | **Sample** refers to the (usually smaller) group of people for which we have collected data on. 72 | 73 | ```{r} 74 | #| label: samp-people 75 | #| eval: true 76 | #| echo: false 77 | #| fig-align: center 78 | #| fig-height: 4.16 79 | ggplot() + 80 | geom_text(data = population_df, 81 | mapping = aes(x = x, 82 | y = y, 83 | label = fontawesome('fa-user')), 84 | family='fontawesome-webfont', size = 20, colour = "grey") + 85 | geom_text(data = sample_df, 86 | mapping = aes(x = x, 87 | y = y, 88 | label = fontawesome('fa-user'), 89 | colour = Value), 90 | family='fontawesome-webfont', size = 20) + 91 | scale_colour_gradient(low = "#baded3", high = "#12664d") + 92 | labs(title = glue::glue("Sample: {sample_size} people")) + 93 | theme_void() + 94 | theme(legend.position = "none", 95 | legend.title = element_blank(), 96 | plot.margin = margin(10, 10, 10, 10), 97 | plot.title = element_text(face = "bold", 98 | hjust = 0.5, 99 | family = "Ubuntu", 100 | size = 36, 101 | margin = margin(b = 10))) 102 | 103 | ``` 104 | 105 | ## Generate sample data {.scrollable} 106 | 107 | For the examples later, let's create a population of data in R...: 108 | 109 | ```{webr-r} 110 | # Generate population data 111 | set.seed(1234) 112 | population = rpois(200, 250) 113 | print("Population generated!") 114 | ``` 115 | 116 | ## Generate sample data {.scrollable} 117 | 118 | ... and draw a sample from it: 119 | 120 | ```{webr-r} 121 | # Pick a sample 122 | set.seed(1234) 123 | sample_size = 10 124 | sample_data = sample(population, size = sample_size, replace = FALSE) 125 | print("You've created a sample of data!") 126 | ``` 127 | 128 | ::: {.fragment} 129 | 130 | What do the values look like? 131 | 132 | ```{webr-r} 133 | sample_data 134 | ``` 135 | 136 | ::: 137 | 138 | ## Mean 139 | 140 | The mean, often simply called the *average*, is defined as *the sum of all values divided by the number of values*. It's a measure of central tendency that tells us what's happening near the middle of the data. 141 | 142 | ::::{style='text-align: center;'} 143 | 144 | $\bar{x} = \frac{1}{n} \sum_{i=i}^{n} x_{i}$ 145 | 146 | :::: 147 | 148 | ::: {.fragment} 149 | 150 | In R, we use the `mean()` function: 151 | 152 | ```{webr-r} 153 | # Calculate mean 154 | mean(sample_data) 155 | ``` 156 | 157 | ::: 158 | 159 | ## Median 160 | 161 | The median of a dataset is the middle value when the data is arranged in ascending order, or the average of the two middle values if the dataset has an even number of observations. 162 | 163 | ::: {.fragment} 164 | 165 | In R, we use the `median()` function: 166 | 167 | ```{webr-r} 168 | # Calculate median 169 | median(sample_data) 170 | ``` 171 | 172 | ::: 173 | 174 | ## Mode 175 | 176 | The mode statistic represents the value that appears most frequently in a dataset. 177 | 178 | ::: {.fragment} 179 | 180 | In R, there is no `mode()` function. Instead, we count how many of each value there are and choose the one with the highest number: 181 | 182 | ```{webr-r} 183 | # Count, sort and extract first element 184 | names(sort(table(sample_data), decreasing = TRUE)[1]) 185 | ``` 186 | 187 | ::: 188 | 189 | ## Range 190 | 191 | The range is the difference between the maximum and minimum values in a dataset. 192 | 193 | ::: {.fragment} 194 | 195 | In R, we can use the `max()` and `min()` function and subtract the values: 196 | 197 | ```{webr-r} 198 | # Subtract max and min values 199 | max(sample_data) - min(sample_data) 200 | ``` 201 | 202 | Note that the `range()` function returns the minimum and maximum, not a single value: 203 | 204 | ```{webr-r} 205 | # Calculate range 206 | range(sample_data) 207 | ``` 208 | 209 | ::: 210 | 211 | ## Sample variance 212 | 213 | The sample variance tells us about how spread out the data is. A lower variance indicates that values tend to be close to the mean, and a higher variance indicates that the values are spread out over a wider range. 214 | 215 | ::::{style='text-align: center;'} 216 | 217 | $s^2 = \frac{\Sigma_{i= 1}^{N} (x_i - \bar{x})^2}{n-1}$ 218 | 219 | :::: 220 | 221 | ::: {.fragment} 222 | 223 | In R, we use the `var()` function: 224 | 225 | ```{webr-r} 226 | # Calculate variance 227 | var(sample_data) 228 | ``` 229 | 230 | ::: 231 | 232 | ## Sample standard deviation 233 | 234 | The sample standard deviation is the square root of the variance. It also tells us about how spread out the data is. 235 | 236 | ::::{style='text-align: center;'} 237 | 238 | $s = \sqrt{\frac{\Sigma_{i= 1}^{N} (x_i - \bar{x})^2}{n-1}}$ 239 | 240 | :::: 241 | 242 | ::: {.fragment} 243 | 244 | In R, we use the `sd()` function: 245 | 246 | ```{webr-r} 247 | # Calculate standard deviation 248 | sd(sample_data) 249 | ``` 250 | 251 | ::: 252 | 253 | ## Descriptive statistics {.smaller} 254 | 255 | Descriptive statistics provide a summary that quantitatively describes a sample of data. 256 | 257 | * Mean: The sum of the values divided by the number of values. 258 | * Median: The middle value of the data when it's sorted. 259 | * Mode: The value that appears most frequently. 260 | * Range: The difference between the maximum and minimum values. 261 | * Variance: The average of the squared differences from the mean. 262 | * Standard deviation: The square root of the variance. 263 | 264 | ## Exercise 265 | 266 | In R: 267 | 268 | * Load the `ames` housing data set using `data(ames, package = "modeldata")` 269 | * Calculate the mean, median, mode, range, variance, and standard deviation of house prices (the `Sale_Price` column). 270 | 271 | > Remember: you can extract a column in R using `dataset$column_name`. 272 | 273 | ## Exercise solutions 274 | 275 | ```{r} 276 | #| echo: true 277 | # load data 278 | data(ames, package = "modeldata") 279 | 280 | # summary statistics 281 | mean(ames$Sale_Price) 282 | median(ames$Sale_Price) 283 | names(sort(table(ames$Sale_Price), decreasing = TRUE)[1]) 284 | max(ames$Sale_Price) - min(ames$Sale_Price) 285 | var(ames$Sale_Price) 286 | sd(ames$Sale_Price) 287 | ``` 288 | 289 | # Questions? {background-color="#1A936F"} 290 | -------------------------------------------------------------------------------- /index_files/figure-revealjs/pop-people-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/figure-revealjs/pop-people-1.png -------------------------------------------------------------------------------- /index_files/figure-revealjs/samp-people-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/figure-revealjs/samp-people-1.png -------------------------------------------------------------------------------- /index_files/libs/clipboard/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.11 3 | * https://clipboardjs.com/ 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light-border]>.tippy-arrow:after,.tippy-box[data-theme~=light-border]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=light-border]>.tippy-arrow:after{border-color:transparent;border-style:solid}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-arrow:after{border-top-color:rgba(0,8,16,.2);border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=light-border][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:rgba(0,8,16,.2);border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:16px}.tippy-box[data-theme~=light-border][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-arrow:after{border-left-color:rgba(0,8,16,.2);border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=light-border][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:rgba(0,8,16,.2)}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=light-border][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=light-border]>.tippy-svg-arrow{fill:#fff}.tippy-box[data-theme~=light-border]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px} -------------------------------------------------------------------------------- /index_files/libs/quarto-html/quarto-html.min.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /index_files/libs/quarto-html/quarto-syntax-highlighting.css: -------------------------------------------------------------------------------- 1 | /* quarto syntax highlight colors */ 2 | :root { 3 | --quarto-hl-ot-color: #003B4F; 4 | --quarto-hl-at-color: #657422; 5 | --quarto-hl-ss-color: #20794D; 6 | --quarto-hl-an-color: #5E5E5E; 7 | --quarto-hl-fu-color: #4758AB; 8 | --quarto-hl-st-color: #20794D; 9 | --quarto-hl-cf-color: #003B4F; 10 | --quarto-hl-op-color: #5E5E5E; 11 | --quarto-hl-er-color: #AD0000; 12 | --quarto-hl-bn-color: #AD0000; 13 | --quarto-hl-al-color: #AD0000; 14 | --quarto-hl-va-color: #111111; 15 | --quarto-hl-bu-color: inherit; 16 | --quarto-hl-ex-color: inherit; 17 | --quarto-hl-pp-color: #AD0000; 18 | --quarto-hl-in-color: #5E5E5E; 19 | --quarto-hl-vs-color: #20794D; 20 | --quarto-hl-wa-color: #5E5E5E; 21 | --quarto-hl-do-color: #5E5E5E; 22 | --quarto-hl-im-color: #00769E; 23 | --quarto-hl-ch-color: #20794D; 24 | --quarto-hl-dt-color: #AD0000; 25 | --quarto-hl-fl-color: #AD0000; 26 | --quarto-hl-co-color: #5E5E5E; 27 | --quarto-hl-cv-color: #5E5E5E; 28 | --quarto-hl-cn-color: #8f5902; 29 | --quarto-hl-sc-color: #5E5E5E; 30 | --quarto-hl-dv-color: #AD0000; 31 | --quarto-hl-kw-color: #003B4F; 32 | } 33 | 34 | /* other quarto variables */ 35 | :root { 36 | --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 37 | } 38 | 39 | pre > code.sourceCode > span { 40 | color: #003B4F; 41 | } 42 | 43 | code span { 44 | color: #003B4F; 45 | } 46 | 47 | code.sourceCode > span { 48 | color: #003B4F; 49 | } 50 | 51 | div.sourceCode, 52 | div.sourceCode pre.sourceCode { 53 | color: #003B4F; 54 | } 55 | 56 | code span.ot { 57 | color: #003B4F; 58 | font-style: inherit; 59 | } 60 | 61 | code span.at { 62 | color: #657422; 63 | font-style: inherit; 64 | } 65 | 66 | code span.ss { 67 | color: #20794D; 68 | font-style: inherit; 69 | } 70 | 71 | code span.an { 72 | color: #5E5E5E; 73 | font-style: inherit; 74 | } 75 | 76 | code span.fu { 77 | color: #4758AB; 78 | font-style: inherit; 79 | } 80 | 81 | code span.st { 82 | color: #20794D; 83 | font-style: inherit; 84 | } 85 | 86 | code span.cf { 87 | color: #003B4F; 88 | font-style: inherit; 89 | } 90 | 91 | code span.op { 92 | color: #5E5E5E; 93 | font-style: inherit; 94 | } 95 | 96 | code span.er { 97 | color: #AD0000; 98 | font-style: inherit; 99 | } 100 | 101 | code span.bn { 102 | color: #AD0000; 103 | font-style: inherit; 104 | } 105 | 106 | code span.al { 107 | color: #AD0000; 108 | font-style: inherit; 109 | } 110 | 111 | code span.va { 112 | color: #111111; 113 | font-style: inherit; 114 | } 115 | 116 | code span.bu { 117 | font-style: inherit; 118 | } 119 | 120 | code span.ex { 121 | font-style: inherit; 122 | } 123 | 124 | code span.pp { 125 | color: #AD0000; 126 | font-style: inherit; 127 | } 128 | 129 | code span.in { 130 | color: #5E5E5E; 131 | font-style: inherit; 132 | } 133 | 134 | code span.vs { 135 | color: #20794D; 136 | font-style: inherit; 137 | } 138 | 139 | code span.wa { 140 | color: #5E5E5E; 141 | font-style: italic; 142 | } 143 | 144 | code span.do { 145 | color: #5E5E5E; 146 | font-style: italic; 147 | } 148 | 149 | code span.im { 150 | color: #00769E; 151 | font-style: inherit; 152 | } 153 | 154 | code span.ch { 155 | color: #20794D; 156 | font-style: inherit; 157 | } 158 | 159 | code span.dt { 160 | color: #AD0000; 161 | font-style: inherit; 162 | } 163 | 164 | code span.fl { 165 | color: #AD0000; 166 | font-style: inherit; 167 | } 168 | 169 | code span.co { 170 | color: #5E5E5E; 171 | font-style: inherit; 172 | } 173 | 174 | code span.cv { 175 | color: #5E5E5E; 176 | font-style: italic; 177 | } 178 | 179 | code span.cn { 180 | color: #8f5902; 181 | font-style: inherit; 182 | } 183 | 184 | code span.sc { 185 | color: #5E5E5E; 186 | font-style: inherit; 187 | } 188 | 189 | code span.dv { 190 | color: #AD0000; 191 | font-style: inherit; 192 | } 193 | 194 | code span.kw { 195 | color: #003B4F; 196 | font-style: inherit; 197 | } 198 | 199 | .prevent-inlining { 200 | content: " !el.hasAttribute("disabled") && !el.getAttribute("aria-hidden") 84 | ); 85 | }; 86 | 87 | /** 88 | * Remove roles and attributes from a tab and its content 89 | * @param {Node} tab The tab 90 | * @param {Node} content The tab content 91 | * @param {Object} settings User settings and options 92 | */ 93 | var destroyTab = function (tab, content, settings) { 94 | // Remove the generated ID 95 | if (tab.id.slice(0, settings.idPrefix.length) === settings.idPrefix) { 96 | tab.id = ""; 97 | } 98 | 99 | // remove event listener 100 | tab.removeEventListener("focus", focusHandler, true); 101 | 102 | // Remove roles 103 | tab.removeAttribute("role"); 104 | tab.removeAttribute("aria-controls"); 105 | tab.removeAttribute("aria-selected"); 106 | tab.removeAttribute("tabindex"); 107 | tab.closest("li").removeAttribute("role"); 108 | content.removeAttribute("role"); 109 | content.removeAttribute("aria-labelledby"); 110 | content.removeAttribute("hidden"); 111 | }; 112 | 113 | /** 114 | * Add the required roles and attributes to a tab and its content 115 | * @param {Node} tab The tab 116 | * @param {Node} content The tab content 117 | * @param {Object} settings User settings and options 118 | */ 119 | var setupTab = function (tab, content, settings) { 120 | // Give tab an ID if it doesn't already have one 121 | if (!tab.id) { 122 | tab.id = settings.idPrefix + content.id; 123 | } 124 | 125 | // Add roles 126 | tab.setAttribute("role", "tab"); 127 | tab.setAttribute("aria-controls", content.id); 128 | tab.closest("li").setAttribute("role", "presentation"); 129 | content.setAttribute("role", "tabpanel"); 130 | content.setAttribute("aria-labelledby", tab.id); 131 | 132 | // Add selected state 133 | if (tab.matches(settings.default)) { 134 | tab.setAttribute("aria-selected", "true"); 135 | } else { 136 | tab.setAttribute("aria-selected", "false"); 137 | content.setAttribute("hidden", "hidden"); 138 | } 139 | 140 | // add focus event listender 141 | tab.addEventListener("focus", focusHandler); 142 | }; 143 | 144 | /** 145 | * Hide a tab and its content 146 | * @param {Node} newTab The new tab that's replacing it 147 | */ 148 | var hide = function (newTab) { 149 | // Variables 150 | var tabGroup = newTab.closest('[role="tablist"]'); 151 | if (!tabGroup) return {}; 152 | var tab = tabGroup.querySelector('[role="tab"][aria-selected="true"]'); 153 | if (!tab) return {}; 154 | var content = document.querySelector(tab.hash); 155 | 156 | // Hide the tab 157 | tab.setAttribute("aria-selected", "false"); 158 | 159 | // Hide the content 160 | if (!content) return { previousTab: tab }; 161 | content.setAttribute("hidden", "hidden"); 162 | 163 | // Return the hidden tab and content 164 | return { 165 | previousTab: tab, 166 | previousContent: content, 167 | }; 168 | }; 169 | 170 | /** 171 | * Show a tab and its content 172 | * @param {Node} tab The tab 173 | * @param {Node} content The tab content 174 | */ 175 | var show = function (tab, content) { 176 | tab.setAttribute("aria-selected", "true"); 177 | content.removeAttribute("hidden"); 178 | tab.focus(); 179 | }; 180 | 181 | /** 182 | * Toggle a new tab 183 | * @param {Node} tab The tab to show 184 | */ 185 | var toggle = function (tab) { 186 | // Make sure there's a tab to toggle and it's not already active 187 | if (!tab || tab.getAttribute("aria-selected") == "true") return; 188 | 189 | // Variables 190 | var content = document.querySelector(tab.hash); 191 | if (!content) return; 192 | 193 | // Hide active tab and content 194 | var details = hide(tab); 195 | 196 | // Show new tab and content 197 | show(tab, content); 198 | 199 | // Add event details 200 | details.tab = tab; 201 | details.content = content; 202 | 203 | // Emit a custom event 204 | emitEvent(tab, details); 205 | }; 206 | 207 | /** 208 | * Get all of the tabs in a tablist 209 | * @param {Node} tab A tab from the list 210 | * @return {Object} The tabs and the index of the currently active one 211 | */ 212 | var getTabsMap = function (tab) { 213 | var tabGroup = tab.closest('[role="tablist"]'); 214 | var tabs = tabGroup ? tabGroup.querySelectorAll('[role="tab"]') : null; 215 | if (!tabs) return; 216 | return { 217 | tabs: tabs, 218 | index: Array.prototype.indexOf.call(tabs, tab), 219 | }; 220 | }; 221 | 222 | /** 223 | * Switch the active tab based on keyboard activity 224 | * @param {Node} tab The currently active tab 225 | * @param {Key} key The key that was pressed 226 | */ 227 | var switchTabs = function (tab, key) { 228 | // Get a map of tabs 229 | var map = getTabsMap(tab); 230 | if (!map) return; 231 | var length = map.tabs.length - 1; 232 | var index; 233 | 234 | // Go to previous tab 235 | if (["ArrowUp", "ArrowLeft", "Up", "Left"].indexOf(key) > -1) { 236 | index = map.index < 1 ? length : map.index - 1; 237 | } 238 | 239 | // Go to next tab 240 | else if (["ArrowDown", "ArrowRight", "Down", "Right"].indexOf(key) > -1) { 241 | index = map.index === length ? 0 : map.index + 1; 242 | } 243 | 244 | // Go to home 245 | else if (key === "Home") { 246 | index = 0; 247 | } 248 | 249 | // Go to end 250 | else if (key === "End") { 251 | index = length; 252 | } 253 | 254 | // Toggle the tab 255 | toggle(map.tabs[index]); 256 | }; 257 | 258 | /** 259 | * Create the Constructor object 260 | */ 261 | var Constructor = function (selector, options) { 262 | // 263 | // Variables 264 | // 265 | 266 | var publicAPIs = {}; 267 | var settings, tabWrapper; 268 | 269 | // 270 | // Methods 271 | // 272 | 273 | publicAPIs.destroy = function () { 274 | // Get all tabs 275 | var tabs = tabWrapper.querySelectorAll("a"); 276 | 277 | // Add roles to tabs 278 | Array.prototype.forEach.call(tabs, function (tab) { 279 | // Get the tab content 280 | var content = document.querySelector(tab.hash); 281 | if (!content) return; 282 | 283 | // Setup the tab 284 | destroyTab(tab, content, settings); 285 | }); 286 | 287 | // Remove role from wrapper 288 | tabWrapper.removeAttribute("role"); 289 | 290 | // Remove event listeners 291 | document.documentElement.removeEventListener( 292 | "click", 293 | clickHandler, 294 | true 295 | ); 296 | tabWrapper.removeEventListener("keydown", keyHandler, true); 297 | 298 | // Reset variables 299 | settings = null; 300 | tabWrapper = null; 301 | }; 302 | 303 | /** 304 | * Setup the DOM with the proper attributes 305 | */ 306 | publicAPIs.setup = function () { 307 | // Variables 308 | tabWrapper = document.querySelector(selector); 309 | if (!tabWrapper) return; 310 | var tabs = tabWrapper.querySelectorAll("a"); 311 | 312 | // Add role to wrapper 313 | tabWrapper.setAttribute("role", "tablist"); 314 | 315 | // Add roles to tabs. provide dynanmic tab indexes if we are within reveal 316 | var contentTabindexes = 317 | window.document.body.classList.contains("reveal-viewport"); 318 | var nextTabindex = 1; 319 | Array.prototype.forEach.call(tabs, function (tab) { 320 | if (contentTabindexes) { 321 | tab.setAttribute("tabindex", "" + nextTabindex++); 322 | } else { 323 | tab.setAttribute("tabindex", "0"); 324 | } 325 | 326 | // Get the tab content 327 | var content = document.querySelector(tab.hash); 328 | if (!content) return; 329 | 330 | // set tab indexes for content 331 | if (contentTabindexes) { 332 | getKeyboardFocusableElements(content).forEach(function (el) { 333 | el.setAttribute("tabindex", "" + nextTabindex++); 334 | }); 335 | } 336 | 337 | // Setup the tab 338 | setupTab(tab, content, settings); 339 | }); 340 | }; 341 | 342 | /** 343 | * Toggle a tab based on an ID 344 | * @param {String|Node} id The tab to toggle 345 | */ 346 | publicAPIs.toggle = function (id) { 347 | // Get the tab 348 | var tab = id; 349 | if (typeof id === "string") { 350 | tab = document.querySelector( 351 | selector + ' [role="tab"][href*="' + id + '"]' 352 | ); 353 | } 354 | 355 | // Toggle the tab 356 | toggle(tab); 357 | }; 358 | 359 | /** 360 | * Handle click events 361 | */ 362 | var clickHandler = function (event) { 363 | // Only run on toggles 364 | var tab = event.target.closest(selector + ' [role="tab"]'); 365 | if (!tab) return; 366 | 367 | // Prevent link behavior 368 | event.preventDefault(); 369 | 370 | // Toggle the tab 371 | toggle(tab); 372 | }; 373 | 374 | /** 375 | * Handle keydown events 376 | */ 377 | var keyHandler = function (event) { 378 | // Only run if a tab is in focus 379 | var tab = document.activeElement; 380 | if (!tab.matches(selector + ' [role="tab"]')) return; 381 | 382 | // Only run for specific keys 383 | if (["Home", "End"].indexOf(event.key) < 0) return; 384 | 385 | // Switch tabs 386 | switchTabs(tab, event.key); 387 | }; 388 | 389 | /** 390 | * Initialize the instance 391 | */ 392 | var init = function () { 393 | // Merge user options with defaults 394 | settings = extend(defaults, options || {}); 395 | 396 | // Setup the DOM 397 | publicAPIs.setup(); 398 | 399 | // Add event listeners 400 | document.documentElement.addEventListener("click", clickHandler, true); 401 | tabWrapper.addEventListener("keydown", keyHandler, true); 402 | }; 403 | 404 | // 405 | // Initialize and return the Public APIs 406 | // 407 | 408 | init(); 409 | return publicAPIs; 410 | }; 411 | 412 | // 413 | // Return the Constructor 414 | // 415 | 416 | return Constructor; 417 | } 418 | ); 419 | -------------------------------------------------------------------------------- /index_files/libs/quarto-html/tippy.css: -------------------------------------------------------------------------------- 1 | .tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v4.0 | 20180602 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | main, menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, main, menu, nav, section { 29 | display: block; 30 | } -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/league-gothic/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License (OFL) 2 | http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL 3 | -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('./league-gothic.eot'); 4 | src: url('./league-gothic.eot?#iefix') format('embedded-opentype'), 5 | url('./league-gothic.woff') format('woff'), 6 | url('./league-gothic.ttf') format('truetype'); 7 | 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.eot -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.ttf -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/league-gothic/league-gothic.woff -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License 2 | 3 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 4 | 5 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 6 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 7 | 8 | —————————————————————————————- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | —————————————————————————————- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 14 | 15 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 16 | 17 | DEFINITIONS 18 | “Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 19 | 20 | “Reserved Font Name” refers to any names specified as such after the copyright statement(s). 21 | 22 | “Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s). 23 | 24 | “Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 25 | 26 | “Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 27 | 28 | PERMISSION & CONDITIONS 29 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 30 | 31 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 32 | 33 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 34 | 35 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 36 | 37 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 38 | 39 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 40 | 41 | TERMINATION 42 | This license becomes null and void if any of the above conditions are not met. 43 | 44 | DISCLAIMER 45 | THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.eot -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.ttf -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-italic.woff -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.eot -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.ttf -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-regular.woff -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.eot -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.ttf -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibold.woff -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.eot -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.ttf -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nrennie/learning-statistics-with-webR/dc797e18d106329f12dc2a4437cd97094bb5f2d1/index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro-semibolditalic.woff -------------------------------------------------------------------------------- /index_files/libs/revealjs/dist/theme/fonts/source-sans-pro/source-sans-pro.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Source Sans Pro'; 3 | src: url('./source-sans-pro-regular.eot'); 4 | src: url('./source-sans-pro-regular.eot?#iefix') format('embedded-opentype'), 5 | url('./source-sans-pro-regular.woff') format('woff'), 6 | url('./source-sans-pro-regular.ttf') format('truetype'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'Source Sans Pro'; 13 | src: url('./source-sans-pro-italic.eot'); 14 | src: url('./source-sans-pro-italic.eot?#iefix') format('embedded-opentype'), 15 | url('./source-sans-pro-italic.woff') format('woff'), 16 | url('./source-sans-pro-italic.ttf') format('truetype'); 17 | font-weight: normal; 18 | font-style: italic; 19 | } 20 | 21 | @font-face { 22 | font-family: 'Source Sans Pro'; 23 | src: url('./source-sans-pro-semibold.eot'); 24 | src: url('./source-sans-pro-semibold.eot?#iefix') format('embedded-opentype'), 25 | url('./source-sans-pro-semibold.woff') format('woff'), 26 | url('./source-sans-pro-semibold.ttf') format('truetype'); 27 | font-weight: 600; 28 | font-style: normal; 29 | } 30 | 31 | @font-face { 32 | font-family: 'Source Sans Pro'; 33 | src: url('./source-sans-pro-semibolditalic.eot'); 34 | src: url('./source-sans-pro-semibolditalic.eot?#iefix') format('embedded-opentype'), 35 | url('./source-sans-pro-semibolditalic.woff') format('woff'), 36 | url('./source-sans-pro-semibolditalic.ttf') format('truetype'); 37 | font-weight: 600; 38 | font-style: italic; 39 | } 40 | -------------------------------------------------------------------------------- /index_files/libs/revealjs/plugin/highlight/monokai.css: -------------------------------------------------------------------------------- 1 | /* 2 | Monokai style - ported by Luigi Maselli - http://grigio.org 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | background: #272822; 10 | color: #ddd; 11 | } 12 | 13 | .hljs-tag, 14 | .hljs-keyword, 15 | .hljs-selector-tag, 16 | .hljs-literal, 17 | .hljs-strong, 18 | .hljs-name { 19 | color: #f92672; 20 | } 21 | 22 | .hljs-code { 23 | color: #66d9ef; 24 | } 25 | 26 | .hljs-class .hljs-title { 27 | color: white; 28 | } 29 | 30 | .hljs-attribute, 31 | .hljs-symbol, 32 | .hljs-regexp, 33 | .hljs-link { 34 | color: #bf79db; 35 | } 36 | 37 | .hljs-string, 38 | .hljs-bullet, 39 | .hljs-subst, 40 | .hljs-title, 41 | .hljs-section, 42 | .hljs-emphasis, 43 | .hljs-type, 44 | .hljs-built_in, 45 | .hljs-builtin-name, 46 | .hljs-selector-attr, 47 | .hljs-selector-pseudo, 48 | .hljs-addition, 49 | .hljs-variable, 50 | .hljs-template-tag, 51 | .hljs-template-variable { 52 | color: #a6e22e; 53 | } 54 | 55 | .hljs-comment, 56 | .hljs-quote, 57 | .hljs-deletion, 58 | .hljs-meta { 59 | color: #75715e; 60 | } 61 | 62 | .hljs-keyword, 63 | .hljs-selector-tag, 64 | .hljs-literal, 65 | .hljs-doctag, 66 | .hljs-title, 67 | .hljs-section, 68 | .hljs-type, 69 | .hljs-selector-id { 70 | font-weight: bold; 71 | } 72 | -------------------------------------------------------------------------------- /index_files/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 | -------------------------------------------------------------------------------- /index_files/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 | -------------------------------------------------------------------------------- /index_files/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 | -------------------------------------------------------------------------------- /index_files/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 | -------------------------------------------------------------------------------- /index_files/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 | -------------------------------------------------------------------------------- /index_files/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 | } ); -------------------------------------------------------------------------------- /index_files/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 | -------------------------------------------------------------------------------- /index_files/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 | } 105 | 106 | return Plugin; 107 | 108 | })( Reveal ); 109 | -------------------------------------------------------------------------------- /index_files/libs/revealjs/plugin/pdf-export/plugin.yml: -------------------------------------------------------------------------------- 1 | name: PdfExport 2 | script: pdfexport.js 3 | -------------------------------------------------------------------------------- /index_files/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 | -------------------------------------------------------------------------------- /index_files/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(kCodeLineNumbersAttr); 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-of-type(n+" +
    169 |                 highlight.first +
    170 |                 "):nth-of-type(-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-of-type(" + 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 | 
    
    
    --------------------------------------------------------------------------------
    /index_files/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 | 
    
    
    --------------------------------------------------------------------------------
    /index_files/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 | 
    
    
    --------------------------------------------------------------------------------
    /index_files/libs/revealjs/plugin/quarto-support/plugin.yml:
    --------------------------------------------------------------------------------
    1 | name: QuartoSupport
    2 | script: support.js
    3 | stylesheet: footer.css
    4 | config:
    5 |   smaller: false
    6 | 
    
    
    --------------------------------------------------------------------------------
    /index_files/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 |   // helper for theme toggling
      8 |   function toggleBackgroundTheme(el, onDarkBackground, onLightBackground) {
      9 |     if (onDarkBackground) {
     10 |       el.classList.add('has-dark-background')
     11 |     } else {
     12 |       el.classList.remove('has-dark-background')
     13 |     }
     14 |     if (onLightBackground) {
     15 |       el.classList.add('has-light-background')
     16 |     } else {
     17 |       el.classList.remove('has-light-background')
     18 |     }
     19 |   }
     20 | 
     21 |   // implement controlsAudo
     22 |   function controlsAuto(deck) {
     23 |     const config = deck.getConfig();
     24 |     if (config.controlsAuto === true) {
     25 |       const iframe = window.location !== window.parent.location;
     26 |       const localhost =
     27 |         window.location.hostname === "localhost" ||
     28 |         window.location.hostname === "127.0.0.1";
     29 |       deck.configure({
     30 |         controls:
     31 |           (iframe && !localhost) ||
     32 |           (deck.hasVerticalSlides() && config.navigationMode !== "linear"),
     33 |       });
     34 |     }
     35 |   }
     36 | 
     37 |   // helper to provide event handlers for all links in a container
     38 |   function handleLinkClickEvents(deck, container) {
     39 |     Array.from(container.querySelectorAll("a")).forEach((el) => {
     40 |       const url = el.getAttribute("href");
     41 |       if (/^(http|www)/gi.test(url)) {
     42 |         el.addEventListener(
     43 |           "click",
     44 |           (ev) => {
     45 |             const fullscreen = !!window.document.fullscreen;
     46 |             const dataPreviewLink = el.getAttribute("data-preview-link");
     47 | 
     48 |             // if there is a local specifcation then use that
     49 |             if (dataPreviewLink) {
     50 |               if (
     51 |                 dataPreviewLink === "true" ||
     52 |                 (dataPreviewLink === "auto" && fullscreen)
     53 |               ) {
     54 |                 ev.preventDefault();
     55 |                 deck.showPreview(url);
     56 |                 return false;
     57 |               }
     58 |             } else {
     59 |               const previewLinks = !!deck.getConfig().previewLinks;
     60 |               const previewLinksAuto =
     61 |                 deck.getConfig().previewLinksAuto === true;
     62 |               if (previewLinks == true || (previewLinksAuto && fullscreen)) {
     63 |                 ev.preventDefault();
     64 |                 deck.showPreview(url);
     65 |                 return false;
     66 |               }
     67 |             }
     68 | 
     69 |             // if the deck is in an iframe we want to open it externally
     70 |             // (don't do this when in vscode though as it has its own
     71 |             // handler for opening links externally that will be play)
     72 |             const iframe = window.location !== window.parent.location;
     73 |             if (
     74 |               iframe &&
     75 |               !window.location.search.includes("quartoPreviewReqId=")
     76 |             ) {
     77 |               ev.preventDefault();
     78 |               ev.stopImmediatePropagation();
     79 |               window.open(url, "_blank");
     80 |               return false;
     81 |             }
     82 | 
     83 |             // if the user has set data-preview-link to "auto" we need to handle the event
     84 |             // (because reveal will interpret "auto" as true)
     85 |             if (dataPreviewLink === "auto") {
     86 |               ev.preventDefault();
     87 |               ev.stopImmediatePropagation();
     88 |               const target =
     89 |                 el.getAttribute("target") ||
     90 |                 (ev.ctrlKey || ev.metaKey ? "_blank" : "");
     91 |               if (target) {
     92 |                 window.open(url, target);
     93 |               } else {
     94 |                 window.location.href = url;
     95 |               }
     96 |               return false;
     97 |             }
     98 |           },
     99 |           false
    100 |         );
    101 |       }
    102 |     });
    103 |   }
    104 | 
    105 |   // implement previewLinksAuto
    106 |   function previewLinksAuto(deck) {
    107 |     handleLinkClickEvents(deck, deck.getRevealElement());
    108 |   }
    109 | 
    110 |   // apply styles
    111 |   function applyGlobalStyles(deck) {
    112 |     if (deck.getConfig()["smaller"] === true) {
    113 |       const revealParent = deck.getRevealElement();
    114 |       revealParent.classList.add("smaller");
    115 |     }
    116 |   }
    117 | 
    118 |   // add logo image
    119 |   function addLogoImage(deck) {
    120 |     const revealParent = deck.getRevealElement();
    121 |     const logoImg = document.querySelector(".slide-logo");
    122 |     if (logoImg) {
    123 |       revealParent.appendChild(logoImg);
    124 |       revealParent.classList.add("has-logo");
    125 |     }
    126 |   }
    127 | 
    128 |   // tweak slide-number element
    129 |   function tweakSlideNumber(deck) {
    130 |     deck.on("slidechanged", function (ev) {
    131 |       const revealParent = deck.getRevealElement();
    132 |       const slideNumberEl = revealParent.querySelector(".slide-number");
    133 |       const onDarkBackground = Reveal.getSlideBackground(ev.indexh, ev.indexv).classList.contains('has-dark-background');
    134 |       const onLightBackground = Reveal.getSlideBackground(ev.indexh, ev.indexv).classList.contains('has-light-background');
    135 |       toggleBackgroundTheme(slideNumberEl, onDarkBackground, onLightBackground);
    136 |     })
    137 |   }
    138 | 
    139 |    // add footer text
    140 |    function addFooter(deck) {
    141 |     const revealParent = deck.getRevealElement();
    142 |     const defaultFooterDiv = document.querySelector(".footer-default");
    143 |     if (defaultFooterDiv) {
    144 |       revealParent.appendChild(defaultFooterDiv);
    145 |       handleLinkClickEvents(deck, defaultFooterDiv);
    146 |       if (!isPrintView()) {
    147 |         deck.on("slidechanged", function (ev) {
    148 |           const prevSlideFooter = document.querySelector(
    149 |             ".reveal > .footer:not(.footer-default)"
    150 |           );
    151 |           if (prevSlideFooter) {
    152 |             prevSlideFooter.remove();
    153 |           }
    154 |           const currentSlideFooter = ev.currentSlide.querySelector(".footer");
    155 |           const onDarkBackground = Reveal.getSlideBackground(ev.indexh, ev.indexv).classList.contains('has-dark-background')
    156 |           const onLightBackground = Reveal.getSlideBackground(ev.indexh, ev.indexv).classList.contains('has-light-background')
    157 |           if (currentSlideFooter) {
    158 |             defaultFooterDiv.style.display = "none";
    159 |             const slideFooter = currentSlideFooter.cloneNode(true);
    160 |             handleLinkClickEvents(deck, slideFooter);
    161 |             deck.getRevealElement().appendChild(slideFooter);
    162 |             toggleBackgroundTheme(slideFooter, onDarkBackground, onLightBackground)
    163 |           } else {
    164 |             defaultFooterDiv.style.display = "block";
    165 |             toggleBackgroundTheme(defaultFooterDiv, onDarkBackground, onLightBackground)
    166 |           }
    167 |         });
    168 |       }
    169 |     }
    170 |   }
    171 | 
    172 |   // add chalkboard buttons
    173 |   function addChalkboardButtons(deck) {
    174 |     const chalkboard = deck.getPlugin("RevealChalkboard");
    175 |     if (chalkboard && !isPrintView()) {
    176 |       const revealParent = deck.getRevealElement();
    177 |       const chalkboardDiv = document.createElement("div");
    178 |       chalkboardDiv.classList.add("slide-chalkboard-buttons");
    179 |       if (document.querySelector(".slide-menu-button")) {
    180 |         chalkboardDiv.classList.add("slide-menu-offset");
    181 |       }
    182 |       // add buttons
    183 |       const buttons = [
    184 |         {
    185 |           icon: "easel2",
    186 |           title: "Toggle Chalkboard (b)",
    187 |           onclick: chalkboard.toggleChalkboard,
    188 |         },
    189 |         {
    190 |           icon: "brush",
    191 |           title: "Toggle Notes Canvas (c)",
    192 |           onclick: chalkboard.toggleNotesCanvas,
    193 |         },
    194 |       ];
    195 |       buttons.forEach(function (button) {
    196 |         const span = document.createElement("span");
    197 |         span.title = button.title;
    198 |         const icon = document.createElement("i");
    199 |         icon.classList.add("fas");
    200 |         icon.classList.add("fa-" + button.icon);
    201 |         span.appendChild(icon);
    202 |         span.onclick = function (event) {
    203 |           event.preventDefault();
    204 |           button.onclick();
    205 |         };
    206 |         chalkboardDiv.appendChild(span);
    207 |       });
    208 |       revealParent.appendChild(chalkboardDiv);
    209 |       const config = deck.getConfig();
    210 |       if (!config.chalkboard.buttons) {
    211 |         chalkboardDiv.classList.add("hidden");
    212 |       }
    213 | 
    214 |       // show and hide chalkboard buttons on slidechange
    215 |       deck.on("slidechanged", function (ev) {
    216 |         const config = deck.getConfig();
    217 |         let buttons = !!config.chalkboard.buttons;
    218 |         const slideButtons = ev.currentSlide.getAttribute(
    219 |           "data-chalkboard-buttons"
    220 |         );
    221 |         if (slideButtons) {
    222 |           if (slideButtons === "true" || slideButtons === "1") {
    223 |             buttons = true;
    224 |           } else if (slideButtons === "false" || slideButtons === "0") {
    225 |             buttons = false;
    226 |           }
    227 |         }
    228 |         if (buttons) {
    229 |           chalkboardDiv.classList.remove("hidden");
    230 |         } else {
    231 |           chalkboardDiv.classList.add("hidden");
    232 |         }
    233 |       });
    234 |     }
    235 |   }
    236 | 
    237 |   function handleTabbyClicks() {
    238 |     const tabs = document.querySelectorAll(".panel-tabset-tabby > li > a");
    239 |     for (let i = 0; i < tabs.length; i++) {
    240 |       const tab = tabs[i];
    241 |       tab.onclick = function (ev) {
    242 |         ev.preventDefault();
    243 |         ev.stopPropagation();
    244 |         return false;
    245 |       };
    246 |     }
    247 |   }
    248 | 
    249 |   function fixupForPrint(deck) {
    250 |     if (isPrintView()) {
    251 |       const slides = deck.getSlides();
    252 |       slides.forEach(function (slide) {
    253 |         slide.removeAttribute("data-auto-animate");
    254 |       });
    255 |       window.document.querySelectorAll(".hljs").forEach(function (el) {
    256 |         el.classList.remove("hljs");
    257 |       });
    258 |       window.document.querySelectorAll(".hljs-ln-code").forEach(function (el) {
    259 |         el.classList.remove("hljs-ln-code");
    260 |       });
    261 |     }
    262 |   }
    263 | 
    264 |   function handleSlideChanges(deck) {
    265 |     // dispatch for htmlwidgets
    266 |     const fireSlideEnter = () => {
    267 |       const event = window.document.createEvent("Event");
    268 |       event.initEvent("slideenter", true, true);
    269 |       window.document.dispatchEvent(event);
    270 |     };
    271 | 
    272 |     const fireSlideChanged = (previousSlide, currentSlide) => {
    273 |       fireSlideEnter();
    274 | 
    275 |       // dispatch for shiny
    276 |       if (window.jQuery) {
    277 |         if (previousSlide) {
    278 |           window.jQuery(previousSlide).trigger("hidden");
    279 |         }
    280 |         if (currentSlide) {
    281 |           window.jQuery(currentSlide).trigger("shown");
    282 |         }
    283 |       }
    284 |     };
    285 | 
    286 |     // fire slideEnter for tabby tab activations (for htmlwidget resize behavior)
    287 |     document.addEventListener("tabby", fireSlideEnter, false);
    288 | 
    289 |     deck.on("slidechanged", function (event) {
    290 |       fireSlideChanged(event.previousSlide, event.currentSlide);
    291 |     });
    292 |   }
    293 | 
    294 |   function workaroundMermaidDistance(deck) {
    295 |     if (window.document.querySelector("pre.mermaid-js")) {
    296 |       const slideCount = deck.getTotalSlides();
    297 |       deck.configure({
    298 |         mobileViewDistance: slideCount,
    299 |         viewDistance: slideCount,
    300 |       });
    301 |     }
    302 |   }
    303 | 
    304 |   return {
    305 |     id: "quarto-support",
    306 |     init: function (deck) {
    307 |       controlsAuto(deck);
    308 |       previewLinksAuto(deck);
    309 |       fixupForPrint(deck);
    310 |       applyGlobalStyles(deck);
    311 |       addLogoImage(deck);
    312 |       tweakSlideNumber(deck);
    313 |       addFooter(deck);
    314 |       addChalkboardButtons(deck);
    315 |       handleTabbyClicks();
    316 |       handleSlideChanges(deck);
    317 |       workaroundMermaidDistance(deck);
    318 |     },
    319 |   };
    320 | };
    321 | 
    
    
    --------------------------------------------------------------------------------
    /index_files/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 | 
    
    
    --------------------------------------------------------------------------------
    /index_files/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 | 
    
    
    --------------------------------------------------------------------------------
    /index_files/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 | 
    
    
    --------------------------------------------------------------------------------
    /index_files/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 | 
    
    
    --------------------------------------------------------------------------------
    /index_files/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;
    
    
    --------------------------------------------------------------------------------
    /index_files/libs/revealjs/plugin/zoom/plugin.js:
    --------------------------------------------------------------------------------
      1 | /*!
      2 |  * reveal.js Zoom plugin
      3 |  */
      4 | const Plugin = {
      5 | 
      6 | 	id: 'zoom',
      7 | 
      8 | 	init: function( reveal ) {
      9 | 
     10 | 		reveal.getRevealElement().addEventListener( 'mousedown', function( event ) {
     11 | 			var defaultModifier = /Linux/.test( window.navigator.platform ) ? 'ctrl' : 'alt';
     12 | 
     13 | 			var modifier = ( reveal.getConfig().zoomKey ? reveal.getConfig().zoomKey : defaultModifier ) + 'Key';
     14 | 			var zoomLevel = ( reveal.getConfig().zoomLevel ? reveal.getConfig().zoomLevel : 2 );
     15 | 
     16 | 			if( event[ modifier ] && !reveal.isOverview() ) {
     17 | 				event.preventDefault();
     18 | 
     19 | 				zoom.to({
     20 | 					x: event.clientX,
     21 | 					y: event.clientY,
     22 | 					scale: zoomLevel,
     23 | 					pan: false
     24 | 				});
     25 | 			}
     26 | 		} );
     27 | 
     28 | 	},
     29 | 
     30 | 	destroy: () => {
     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 | 
    
    
    --------------------------------------------------------------------------------
    /index_files/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 | 
    
    
    --------------------------------------------------------------------------------
    /index_files/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 | 
    
    
    --------------------------------------------------------------------------------
    /learning-statistics-with-webR.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 | 
    
    
    --------------------------------------------------------------------------------
    /webr-serviceworker.js:
    --------------------------------------------------------------------------------
    1 | importScripts('https://webr.r-wasm.org/v0.2.2/webr-serviceworker.js');
    2 | 
    
    
    --------------------------------------------------------------------------------
    /webr-worker.js:
    --------------------------------------------------------------------------------
    1 | importScripts('https://webr.r-wasm.org/v0.2.2/webr-worker.js');
    2 | 
    
    
    --------------------------------------------------------------------------------