${outputResult}
`;
176 | })
177 | .join("\n");
178 |
179 |
180 | // Clean the state
181 | // We're now able to process pager events.
182 | // As a result, we cannot maintain a true 1-to-1 output order
183 | // without individually feeding each line
184 | const msgs = await mainWebR.flush();
185 |
186 | // Use `map` to process the filtered "pager" events asynchronously
187 | const pager = await Promise.all(
188 | msgs.filter(msg => msg.type === 'pager').map(
189 | async (msg) => {
190 | return await qwebrParseTypePager(msg);
191 | }
192 | )
193 | );
194 |
195 | // Nullify the output area of content
196 | elements.outputCodeDiv.innerHTML = "";
197 | elements.outputGraphDiv.innerHTML = "";
198 |
199 | // Design an output object for messages
200 | const pre = document.createElement("pre");
201 | if (/\S/.test(out)) {
202 | // Display results as HTML elements to retain output styling
203 | const div = document.createElement("div");
204 | div.innerHTML = out;
205 |
206 | // Calculate a scaled font-size value
207 | const scaledFontSize = qwebrScaledFontSize(
208 | elements.outputCodeDiv, options);
209 |
210 | // Override output code cell size
211 | pre.style.fontSize = `${scaledFontSize}px`;
212 | pre.appendChild(div);
213 | } else {
214 | // If nothing is present, hide the element.
215 | pre.style.visibility = "hidden";
216 | }
217 |
218 | elements.outputCodeDiv.appendChild(pre);
219 |
220 | // Determine if we have graphs to display
221 | if (result.images.length > 0) {
222 |
223 | // Create figure element
224 | const figureElement = document.createElement("figure");
225 | figureElement.className = "qwebr-canvas-image";
226 |
227 | // Place each rendered graphic onto a canvas element
228 | result.images.forEach((img) => {
229 |
230 | // Construct canvas for object
231 | const canvas = document.createElement("canvas");
232 |
233 | // Add an image download button
234 | qwebrImageCanvasDownloadButton(canvas, figureElement);
235 |
236 | // Set canvas size to image
237 | canvas.width = img.width;
238 | canvas.height = img.height;
239 |
240 | // Apply output truncations
241 | canvas.style.width = options["out-width"] ? options["out-width"] : `${fig_width}px`;
242 | if (options["out-height"]) {
243 | canvas.style.height = options["out-height"];
244 | }
245 |
246 | // Apply styling
247 | canvas.style.display = "block";
248 | canvas.style.margin = "auto";
249 |
250 | // Draw image onto Canvas
251 | const ctx = canvas.getContext("2d");
252 | ctx.drawImage(img, 0, 0, img.width, img.height);
253 |
254 | // Append canvas to figure output area
255 | figureElement.appendChild(canvas);
256 |
257 | });
258 |
259 | if (options['fig-cap']) {
260 | // Create figcaption element
261 | const figcaptionElement = document.createElement('figcaption');
262 | figcaptionElement.innerText = options['fig-cap'];
263 | // Append figcaption to figure
264 | figureElement.appendChild(figcaptionElement);
265 | }
266 |
267 | elements.outputGraphDiv.appendChild(figureElement);
268 |
269 | }
270 |
271 | // Display the pager data
272 | if (pager) {
273 | // Use the `pre` element to preserve whitespace.
274 | pager.forEach((paged_data, index) => {
275 | let pre_pager = document.createElement("pre");
276 | pre_pager.innerText = paged_data;
277 | pre_pager.classList.add("qwebr-output-code-pager");
278 | pre_pager.setAttribute("id", `qwebr-output-code-pager-editor-${elements.id}-result-${index + 1}`);
279 | elements.outputCodeDiv.appendChild(pre_pager);
280 | });
281 | }
282 | } finally {
283 | // Clean up the remaining code
284 | mainWebRCodeShelter.purge();
285 | }
286 | }
287 |
288 | // Function to execute the code (accepts code as an argument)
289 | globalThis.qwebrExecuteCode = async function (
290 | codeToRun,
291 | id,
292 | options = {}) {
293 |
294 | // If options are not passed, we fall back on the bare minimum to handle the computation
295 | if (qwebrIsObjectEmpty(options)) {
296 | options = {
297 | "context": "interactive",
298 | "fig-width": 7, "fig-height": 5,
299 | "out-width": "700px", "out-height": "",
300 | "dpi": 72,
301 | "results": "markup",
302 | "warning": "true", "message": "true",
303 | };
304 | }
305 |
306 | // Next, we access the compute areas values
307 | const elements = {
308 | runButton: document.getElementById(`qwebr-button-run-${id}`),
309 | outputCodeDiv: document.getElementById(`qwebr-output-code-area-${id}`),
310 | outputGraphDiv: document.getElementById(`qwebr-output-graph-area-${id}`),
311 | id: id,
312 | }
313 |
314 | // Disallowing execution of other code cells
315 | document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
316 | btn.disabled = true;
317 | });
318 |
319 | if (options.context == EvalTypes.Interactive) {
320 | // Emphasize the active code cell
321 | elements.runButton.innerHTML = ' Run Code';
322 | }
323 |
324 | // Evaluate the code and parse the output into the document
325 | await qwebrComputeEngine(codeToRun, elements, options);
326 |
327 | // Switch to allowing execution of code
328 | document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
329 | btn.disabled = false;
330 | });
331 |
332 | if (options.context == EvalTypes.Interactive) {
333 | // Revert to the initial code cell state
334 | elements.runButton.innerHTML = ' Run Code';
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/_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-history.js:
--------------------------------------------------------------------------------
1 | // Define a global storage and retrieval solution ----
2 |
3 | // Store commands executed in R
4 | globalThis.qwebrRCommandHistory = [];
5 |
6 | // Function to retrieve the command history
7 | globalThis.qwebrFormatRHistory = function() {
8 | return qwebrRCommandHistory.join("\n\n");
9 | }
10 |
11 | // Retrieve HTML Elements ----
12 |
13 | // Get the command modal
14 | const command_history_modal = document.getElementById("qwebr-history-modal");
15 |
16 | // Get the button that opens the command modal
17 | const command_history_btn = document.getElementById("qwebrRHistoryButton");
18 |
19 | // Get the element that closes the command modal
20 | const command_history_close_span = document.getElementById("qwebr-command-history-close-btn");
21 |
22 | // Get the download button for r history information
23 | const command_history_download_btn = document.getElementById("qwebr-download-history-btn");
24 |
25 | // Plug in command history into modal/download button ----
26 |
27 | // Function to populate the modal with command history
28 | function populateCommandHistoryModal() {
29 | document.getElementById("qwebr-command-history-contents").innerHTML = qwebrFormatRHistory() || "No commands have been executed yet.";
30 | }
31 |
32 | // Function to format the current date and time to
33 | // a string with the format YYYY-MM-DD-HH-MM-SS
34 | function formatDateTime() {
35 | const now = new Date();
36 |
37 | const year = now.getFullYear();
38 | const day = String(now.getDate()).padStart(2, '0');
39 | const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are zero-based
40 | const hours = String(now.getHours()).padStart(2, '0');
41 | const minutes = String(now.getMinutes()).padStart(2, '0');
42 | const seconds = String(now.getSeconds()).padStart(2, '0');
43 |
44 | return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}`;
45 | }
46 |
47 |
48 | // Function to convert document title with datetime to a safe filename
49 | function safeFileName() {
50 | // Get the current page title
51 | let pageTitle = document.title;
52 |
53 | // Combine the current page title with the current date and time
54 | let pageNameWithDateTime = `Rhistory-${pageTitle}-${formatDateTime()}`;
55 |
56 | // Replace unsafe characters with safe alternatives
57 | let safeFilename = pageNameWithDateTime.replace(/[\\/:\*\?! "<>\|]/g, '-');
58 |
59 | return safeFilename;
60 | }
61 |
62 |
63 | // Function to download list contents as text file
64 | function downloadRHistory() {
65 | // Get the current page title + datetime and use it as the filename
66 | const filename = `${safeFileName()}.R`;
67 |
68 | // Get the text contents of the R History list
69 | const text = qwebrFormatRHistory();
70 |
71 | // Create a new Blob object with the text contents
72 | const blob = new Blob([text], { type: 'text/plain' });
73 |
74 | // Create a new anchor element for the download
75 | const a = document.createElement('a');
76 | a.style.display = 'none';
77 | a.href = URL.createObjectURL(blob);
78 | a.download = filename;
79 |
80 | // Append the anchor to the body, click it, and remove it
81 | document.body.appendChild(a);
82 | a.click();
83 | document.body.removeChild(a);
84 | }
85 |
86 | // Register event handlers ----
87 |
88 | // When the user clicks the View R History button, open the command modal
89 | command_history_btn.onclick = function() {
90 | populateCommandHistoryModal();
91 | command_history_modal.style.display = "block";
92 | }
93 |
94 | // When the user clicks on (x), close the command modal
95 | command_history_close_span.onclick = function() {
96 | command_history_modal.style.display = "none";
97 | }
98 |
99 | // When the user clicks anywhere outside of the command modal, close it
100 | window.onclick = function(event) {
101 | if (event.target == command_history_modal) {
102 | command_history_modal.style.display = "none";
103 | }
104 | }
105 |
106 | // Add an onclick event listener to the download button so that
107 | // the user can download the R history as a text file
108 | command_history_download_btn.onclick = function() {
109 | downloadRHistory();
110 | };
--------------------------------------------------------------------------------
/_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 | // Verify if OffScreenCanvas is supported
5 | globalThis.qwebrOffScreenCanvasSupport = function() {
6 | return typeof OffscreenCanvas !== 'undefined'
7 | }
8 |
9 | // Function to set the button text
10 | globalThis.qwebrSetInteractiveButtonState = function(buttonText, enableCodeButton = true) {
11 | document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
12 | btn.innerHTML = buttonText;
13 | btn.disabled = !enableCodeButton;
14 | });
15 | }
16 |
17 | // Function to update the status message in non-interactive cells
18 | globalThis.qwebrUpdateStatusMessage = function(message) {
19 | document.querySelectorAll(".qwebr-status-text.qwebr-cell-needs-evaluation").forEach((elem) => {
20 | elem.innerText = message;
21 | });
22 | }
23 |
24 | // Function to update the status message
25 | globalThis.qwebrUpdateStatusHeader = function(message) {
26 | qwebrStartupMessage.innerHTML = `
27 |
28 | ${message}`;
29 | }
30 |
31 | // Function to return true if element is found, false if not
32 | globalThis.qwebrCheckHTMLElementExists = function(selector) {
33 | const element = document.querySelector(selector);
34 | return !!element;
35 | }
36 |
37 | // Function that detects whether reveal.js slides are present
38 | globalThis.qwebrIsRevealJS = function() {
39 | // If the '.reveal .slides' selector exists, RevealJS is likely present
40 | return qwebrCheckHTMLElementExists('.reveal .slides');
41 | }
42 |
43 | // Initialize the Quarto sidebar element
44 | function qwebrSetupQuartoSidebar() {
45 | var newSideBarDiv = document.createElement('div');
46 | newSideBarDiv.id = 'quarto-margin-sidebar';
47 | newSideBarDiv.className = 'sidebar margin-sidebar';
48 | newSideBarDiv.style.top = '0px';
49 | newSideBarDiv.style.maxHeight = 'calc(0px + 100vh)';
50 |
51 | return newSideBarDiv;
52 | }
53 |
54 | // Position the sidebar in the document
55 | function qwebrPlaceQuartoSidebar() {
56 | // Get the reference to the element with id 'quarto-document-content'
57 | var referenceNode = document.getElementById('quarto-document-content');
58 |
59 | // Create the new div element
60 | var newSideBarDiv = qwebrSetupQuartoSidebar();
61 |
62 | // Insert the new div before the 'quarto-document-content' element
63 | referenceNode.parentNode.insertBefore(newSideBarDiv, referenceNode);
64 | }
65 |
66 | function qwebrPlaceMessageContents(content, html_location = "title-block-header", revealjs_location = "title-slide") {
67 |
68 | // Get references to header elements
69 | const headerHTML = document.getElementById(html_location);
70 | const headerRevealJS = document.getElementById(revealjs_location);
71 |
72 | // Determine where to insert the quartoTitleMeta element
73 | if (headerHTML || headerRevealJS) {
74 | // Append to the existing "title-block-header" element or "title-slide" div
75 | (headerHTML || headerRevealJS).appendChild(content);
76 | } else {
77 | // If neither headerHTML nor headerRevealJS is found, insert after "webr-monaco-editor-init" script
78 | const monacoScript = document.getElementById("qwebr-monaco-editor-init");
79 | const header = document.createElement("header");
80 | header.setAttribute("id", "title-block-header");
81 | header.appendChild(content);
82 | monacoScript.after(header);
83 | }
84 | }
85 |
86 |
87 |
88 | function qwebrOffScreenCanvasSupportWarningMessage() {
89 |
90 | // Verify canvas is supported.
91 | if(qwebrOffScreenCanvasSupport()) return;
92 |
93 | // Create the main container div
94 | var calloutContainer = document.createElement('div');
95 | calloutContainer.classList.add('callout', 'callout-style-default', 'callout-warning', 'callout-titled');
96 |
97 | // Create the header div
98 | var headerDiv = document.createElement('div');
99 | headerDiv.classList.add('callout-header', 'd-flex', 'align-content-center');
100 |
101 | // Create the icon container div
102 | var iconContainer = document.createElement('div');
103 | iconContainer.classList.add('callout-icon-container');
104 |
105 | // Create the icon element
106 | var iconElement = document.createElement('i');
107 | iconElement.classList.add('callout-icon');
108 |
109 | // Append the icon element to the icon container
110 | iconContainer.appendChild(iconElement);
111 |
112 | // Create the title container div
113 | var titleContainer = document.createElement('div');
114 | titleContainer.classList.add('callout-title-container', 'flex-fill');
115 | titleContainer.innerText = 'Warning: Web Browser Does Not Support Graphing!';
116 |
117 | // Append the icon container and title container to the header div
118 | headerDiv.appendChild(iconContainer);
119 | headerDiv.appendChild(titleContainer);
120 |
121 | // Create the body container div
122 | var bodyContainer = document.createElement('div');
123 | bodyContainer.classList.add('callout-body-container', 'callout-body');
124 |
125 | // Create the paragraph element for the body content
126 | var paragraphElement = document.createElement('p');
127 | paragraphElement.innerHTML = 'This web browser does not have support for displaying graphs through the quarto-webr
extension since it lacks an OffScreenCanvas
. Please upgrade your web browser to one that supports OffScreenCanvas
.';
128 |
129 | // Append the paragraph element to the body container
130 | bodyContainer.appendChild(paragraphElement);
131 |
132 | // Append the header div and body container to the main container div
133 | calloutContainer.appendChild(headerDiv);
134 | calloutContainer.appendChild(bodyContainer);
135 |
136 | // Append the main container div to the document depending on format
137 | qwebrPlaceMessageContents(calloutContainer, "title-block-header");
138 |
139 | }
140 |
141 |
142 | // Function that attaches the document status message and diagnostics
143 | function displayStartupMessage(showStartupMessage, showHeaderMessage) {
144 | if (!showStartupMessage) {
145 | return;
146 | }
147 |
148 | // Create the outermost div element for metadata
149 | const quartoTitleMeta = document.createElement("div");
150 | quartoTitleMeta.classList.add("quarto-title-meta");
151 |
152 | // Create the first inner div element
153 | const firstInnerDiv = document.createElement("div");
154 | firstInnerDiv.setAttribute("id", "qwebr-status-message-area");
155 |
156 | // Create the second inner div element for "WebR Status" heading and contents
157 | const secondInnerDiv = document.createElement("div");
158 | secondInnerDiv.setAttribute("id", "qwebr-status-message-title");
159 | secondInnerDiv.classList.add("quarto-title-meta-heading");
160 | secondInnerDiv.innerText = "WebR Status";
161 |
162 | // Create another inner div for contents
163 | const secondInnerDivContents = document.createElement("div");
164 | secondInnerDivContents.setAttribute("id", "qwebr-status-message-body");
165 | secondInnerDivContents.classList.add("quarto-title-meta-contents");
166 |
167 | // Describe the WebR state
168 | qwebrStartupMessage.innerText = "🟡 Loading...";
169 | qwebrStartupMessage.setAttribute("id", "qwebr-status-message-text");
170 | // Add `aria-live` to auto-announce the startup status to screen readers
171 | qwebrStartupMessage.setAttribute("aria-live", "assertive");
172 |
173 | // Append the startup message to the contents
174 | secondInnerDivContents.appendChild(qwebrStartupMessage);
175 |
176 | // Add a status indicator for COOP and COEP Headers if needed
177 | if (showHeaderMessage) {
178 | const crossOriginMessage = document.createElement("p");
179 | crossOriginMessage.innerText = `${crossOriginIsolated ? '🟢' : '🟡'} COOP & COEP Headers`;
180 | crossOriginMessage.setAttribute("id", "qwebr-coop-coep-header");
181 | secondInnerDivContents.appendChild(crossOriginMessage);
182 | }
183 |
184 | // Combine the inner divs and contents
185 | firstInnerDiv.appendChild(secondInnerDiv);
186 | firstInnerDiv.appendChild(secondInnerDivContents);
187 | quartoTitleMeta.appendChild(firstInnerDiv);
188 |
189 | // Place message on webpage
190 | qwebrPlaceMessageContents(quartoTitleMeta);
191 | }
192 |
193 | function qwebrAddCommandHistoryModal() {
194 | // Create the modal div
195 | var modalDiv = document.createElement('div');
196 | modalDiv.id = 'qwebr-history-modal';
197 | modalDiv.className = 'qwebr-modal';
198 |
199 | // Create the modal content div
200 | var modalContentDiv = document.createElement('div');
201 | modalContentDiv.className = 'qwebr-modal-content';
202 |
203 | // Create the span for closing the modal
204 | var closeSpan = document.createElement('span');
205 | closeSpan.id = 'qwebr-command-history-close-btn';
206 | closeSpan.className = 'qwebr-modal-close';
207 | closeSpan.innerHTML = '×';
208 |
209 | // Create the h1 element for the modal
210 | var modalH1 = document.createElement('h1');
211 | modalH1.textContent = 'R History Command Contents';
212 |
213 | // Create an anchor element for downloading the Rhistory file
214 | var downloadLink = document.createElement('a');
215 | downloadLink.href = '#';
216 | downloadLink.id = 'qwebr-download-history-btn';
217 | downloadLink.className = 'qwebr-download-btn';
218 |
219 | // Create an 'i' element for the icon
220 | var icon = document.createElement('i');
221 | icon.className = 'bi bi-file-code';
222 |
223 | // Append the icon to the anchor element
224 | downloadLink.appendChild(icon);
225 |
226 | // Add the text 'Download R History' to the anchor element
227 | downloadLink.appendChild(document.createTextNode(' Download R History File'));
228 |
229 | // Create the pre for command history contents
230 | var commandContentsPre = document.createElement('pre');
231 | commandContentsPre.id = 'qwebr-command-history-contents';
232 | commandContentsPre.className = 'qwebr-modal-content-code';
233 |
234 | // Append the close span, h1, and history contents pre to the modal content div
235 | modalContentDiv.appendChild(closeSpan);
236 | modalContentDiv.appendChild(modalH1);
237 | modalContentDiv.appendChild(downloadLink);
238 | modalContentDiv.appendChild(commandContentsPre);
239 |
240 | // Append the modal content div to the modal div
241 | modalDiv.appendChild(modalContentDiv);
242 |
243 | // Append the modal div to the body
244 | document.body.appendChild(modalDiv);
245 | }
246 |
247 | function qwebrRegisterRevealJSCommandHistoryModal() {
248 | // Select the