diff --git a/src/aside.js b/src/aside.js
index 062d8a8..5ebd6ac 100644
--- a/src/aside.js
+++ b/src/aside.js
@@ -44,6 +44,10 @@ const NON_SIMPLE_CLICK_ACTIONS = {
[BUTTON_ACTIONS.showSettingsBar]: () => {
showAsideBar('#settings')
$('.scroll-buttons-container').setAttribute('hidden', '')
+ },
+ [BUTTON_ACTIONS.showConsoleBar]: () => {
+ showAsideBar('#console')
+ $('.scroll-buttons-container').setAttribute('hidden', '')
}
}
diff --git a/src/console-script.js b/src/console-script.js
new file mode 100644
index 0000000..e5bdf07
--- /dev/null
+++ b/src/console-script.js
@@ -0,0 +1,41 @@
+export const generateConsoleScript = () => {
+ return ``
+}
diff --git a/src/console.js b/src/console.js
new file mode 100644
index 0000000..ab93391
--- /dev/null
+++ b/src/console.js
@@ -0,0 +1,54 @@
+import { CONSOLE_ICONS } from './constants/console-icons'
+import { $ } from './utils/dom'
+
+const $iframe = $('iframe')
+const $consoleList = $('#console .console-list')
+
+const clearConsole = () => {
+ $consoleList.innerHTML = ''
+}
+
+const isPrimitive = (item) => {
+ return ['string', 'number', 'boolean', 'symbol', 'bigint'].includes(typeof item) || item === null || item === undefined
+}
+
+const createListItem = (content, type) => {
+ const $li = document.createElement('li')
+ $li.classList.add(`log-${type.split(':')[1]}`)
+
+ $li.innerHTML = CONSOLE_ICONS[type]
+
+ const $span = document.createElement('span')
+ $span.textContent = content
+ $li.appendChild($span)
+
+ return $li
+}
+
+const handlers = {
+ system: (payload) => {
+ if (payload === 'clear') {
+ clearConsole()
+ }
+ },
+ error: (payload) => {
+ const { line, column, message } = payload
+ const errorItem = createListItem(`${line}:${column} ${message}`, 'error')
+ errorItem.classList.add('error')
+ $consoleList.appendChild(errorItem)
+ },
+ default: (payload, type) => {
+ const content = payload.find(isPrimitive) ? payload.join(' ') : payload.map(item => JSON.stringify(item)).join(' ')
+ const listItem = createListItem(content, type)
+ $consoleList.appendChild(listItem)
+ }
+}
+
+window.addEventListener('message', (ev) => {
+ if (ev.source !== $iframe.contentWindow) return
+ const { type, payload: rawPayload } = ev.data.console
+ const payload = JSON.parse(rawPayload)
+
+ const handler = handlers[type] || handlers.default
+ handler(payload, type)
+})
diff --git a/src/constants/button-actions.js b/src/constants/button-actions.js
index a25173b..254d154 100644
--- a/src/constants/button-actions.js
+++ b/src/constants/button-actions.js
@@ -4,5 +4,6 @@ export const BUTTON_ACTIONS = {
copyToClipboard: 'copy-to-clipboard',
closeAsideBar: 'close-aside-bar',
showSkypackBar: 'show-skypack-bar',
- showSettingsBar: 'show-settings-bar'
+ showSettingsBar: 'show-settings-bar',
+ showConsoleBar: 'show-console-bar'
}
diff --git a/src/constants/console-icons.js b/src/constants/console-icons.js
new file mode 100644
index 0000000..40a8a57
--- /dev/null
+++ b/src/constants/console-icons.js
@@ -0,0 +1,76 @@
+export const CONSOLE_ICONS = {
+ 'log:info': `
`,
+ 'log:error': `
`,
+ 'log:warn': `
`,
+ 'log:log': `
`,
+ error: null
+}
diff --git a/src/css/console.css b/src/css/console.css
new file mode 100644
index 0000000..c11fb86
--- /dev/null
+++ b/src/css/console.css
@@ -0,0 +1,67 @@
+.console {
+ & .console-header {
+ background: var(--aside-bar-background);
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ padding-top: 1em;
+ }
+
+ & .console-type {
+ padding-bottom: 8px;
+ opacity: 0.6;
+ }
+
+ & .console-item {
+ color: #fff;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ padding: 0 1em 1em;
+ }
+
+ & .console-item strong {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ & .console-list {
+ list-style: none;
+ padding: 0;
+ display: grid;
+ gap: 0.5em;
+
+ & li {
+ display: flex;
+ align-items: center;
+ gap: 1em;
+ word-break: break-all;
+ padding: 0 0.5em;
+ }
+
+ & .error {
+ background: rgb(235, 189, 189);
+ border: 2px solid rgb(204, 0, 0);
+ color: black;
+ }
+ & .error::before {
+ content: '❗';
+ margin-right: 4px;
+ width: 1em;
+ }
+
+ & .log-log {
+ color: rgb(205, 238, 181);
+ }
+ & .log-info {
+ color: rgb(181, 181, 238);
+ }
+ & .log-error {
+ color: rgb(227, 189, 189);
+ }
+ & .log-warn {
+ color: rgb(247, 231, 161);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index 525274b..d580d56 100644
--- a/src/main.js
+++ b/src/main.js
@@ -15,6 +15,7 @@ import './skypack.js'
import './settings.js'
import './scroll.js'
import './drag-file.js'
+import './console.js'
import { BUTTON_ACTIONS } from './constants/button-actions.js'
diff --git a/src/style.css b/src/style.css
index 8fca401..b9c791d 100644
--- a/src/style.css
+++ b/src/style.css
@@ -8,6 +8,7 @@
@import url('./css/skypack.css');
@import url('./css/settings.css');
@import url('./css/drag-drop-area.css');
+@import url('./css/console.css');
.scroll-buttons-container {
display: none;
diff --git a/src/utils/WindowPreviewer.js b/src/utils/WindowPreviewer.js
index 765f33c..36deee1 100644
--- a/src/utils/WindowPreviewer.js
+++ b/src/utils/WindowPreviewer.js
@@ -12,7 +12,7 @@ export function updatePreview ({ html, css, js }) {
URL.revokeObjectURL(previewUrl)
}
- const htmlForPreview = createHtml({ html, css, js })
+ const htmlForPreview = createHtml({ html, css, js }, true)
const blob = new window.Blob([htmlForPreview], { type: 'text/html' })
diff --git a/src/utils/createHtml.js b/src/utils/createHtml.js
index c7a4571..2e67b82 100644
--- a/src/utils/createHtml.js
+++ b/src/utils/createHtml.js
@@ -1,14 +1,17 @@
// @ts-check
+import { generateConsoleScript } from '../console-script'
+
/**
* Create an index.html content from provided data
* @param {object} params - The parameters
* @param {string} params.css - CSS
* @param {string} params.html - HTML content
* @param {string} params.js - JavaScript
+ * @param {boolean} isEditor - Whether the code is being run in the editor or preview
* @returns {string}
*/
-export const createHtml = ({ css, html, js }) => {
+export const createHtml = ({ css, html, js }, isEditor = false) => {
return `
@@ -17,6 +20,7 @@ export const createHtml = ({ css, html, js }) => {
+ ${isEditor ? generateConsoleScript() : ''}
${html}