Add some new commands for the Crawl4ai script transpiler and creating an interactive tutorial that allows users to go through multiple steps and apply the syntax to automate the page. Fixed some issues and add several new commands for setting input values, variables, clearing input fields, and more.

This commit is contained in:
UncleCode
2025-06-06 23:03:26 +08:00
parent 3f6f2e998c
commit ca03acbc82
17 changed files with 4368 additions and 27 deletions

View File

@@ -0,0 +1,667 @@
/* ================================================================
C4A-Script Tutorial - App Layout CSS
Terminal theme with Dank Mono font
================================================================ */
/* CSS Variables */
:root {
--bg-primary: #070708;
--bg-secondary: #0e0e10;
--bg-tertiary: #1a1a1b;
--border-color: #2a2a2c;
--border-hover: #3a3a3c;
--text-primary: #e0e0e0;
--text-secondary: #8b8b8d;
--text-muted: #606065;
--primary-color: #0fbbaa;
--primary-hover: #0da89a;
--primary-dim: #0a8577;
--error-color: #ff5555;
--warning-color: #ffb86c;
--success-color: #50fa7b;
--info-color: #8be9fd;
--code-bg: #1e1e20;
--modal-overlay: rgba(0, 0, 0, 0.8);
}
/* Base Reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Fonts */
@font-face {
font-family: 'Dank Mono';
src: url('DankMono-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Dank Mono';
src: url('DankMono-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Dank Mono';
src: url('DankMono-Italic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
}
/* Body & App Container */
body {
font-family: 'Dank Mono', 'Monaco', 'Consolas', monospace;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 14px;
line-height: 1.6;
overflow: hidden;
}
.app-container {
display: flex;
height: 100vh;
width: 100vw;
overflow: hidden;
}
/* Panels */
.editor-panel,
.playground-panel {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.editor-panel {
flex: 1;
background: var(--bg-secondary);
border-right: 1px solid var(--border-color);
min-width: 400px;
}
.playground-panel {
flex: 1;
background: var(--bg-primary);
min-width: 400px;
}
/* Panel Headers */
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: var(--bg-tertiary);
border-bottom: 1px solid var(--border-color);
flex-shrink: 0;
}
.panel-header h2 {
font-size: 16px;
font-weight: 600;
color: var(--primary-color);
margin: 0;
}
.header-actions {
display: flex;
gap: 8px;
}
/* Action Buttons */
.action-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: var(--bg-secondary);
color: var(--text-secondary);
border: 1px solid var(--border-color);
border-radius: 4px;
font-family: inherit;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
}
.action-btn:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
border-color: var(--border-hover);
}
.action-btn.primary {
background: var(--primary-color);
color: var(--bg-primary);
border-color: var(--primary-color);
}
.action-btn.primary:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
.action-btn .icon {
font-size: 16px;
}
/* Editor Wrapper */
.editor-wrapper {
flex: 1;
display: flex;
overflow: hidden;
position: relative;
z-index: 1; /* Ensure it's above any potential overlays */
}
.editor-wrapper .CodeMirror {
flex: 1;
height: 100%;
font-family: 'Dank Mono', monospace;
font-size: 14px;
line-height: 1.5;
}
/* Ensure CodeMirror is interactive */
.CodeMirror {
background: var(--bg-primary) !important;
}
.CodeMirror-scroll {
overflow: auto !important;
}
/* Make cursor more visible */
.CodeMirror-cursor {
border-left: 2px solid var(--primary-color) !important;
border-left-width: 2px !important;
opacity: 1 !important;
visibility: visible !important;
}
/* Ensure cursor is visible when focused */
.CodeMirror-focused .CodeMirror-cursor {
visibility: visible !important;
}
/* Fix for CodeMirror in flex container */
.CodeMirror-sizer {
min-height: auto !important;
}
/* Remove aggressive pointer-events override */
.CodeMirror-code {
cursor: text;
}
.editor-wrapper textarea {
display: none;
}
/* Output Section (Bottom of Editor) */
.output-section {
height: 250px;
border-top: 1px solid var(--border-color);
display: flex;
flex-direction: column;
flex-shrink: 0;
}
/* Tabs */
.tabs {
display: flex;
background: var(--bg-tertiary);
border-bottom: 1px solid var(--border-color);
flex-shrink: 0;
}
.tab {
padding: 8px 20px;
background: transparent;
color: var(--text-secondary);
border: none;
border-bottom: 2px solid transparent;
font-family: inherit;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
}
.tab:hover {
color: var(--text-primary);
background: var(--bg-secondary);
}
.tab.active {
color: var(--primary-color);
border-bottom-color: var(--primary-color);
}
/* Tab Content */
.tab-content {
flex: 1;
overflow: hidden;
}
.tab-pane {
display: none;
height: 100%;
overflow-y: auto;
}
.tab-pane.active {
display: block;
}
/* Console */
.console {
padding: 12px;
background: var(--bg-primary);
font-size: 13px;
min-height: 100%;
}
.console-line {
margin-bottom: 8px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.console-prompt {
color: var(--primary-color);
flex-shrink: 0;
}
.console-text {
color: var(--text-primary);
}
.console-error {
color: var(--error-color);
}
.console-warning {
color: var(--warning-color);
}
.console-success {
color: var(--success-color);
}
/* JavaScript Output */
.js-output-header {
display: flex;
justify-content: flex-end;
padding: 8px 12px;
background: var(--bg-tertiary);
border-bottom: 1px solid var(--border-color);
}
.js-actions {
display: flex;
gap: 8px;
}
.mini-btn {
padding: 4px 8px;
background: var(--bg-secondary);
color: var(--text-secondary);
border: 1px solid var(--border-color);
border-radius: 3px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.mini-btn:hover {
background: var(--bg-primary);
color: var(--text-primary);
}
.js-output {
padding: 12px;
background: var(--code-bg);
color: var(--text-primary);
font-family: 'Dank Mono', monospace;
font-size: 13px;
line-height: 1.5;
white-space: pre-wrap;
margin: 0;
min-height: calc(100% - 44px);
}
/* Execution Progress */
.execution-progress {
padding: 12px;
background: var(--bg-primary);
}
.progress-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
font-size: 13px;
}
.progress-icon {
color: var(--text-muted);
}
.progress-item.active .progress-icon {
color: var(--info-color);
animation: pulse 1s infinite;
}
.progress-item.completed .progress-icon {
color: var(--success-color);
}
.progress-item.error .progress-icon {
color: var(--error-color);
}
/* Playground */
.playground-wrapper {
flex: 1;
overflow: hidden;
}
#playground-frame {
width: 100%;
height: 100%;
border: none;
background: var(--bg-secondary);
}
/* Tutorial Intro Modal */
.tutorial-intro-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--modal-overlay);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
transition: opacity 0.3s;
}
.tutorial-intro-modal.hidden {
display: none;
}
.intro-content {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 32px;
max-width: 500px;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.6);
}
.intro-content h2 {
color: var(--primary-color);
margin-bottom: 16px;
font-size: 24px;
}
.intro-content p {
color: var(--text-primary);
margin-bottom: 16px;
line-height: 1.6;
}
.intro-content ul {
list-style: none;
margin-bottom: 24px;
}
.intro-content li {
color: var(--text-secondary);
margin-bottom: 8px;
padding-left: 20px;
position: relative;
}
.intro-content li:before {
content: "▸";
position: absolute;
left: 0;
color: var(--primary-color);
}
.intro-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
}
.intro-btn {
padding: 10px 24px;
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 4px;
font-family: inherit;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.intro-btn:hover {
background: var(--bg-primary);
border-color: var(--border-hover);
}
.intro-btn.primary {
background: var(--primary-color);
color: var(--bg-primary);
border-color: var(--primary-color);
}
.intro-btn.primary:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
/* Tutorial Navigation Bar */
.tutorial-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
background: var(--bg-tertiary);
border-bottom: 1px solid var(--primary-color);
z-index: 1000;
transition: transform 0.3s;
}
.tutorial-nav.hidden {
transform: translateY(-100%);
}
.tutorial-nav-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
}
.tutorial-left {
flex: 1;
}
.tutorial-step-title {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 8px;
}
.tutorial-step-title span:first-child {
color: var(--text-secondary);
font-size: 12px;
text-transform: uppercase;
}
.tutorial-step-title span:last-child {
color: var(--primary-color);
font-weight: 600;
font-size: 16px;
}
.tutorial-description {
color: var(--text-primary);
margin: 0;
font-size: 14px;
max-width: 600px;
}
.tutorial-right {
display: flex;
align-items: center;
}
.tutorial-progress-bar {
height: 3px;
background: var(--bg-secondary);
position: absolute;
bottom: 0;
left: 0;
right: 0;
}
.tutorial-progress-bar .progress-fill {
height: 100%;
background: var(--primary-color);
transition: width 0.3s;
}
/* Adjust app container when tutorial is active */
.app-container.tutorial-active {
padding-top: 80px;
}
.tutorial-controls {
display: flex;
gap: 12px;
}
.nav-btn {
padding: 8px 16px;
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 4px;
font-family: inherit;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
}
.nav-btn:hover:not(:disabled) {
background: var(--bg-primary);
border-color: var(--border-hover);
}
.nav-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.nav-btn.primary {
background: var(--primary-color);
color: var(--bg-primary);
border-color: var(--primary-color);
}
.nav-btn.primary:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
.exit-btn {
width: 32px;
height: 32px;
background: transparent;
color: var(--text-secondary);
border: none;
font-size: 20px;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s;
margin-left: 16px;
}
.exit-btn:hover {
background: var(--bg-secondary);
color: var(--text-primary);
}
/* Fullscreen Mode */
.playground-panel.fullscreen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1500;
}
/* Animations */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: var(--bg-secondary);
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--border-hover);
}
/* Responsive */
@media (max-width: 768px) {
.app-container {
flex-direction: column;
}
.editor-panel,
.playground-panel {
min-width: auto;
width: 100%;
}
.editor-panel {
border-right: none;
border-bottom: 1px solid var(--border-color);
}
.output-section {
height: 200px;
}
}

View File

@@ -0,0 +1,625 @@
// C4A-Script Tutorial App Controller
class TutorialApp {
constructor() {
this.editor = null;
this.currentScript = '';
this.currentJS = [];
this.isEditingJS = false;
this.tutorialMode = false;
this.currentStep = 0;
this.tutorialSteps = [];
this.init();
}
init() {
this.setupEditors();
this.setupButtons();
this.setupTabs();
this.setupTutorial();
this.checkFirstVisit();
}
setupEditors() {
// C4A Script Editor
const c4aTextarea = document.getElementById('c4a-editor');
this.editor = CodeMirror.fromTextArea(c4aTextarea, {
mode: 'javascript', // Use JS mode for now
theme: 'material-darker',
lineNumbers: true,
lineWrapping: true,
autoCloseBrackets: true,
matchBrackets: true,
readOnly: false,
cursorBlinkRate: 530,
inputStyle: 'contenteditable' // Changed from 'textarea' to prevent cursor issues
});
// Save script on change
this.editor.on('change', () => {
this.currentScript = this.editor.getValue();
localStorage.setItem('c4a-script', this.currentScript);
});
// Load saved script
const saved = localStorage.getItem('c4a-script');
if (saved && !this.tutorialMode) {
this.editor.setValue(saved);
}
// Ensure editor is properly sized and interactive
setTimeout(() => {
this.editor.refresh();
// Set cursor position instead of just focusing
const doc = this.editor.getDoc();
doc.setCursor(doc.lineCount() - 1, 0);
this.editor.focus();
}, 100);
// Single unified click handler for focus
const editorElement = this.editor.getWrapperElement();
editorElement.addEventListener('mousedown', (e) => {
// Use mousedown instead of click for immediate response
e.stopPropagation();
// Ensure editor gets focus on next tick
setTimeout(() => {
if (!this.editor.hasFocus()) {
this.editor.focus();
}
}, 0);
});
}
setupButtons() {
// Run button
document.getElementById('run-btn').addEventListener('click', () => {
this.runScript();
});
// Clear button
document.getElementById('clear-btn').addEventListener('click', () => {
this.editor.setValue('');
this.clearConsole();
});
// Examples button
document.getElementById('examples-btn').addEventListener('click', () => {
this.showExamples();
});
// Tutorial button
document.getElementById('tutorial-btn').addEventListener('click', () => {
this.showIntroModal();
});
// Copy JS button
document.getElementById('copy-js-btn').addEventListener('click', () => {
this.copyJS();
});
// Edit JS button
document.getElementById('edit-js-btn').addEventListener('click', () => {
this.toggleJSEdit();
});
// Reset playground
document.getElementById('reset-playground').addEventListener('click', () => {
this.resetPlayground();
});
// Fullscreen
document.getElementById('fullscreen-btn').addEventListener('click', () => {
this.toggleFullscreen();
});
// Intro modal buttons
document.getElementById('start-tutorial-btn').addEventListener('click', () => {
this.hideIntroModal();
this.startTutorial();
});
document.getElementById('skip-tutorial-btn').addEventListener('click', () => {
this.hideIntroModal();
});
// Tutorial navigation
document.getElementById('tutorial-prev').addEventListener('click', () => {
this.prevStep();
});
document.getElementById('tutorial-next').addEventListener('click', () => {
this.nextStep();
});
document.getElementById('tutorial-exit').addEventListener('click', () => {
this.exitTutorial();
});
}
setupTabs() {
const tabs = document.querySelectorAll('.tab');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const tabName = tab.getAttribute('data-tab');
this.switchTab(tabName);
});
});
// Remove execution tab since we're removing it
const progressTab = document.querySelector('[data-tab="progress"]');
if (progressTab) progressTab.remove();
}
switchTab(tabName) {
// Update tab buttons
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
// Update tab panes
document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
document.getElementById(`${tabName}-tab`).classList.add('active');
}
checkFirstVisit() {
const hasVisited = localStorage.getItem('c4a-tutorial-visited');
if (!hasVisited) {
setTimeout(() => this.showIntroModal(), 500);
}
}
showIntroModal() {
document.getElementById('tutorial-intro').classList.remove('hidden');
}
hideIntroModal() {
document.getElementById('tutorial-intro').classList.add('hidden');
localStorage.setItem('c4a-tutorial-visited', 'true');
}
setupTutorial() {
this.tutorialSteps = [
{
title: "Welcome to C4A-Script!",
description: "C4A-Script is a simple language for web automation. Let's start by handling popups that appear on websites.",
script: "# Welcome to C4A-Script Tutorial!\n# Let's start by waiting for the page to load\n\nWAIT `body` 2",
validate: () => true
},
{
title: "Handle Cookie Banner",
description: "Check if cookie banner exists and accept it.",
script: "# Wait for page and handle cookie banner\nWAIT `body` 2\n\n# Check if cookie banner exists, then click accept\nIF (EXISTS `.cookie-banner`) THEN CLICK `.accept`",
validate: () => {
const iframe = document.getElementById('playground-frame');
return !iframe.contentDocument?.querySelector('.cookie-banner');
}
},
{
title: "Handle Newsletter Popup",
description: "Now let's handle the newsletter popup that appears after 3 seconds.",
script: "# Wait for newsletter popup to appear\nWAIT 3\n\n# Close the newsletter popup\nIF (EXISTS `#newsletter-popup`) THEN CLICK `.close`",
validate: () => {
const iframe = document.getElementById('playground-frame');
return !iframe.contentDocument?.querySelector('#newsletter-popup');
}
},
{
title: "Start Interactive Elements",
description: "Click the start button to reveal more interactive elements.",
script: "# Click the start tutorial button\nCLICK `#start-tutorial`",
validate: () => true
},
{
title: "Login Process",
description: "Now let's complete the login form with email and password using the new SET command.",
script: "# Login process\nCLICK `#login-btn`\nWAIT `.login-form` 2\n\n# Fill form fields using SET\nSET `#email` \"demo@example.com\"\nSET `#password` \"demo123\"\n\n# Submit the form\nCLICK `button[type=\"submit\"]`\nWAIT `.success` 2",
validate: () => {
const iframe = document.getElementById('playground-frame');
return iframe.contentDocument?.querySelector('.user-info')?.style.display === 'flex';
}
},
{
title: "Navigate and Scroll",
description: "Navigate to the catalog and use scrolling to load more products.",
script: "# Navigate to catalog\nCLICK `#catalog-link`\nWAIT `.product-grid` 3\n\n# Scroll to load more products\nSCROLL DOWN 500\nWAIT 1\nSCROLL DOWN 500\nWAIT 1\n\n# Apply a filter\nCLICK `.filter-group input[type=\"checkbox\"]`",
validate: () => true
},
{
title: "Advanced - Procedures",
description: "Create reusable command groups with PROC for common tasks.",
script: "# Define a procedure to check login status\nPROC check_login\n IF (NOT EXISTS `.user-info`) THEN CLICK `#login-btn`\n IF (NOT EXISTS `.user-info`) THEN WAIT `.login-form` 2\n IF (NOT EXISTS `.user-info`) THEN SET `#email` \"demo@example.com\"\n IF (NOT EXISTS `.user-info`) THEN SET `#password` \"demo123\"\n IF (NOT EXISTS `.user-info`) THEN CLICK `button[type=\"submit\"]`\nENDPROC\n\n# Example: Navigate between sections\nCLICK `a[href=\"#tabs\"]`\nWAIT 1\nCLICK `a[href=\"#forms\"]`\nWAIT 1\n\n# If we get logged out, use our procedure\ncheck_login",
validate: () => true
},
{
title: "More Commands",
description: "Explore additional C4A commands with the Forms section.",
script: "# First, let's fill the contact form with variables\n\n# Set variables\nSETVAR name = \"Alice Smith\"\nSETVAR msg = \"I'd like to know more about your Premium plan!\"\n\n# Fill contact form\nSET `#contact-name` $name\nSET `#contact-email` \"alice@example.com\"\nSET `#contact-message` $msg\n\n# Select dropdown option\nCLICK `#contact-subject`\nCLICK `option[value=\"support\"]`\nWAIT 0.5\n\n# Submit contact form\nCLICK `.btn-primary`\nWAIT 1\n\n# Now let's do the multi-step survey\nSCROLL DOWN 400\nWAIT 0.5\n\n# Step 1: Personal info\nSET `#full-name` \"Bob Johnson\"\nSET `#survey-email` \"bob@example.com\"\nCLICK `.next-step`\nWAIT 0.5\n\n# Step 2: Select interests (multi-select)\nCLICK `#interests`\nCLICK `option[value=\"technology\"]`\nCLICK `option[value=\"science\"]`\nCLICK `.next-step`\nWAIT 0.5\n\n# Step 3: Submit survey\nCLICK `#submit-survey`\n\n# Check results with JavaScript\nEVAL `console.log('Forms completed successfully!')`",
validate: () => true
},
{
title: "Congratulations!",
description: "You've mastered C4A-Script basics! You can now automate complex web interactions. Try the examples or create your own scripts.",
script: "# 🎉 You've completed the tutorial!\n\n# You learned:\n# ✓ WAIT - Wait for elements or time\n# ✓ IF/THEN - Conditional actions\n# ✓ NOT - Negate conditions\n# ✓ CLICK - Click elements\n# ✓ SET - Set input field values\n# ✓ SETVAR - Create variables\n# ✓ SCROLL - Scroll the page\n# ✓ PROC - Create procedures\n# ✓ And much more!\n\n# Try the Examples button for more scripts\n# Happy automating with C4A-Script!",
validate: () => true
}
];
}
startTutorial() {
this.tutorialMode = true;
this.currentStep = 0;
document.getElementById('tutorial-nav').classList.remove('hidden');
document.querySelector('.app-container').classList.add('tutorial-active');
this.showStep(0);
}
exitTutorial() {
this.tutorialMode = false;
document.getElementById('tutorial-nav').classList.add('hidden');
document.querySelector('.app-container').classList.remove('tutorial-active');
}
showStep(index) {
const step = this.tutorialSteps[index];
if (!step) return;
// Update navigation UI
document.getElementById('tutorial-step-info').textContent = `Step ${index + 1} of ${this.tutorialSteps.length}`;
document.getElementById('tutorial-title').textContent = step.title;
// Update progress bar
const progress = ((index + 1) / this.tutorialSteps.length) * 100;
document.getElementById('tutorial-progress-fill').style.width = `${progress}%`;
// Update buttons
document.getElementById('tutorial-prev').disabled = index === 0;
const nextBtn = document.getElementById('tutorial-next');
if (index === this.tutorialSteps.length - 1) {
nextBtn.textContent = 'Finish';
} else {
nextBtn.textContent = 'Next →';
}
// Set script
this.editor.setValue(step.script);
// Update description in nav bar
document.getElementById('tutorial-description').textContent = step.description;
// Focus editor after setting content and ensure it's editable
// Use requestAnimationFrame for better timing
requestAnimationFrame(() => {
this.editor.setOption('readOnly', false);
this.editor.refresh();
// Place cursor at end first, then focus
const doc = this.editor.getDoc();
const lastLine = doc.lineCount() - 1;
const lastCh = doc.getLine(lastLine).length;
doc.setCursor(lastLine, lastCh);
this.editor.focus();
});
}
// Removed showTooltip method - no longer needed
nextStep() {
if (this.currentStep < this.tutorialSteps.length - 1) {
this.currentStep++;
this.showStep(this.currentStep);
} else {
this.exitTutorial();
}
}
prevStep() {
if (this.currentStep > 0) {
this.currentStep--;
this.showStep(this.currentStep);
}
}
async runScript() {
const script = this.editor.getValue();
if (!script.trim()) {
this.addConsoleMessage('No script to run', 'error');
return;
}
this.clearConsole();
this.switchTab('console');
this.addConsoleMessage('Compiling C4A script...');
try {
// If in JS edit mode, use the edited JS directly
if (this.isEditingJS) {
const jsCode = document.getElementById('js-output').textContent.split('\n').filter(line => line.trim());
await this.executeJS(jsCode);
return;
}
// Compile C4A to JS
const compiled = await this.compileScript(script);
if (compiled.success) {
this.currentJS = compiled.jsCode;
this.displayJS(compiled.jsCode);
this.addConsoleMessage(`✓ Compiled successfully (${compiled.jsCode.length} statements)`, 'success');
// Execute the JS
await this.executeJS(compiled.jsCode);
} else {
// Show compilation error
const error = compiled.error;
this.addConsoleMessage(`✗ Compilation error at line ${error.line}:${error.column}`, 'error');
this.addConsoleMessage(` ${error.message}`, 'error');
if (error.suggestion) {
this.addConsoleMessage(` 💡 ${error.suggestion}`, 'warning');
}
}
} catch (error) {
this.addConsoleMessage(`Error: ${error.message}`, 'error');
}
}
async compileScript(script) {
try {
const response = await fetch('/api/compile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ script })
});
if (!response.ok) {
throw new Error('Compilation service unavailable');
}
return await response.json();
} catch (error) {
// Return error if compilation service is unavailable
return {
success: false,
error: {
line: 1,
column: 1,
message: "C4A compilation service unavailable. Please ensure the server is running.",
suggestion: "Start the C4A server or check your connection"
}
};
}
}
displayJS(jsCode) {
const formatted = jsCode.map((line, i) =>
`${(i + 1).toString().padStart(2, ' ')}. ${line}`
).join('\n');
document.getElementById('js-output').textContent = formatted;
}
async executeJS(jsCode) {
this.addConsoleMessage('Executing JavaScript...');
// Send all code to iframe to execute at once
await this.executeInIframe(jsCode);
}
async executeInIframe(jsCode) {
const iframe = document.getElementById('playground-frame');
const iframeWindow = iframe.contentWindow;
// Create a unique ID for this execution
const executionId = 'exec_' + Date.now();
// Create the full script to execute in iframe
const fullScript = `
(async () => {
const results = [];
try {
${jsCode.map((code, i) => `
try {
${code}
results.push({ index: ${i}, success: true, code: ${JSON.stringify(code)} });
} catch (error) {
results.push({ index: ${i}, success: false, error: error.message, code: ${JSON.stringify(code)} });
throw error; // Stop execution on first error
}
`).join('\n')}
// Send success message
window.parent.postMessage({
type: 'c4a-execution-complete',
id: '${executionId}',
success: true,
results: results
}, '*');
} catch (error) {
// Send error message
window.parent.postMessage({
type: 'c4a-execution-complete',
id: '${executionId}',
success: false,
error: error.message,
results: results
}, '*');
}
})();
`;
// Wait for execution result
return new Promise((resolve, reject) => {
const messageHandler = (event) => {
if (event.data.type === 'c4a-execution-complete' && event.data.id === executionId) {
window.removeEventListener('message', messageHandler);
// Log results
if (event.data.results) {
event.data.results.forEach(result => {
if (result.success) {
this.addConsoleMessage(`${result.code}`, 'success');
} else {
this.addConsoleMessage(`${result.code}`, 'error');
this.addConsoleMessage(` Error: ${result.error}`, 'error');
}
});
}
if (event.data.success) {
this.addConsoleMessage('Execution completed successfully', 'success');
resolve();
} else {
this.addConsoleMessage('Execution stopped due to error', 'error');
resolve(); // Still resolve to not break the flow
}
}
};
window.addEventListener('message', messageHandler);
// Inject and execute the script
const script = iframe.contentDocument.createElement('script');
script.textContent = fullScript;
iframe.contentDocument.body.appendChild(script);
script.remove();
// Timeout after 10 seconds
setTimeout(() => {
window.removeEventListener('message', messageHandler);
this.addConsoleMessage('Execution timeout', 'warning');
resolve();
}, 10000);
});
}
highlightElement(element) {
const originalBorder = element.style.border;
const originalBackground = element.style.backgroundColor;
element.style.border = '2px solid #0fbbaa';
element.style.backgroundColor = 'rgba(15, 187, 170, 0.1)';
setTimeout(() => {
element.style.border = originalBorder;
element.style.backgroundColor = originalBackground;
}, 1000);
}
// Removed progress methods - everything goes to console now
addConsoleMessage(message, type = 'text') {
const consoleEl = document.getElementById('console-output');
const line = document.createElement('div');
line.className = 'console-line';
line.innerHTML = `
<span class="console-prompt">$</span>
<span class="console-${type}">${message}</span>
`;
consoleEl.appendChild(line);
// Scroll the console container (parent of console-output)
const consoleContainer = consoleEl.parentElement;
if (consoleContainer) {
// Use requestAnimationFrame to ensure DOM has updated
requestAnimationFrame(() => {
consoleContainer.scrollTop = consoleContainer.scrollHeight;
});
}
}
clearConsole() {
document.getElementById('console-output').innerHTML = `
<div class="console-line">
<span class="console-prompt">$</span>
<span class="console-text">Ready to run C4A scripts...</span>
</div>
`;
}
copyJS() {
const text = this.currentJS.join('\n');
navigator.clipboard.writeText(text).then(() => {
this.addConsoleMessage('JavaScript copied to clipboard', 'success');
});
}
toggleJSEdit() {
this.isEditingJS = !this.isEditingJS;
const editBtn = document.getElementById('edit-js-btn');
const jsOutput = document.getElementById('js-output');
if (this.isEditingJS) {
editBtn.innerHTML = '<span>💾</span>';
jsOutput.contentEditable = true;
jsOutput.style.outline = '1px solid #0fbbaa';
this.addConsoleMessage('JS edit mode enabled - modify and run', 'warning');
} else {
editBtn.innerHTML = '<span>✏️</span>';
jsOutput.contentEditable = false;
jsOutput.style.outline = 'none';
this.addConsoleMessage('JS edit mode disabled', 'text');
}
}
resetPlayground() {
const iframe = document.getElementById('playground-frame');
iframe.src = iframe.src;
this.addConsoleMessage('Playground reset', 'success');
}
toggleFullscreen() {
const playgroundPanel = document.querySelector('.playground-panel');
playgroundPanel.classList.toggle('fullscreen');
}
showExamples() {
const examples = [
{
name: 'Quick Start - Handle Popups',
script: `# Handle popups quickly\nWAIT \`body\` 2\nIF (EXISTS \`.cookie-banner\`) THEN CLICK \`.accept\`\nWAIT 3\nIF (EXISTS \`#newsletter-popup\`) THEN CLICK \`.close\``
},
{
name: 'Complete Login Flow',
script: `# Full login process\nCLICK \`#login-btn\`\nWAIT \`.login-form\` 2\n\n# Set credentials using the new SET command\nSET \`#email\` "demo@example.com"\nSET \`#password\` "demo123"\n\n# Submit\nCLICK \`button[type="submit"]\`\nWAIT \`.success\` 2`
},
{
name: 'Product Browsing',
script: `# Browse products with filters\nCLICK \`#catalog-link\`\nWAIT \`.product-grid\` 3\n\n# Apply filters\nCLICK \`.filter-button\`\nWAIT 0.5\nCLICK \`input[value="electronics"]\`\n\n# Load more products\nREPEAT (SCROLL DOWN 800, 3)\nWAIT 1`
},
{
name: 'Advanced Form Wizard',
script: `# Multi-step form with validation\nCLICK \`a[href="#forms"]\`\nWAIT \`#survey-form\` 2\n\n# Step 1: Personal Info\nSET \`#full-name\` "John Doe"\nSET \`#survey-email\` "john@example.com"\nCLICK \`.next-step\`\nWAIT 1\n\n# Step 2: Preferences\nCLICK \`#interests\`\nCLICK \`option[value="tech"]\`\nCLICK \`option[value="music"]\`\nCLICK \`.next-step\`\nWAIT 1\n\n# Step 3: Submit\nIF (EXISTS \`#comments\`) THEN SET \`#comments\` "Great experience!"\nCLICK \`#submit-survey\`\nWAIT \`.success-message\` 3`
}
];
const currentIndex = parseInt(localStorage.getItem('exampleIndex') || '0');
const example = examples[currentIndex % examples.length];
this.editor.setValue(example.script);
this.addConsoleMessage(`Loaded example: ${example.name}`, 'success');
localStorage.setItem('exampleIndex', ((currentIndex + 1) % examples.length).toString());
}
wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Add animations CSS
const style = document.createElement('style');
style.textContent = `
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeOut {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-10px); }
}
`;
document.head.appendChild(style);
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
window.tutorialApp = new TutorialApp();
console.log('🎓 C4A-Script Tutorial initialized!');
});

View File

@@ -0,0 +1,531 @@
/* DankMono Font Faces */
@font-face {
font-family: 'DankMono';
src: url('DankMono-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'DankMono';
src: url('DankMono-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'DankMono';
src: url('DankMono-Italic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
}
/* Root Variables - Matching docs theme */
:root {
--global-font-size: 14px;
--global-code-font-size: 13px;
--global-line-height: 1.5em;
--global-space: 10px;
--font-stack: DankMono, Monaco, Courier New, monospace;
--mono-font-stack: DankMono, Monaco, Courier New, monospace;
--background-color: #070708;
--font-color: #e8e9ed;
--invert-font-color: #222225;
--secondary-color: #d5cec0;
--tertiary-color: #a3abba;
--primary-color: #0fbbaa;
--error-color: #ff3c74;
--progress-bar-background: #3f3f44;
--progress-bar-fill: #09b5a5;
--code-bg-color: #3f3f44;
--block-background-color: #202020;
--header-height: 55px;
}
/* Base Styles */
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: var(--font-stack);
font-size: var(--global-font-size);
line-height: var(--global-line-height);
color: var(--font-color);
background-color: var(--background-color);
}
/* Terminal Framework */
.terminal {
min-height: 100vh;
}
.container {
width: 100%;
margin: 0 auto;
}
/* Header */
.header-container {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--header-height);
background-color: var(--background-color);
border-bottom: 1px solid var(--progress-bar-background);
z-index: 1000;
padding: 0 calc(var(--global-space) * 2);
}
.terminal-nav {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
}
.terminal-logo h1 {
margin: 0;
font-size: 1.2em;
color: var(--primary-color);
font-weight: 400;
}
.terminal-menu ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
gap: 2em;
}
.terminal-menu a {
color: var(--secondary-color);
text-decoration: none;
transition: color 0.2s;
}
.terminal-menu a:hover,
.terminal-menu a.active {
color: var(--primary-color);
}
/* Main Container */
.main-container {
padding-top: calc(var(--header-height) + 2em);
padding-left: 2em;
padding-right: 2em;
max-width: 1400px;
margin: 0 auto;
}
/* Tutorial Grid */
.tutorial-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2em;
align-items: start;
}
/* Terminal Cards */
.terminal-card {
background-color: var(--block-background-color);
border: 1px solid var(--progress-bar-background);
margin-bottom: 1.5em;
}
.terminal-card header {
background-color: var(--progress-bar-background);
padding: 0.8em 1em;
font-weight: 700;
color: var(--font-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.terminal-card > div {
padding: 1.5em;
}
/* Editor Section */
.editor-controls {
display: flex;
gap: 0.5em;
}
.editor-container {
height: 300px;
overflow: hidden;
}
#c4a-editor {
width: 100%;
height: 100%;
font-family: var(--mono-font-stack);
font-size: var(--global-code-font-size);
background-color: var(--code-bg-color);
color: var(--font-color);
border: none;
padding: 1em;
resize: none;
}
/* JS Output */
.js-output-container {
max-height: 300px;
overflow-y: auto;
}
.js-output-container pre {
margin: 0;
padding: 1em;
background-color: var(--code-bg-color);
}
.js-output-container code {
font-family: var(--mono-font-stack);
font-size: var(--global-code-font-size);
color: var(--font-color);
white-space: pre-wrap;
}
/* Console Output */
.console-output {
font-family: var(--mono-font-stack);
font-size: var(--global-code-font-size);
max-height: 200px;
overflow-y: auto;
padding: 1em;
}
.console-line {
margin-bottom: 0.5em;
}
.console-prompt {
color: var(--primary-color);
margin-right: 0.5em;
}
.console-text {
color: var(--font-color);
}
.console-error {
color: var(--error-color);
}
.console-success {
color: var(--primary-color);
}
/* Playground */
.playground-container {
height: 600px;
background-color: #fff;
border: 1px solid var(--progress-bar-background);
}
#playground-frame {
width: 100%;
height: 100%;
border: none;
}
/* Execution Progress */
.execution-progress {
padding: 1em;
}
.progress-item {
display: flex;
align-items: center;
gap: 0.8em;
margin-bottom: 0.8em;
color: var(--secondary-color);
}
.progress-item.active {
color: var(--primary-color);
}
.progress-item.completed {
color: var(--tertiary-color);
}
.progress-item.error {
color: var(--error-color);
}
.progress-icon {
font-size: 1.2em;
}
/* Buttons */
.btn {
background-color: var(--primary-color);
color: var(--background-color);
border: none;
padding: 0.5em 1em;
font-family: var(--font-stack);
font-size: 0.9em;
cursor: pointer;
transition: all 0.2s;
}
.btn:hover {
background-color: var(--progress-bar-fill);
}
.btn-sm {
padding: 0.3em 0.8em;
font-size: 0.85em;
}
.btn-ghost {
background-color: transparent;
color: var(--secondary-color);
border: 1px solid var(--progress-bar-background);
}
.btn-ghost:hover {
background-color: var(--progress-bar-background);
color: var(--font-color);
}
/* Scrollbars */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--block-background-color);
}
::-webkit-scrollbar-thumb {
background: var(--progress-bar-background);
}
::-webkit-scrollbar-thumb:hover {
background: var(--secondary-color);
}
/* CodeMirror Theme Override */
.CodeMirror {
font-family: var(--mono-font-stack) !important;
font-size: var(--global-code-font-size) !important;
background-color: var(--code-bg-color) !important;
color: var(--font-color) !important;
height: 100% !important;
}
.CodeMirror-gutters {
background-color: var(--progress-bar-background) !important;
border-right: 1px solid var(--progress-bar-background) !important;
}
/* Responsive */
@media (max-width: 1200px) {
.tutorial-grid {
grid-template-columns: 1fr;
}
.playground-section {
order: -1;
}
}
/* Links */
a {
color: var(--primary-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* Lists */
ul, ol {
padding-left: 2em;
}
li {
margin-bottom: 0.5em;
}
/* Code */
code {
background-color: var(--code-bg-color);
padding: 0.2em 0.4em;
font-family: var(--mono-font-stack);
font-size: 0.9em;
}
/* Headings */
h1, h2, h3, h4, h5, h6 {
font-weight: 700;
margin-top: 1.5em;
margin-bottom: 0.8em;
}
h3 {
color: var(--primary-color);
font-size: 1.1em;
}
/* Tutorial Panel */
.tutorial-panel {
position: absolute;
top: 60px;
right: 20px;
width: 380px;
background: #1a1a1b;
border: 1px solid #2a2a2c;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
z-index: 1000;
transition: all 0.3s ease;
}
.tutorial-panel.hidden {
display: none;
}
.tutorial-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #2a2a2c;
}
.tutorial-header h3 {
margin: 0;
color: #0fbbaa;
font-size: 18px;
}
.close-btn {
background: none;
border: none;
color: #8b8b8d;
font-size: 24px;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 0.2s;
}
.close-btn:hover {
background: #2a2a2c;
color: #e0e0e0;
}
.tutorial-content {
padding: 20px;
}
.tutorial-content p {
margin: 0 0 16px 0;
color: #e0e0e0;
line-height: 1.6;
}
.tutorial-progress {
margin-top: 16px;
}
.tutorial-progress span {
display: block;
margin-bottom: 8px;
color: #8b8b8d;
font-size: 12px;
text-transform: uppercase;
}
.progress-bar {
height: 4px;
background: #2a2a2c;
border-radius: 2px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #0fbbaa;
transition: width 0.3s ease;
}
.tutorial-actions {
display: flex;
gap: 12px;
padding: 0 20px 20px;
}
.tutorial-btn {
flex: 1;
padding: 10px 16px;
background: #2a2a2c;
color: #e0e0e0;
border: 1px solid #3a3a3c;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.tutorial-btn:hover:not(:disabled) {
background: #3a3a3c;
transform: translateY(-1px);
}
.tutorial-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.tutorial-btn.primary {
background: #0fbbaa;
color: #070708;
border-color: #0fbbaa;
}
.tutorial-btn.primary:hover {
background: #0da89a;
border-color: #0da89a;
}
/* Tutorial Highlights */
.tutorial-highlight {
position: relative;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(15, 187, 170, 0.4);
}
50% {
box-shadow: 0 0 0 10px rgba(15, 187, 170, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(15, 187, 170, 0);
}
}
.editor-card {
position: relative;
}