diff --git a/_extensions/webr/_extension.yml b/_extensions/webr/_extension.yml
index 51da7f46..6139b24c 100644
--- a/_extensions/webr/_extension.yml
+++ b/_extensions/webr/_extension.yml
@@ -1,7 +1,7 @@
name: webr
title: Embedded webr code cells
author: James Joseph Balamuta
-version: 0.4.0-dev.3
+version: 0.4.0-dev.4
quarto-required: ">=1.2.198"
contributes:
filters:
diff --git a/_extensions/webr/qwebr-cell-elements.js b/_extensions/webr/qwebr-cell-elements.js
new file mode 100644
index 00000000..df41e26e
--- /dev/null
+++ b/_extensions/webr/qwebr-cell-elements.js
@@ -0,0 +1,115 @@
+// Function that dispatches the creation request
+globalThis.qwebrCreateHTMLElement = function (insertElement,
+ qwebrCounter,
+ evalType = EvalTypes.Interactive,
+ options = {}) {
+
+ // Figure out the routine to use to insert the element.
+ let qwebrElement;
+ switch ( evalType ) {
+ case EvalTypes.Interactive:
+ qwebrElement = qwebrCreateInteractiveElement(qwebrCounter);
+ case EvalTypes.Output:
+ qwebrElement = qwebrCreateNonInteractiveOutputElement(qwebrCounter);
+ case EvalTypes.Setup:
+ qwebrElement = qwebrCreateNonInteractiveSetupElement(qwebrCounter);
+ default:
+ qwebrElement = document.createElement('div');
+ qwebrElement.textContent = 'Error creating element';
+ }
+
+ // Insert the dynamically generated object at the document location.
+ insertElement.appendChild(qwebrElement);
+};
+
+// Function that setups the interactive element creation
+globalThis.qwebrCreateInteractiveElement = function (qwebrCounter) {
+
+ // Create main div element
+ var mainDiv = document.createElement('div');
+ mainDiv.id = 'qwebr-interactive-area-' + qwebrCounter;
+ mainDiv.className = 'qwebr-interactive-area';
+
+ // Create button element
+ var button = document.createElement('button');
+ button.className = 'btn btn-default qwebr-button-run';
+ button.disabled = true;
+ button.type = 'button';
+ button.id = 'qwebr-button-run-' + qwebrCounter;
+ button.textContent = '🟡 Loading webR...';
+
+ // Create console area div
+ var consoleAreaDiv = document.createElement('div');
+ consoleAreaDiv.id = 'qwebr-console-area-' + qwebrCounter;
+ consoleAreaDiv.className = 'qwebr-console-area';
+
+ // Create editor div
+ var editorDiv = document.createElement('div');
+ editorDiv.id = 'qwebr-editor-' + qwebrCounter;
+ editorDiv.className = 'qwebr-editor';
+
+ // Create output code area div
+ var outputCodeAreaDiv = document.createElement('div');
+ outputCodeAreaDiv.id = 'qwebr-output-code-area-' + qwebrCounter;
+ outputCodeAreaDiv.className = 'qwebr-output-code-area';
+ outputCodeAreaDiv.setAttribute('aria-live', 'assertive');
+
+ // Create pre element inside output code area
+ var preElement = document.createElement('pre');
+ preElement.style.visibility = 'hidden';
+ outputCodeAreaDiv.appendChild(preElement);
+
+ // Create output graph area div
+ var outputGraphAreaDiv = document.createElement('div');
+ outputGraphAreaDiv.id = 'qwebr-output-graph-area-' + qwebrCounter;
+ outputGraphAreaDiv.className = 'qwebr-output-graph-area';
+
+ // Append all elements to the main div
+ mainDiv.appendChild(button);
+ consoleAreaDiv.appendChild(editorDiv);
+ consoleAreaDiv.appendChild(outputCodeAreaDiv);
+ mainDiv.appendChild(consoleAreaDiv);
+ mainDiv.appendChild(outputGraphAreaDiv);
+
+ return mainDiv;
+}
+
+// Function that adds output structure for non-interactive output
+globalThis.qwebrCreateNonInteractiveOutputElement = function(qwebrCounter) {
+ // Create main div element
+ var mainDiv = document.createElement('div');
+ mainDiv.id = 'qwebr-noninteractive-area-' + qwebrCounter;
+ mainDiv.className = 'qwebr-noninteractive-area';
+
+ // Create output code area div
+ var outputCodeAreaDiv = document.createElement('div');
+ outputCodeAreaDiv.id = 'qwebr-output-code-area-' + qwebrCounter;
+ outputCodeAreaDiv.className = 'qwebr-output-code-area';
+ outputCodeAreaDiv.setAttribute('aria-live', 'assertive');
+
+ // Create pre element inside output code area
+ var preElement = document.createElement('pre');
+ preElement.style.visibility = 'hidden';
+ outputCodeAreaDiv.appendChild(preElement);
+
+ // Create output graph area div
+ var outputGraphAreaDiv = document.createElement('div');
+ outputGraphAreaDiv.id = 'qwebr-output-graph-area-' + qwebrCounter;
+ outputGraphAreaDiv.className = 'qwebr-output-graph-area';
+
+ // Append all elements to the main div
+ mainDiv.appendChild(outputCodeAreaDiv);
+ mainDiv.appendChild(outputGraphAreaDiv);
+
+ return mainDiv;
+};
+
+// Function that adds a stub in the page to indicate a setup cell was used.
+globalThis.qwebrCreateNonInteractiveSetupElement = function(qwebrCounter) {
+ // Create main div element
+ var mainDiv = document.createElement('div');
+ mainDiv.id = 'qwebr-noninteractive-setup-area-' + qwebrCounter;
+ mainDiv.className = 'qwebr-noninteractive-setup-area';
+
+ return mainDiv;
+}
\ No newline at end of file
diff --git a/_extensions/webr/qwebr-compute-engine.js b/_extensions/webr/qwebr-compute-engine.js
new file mode 100644
index 00000000..173c2e6f
--- /dev/null
+++ b/_extensions/webr/qwebr-compute-engine.js
@@ -0,0 +1,210 @@
+// Supported Evaluation Types for Context
+globalThis.EvalTypes = Object.freeze({
+ Interactive: 'interactive',
+ Setup: 'setup',
+ Output: 'output',
+});
+
+// Function to verify a given JavaScript Object is empty
+globalThis.qwebrIsObjectEmpty = function (arr) {
+ return Object.keys(arr).length === 0;
+}
+
+// Function to parse the pager results
+globalThis.qwebrParseTypePager = async function (msg) {
+
+ // Split out the event data
+ const { path, title, deleteFile } = msg.data;
+
+ // Process the pager data by reading the information from disk
+ const paged_data = await webR.FS.readFile(path).then((data) => {
+ // Obtain the file content
+ let content = new TextDecoder().decode(data);
+
+ // Remove excessive backspace characters until none remain
+ while(content.match(/.[\b]/)){
+ content = content.replace(/.[\b]/g, '');
+ }
+
+ // Returned cleaned data
+ return content;
+ });
+
+ // Unlink file if needed
+ if (deleteFile) {
+ await webR.FS.unlink(path);
+ }
+
+ // Return extracted data with spaces
+ return paged_data;
+}
+
+// Function to run the code using webR and parse the output
+globalThis.qwebrComputeEngine = async function(
+ codeToRun,
+ elements,
+ options) {
+
+ // Call into the R compute engine that persists within the document scope.
+ // To be prepared for all scenarios, the following happens:
+ // 1. We setup a canvas device to write to by making a namespace call into the {webr} package
+ // 2. We use values inside of the options array to set the figure size.
+ // 3. We capture the output stream information (STDOUT and STERR)
+ // 4. While parsing the results, we disable image creation.
+
+ // Create a canvas variable for graphics
+ let canvas = undefined;
+
+ // Create a pager variable for help/file contents
+ let pager = [];
+
+ // ----
+
+ // Initialize webR
+ await webR.init();
+
+ // Setup a webR canvas by making a namespace call into the {webr} package
+ await webR.evalRVoid(`webr::canvas(width=${options["fig-width"]}, height=${options["fig-height"]})`);
+
+ const result = await webRCodeShelter.captureR(codeToRun, {
+ withAutoprint: true,
+ captureStreams: true,
+ captureConditions: false//,
+ // env: webR.objs.emptyEnv, // maintain a global environment for webR v0.2.0
+ });
+
+ // -----
+
+ // Start attempting to parse the result data
+ try {
+
+ // Stop creating images
+ await webR.evalRVoid("dev.off()");
+
+ // Merge output streams of STDOUT and STDErr (messages and errors are combined.)
+ const out = result.output
+ .filter(evt => evt.type === "stdout" || evt.type === "stderr")
+ .map((evt, index) => {
+ const className = `qwebr-output-code-${evt.type}`;
+ return `${qwebrEscapeHTMLCharacters(evt.data)}
`;
+ })
+ .join("\n");
+
+
+ // Clean the state
+ // We're now able to process both graphics and pager events.
+ // As a result, we cannot maintain a true 1-to-1 output order
+ // without individually feeding each line
+ const msgs = await webR.flush();
+
+ // Output each image event stored
+ msgs.forEach((msg) => {
+ // Determine if old canvas can be used or a new canvas is required.
+ if (msg.type === 'canvas'){
+ // Add image to the current canvas
+ if (msg.data.event === 'canvasImage') {
+ canvas.getContext('2d').drawImage(msg.data.image, 0, 0);
+ } else if (msg.data.event === 'canvasNewPage') {
+ // Generate a new canvas element
+ canvas = document.createElement("canvas");
+ canvas.setAttribute("width", 2 * options["fig-width"]);
+ canvas.setAttribute("height", 2 * options["fig-height"]);
+ canvas.style.width = "700px";
+ canvas.style.display = "block";
+ canvas.style.margin = "auto";
+ }
+ }
+ });
+
+ // 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;
+ pre.appendChild(div);
+ } else {
+ // If nothing is present, hide the element.
+ pre.style.visibility = "hidden";
+ }
+
+ elements.outputCodeDiv.appendChild(pre);
+
+ // Place the graphics on the canvas
+ if (canvas) {
+ elements.outputGraphDiv.appendChild(canvas);
+ }
+
+ // 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
+ webRCodeShelter.purge();
+ }
+}
+
+// Function to execute the code (accepts code as an argument)
+globalThis.qwebrExecuteCode = async function (
+ codeToRun,
+ id,
+ evalType = EvalTypes.Interactive,
+ options = {}) {
+
+ // If options are not passed, we fall back on the bare minimum to handle the computation
+ if (qwebrIsObjectEmpty(options)) {
+ options = { "fig-width": 504, "fig-height": 360 };
+ }
+
+ // 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 (evalType == 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 (evalType == EvalTypes.Interactive) {
+ // Revert to the initial code cell state
+ elements.runButton.innerHTML = ' Run Code';
+ }
+}
diff --git a/_extensions/webr/qwebr-init.js b/_extensions/webr/qwebr-init.js
new file mode 100644
index 00000000..533fb468
--- /dev/null
+++ b/_extensions/webr/qwebr-init.js
@@ -0,0 +1,193 @@
+// Start a timer
+const initializeWebRTimerStart = performance.now();
+
+// Determine if we need to install R packages
+var installRPackagesList = [{{INSTALLRPACKAGESLIST}}];
+// Check to see if we have an empty array, if we do set to skip the installation.
+var setupRPackages = !(installRPackagesList.indexOf("") !== -1);
+var autoloadRPackages = {{AUTOLOADRPACKAGES}};
+
+// Display a startup message?
+var showStartupMessage = {{SHOWSTARTUPMESSAGE}};
+var showHeaderMessage = {{SHOWHEADERMESSAGE}};
+if (showStartupMessage) {
+
+ // 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
+ var startupMessageWebR = document.createElement("p");
+ startupMessageWebR.innerText = "🟡 Loading...";
+ startupMessageWebR.setAttribute("id", "qwebr-status-message-text");
+ // Add `aria-live` to auto-announce the startup status to screen readers
+ startupMessageWebR.setAttribute("aria-live", "assertive");
+
+ // Append the startup message to the contents
+ secondInnerDivContents.appendChild(startupMessageWebR);
+
+ // 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) {
+ // Append to the existing "title-block-header" element
+ headerHTML.appendChild(quartoTitleMeta);
+ } else if (headerRevealJS) {
+ // If using RevealJS, add to the "title-slide" div
+ headerRevealJS.appendChild(firstInnerDiv);
+ } 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);
+ }
+}
+
+// Retrieve the webr.mjs
+import { WebR, ChannelType } from "{{BASEURL}}webr.mjs";
+
+// Populate WebR options with defaults or new values based on
+// webr meta
+globalThis.webR = new WebR({
+ "baseURL": "{{BASEURL}}",
+ "serviceWorkerUrl": "{{SERVICEWORKERURL}}",
+ "homedir": "{{HOMEDIR}}",
+ "channelType": {{CHANNELTYPE}}
+});
+
+// Initialization WebR
+await webR.init();
+
+// Setup a shelter
+globalThis.webRCodeShelter = await new webR.Shelter();
+
+// Setup a pager to allow processing help documentation
+await webR.evalRVoid('webr::pager_install()');
+
+// Function to set the button text
+function qwebrSetInteractiveButtonState(buttonText, enableCodeButton = true) {
+ document.querySelectorAll(".qwebr-button-run").forEach((btn) => {
+ btn.innerHTML = buttonText;
+ btn.disabled = !enableCodeButton;
+ });
+}
+
+// Function to update the status message
+function qwebrUpdateStatusHeader(message) {
+ startupMessageWebR.innerHTML = `
+
+ ${message}`;
+}
+
+// Function to install a single package
+async function qwebrInstallRPackage(packageName) {
+ await globalThis.webR.installPackages([packageName]);
+}
+
+// Function to load a single package
+async function qwebrLoadRPackage(packageName) {
+ await globalThis.webR.evalRVoid(`library(${packageName});`);
+}
+
+// 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
+ if (displayStatusMessageUpdate) {
+ qwebrUpdateStatusHeader(formattedMessage);
+ }
+
+ // Run package installation
+ if (processType === 'install') {
+ await qwebrInstallRPackage(activePackage);
+ } else {
+ await qwebrLoadRPackage(activePackage);
+ }
+ }
+
+ // Clean slate
+ if (processType === 'load') {
+ await globalThis.webR.flush();
+ }
+}
+
+
+// Check to see if any packages need to be installed
+if (setupRPackages) {
+ // Obtain only a unique list of packages
+ const uniqueRPackageList = Array.from(new Set(installRPackagesList));
+
+ // Install R packages one at a time (either silently or with a status update)
+ await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'install', showStartupMessage);
+
+ if(autoloadRPackages) {
+ // Load R packages one at a time (either silently or with a status update)
+ await qwebrProcessRPackagesWithStatus(uniqueRPackageList, 'load', showStartupMessage);
+ }
+}
+
+// Stop timer
+const initializeWebRTimerEnd = performance.now();
+
+// Release document status as ready
+if (showStartupMessage) {
+ startupMessageWebR.innerText = "🟢 Ready!"
+}
+
+qwebrSetInteractiveButtonState(
+ ` Run Code`,
+ true
+);
+
+// Global version of the Escape HTML function that converts HTML
+// characters to their HTML entities.
+globalThis.qwebrEscapeHTMLCharacters = function(unsafe) {
+ return unsafe
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+};
\ No newline at end of file
diff --git a/_extensions/webr/qwebr-monaco-editor-element.js b/_extensions/webr/qwebr-monaco-editor-element.js
new file mode 100644
index 00000000..21c4f124
--- /dev/null
+++ b/_extensions/webr/qwebr-monaco-editor-element.js
@@ -0,0 +1,132 @@
+// Global dictionary to store Monaco Editor instances
+const qwebrEditorInstances = {};
+
+// Function that builds and registers a Monaco Editor instance
+globalThis.qwebrCreateMonacoEditorInstance = function (
+ initialCode,
+ qwebrCounter) {
+
+ // Retrieve the previously created document elements
+ let runButton = document.getElementById(`qwebr-button-run-${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: '17.5pt', // Bootstrap is 1 rem
+ renderLineHighlight: "none", // Disable current line highlighting
+ hideCursorInOverviewRuler: true // Remove cursor indictor in right hand side scroll bar
+ });
+
+ // 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
+ editor.__qwebrinitialCode = initialCode;
+
+ // Dynamically modify the height of the editor window if new lines are added.
+ let ignoreEvent = false;
+ const updateHeight = () => {
+ const contentHeight = editor.getContentHeight();
+ // We're avoiding a width change
+ //editorDiv.style.width = `${width}px`;
+ editorDiv.style.height = `${contentHeight}px`;
+ try {
+ ignoreEvent = true;
+
+ // The key to resizing is this call
+ editor.layout();
+ } finally {
+ ignoreEvent = false;
+ }
+ };
+
+ // Helper function to check if selected text is empty
+ function isEmptyCodeText(selectedCodeText) {
+ return (selectedCodeText === null || selectedCodeText === undefined || selectedCodeText === "");
+ }
+
+ // Registry of keyboard shortcuts that should be re-added to each editor window
+ // when focus changes.
+ const addWebRKeyboardShortCutCommands = () => {
+ // Add a keydown event listener for Shift+Enter to run all code in cell
+ editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => {
+
+ // Retrieve all text inside the editor
+ qwebrExecuteCode(editor.getValue(), editor.__qwebrCounter);
+ });
+
+ // Add a keydown event listener for CMD/Ctrl+Enter to run selected code
+ editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
+
+ // Get the selected text from the editor
+ const selectedText = editor.getModel().getValueInRange(editor.getSelection());
+ // Check if no code is selected
+ if (isEmptyCodeText(selectedText)) {
+ // Obtain the current cursor position
+ let currentPosition = editor.getPosition();
+ // Retrieve the current line content
+ let currentLine = editor.getModel().getLineContent(currentPosition.lineNumber);
+
+ // Propose a new position to move the cursor to
+ let newPosition = new monaco.Position(currentPosition.lineNumber + 1, 1);
+
+ // Check if the new position is beyond the last line of the editor
+ if (newPosition.lineNumber > editor.getModel().getLineCount()) {
+ // Add a new line at the end of the editor
+ editor.executeEdits("addNewLine", [{
+ range: new monaco.Range(newPosition.lineNumber, 1, newPosition.lineNumber, 1),
+ text: "\n",
+ forceMoveMarkers: true,
+ }]);
+ }
+
+ // Run the entire line of code.
+ qwebrExecuteCode(currentLine, editor.__qwebrCounter,
+ EvalTypes.Interactive);
+
+ // Move cursor to new position
+ editor.setPosition(newPosition);
+ } else {
+ // Code to run when Ctrl+Enter is pressed with selected code
+ qwebrExecuteCode(selectedText, editor.__qwebrCounter, EvalTypes.Interactive);
+ }
+ });
+ }
+
+ // Register an on focus event handler for when a code cell is selected to update
+ // what keyboard shortcut commands should work.
+ // This is a workaround to fix a regression that happened with multiple
+ // editor windows since Monaco 0.32.0
+ // https://github.com/microsoft/monaco-editor/issues/2947
+ editor.onDidFocusEditorText(addWebRKeyboardShortCutCommands);
+
+ // Register an on change event for when new code is added to the editor window
+ editor.onDidContentSizeChange(updateHeight);
+
+ // Manually re-update height to account for the content we inserted into the call
+ updateHeight();
+
+ // Store the editor instance in the global dictionary
+ qwebrEditorInstances[editor.__qwebrCounter] = editor;
+
+ });
+
+ // Add a click event listener to the run button
+ runButton.onclick = function () {
+ qwebrExecuteCode(editor.getValue(), editor.__qwebrCounter, EvalTypes.Interactive);
+ };
+
+}
\ No newline at end of file
diff --git a/_extensions/webr/qwebr-styling.css b/_extensions/webr/qwebr-styling.css
new file mode 100644
index 00000000..f9d7ea72
--- /dev/null
+++ b/_extensions/webr/qwebr-styling.css
@@ -0,0 +1,104 @@
+.monaco-editor pre {
+ background-color: unset !important;
+}
+
+.qwebr-icon-status-spinner {
+ color: #7894c4;
+}
+
+.qwebr-icon-run-code {
+ color: #0d9c29
+}
+
+.qwebr-output-code-stdout {
+ color: #111;
+}
+
+.qwebr-output-code-stderr {
+ color: #db4133;
+}
+
+.qwebr-editor {
+ border: 1px solid #EEEEEE;
+}
+
+.qwebr-button-run {
+ background-color: #EEEEEE;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0; /* Extra styling for consistency */
+ display: inline-block;
+ font-weight: 400;
+ line-height: 1.5;
+ color: #000;
+ text-align: center;
+ text-decoration: none;
+ -webkit-text-decoration: none;
+ -moz-text-decoration: none;
+ -ms-text-decoration: none;
+ -o-text-decoration: none;
+ /* vertical-align: middle; */ /* Prevents a space from appearing between the code cell and button */
+ -webkit-user-select: none;
+ border-color: #dee2e6;
+ border: 1px solid rgba(0,0,0,0);
+ padding: 0.375rem 0.75rem;
+ font-size: 1rem;
+ border-top-right-radius: 0.25rem;
+ border-top-left-radius: 0.25rem;
+ transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
+}
+
+.qwebr-button-run:hover {
+ color: #000;
+ background-color: #e3e6ea;
+ border-color: #e1e5e9;
+}
+
+.qwebr-button-run:disabled,.qwebr-button-run.disabled,fieldset:disabled .qwebr-button-run {
+ pointer-events: none;
+ opacity: .65
+}
+
+/* Custom styling for RevealJS Presentations*/
+
+/* Reset the style of the interactive area */
+.reveal div.qwebr-interactive-area {
+ display: block;
+ box-shadow: none;
+ max-width: 100%;
+ max-height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+/* Provide space to entries */
+.reveal div.qwebr-output-code-area pre div {
+ margin: 1px 2px 1px 10px;
+}
+
+/* Collapse the inside code tags to avoid extra space between line outputs */
+.reveal pre div code.qwebr-output-code-stdout, .reveal pre div code.qwebr-output-code-stderr {
+ padding: 0;
+ display: contents;
+}
+
+.reveal pre div code.qwebr-output-code-stdout {
+ color: #111;
+}
+
+.reveal pre div code.qwebr-output-code-stderr {
+ color: #db4133;
+}
+
+
+/* Create a border around console and output (does not effect graphs) */
+.reveal div.qwebr-console-area {
+ border: 1px solid #EEEEEE;
+ box-shadow: 2px 2px 10px #EEEEEE;
+}
+
+/* Cap output height and allow text to scroll */
+/* TODO: Is there a better way to fit contents/max it parallel to the monaco editor size? */
+.reveal div.qwebr-output-code-area pre {
+ max-height: 400px;
+ overflow: scroll;
+}
diff --git a/_extensions/webr/webr-context-interactive.html b/_extensions/webr/webr-context-interactive.html
index 55f91b3a..63d8d2bf 100644
--- a/_extensions/webr/webr-context-interactive.html
+++ b/_extensions/webr/webr-context-interactive.html
@@ -1,278 +1,19 @@
-