// ===== Console Module - Interactive JavaScript Console =====
// Wait for editor to be ready
document.addEventListener('DOMContentLoaded', () => {
// Get editor reference from global API
const { editor, getValue } = window.editorAPI || {};
if (!editor) {
console.warn('Editor not found, console functionality may be limited');
return;
}
// DOM elements
const consoleBtn = document.getElementById("consoleBtn");
const consoleOverlay = document.getElementById("consoleOverlay");
const consoleClose = document.getElementById("consoleClose");
const consoleOutput = document.getElementById("consoleOutput");
const consoleForm = document.getElementById("consoleForm");
const consoleInput = document.getElementById("consoleInput");
const clearConsole = document.getElementById("clearConsole");
const runCode = document.getElementById("runCode");
// Console history and state
let commandHistory = [];
let historyIndex = -1;
let consoleContext = {};
// ===== Overlay Management =====
function openOverlay() {
consoleOverlay.classList.add("open");
setTimeout(() => {
consoleInput.focus();
if (consoleOutput.children.length === 0) {
addOutput('JavaScript Console Ready. Type commands or run editor code.', 'log');
addOutput('Try: Math.random(), new Date(), or any JavaScript!', 'log');
}
}, 0);
}
function closeOverlay() {
consoleOverlay.classList.remove("open");
consoleBtn.focus();
}
// Event listeners
consoleBtn.addEventListener("click", openOverlay);
consoleClose.addEventListener("click", closeOverlay);
// ESC key to close
window.addEventListener("keydown", (e) => {
if (consoleOverlay.classList.contains("open") && e.key === "Escape") {
closeOverlay();
}
});
// ===== Console Output Management =====
function addOutput(content, type = 'log') {
const line = document.createElement('div');
line.className = `console-line console-${type}-line`;
if (typeof content === 'object') {
line.textContent = JSON.stringify(content, null, 2);
} else {
line.textContent = String(content);
}
consoleOutput.appendChild(line);
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
function addInputLine(command) {
const line = document.createElement('div');
line.className = 'console-line console-input-line';
line.textContent = `> ${command}`;
consoleOutput.appendChild(line);
}
function clearConsoleOutput() {
consoleOutput.innerHTML = '';
addOutput('Console cleared', 'log');
}
// ===== JavaScript Execution =====
// Override console methods to capture output
const originalConsole = {
log: console.log,
error: console.error,
warn: console.warn,
info: console.info
};
function setupConsoleInterception() {
console.log = (...args) => {
originalConsole.log(...args);
addOutput(args.join(' '), 'log');
};
console.error = (...args) => {
originalConsole.error(...args);
addOutput(args.join(' '), 'error');
};
console.warn = (...args) => {
originalConsole.warn(...args);
addOutput(args.join(' '), 'error');
};
console.info = (...args) => {
originalConsole.info(...args);
addOutput(args.join(' '), 'log');
};
}
function restoreConsole() {
console.log = originalConsole.log;
console.error = originalConsole.error;
console.warn = originalConsole.warn;
console.info = originalConsole.info;
}
// Execute JavaScript code safely
function executeCode(code) {
try {
setupConsoleInterception();
// Create a function to execute the code with access to context
const func = new Function(
...Object.keys(consoleContext),
`
"use strict";
return eval(${JSON.stringify(code)});
`
);
const result = func(...Object.values(consoleContext));
// Only show result if it's not undefined and wasn't logged
if (result !== undefined) {
addOutput(result, 'output');
}
return result;
} catch (error) {
addOutput(`Error: ${error.message}`, 'error');
return undefined;
} finally {
restoreConsole();
}
}
// ===== Command History =====
function addToHistory(command) {
// Remove duplicate if exists
const existingIndex = commandHistory.indexOf(command);
if (existingIndex > -1) {
commandHistory.splice(existingIndex, 1);
}
// Add to end and limit history size
commandHistory.push(command);
if (commandHistory.length > 50) {
commandHistory.shift();
}
historyIndex = commandHistory.length;
}
function navigateHistory(direction) {
if (commandHistory.length === 0) return;
if (direction === 'up') {
historyIndex = Math.max(0, historyIndex - 1);
} else if (direction === 'down') {
historyIndex = Math.min(commandHistory.length, historyIndex + 1);
}
if (historyIndex < commandHistory.length) {
consoleInput.value = commandHistory[historyIndex];
} else {
consoleInput.value = '';
}
}
// ===== Event Handlers =====
// Form submission
consoleForm.addEventListener('submit', (e) => {
e.preventDefault();
const command = consoleInput.value.trim();
if (!command) return;
// Add to display and history
addInputLine(command);
addToHistory(command);
// Execute command
executeCode(command);
// Clear input
consoleInput.value = '';
});
// History navigation
consoleInput.addEventListener('keydown', (e) => {
if (e.key === 'ArrowUp') {
e.preventDefault();
navigateHistory('up');
} else if (e.key === 'ArrowDown') {
e.preventDefault();
navigateHistory('down');
} else if (e.key === 'Tab') {
e.preventDefault();
// Basic autocompletion for common objects
const value = consoleInput.value;
const lastWord = value.split(/\s/).pop();
if (lastWord.includes('.')) {
// Don't autocomplete for now, just prevent tab
return;
}
const suggestions = ['console', 'document', 'window', 'Math', 'Date', 'Array', 'Object', 'JSON'];
const matches = suggestions.filter(s => s.startsWith(lastWord));
if (matches.length === 1) {
const newValue = value.replace(new RegExp(lastWord + '$'), matches[0]);
consoleInput.value = newValue;
}
}
});
// Clear console
clearConsole.addEventListener('click', clearConsoleOutput);
// Run current editor code
runCode.addEventListener('click', () => {
const code = getValue ? getValue() : editor.getValue();
const mode = editor.session.getMode().$id;
if (mode !== 'ace/mode/javascript' && !code.includes('<script>')) {
addOutput('Note: Editor content is not JavaScript. Running anyway...', 'log');
}
// Extract JavaScript from HTML if needed
let jsCode = code;
if (mode === 'ace/mode/html') {
const scriptMatches = code.match(/<script[^>]*>([\s\S]*?)<\/script>/gi);
if (scriptMatches) {
jsCode = scriptMatches.map(match =>
match.replace(/<script[^>]*>|<\/script>/gi, '')
).join('\n\n');
} else {
addOutput('No <script> tags found in HTML', 'error');
return;
}
}
addInputLine('// Running editor code...');
executeCode(jsCode);
});
// ===== Built-in Commands & Utilities =====
// Add some helpful utilities to the console context
consoleContext.help = function() {
const helpText = `
JavaScript Console Help:
• Type any JavaScript expression
• Use arrow keys for command history
• Tab for basic autocompletion
• clear() - Clear console
• help() - Show this help
• editor() - Get editor content
• run() - Run editor code
• Math, Date, Array, Object - Built-in objects available
`;
addOutput(helpText.trim(), 'log');
};
consoleContext.clear = clearConsoleOutput;
consoleContext.editor = function() {
return getValue ? getValue() : editor.getValue();
};
consoleContext.run = function() {
runCode.click();
};
// ===== Keyboard Shortcuts =====
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + Shift + C for console
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'C') {
e.preventDefault();
openOverlay();
}
// Ctrl/Cmd + Enter to run code from anywhere
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
runCode.click();
}
});
// ===== Auto-run on certain events =====
// Optionally run code when editor content changes (commented out for performance)
// editor.on('change', _.debounce(() => {
// if (consoleOverlay.classList.contains('open')) {
// // Auto-run on change if console is open
// }
// }, 1000));
// ===== Export Console API =====
window.consoleAPI = {
openOverlay,
closeOverlay,
executeCode,
addOutput,
clearConsoleOutput,
commandHistory
};
console.log("Console module loaded successfully");
});