${outputResult}
`;
+ })
+ .join("\n");
+
+
+ // Clean the state
+ // We're now able to process pager events.
+ // As a result, we cannot maintain a true 1-to-1 output order
+ // without individually feeding each line
+ const msgs = await mainWebR.flush();
+
+ // Use `map` to process the filtered "pager" events asynchronously
+ const pager = await Promise.all(
+ msgs.filter(msg => msg.type === 'pager').map(
+ async (msg) => {
+ return await qwebrParseTypePager(msg);
+ }
+ )
+ );
+
+ // Nullify the output area of content
+ elements.outputCodeDiv.innerHTML = "";
+ elements.outputGraphDiv.innerHTML = "";
+
+ // Design an output object for messages
+ const pre = document.createElement("pre");
+ if (/\S/.test(out)) {
+ // Display results as HTML elements to retain output styling
+ const div = document.createElement("div");
+ div.innerHTML = out;
+
+ // Calculate a scaled font-size value
+ const scaledFontSize = qwebrScaledFontSize(
+ elements.outputCodeDiv, options);
+
+ // Override output code cell size
+ pre.style.fontSize = `${scaledFontSize}px`;
+ pre.appendChild(div);
+ } else {
+ // If nothing is present, hide the element.
+ pre.style.visibility = "hidden";
+ }
+
+ elements.outputCodeDiv.appendChild(pre);
+
+ // Determine if we have graphs to display
+ if (result.images.length > 0) {
+ // Create figure element
+ const figureElement = document.createElement('figure');
+
+ // Place each rendered graphic onto a canvas element
+ result.images.forEach((img) => {
+ // Construct canvas for object
+ const canvas = document.createElement("canvas");
+
+ // Set canvas size to image
+ canvas.width = img.width;
+ canvas.height = img.height;
+
+ // Apply output truncations
+ canvas.style.width = options["out-width"] ? options["out-width"] : `${fig_width}px`;
+ if (options["out-height"]) {
+ canvas.style.height = options["out-height"];
+ }
+
+ // Apply styling
+ canvas.style.display = "block";
+ canvas.style.margin = "auto";
+
+ // Draw image onto Canvas
+ const ctx = canvas.getContext("2d");
+ ctx.drawImage(img, 0, 0, img.width, img.height);
+
+ // Append canvas to figure output area
+ figureElement.appendChild(canvas);
+ });
+
+ if (options['fig-cap']) {
+ // Create figcaption element
+ const figcaptionElement = document.createElement('figcaption');
+ figcaptionElement.innerText = options['fig-cap'];
+ // Append figcaption to figure
+ figureElement.appendChild(figcaptionElement);
+ }
+
+ elements.outputGraphDiv.appendChild(figureElement);
+ }
+
+ // Display the pager data
+ if (pager) {
+ // Use the `pre` element to preserve whitespace.
+ pager.forEach((paged_data, index) => {
+ let pre_pager = document.createElement("pre");
+ pre_pager.innerText = paged_data;
+ pre_pager.classList.add("qwebr-output-code-pager");
+ pre_pager.setAttribute("id", `qwebr-output-code-pager-editor-${elements.id}-result-${index + 1}`);
+ elements.outputCodeDiv.appendChild(pre_pager);
+ });
+ }
+ } finally {
+ // Clean up the remaining code
+ mainWebRCodeShelter.purge();
+ }
+}
+
+// Function to execute the code (accepts code as an argument)
+globalThis.qwebrExecuteCode = async function (
+ codeToRun,
+ id,
+ options = {}) {
+
+ // If options are not passed, we fall back on the bare minimum to handle the computation
+ if (qwebrIsObjectEmpty(options)) {
+ options = {
+ "context": "interactive",
+ "fig-width": 7, "fig-height": 5,
+ "out-width": "700px", "out-height": "",
+ "dpi": 72,
+ "results": "markup",
+ "warning": "true", "message": "true",
+ };
+ }
+
+ // Next, we access the compute areas values
+ const elements = {
+ runButton: document.getElementById(`qwebr-button-run-${id}`),
+ outputCodeDiv: document.getElementById(`qwebr-output-code-area-${id}`),
+ outputGraphDiv: document.getElementById(`qwebr-output-graph-area-${id}`),
+ id: id,
+ }
+
+ // Disallowing execution of other code cells
+ document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
+ btn.disabled = true;
+ });
+
+ if (options.context == EvalTypes.Interactive) {
+ // Emphasize the active code cell
+ elements.runButton.innerHTML = ' Run Code';
+ }
+
+ // Evaluate the code and parse the output into the document
+ await qwebrComputeEngine(codeToRun, elements, options);
+
+ // Switch to allowing execution of code
+ document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
+ btn.disabled = false;
+ });
+
+ if (options.context == EvalTypes.Interactive) {
+ // Revert to the initial code cell state
+ elements.runButton.innerHTML = ' Run Code';
+ }
+}
diff --git a/book/_extensions/coatless/webr/qwebr-document-engine-initialization.js b/book/_extensions/coatless/webr/qwebr-document-engine-initialization.js
new file mode 100644
index 0000000000..1d447e8bdb
--- /dev/null
+++ b/book/_extensions/coatless/webr/qwebr-document-engine-initialization.js
@@ -0,0 +1,94 @@
+// Function to install a single package
+async function qwebrInstallRPackage(packageName) {
+ await mainWebR.evalRVoid(`webr::install('${packageName}');`);
+}
+
+// Function to load a single package
+async function qwebrLoadRPackage(packageName) {
+ await mainWebR.evalRVoid(`require('${packageName}', quietly = TRUE)`);
+}
+
+// Generic function to process R packages
+async function qwebrProcessRPackagesWithStatus(packages, processType, displayStatusMessageUpdate = true) {
+ // Switch between contexts
+ const messagePrefix = processType === 'install' ? 'Installing' : 'Loading';
+
+ // Modify button state
+ qwebrSetInteractiveButtonState(`🟡 ${messagePrefix} package ...`, false);
+
+ // Iterate over packages
+ for (let i = 0; i < packages.length; i++) {
+ const activePackage = packages[i];
+ const formattedMessage = `${messagePrefix} package ${i + 1} out of ${packages.length}: ${activePackage}`;
+
+ // Display the update in header
+ if (displayStatusMessageUpdate) {
+ qwebrUpdateStatusHeader(formattedMessage);
+ }
+
+ // Display the update in non-active areas
+ qwebrUpdateStatusMessage(formattedMessage);
+
+ // Run package installation
+ if (processType === 'install') {
+ await qwebrInstallRPackage(activePackage);
+ } else {
+ await qwebrLoadRPackage(activePackage);
+ }
+ }
+
+ // Clean slate
+ if (processType === 'load') {
+ await mainWebR.flush();
+ }
+}
+
+// Start a timer
+const initializeWebRTimerStart = performance.now();
+
+// Encase with a dynamic import statement
+globalThis.qwebrInstance = import(qwebrCustomizedWebROptions.baseURL + "webr.mjs").then(
+ async ({ WebR, ChannelType }) => {
+ // Populate WebR options with defaults or new values based on `webr` meta
+ globalThis.mainWebR = new WebR(qwebrCustomizedWebROptions);
+
+ // Initialization WebR
+ await mainWebR.init();
+
+ // Setup a shelter
+ globalThis.mainWebRCodeShelter = await new mainWebR.Shelter();
+
+ // Setup a pager to allow processing help documentation
+ await mainWebR.evalRVoid('webr::pager_install()');
+
+ // Override the existing install.packages() to use webr::install()
+ await mainWebR.evalRVoid('webr::shim_install()');
+
+ // Specify the repositories to pull from
+ // Note: webR does not use the `repos` option, but instead uses `webr_pkg_repos`
+ // inside of `install()`. However, other R functions still pull from `repos`.
+ await mainWebR.evalRVoid(`
+ options(
+ webr_pkg_repos = c(${qwebrPackageRepoURLS.map(repoURL => `'${repoURL}'`).join(',')}),
+ repos = c(${qwebrPackageRepoURLS.map(repoURL => `'${repoURL}'`).join(',')})
+ )
+ `);
+
+ // Check to see if any packages need to be installed
+ if (qwebrSetupRPackages) {
+ // Obtain only a unique list of packages
+ const uniqueRPackageList = Array.from(new Set(qwebrInstallRPackagesList));
+
+ // Install R packages one at a time (either silently or with a status update)
+ await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'install', qwebrShowStartupMessage);
+
+ if (qwebrAutoloadRPackages) {
+ // Load R packages one at a time (either silently or with a status update)
+ await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'load', qwebrShowStartupMessage);
+ }
+ }
+ }
+);
+
+// Stop timer
+const initializeWebRTimerEnd = performance.now();
diff --git a/book/_extensions/coatless/webr/qwebr-document-settings.js b/book/_extensions/coatless/webr/qwebr-document-settings.js
new file mode 100644
index 0000000000..70495cf667
--- /dev/null
+++ b/book/_extensions/coatless/webr/qwebr-document-settings.js
@@ -0,0 +1,26 @@
+// Document level settings ----
+
+// Determine if we need to install R packages
+globalThis.qwebrInstallRPackagesList = [{{INSTALLRPACKAGESLIST}}];
+
+// Specify possible locations to search for the repository
+globalThis.qwebrPackageRepoURLS = [{{RPACKAGEREPOURLS}}];
+
+// Check to see if we have an empty array, if we do set to skip the installation.
+globalThis.qwebrSetupRPackages = !(qwebrInstallRPackagesList.indexOf("") !== -1);
+globalThis.qwebrAutoloadRPackages = {{AUTOLOADRPACKAGES}};
+
+// Display a startup message?
+globalThis.qwebrShowStartupMessage = {{SHOWSTARTUPMESSAGE}};
+globalThis.qwebrShowHeaderMessage = {{SHOWHEADERMESSAGE}};
+
+// Describe the webR settings that should be used
+globalThis.qwebrCustomizedWebROptions = {
+ "baseURL": "{{BASEURL}}",
+ "serviceWorkerUrl": "{{SERVICEWORKERURL}}",
+ "homedir": "{{HOMEDIR}}",
+ "channelType": "{{CHANNELTYPE}}"
+};
+
+// Store cell data
+globalThis.qwebrCellDetails = {{QWEBRCELLDETAILS}};
diff --git a/book/_extensions/coatless/webr/qwebr-document-status.js b/book/_extensions/coatless/webr/qwebr-document-status.js
new file mode 100644
index 0000000000..c13a5f5277
--- /dev/null
+++ b/book/_extensions/coatless/webr/qwebr-document-status.js
@@ -0,0 +1,92 @@
+// Declare startupMessageQWebR globally
+globalThis.qwebrStartupMessage = document.createElement("p");
+
+
+// Function to set the button text
+globalThis.qwebrSetInteractiveButtonState = function(buttonText, enableCodeButton = true) {
+ document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
+ btn.innerHTML = buttonText;
+ btn.disabled = !enableCodeButton;
+ });
+}
+
+// Function to update the status message in non-interactive cells
+globalThis.qwebrUpdateStatusMessage = function(message) {
+ document.querySelectorAll(".qwebr-status-text.qwebr-cell-needs-evaluation").forEach((elem) => {
+ elem.innerText = message;
+ });
+}
+
+// Function to update the status message
+globalThis.qwebrUpdateStatusHeader = function(message) {
+ qwebrStartupMessage.innerHTML = `
+
+ ${message}`;
+}
+
+// Function that attaches the document status message and diagnostics
+function displayStartupMessage(showStartupMessage, showHeaderMessage) {
+ if (!showStartupMessage) {
+ return;
+ }
+
+ // Get references to header elements
+ const headerHTML = document.getElementById("title-block-header");
+ const headerRevealJS = document.getElementById("title-slide");
+
+ // Create the outermost div element for metadata
+ const quartoTitleMeta = document.createElement("div");
+ quartoTitleMeta.classList.add("quarto-title-meta");
+
+ // Create the first inner div element
+ const firstInnerDiv = document.createElement("div");
+ firstInnerDiv.setAttribute("id", "qwebr-status-message-area");
+
+ // Create the second inner div element for "WebR Status" heading and contents
+ const secondInnerDiv = document.createElement("div");
+ secondInnerDiv.setAttribute("id", "qwebr-status-message-title");
+ secondInnerDiv.classList.add("quarto-title-meta-heading");
+ secondInnerDiv.innerText = "WebR Status";
+
+ // Create another inner div for contents
+ const secondInnerDivContents = document.createElement("div");
+ secondInnerDivContents.setAttribute("id", "qwebr-status-message-body");
+ secondInnerDivContents.classList.add("quarto-title-meta-contents");
+
+ // Describe the WebR state
+ qwebrStartupMessage.innerText = "🟡 Loading...";
+ qwebrStartupMessage.setAttribute("id", "qwebr-status-message-text");
+ // Add `aria-live` to auto-announce the startup status to screen readers
+ qwebrStartupMessage.setAttribute("aria-live", "assertive");
+
+ // Append the startup message to the contents
+ secondInnerDivContents.appendChild(qwebrStartupMessage);
+
+ // Add a status indicator for COOP and COEP Headers if needed
+ if (showHeaderMessage) {
+ const crossOriginMessage = document.createElement("p");
+ crossOriginMessage.innerText = `${crossOriginIsolated ? '🟢' : '🟡'} COOP & COEP Headers`;
+ crossOriginMessage.setAttribute("id", "qwebr-coop-coep-header");
+ secondInnerDivContents.appendChild(crossOriginMessage);
+ }
+
+ // Combine the inner divs and contents
+ firstInnerDiv.appendChild(secondInnerDiv);
+ firstInnerDiv.appendChild(secondInnerDivContents);
+ quartoTitleMeta.appendChild(firstInnerDiv);
+
+ // Determine where to insert the quartoTitleMeta element
+ if (headerHTML || headerRevealJS) {
+ // Append to the existing "title-block-header" element or "title-slide" div
+ (headerHTML || headerRevealJS).appendChild(quartoTitleMeta);
+ } else {
+ // If neither headerHTML nor headerRevealJS is found, insert after "webr-monaco-editor-init" script
+ const monacoScript = document.getElementById("qwebr-monaco-editor-init");
+ const header = document.createElement("header");
+ header.setAttribute("id", "title-block-header");
+ header.appendChild(quartoTitleMeta);
+ monacoScript.after(header);
+ }
+}
+
+displayStartupMessage(qwebrShowStartupMessage, qwebrShowHeaderMessage);
\ No newline at end of file
diff --git a/book/_extensions/coatless/webr/qwebr-monaco-editor-element.js b/book/_extensions/coatless/webr/qwebr-monaco-editor-element.js
new file mode 100644
index 0000000000..24552861ed
--- /dev/null
+++ b/book/_extensions/coatless/webr/qwebr-monaco-editor-element.js
@@ -0,0 +1,171 @@
+// Global array to store Monaco Editor instances
+globalThis.qwebrEditorInstances = [];
+
+// Function that builds and registers a Monaco Editor instance
+globalThis.qwebrCreateMonacoEditorInstance = function (cellData) {
+
+ const initialCode = cellData.code;
+ const qwebrCounter = cellData.id;
+ const qwebrOptions = cellData.options;
+
+ // Retrieve the previously created document elements
+ let runButton = document.getElementById(`qwebr-button-run-${qwebrCounter}`);
+ let resetButton = document.getElementById(`qwebr-button-reset-${qwebrCounter}`);
+ let copyButton = document.getElementById(`qwebr-button-copy-${qwebrCounter}`);
+ let editorDiv = document.getElementById(`qwebr-editor-${qwebrCounter}`);
+
+ // Load the Monaco Editor and create an instance
+ let editor;
+ require(['vs/editor/editor.main'], function () {
+ editor = monaco.editor.create(editorDiv, {
+ value: initialCode,
+ language: 'r',
+ theme: 'vs-light',
+ automaticLayout: true, // Works wonderfully with RevealJS
+ scrollBeyondLastLine: false,
+ minimap: {
+ enabled: false
+ },
+ fontSize: qwebrScaledFontSize(editorDiv, qwebrOptions),
+ renderLineHighlight: "none", // Disable current line highlighting
+ hideCursorInOverviewRuler: true, // Remove cursor indictor in right hand side scroll bar
+ readOnly: qwebrOptions['read-only'] ?? false,
+ quickSuggestions: qwebrOptions['editor-quick-suggestions'] ?? false
+ });
+
+ // Store the official counter ID to be used in keyboard shortcuts
+ editor.__qwebrCounter = qwebrCounter;
+
+ // Store the official div container ID
+ editor.__qwebrEditorId = `qwebr-editor-${qwebrCounter}`;
+
+ // Store the initial code value and options
+ editor.__qwebrinitialCode = initialCode;
+ editor.__qwebrOptions = qwebrOptions;
+
+ // Set at the model level the preferred end of line (EOL) character to LF.
+ // This prevent `\r\n` from being given to the webR engine if the user is on Windows.
+ // See details in: https://github.com/coatless/quarto-webr/issues/94
+ // Associated error text:
+ // Error: v
z@l=6!1--#SpFgPGvAj&Qtj5ymOzN;`Fqrh|#lvgL^)L8yI_}}QtvZwNxOB9ts!8jJ
z*5&@<(zrXoy(ctNPp|a&$7F(T_uC$Sm^+7MaTnkgG^w_rI2;eT13n$}c*bdW(C5+C
z`07M?EdOq|t3tjcORMI`>g2aeyQoETFNia{FQ% zW-u|d3P@&hc>E#=SH~*FfpRhs& WoG0Z
zGyjwW%vFZ%Q6!LLh4o}{=;S9HTQ0{bli#*q}pXjreiuUU~>7_LVhF31?rE3%lwyURSuVrlGAe>-AJtH+59_
zz5aChc&{tu8DBcy@8u(D9od-gD7VY+cLzE5ZYz~7Nb8D{5}%)UKHYgPJ^bYNEuMH=
z%bA$#@mHHCcRw<^ms|f2%H9J^lCwM)oxgIf>Z
FJ)H?w+(Wvoo_fXrr{d
z+SLZFw9+b}1VTcvN(2Rj0276gNCF#yh!(nFRtK)Zfp9MzT?cH$!9K=*IOl?m!M=vp
zeZT4%X(f2h^PHaP?&|953V;3o7vAsvzMos*xK*D0imN1&5l7JLennuv;e@oEP__EV
zWIdUZ5fn7jz{BuRb7KqjB)%GrMkNbFS>oZVr@d;-HKDz*1$N0W)PW6U@P_|X_!{MF
zF;iCx?zkq#14ef93(o@A`Pz^Lk8J_tkOoOeRVa}~gO);^CuDe&W#dD)Q73D!Ln(8x
zaiD@r>rl?W?DTWmsbDEMY3KZ1y1vWL*(dGcVf&PpkL}bR$j=7BY(5&
!apwhsP
zDHGp^OHNVeUM!5W#6s|nbS({gQq&0T5ih-3D^^QT)L}kAm+t~rqaT*mVoPCYspt2A
z>C$RK2D)Ach%U)U+>lmyitL?r3Q60u7uggw}pc7-!QFzGo_xMXt5
ze*K{Yj|2t2&VV*RFW2El144lAKui*UJUjLF*o`LyWdvVf1wD9HjsX}h3kYSylgPP|
zQjYsW5;52Yv<
PG@LRbATX*LNq!l60#R^5g5fIRo1ZcPwB5VtT(Aa)S*V8)
z&BBBGH&_j5SM0^727?s}nl+1b$9WcvB64VDVU(Jpry+?T4jZ%%D+a*
i9i$;V$)KWCr=Q
z*d(Yz3j#VD(nu%$1z=2AD#ciLmn>~rZY9i?dPw8thzaL7$cX#_vz0Vj@oL^<(zltX
z#VpSN>&=a%Y((z^BjNa#DeEz>;N;o2+