Add codesandbox
This commit is contained in:
@@ -0,0 +1,462 @@
|
||||
<template>
|
||||
<div class="code-sandbox" :style="sandboxStyle">
|
||||
<div class="editor-container" :style="editorContainerStyle">
|
||||
<v-ace-editor
|
||||
v-model:value="code"
|
||||
:style="editorStyle"
|
||||
lang="javascript"
|
||||
:theme="isDark ? 'github_dark' : 'github'"
|
||||
@init="editorInit"
|
||||
:options="{
|
||||
showGutter: true,
|
||||
minLines: 1,
|
||||
maxLines: props.fullHeight ? null : 20,
|
||||
showPrintMargin: false
|
||||
}"
|
||||
/>
|
||||
<div class="editor-buttons">
|
||||
<button v-if="!hideRunButton" @click="executeCode" class="primary-button">Run Code</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showLogs || fullHeight" class="output-container" :style="outputContainerStyle">
|
||||
<div class="output-container-buttons">
|
||||
<button v-if="!fullHeight" @click="showLogs = false" class="secondary-button">Close</button>
|
||||
</div>
|
||||
<div class="console-output" :style="consoleOutputStyle">
|
||||
<div v-for="(log, index) in logs" :key="index" :class="['log-entry', log.type]">
|
||||
<span class="log-prefix">[{{ log.type }}]:</span> {{ log.content }}
|
||||
</div>
|
||||
<div v-if="logs.length === 0" class="empty-state">
|
||||
No console output yet. Run your code to see results here.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="iframe-container" style="display:none">
|
||||
<iframe
|
||||
ref="sandboxIframe"
|
||||
sandbox="allow-scripts allow-same-origin allow-modals allow-forms allow-popups"
|
||||
allow="accelerometer; camera; encrypted-media; geolocation; gyroscope; microphone; midi"
|
||||
class="sandbox-iframe"
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
<!-- Hidden slot to capture the default code -->
|
||||
<!-- We need this because we want to place our code in slots -->
|
||||
<div ref="codeRef" style="display: none;">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useData } from 'vitepress';
|
||||
import { ref, onMounted, onBeforeUnmount, computed } from 'vue';
|
||||
import { VAceEditor } from 'vue3-ace-editor';
|
||||
import ace from 'ace-builds';
|
||||
import 'ace-builds/src-noconflict/theme-github';
|
||||
import 'ace-builds/src-noconflict/theme-github_dark';
|
||||
import 'ace-builds/src-noconflict/mode-javascript';
|
||||
|
||||
// Import the iframe HTML template
|
||||
// NOTE: Having this in an external file is many-fold neater than stashing it all in here.
|
||||
import iframeTemplateHtml from './CodeSandboxTemplate.html?raw';
|
||||
|
||||
const { isDark } = useData();
|
||||
|
||||
// Define props for the component
|
||||
const props = defineProps({
|
||||
enableNetwork: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
fullHeight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
editorHeight: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
outputHeight: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
hideRunButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
});
|
||||
|
||||
// Define types for console logs
|
||||
interface ConsoleLog {
|
||||
type: 'log' | 'info' | 'warn' | 'error';
|
||||
content: string;
|
||||
}
|
||||
|
||||
// Define share URL base
|
||||
const SHARE_BASE_URL = 'https://stack.xo.cash/playground.html';
|
||||
|
||||
// Define a unique ID to pass into the IFrame.
|
||||
// NOTE: We do this so that we can reliably identify where the postMessage came from.
|
||||
// If we do not do this and have multiple instances of CodeSandbox, then they will cross-chatter.
|
||||
const thisSandboxID = generateSandboxId();
|
||||
|
||||
// State
|
||||
const codeRef = ref(null);
|
||||
const code = ref<string>('');
|
||||
const logs = ref<ConsoleLog[]>([]);
|
||||
const theme = ref('github_dark');
|
||||
const showLogs = ref(false);
|
||||
const sandboxIframe = ref<HTMLIFrameElement | null>(null);
|
||||
|
||||
// Computed styles based on props
|
||||
const sandboxStyle = computed(() => ({
|
||||
height: props.fullHeight ? '100%' : props.height,
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}));
|
||||
|
||||
const editorContainerStyle = computed(() => ({
|
||||
height: props.fullHeight ? props.editorHeight !== 'auto' ? props.editorHeight : '50%' : 'auto',
|
||||
flex: props.fullHeight && props.editorHeight === 'auto' ? '1 0 auto' : 'none',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
overflow: 'hidden' // Ensure overflowing content doesn't spill out
|
||||
}));
|
||||
|
||||
const editorStyle = computed(() => ({
|
||||
width: '100%',
|
||||
height: '100%', // Fill container height
|
||||
minHeight: props.fullHeight ? '0' : '3em'
|
||||
}));
|
||||
|
||||
const outputContainerStyle = computed(() => ({
|
||||
height: props.fullHeight ? props.outputHeight !== 'auto' ? props.outputHeight : 'auto' : 'auto',
|
||||
flex: props.fullHeight && props.outputHeight === 'auto' ? '1 0 auto' : 'none',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}));
|
||||
|
||||
const consoleOutputStyle = computed(() => ({
|
||||
flex: props.fullHeight ? '1 1 auto' : 'none',
|
||||
minHeight: props.fullHeight ? '0' : '120px',
|
||||
maxHeight: props.fullHeight ? 'none' : '250px'
|
||||
}));
|
||||
|
||||
// Methods
|
||||
const clearLogs = () => {
|
||||
logs.value = [];
|
||||
};
|
||||
|
||||
function editorInit(editor: any) {
|
||||
editor.session.setTabSize(2);
|
||||
editor.renderer.setScrollMargin(5, 5, 0, 0);
|
||||
|
||||
// Make sure the editor properly reflects its container size
|
||||
editor.renderer.updateFull();
|
||||
}
|
||||
|
||||
// Generate a shareable URL with the current code
|
||||
const shareCode = () => {
|
||||
try {
|
||||
// Convert the code to Base64 to make it more compact
|
||||
// First, we get a UTF-8 string, then encode to Base64
|
||||
const base64Code = btoa(unescape(encodeURIComponent(code.value)));
|
||||
|
||||
// Generate the shareable URL with the Base64 encoded content in the hash fragment
|
||||
const shareUrl = `${SHARE_BASE_URL}#code=${encodeURIComponent(base64Code)}`;
|
||||
|
||||
// Check URL length - hash fragments can be longer, but still have limits
|
||||
if (shareUrl.length > 10000) {
|
||||
logs.value.push({
|
||||
type: 'warn',
|
||||
content: `URL is ${shareUrl.length} characters long, which is extremely long and may not work in some environments. Consider shortening your code.`
|
||||
});
|
||||
} else if (shareUrl.length > 4000) {
|
||||
logs.value.push({
|
||||
type: 'info',
|
||||
content: `URL is ${shareUrl.length} characters long. While this should work in most modern browsers, it might be problematic in some environments.`
|
||||
});
|
||||
}
|
||||
|
||||
// Copy to clipboard
|
||||
navigator.clipboard.writeText(shareUrl)
|
||||
.then(() => {
|
||||
logs.value.push({
|
||||
type: 'info',
|
||||
content: 'Shareable URL copied to clipboard!'
|
||||
});
|
||||
showLogs.value = true;
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Failed to copy URL: ', err);
|
||||
logs.value.push({
|
||||
type: 'error',
|
||||
content: 'Failed to copy URL to clipboard. URL is: ' + shareUrl
|
||||
});
|
||||
showLogs.value = true;
|
||||
});
|
||||
} catch (error) {
|
||||
logs.value.push({
|
||||
type: 'error',
|
||||
content: 'Failed to generate shareable URL: ' + error
|
||||
});
|
||||
showLogs.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Create HTML content for the iframe by replacing the placeholder with user code
|
||||
const createIframeContent = (sandboxID: string, userCode: string): string => {
|
||||
return iframeTemplateHtml.replace('__userCode__;', userCode).replaceAll('__sandboxID__', sandboxID);
|
||||
};
|
||||
|
||||
function generateSandboxId(length: number = 6): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
|
||||
}
|
||||
|
||||
// Execute the code in the iframe
|
||||
const executeCode = () => {
|
||||
clearLogs();
|
||||
|
||||
showLogs.value = true;
|
||||
|
||||
if (sandboxIframe.value) {
|
||||
// Add an allow-scripts attribute dynamically if network is enabled
|
||||
if (props.enableNetwork) {
|
||||
sandboxIframe.value.sandbox.add('allow-scripts');
|
||||
sandboxIframe.value.sandbox.add('allow-same-origin');
|
||||
sandboxIframe.value.sandbox.add('allow-modals');
|
||||
sandboxIframe.value.sandbox.add('allow-forms');
|
||||
sandboxIframe.value.sandbox.add('allow-popups');
|
||||
}
|
||||
|
||||
// Create the HTML content
|
||||
const iframeContent = createIframeContent(thisSandboxID, code.value);
|
||||
|
||||
// Create a Blob containing the HTML content
|
||||
const blob = new Blob([iframeContent], { type: 'text/html' });
|
||||
|
||||
// Create a URL for the Blob
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
// Set the iframe's src to the Blob URL
|
||||
sandboxIframe.value.src = blobUrl;
|
||||
|
||||
// Clean up the Blob URL after the iframe has loaded
|
||||
sandboxIframe.value.onload = () => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
|
||||
// Log info about module support
|
||||
if (props.enableNetwork) {
|
||||
logs.value.push({
|
||||
type: 'info',
|
||||
content: 'ESM imports enabled - you can use external libraries'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Handle load errors
|
||||
sandboxIframe.value.onerror = () => {
|
||||
logs.value.push({
|
||||
type: 'error',
|
||||
content: 'Failed to load sandbox environment'
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Handle messages from the iframe
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
const { sandboxID, type, content } = event.data;
|
||||
|
||||
// Make sure the message has the expected format
|
||||
if (sandboxID && type && content) {
|
||||
// Make sure the messsage was targetting this Sandbox specifically.
|
||||
if(sandboxID !== thisSandboxID) {
|
||||
return;
|
||||
}
|
||||
|
||||
logs.value.push({
|
||||
type: type as 'log' | 'info' | 'warn' | 'error',
|
||||
content
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Lifecycle hooks
|
||||
onMounted(() => {
|
||||
// Check if there's code in the URL hash fragment
|
||||
if (window.location.hash && window.location.hash.startsWith('#code=')) {
|
||||
const codeParam = window.location.hash.substring(6); // Remove '#code='
|
||||
|
||||
// If code exists in URL hash, use it
|
||||
try {
|
||||
// Always assume Base64 encoding
|
||||
code.value = decodeURIComponent(escape(atob(decodeURIComponent(codeParam))));
|
||||
} catch (error) {
|
||||
console.error('Failed to decode URL hash parameter:', error);
|
||||
logs.value.push({
|
||||
type: 'error',
|
||||
content: 'Failed to decode URL hash parameter: ' + error
|
||||
});
|
||||
// Fallback to slot content if URL decoding fails
|
||||
loadCodeFromSlot();
|
||||
}
|
||||
} else {
|
||||
// Otherwise check query parameters (for backward compatibility)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const queryCodeParam = urlParams.get('code');
|
||||
|
||||
if (queryCodeParam) {
|
||||
try {
|
||||
// Try to decode from Base64
|
||||
code.value = decodeURIComponent(escape(atob(decodeURIComponent(queryCodeParam))));
|
||||
} catch (error) {
|
||||
console.error('Failed to decode URL query parameter:', error);
|
||||
// Fallback to slot content
|
||||
loadCodeFromSlot();
|
||||
}
|
||||
} else {
|
||||
// No code in hash or query, load from slot
|
||||
loadCodeFromSlot();
|
||||
}
|
||||
}
|
||||
|
||||
// Add event listener for messages from the iframe
|
||||
window.addEventListener('message', handleMessage);
|
||||
});
|
||||
|
||||
// Function to load code from slot
|
||||
const loadCodeFromSlot = () => {
|
||||
if (codeRef.value) {
|
||||
const template = codeRef.value;
|
||||
if (template) {
|
||||
code.value = template.innerText.trim();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// Clean up event listener
|
||||
window.removeEventListener('message', handleMessage);
|
||||
});
|
||||
|
||||
// Expose our functions so that they can be called from parent components.
|
||||
defineExpose({
|
||||
executeCode,
|
||||
shareCode
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.code-sandbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
position: relative;
|
||||
background: var(--vp-code-block-bg);
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; /* Contain overflow */
|
||||
}
|
||||
|
||||
.editor-container .ace_editor {
|
||||
background: transparent !important;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
line-height: var(--vp-code-line-height);
|
||||
font-size: var(--vp-code-font-size);
|
||||
flex: 1 1 auto; /* Allow editor to grow */
|
||||
overflow: auto; /* Enable scrolling */
|
||||
min-height: 3em;
|
||||
}
|
||||
|
||||
.editor-container .ace_editor .ace_gutter {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.editor-buttons {
|
||||
position: absolute;
|
||||
padding:10px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.output-container {
|
||||
position: relative;
|
||||
background-color: var(--vp-code-block-bg);
|
||||
border-top: 1px solid var(--vp-button-brand-bg);
|
||||
}
|
||||
|
||||
.output-container-buttons {
|
||||
position: absolute;
|
||||
padding:10px;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.console-output {
|
||||
min-height: 120px;
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
line-height: var(--vp-code-line-height);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
margin-bottom: 4px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.info {
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
.warn {
|
||||
color: #ff9900;
|
||||
background-color: rgba(255, 153, 0, 0.1);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #cc0000;
|
||||
background-color: rgba(204, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.log-prefix {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.sandbox-iframe {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,122 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Code Sandbox</title>
|
||||
<script>
|
||||
// Override console methods to capture and post messages to parent
|
||||
const originalConsole = window.console;
|
||||
|
||||
// Define console overrides
|
||||
window.console = {
|
||||
log: function (...args) {
|
||||
originalConsole.log(...args);
|
||||
window.parent.postMessage(
|
||||
{
|
||||
sandboxID: '__sandboxID__',
|
||||
type: 'log',
|
||||
content: args
|
||||
.map((arg) =>
|
||||
typeof arg === 'object' ? JSON.stringify(arg) : String(arg),
|
||||
)
|
||||
.join(' '),
|
||||
},
|
||||
'*',
|
||||
);
|
||||
},
|
||||
|
||||
info: function (...args) {
|
||||
originalConsole.info(...args);
|
||||
window.parent.postMessage(
|
||||
{
|
||||
sandboxID: '__sandboxID__',
|
||||
type: 'info',
|
||||
content: args
|
||||
.map((arg) =>
|
||||
typeof arg === 'object' ? JSON.stringify(arg) : String(arg),
|
||||
)
|
||||
.join(' '),
|
||||
},
|
||||
'*',
|
||||
);
|
||||
},
|
||||
|
||||
warn: function (...args) {
|
||||
originalConsole.warn(...args);
|
||||
window.parent.postMessage(
|
||||
{
|
||||
sandboxID: '__sandboxID__',
|
||||
type: 'warn',
|
||||
content: args
|
||||
.map((arg) =>
|
||||
typeof arg === 'object' ? JSON.stringify(arg) : String(arg),
|
||||
)
|
||||
.join(' '),
|
||||
},
|
||||
'*',
|
||||
);
|
||||
},
|
||||
|
||||
error: function (...args) {
|
||||
originalConsole.error(...args);
|
||||
window.parent.postMessage(
|
||||
{
|
||||
sandboxID: '__sandboxID__',
|
||||
type: 'error',
|
||||
content: args
|
||||
.map((arg) =>
|
||||
typeof arg === 'object' ? JSON.stringify(arg) : String(arg),
|
||||
)
|
||||
.join(' '),
|
||||
},
|
||||
'*',
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
// Catch global errors
|
||||
window.onerror = function (message, source, lineno, colno, error) {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
sandboxID: '__sandboxID__',
|
||||
type: 'error',
|
||||
content: 'Error: ' + message,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
return true; // Prevents the error from appearing in the iframe's console
|
||||
};
|
||||
|
||||
// Handle unhandled promise rejections
|
||||
window.addEventListener('unhandledrejection', function (event) {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'error',
|
||||
content: 'Unhandled Promise Rejection: ' + event.reason,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- The code runs in this sandbox environment -->
|
||||
|
||||
<!-- Module script for ESM support -->
|
||||
<script type="module">
|
||||
// Helper function to handle both dynamic and static imports
|
||||
async function runUserCode() {
|
||||
try {
|
||||
// Execute the user code
|
||||
__userCode__;
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
runUserCode().catch((error) => {
|
||||
console.error('Error in user code execution:', error.message);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div id="playground">
|
||||
<div class="top">
|
||||
<div class="logo">
|
||||
<img class="light-only" src="/logo-light.svg" alt="XO Stack Logo">
|
||||
<img class="dark-only" src="/logo-dark.svg" alt="XO Stack Logo">
|
||||
XO Stack - Playground
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<button class="primary-button" @click="shareCode">Share</button>
|
||||
<button class="primary-button" @click="executeCode">Run Code</button>
|
||||
</div>
|
||||
</div>
|
||||
<CodeSandbox
|
||||
ref="sandbox"
|
||||
height="100vh"
|
||||
editorHeight="80%"
|
||||
outputHeight="20%"
|
||||
fullHeight
|
||||
hideRunButton
|
||||
>
|
||||
<slot></slot>
|
||||
</CodeSandbox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useData } from 'vitepress';
|
||||
|
||||
import CodeSandbox from '@theme/components/CodeSandbox.vue'
|
||||
|
||||
const { isDark } = useData();
|
||||
|
||||
const sandbox = ref<InstanceType<typeof CodeSandbox> | null>(null);
|
||||
|
||||
async function executeCode() {
|
||||
sandbox.value.executeCode();
|
||||
}
|
||||
|
||||
async function shareCode() {
|
||||
sandbox.value.shareCode()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
#playground {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
background-color: var(--vp-c-bg);
|
||||
border-bottom: 1px solid var(--vp-button-brand-bg);
|
||||
|
||||
}
|
||||
.logo {
|
||||
display: flex; align-items: center;
|
||||
font-size: 16px;
|
||||
margin-right: auto;
|
||||
font-family: var(--vp-font-family-base);
|
||||
}
|
||||
.logo img {
|
||||
margin-right: 10px;
|
||||
height: 50px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.left {
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
.right {
|
||||
width: 20%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
background-color: #1e1e1e;
|
||||
color: #ddd;
|
||||
font-family: 'Consolas', monospace;
|
||||
}
|
||||
#monaco-editor-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.console-output {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user