rubydojo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +25 -0
- data/Rakefile +6 -0
- data/app/assets/stylesheets/rubydojo/application.css +738 -0
- data/app/controllers/rubydojo/application_controller.rb +4 -0
- data/app/controllers/rubydojo/dashboard_controller.rb +13 -0
- data/app/controllers/rubydojo/lessons_controller.rb +99 -0
- data/app/helpers/rubydojo/application_helper.rb +4 -0
- data/app/models/rubydojo/application_record.rb +5 -0
- data/app/views/layouts/rubydojo/application.html.erb +61 -0
- data/app/views/rubydojo/dashboard/index.html.erb +56 -0
- data/app/views/rubydojo/lessons/show.html.erb +220 -0
- data/config/routes.rb +7 -0
- data/lib/rubydojo/engine.rb +5 -0
- data/lib/rubydojo/lessons.rb +736 -0
- data/lib/rubydojo/version.rb +3 -0
- data/lib/rubydojo.rb +7 -0
- data/lib/tasks/rubydojo_tasks.rake +4 -0
- metadata +82 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Rubydojo
|
|
2
|
+
class DashboardController < ApplicationController
|
|
3
|
+
def index
|
|
4
|
+
@lessons = Rubydojo::Lesson.all
|
|
5
|
+
@completed_lessons = session[:completed_lessons] || []
|
|
6
|
+
@progress_percent = if @lessons.any?
|
|
7
|
+
((@completed_lessons.size.to_f / @lessons.size) * 100).round
|
|
8
|
+
else
|
|
9
|
+
0
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
module Rubydojo
|
|
2
|
+
class LessonsController < ApplicationController
|
|
3
|
+
before_action :find_lesson, only: [:show, :run, :validate]
|
|
4
|
+
|
|
5
|
+
def show
|
|
6
|
+
@completed_lessons = session[:completed_lessons] || []
|
|
7
|
+
@is_completed = @completed_lessons.include?(@lesson.id.to_s)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def run
|
|
11
|
+
code = params[:code].to_s
|
|
12
|
+
eval_result = evaluate_ruby(code)
|
|
13
|
+
|
|
14
|
+
render json: {
|
|
15
|
+
stdout: eval_result[:stdout],
|
|
16
|
+
result: eval_result[:result],
|
|
17
|
+
error: eval_result[:error]
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def validate
|
|
22
|
+
code = params[:code].to_s
|
|
23
|
+
validation_code = @lesson.validation_code
|
|
24
|
+
|
|
25
|
+
eval_result = evaluate_ruby(code, validation_code)
|
|
26
|
+
|
|
27
|
+
if eval_result[:error].nil?
|
|
28
|
+
# Mark lesson as completed
|
|
29
|
+
session[:completed_lessons] ||= []
|
|
30
|
+
unless session[:completed_lessons].include?(@lesson.id.to_s)
|
|
31
|
+
session[:completed_lessons] << @lesson.id.to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
render json: {
|
|
35
|
+
success: true,
|
|
36
|
+
stdout: eval_result[:stdout],
|
|
37
|
+
result: eval_result[:result]
|
|
38
|
+
}
|
|
39
|
+
else
|
|
40
|
+
render json: {
|
|
41
|
+
success: false,
|
|
42
|
+
error: eval_result[:error],
|
|
43
|
+
stdout: eval_result[:stdout]
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def find_lesson
|
|
51
|
+
@lesson = Rubydojo::Lesson.find(params[:id])
|
|
52
|
+
unless @lesson
|
|
53
|
+
redirect_to root_path, alert: "Lesson not found."
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def evaluate_ruby(user_code, validation_code = nil)
|
|
58
|
+
require "stringio"
|
|
59
|
+
|
|
60
|
+
stdout_io = StringIO.new
|
|
61
|
+
original_stdout = $stdout
|
|
62
|
+
$stdout = stdout_io
|
|
63
|
+
|
|
64
|
+
error = nil
|
|
65
|
+
result = nil
|
|
66
|
+
|
|
67
|
+
# Create an isolated sandbox class and instance to prevent global namespace/constant pollution
|
|
68
|
+
sandbox_class = Class.new
|
|
69
|
+
sandbox_class.class_eval do
|
|
70
|
+
def get_binding
|
|
71
|
+
binding
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
sandbox_instance = sandbox_class.new
|
|
75
|
+
eval_binding = sandbox_instance.get_binding
|
|
76
|
+
|
|
77
|
+
begin
|
|
78
|
+
# Evaluate user code
|
|
79
|
+
result = eval(user_code, eval_binding, "(user_code)", 1)
|
|
80
|
+
|
|
81
|
+
# Evaluate validation code if provided
|
|
82
|
+
if validation_code.present?
|
|
83
|
+
eval(validation_code, eval_binding, "(validation_code)", 1)
|
|
84
|
+
end
|
|
85
|
+
rescue Exception => e
|
|
86
|
+
error = e.message
|
|
87
|
+
ensure
|
|
88
|
+
# Restore stdout
|
|
89
|
+
$stdout = original_stdout
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
{
|
|
93
|
+
stdout: stdout_io.string,
|
|
94
|
+
result: result.inspect,
|
|
95
|
+
error: error
|
|
96
|
+
}
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Rubydojo | Interactive Ruby Roadmap & Playground</title>
|
|
7
|
+
<meta name="description" content="An interactive suite inside your Rails development environment designed to accelerate learning the Ruby language with structured roadmaps, live compilation, and exercises.">
|
|
8
|
+
|
|
9
|
+
<%= csrf_meta_tags %>
|
|
10
|
+
<%= csp_meta_tag %>
|
|
11
|
+
|
|
12
|
+
<!-- Google Fonts -->
|
|
13
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
15
|
+
|
|
16
|
+
<!-- CodeMirror CDN Stylesheets -->
|
|
17
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css">
|
|
18
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/material-palenight.min.css">
|
|
19
|
+
|
|
20
|
+
<!-- Engine Custom CSS -->
|
|
21
|
+
<%= stylesheet_link_tag "rubydojo/application", media: "all" %>
|
|
22
|
+
|
|
23
|
+
<%= yield :head %>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
|
|
27
|
+
<!-- Top Glassmorphism Navigation -->
|
|
28
|
+
<header class="app-header">
|
|
29
|
+
<a href="<%= root_path %>" class="logo">
|
|
30
|
+
<svg viewBox="0 0 100 100" style="fill: currentColor; width: 2.2rem; height: 2.2rem; padding: 0.2rem; background: linear-gradient(135deg, var(--color-primary), var(--color-secondary)); border-radius: 0.5rem; color: white; box-shadow: 0 4px 10px var(--color-primary-glow);">
|
|
31
|
+
<polygon points="50,15 80,15 90,40 50,85 10,40 20,15" />
|
|
32
|
+
<polygon points="50,15 20,15 10,40 35,40" style="fill: rgba(255,255,255,0.25);" />
|
|
33
|
+
<polygon points="50,15 80,15 90,40 65,40" style="fill: rgba(0,0,0,0.15);" />
|
|
34
|
+
<polygon points="35,40 65,40 50,85" style="fill: rgba(255,255,255,0.15);" />
|
|
35
|
+
</svg>
|
|
36
|
+
<span>Rubydojo</span>
|
|
37
|
+
</a>
|
|
38
|
+
|
|
39
|
+
<nav class="nav-links">
|
|
40
|
+
<a href="<%= root_path %>" class="nav-btn secondary">Roadmap</a>
|
|
41
|
+
<a href="https://ruby-doc.org" target="_blank" rel="noopener noreferrer" class="nav-btn secondary">Ruby Docs</a>
|
|
42
|
+
<a href="https://guides.rubyonrails.org" target="_blank" rel="noopener noreferrer" class="nav-btn secondary">Rails Guides</a>
|
|
43
|
+
</nav>
|
|
44
|
+
</header>
|
|
45
|
+
|
|
46
|
+
<main>
|
|
47
|
+
<%= yield %>
|
|
48
|
+
</main>
|
|
49
|
+
|
|
50
|
+
<!-- CodeMirror CDN JavaScripts -->
|
|
51
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.js"></script>
|
|
52
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/ruby/ruby.min.js"></script>
|
|
53
|
+
|
|
54
|
+
<!-- Engine Custom JavaScript -->
|
|
55
|
+
<script>
|
|
56
|
+
// Global asset config or simple helpers if needed
|
|
57
|
+
window.Rubydojo = {};
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<div class="container animate-fade-in">
|
|
2
|
+
<!-- Hero Section -->
|
|
3
|
+
<section class="hero-section">
|
|
4
|
+
<h1 class="hero-title">Your Ruby Learning Roadmap</h1>
|
|
5
|
+
<p class="hero-subtitle">
|
|
6
|
+
Master Ruby fundamentals, dynamic programming, and Rails patterns step-by-step with interactive coding exercises.
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<!-- Progress Tracker -->
|
|
10
|
+
<div class="progress-container">
|
|
11
|
+
<div class="progress-text">
|
|
12
|
+
<span>Roadmap Progress</span>
|
|
13
|
+
<strong><%= @completed_lessons.size %> / <%= @lessons.size %> Completed</strong>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="progress-bar-bg">
|
|
16
|
+
<div class="progress-bar-fill" style="width: <%= @progress_percent %>%;"></div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</section>
|
|
20
|
+
|
|
21
|
+
<!-- Interactive Roadmap Path -->
|
|
22
|
+
<section class="roadmap-path">
|
|
23
|
+
<div class="roadmap-line"></div>
|
|
24
|
+
|
|
25
|
+
<% @lessons.each_with_index do |lesson, index| %>
|
|
26
|
+
<% is_done = @completed_lessons.include?(lesson.id.to_s) %>
|
|
27
|
+
<div class="roadmap-node <%= 'completed' if is_done %>" style="animation-delay: <%= index * 0.1 %>s">
|
|
28
|
+
<!-- Node Number Circle -->
|
|
29
|
+
<div class="node-circle">
|
|
30
|
+
<% if is_done %>
|
|
31
|
+
✓
|
|
32
|
+
<% else %>
|
|
33
|
+
<%= index + 1 %>
|
|
34
|
+
<% end %>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<!-- Node Content Card -->
|
|
38
|
+
<div class="node-card <%= 'completed' if is_done %>">
|
|
39
|
+
<div class="node-badge"><%= lesson.level %></div>
|
|
40
|
+
<h2 class="node-title"><%= lesson.title %></h2>
|
|
41
|
+
<p class="node-description"><%= lesson.description %></p>
|
|
42
|
+
|
|
43
|
+
<div class="node-footer">
|
|
44
|
+
<span class="node-status-text">
|
|
45
|
+
<span class="status-indicator <%= is_done ? 'completed' : 'pending' %>"></span>
|
|
46
|
+
<%= is_done ? 'Completed' : 'Not Started' %>
|
|
47
|
+
</span>
|
|
48
|
+
<a href="<%= lesson_path(lesson.id) %>" class="nav-btn <%= is_done ? 'secondary' : 'primary' %>">
|
|
49
|
+
<%= is_done ? 'Review Lesson' : 'Start Lesson' %>
|
|
50
|
+
</a>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
</section>
|
|
56
|
+
</div>
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
<div class="lesson-layout">
|
|
2
|
+
<!-- Left Side: Instruction Pane -->
|
|
3
|
+
<div class="instruction-pane">
|
|
4
|
+
<a href="<%= root_path %>" class="back-link">
|
|
5
|
+
<span>←</span> Back to Roadmap
|
|
6
|
+
</a>
|
|
7
|
+
|
|
8
|
+
<!-- Raw Markdown content stored in a script tag for JS parsing -->
|
|
9
|
+
<script id="markdown-raw" type="text/markdown"><%= raw @lesson.explanation %></script>
|
|
10
|
+
|
|
11
|
+
<!-- Rendered HTML goes here -->
|
|
12
|
+
<div id="markdown-viewer" class="markdown-content"></div>
|
|
13
|
+
|
|
14
|
+
<!-- Hint Box -->
|
|
15
|
+
<% if @lesson.hint.present? %>
|
|
16
|
+
<button id="btn-hint" class="btn btn-secondary" style="margin-top: 2rem; width: 100%;">
|
|
17
|
+
💡 Toggle Hint
|
|
18
|
+
</button>
|
|
19
|
+
<div id="hint-box" class="hint-box">
|
|
20
|
+
<strong>Hint:</strong> <%= @lesson.hint %>
|
|
21
|
+
</div>
|
|
22
|
+
<% end %>
|
|
23
|
+
|
|
24
|
+
<!-- Success banner if completed -->
|
|
25
|
+
<div id="success-banner" class="success-banner" style="display: <%= @is_completed ? 'flex' : 'none' %>;">
|
|
26
|
+
<span class="success-banner-icon">🎉</span>
|
|
27
|
+
<div>
|
|
28
|
+
<strong>Excellent Work!</strong> You've successfully completed this lesson.
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Right Side: Code Editor and Terminal Console -->
|
|
34
|
+
<div class="workspace-pane">
|
|
35
|
+
<div class="pane-header">
|
|
36
|
+
<div class="pane-title">
|
|
37
|
+
<span>solution.rb</span>
|
|
38
|
+
</div>
|
|
39
|
+
<span class="node-badge" style="color: var(--color-primary);"><%= @lesson.level %></span>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Editor -->
|
|
43
|
+
<div class="editor-container">
|
|
44
|
+
<textarea id="code-editor"><%= @lesson.code_template %></textarea>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<!-- Simulated Terminal -->
|
|
48
|
+
<div class="terminal-container">
|
|
49
|
+
<div class="terminal-header">
|
|
50
|
+
<span>CONSOLE OUTPUT</span>
|
|
51
|
+
<span id="terminal-clear" style="cursor: pointer; hover: color: white;">Clear</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div id="terminal-body" class="terminal-body">
|
|
54
|
+
<div class="terminal-line system">
|
|
55
|
+
<span class="prompt-symbol">system ></span>
|
|
56
|
+
<span>Welcome to Rubydojo interactive terminal. Write code and hit Run.</span>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<!-- Workspace Actions -->
|
|
62
|
+
<div class="workspace-actions">
|
|
63
|
+
<button id="btn-run" class="btn btn-secondary">
|
|
64
|
+
▶ Run Code
|
|
65
|
+
</button>
|
|
66
|
+
|
|
67
|
+
<button id="btn-validate" class="btn btn-primary">
|
|
68
|
+
✔ Validate Solution
|
|
69
|
+
</button>
|
|
70
|
+
|
|
71
|
+
<% next_les = Rubydojo::Lesson.next_lesson(@lesson.id) %>
|
|
72
|
+
<% if next_les %>
|
|
73
|
+
<a id="btn-next" href="<%= lesson_path(next_les.id) %>" class="btn btn-success" style="display: <%= @is_completed ? 'inline-flex' : 'none' %>;">
|
|
74
|
+
Next Lesson ➔
|
|
75
|
+
</a>
|
|
76
|
+
<% else %>
|
|
77
|
+
<a id="btn-next" href="<%= root_path %>" class="btn btn-success" style="display: <%= @is_completed ? 'inline-flex' : 'none' %>;">
|
|
78
|
+
Roadmap Complete ➔
|
|
79
|
+
</a>
|
|
80
|
+
<% end %>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<!-- Load Marked.js from CDN for browser markdown parsing -->
|
|
86
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
87
|
+
|
|
88
|
+
<script>
|
|
89
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
90
|
+
// 1. Render Markdown Explanation
|
|
91
|
+
const rawMarkdown = document.getElementById("markdown-raw").textContent;
|
|
92
|
+
document.getElementById("markdown-viewer").innerHTML = marked.parse(rawMarkdown);
|
|
93
|
+
|
|
94
|
+
// 2. Initialize CodeMirror Editor
|
|
95
|
+
const editorEl = document.getElementById("code-editor");
|
|
96
|
+
const editor = CodeMirror.fromTextArea(editorEl, {
|
|
97
|
+
mode: "ruby",
|
|
98
|
+
theme: "material-palenight",
|
|
99
|
+
lineNumbers: true,
|
|
100
|
+
tabSize: 2,
|
|
101
|
+
indentWithTabs: false,
|
|
102
|
+
smartIndent: true,
|
|
103
|
+
lineWrapping: true
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// 3. Setup Terminal helpers
|
|
107
|
+
const terminalBody = document.getElementById("terminal-body");
|
|
108
|
+
|
|
109
|
+
function writeToTerminal(content, type = 'output') {
|
|
110
|
+
const line = document.createElement("div");
|
|
111
|
+
line.className = `terminal-line ${type}`;
|
|
112
|
+
|
|
113
|
+
const prompt = document.createElement("span");
|
|
114
|
+
prompt.className = "prompt-symbol";
|
|
115
|
+
prompt.textContent = type === 'system' ? "system >" : "ruby >";
|
|
116
|
+
|
|
117
|
+
const text = document.createElement("span");
|
|
118
|
+
text.textContent = content;
|
|
119
|
+
|
|
120
|
+
line.appendChild(prompt);
|
|
121
|
+
line.appendChild(text);
|
|
122
|
+
terminalBody.appendChild(line);
|
|
123
|
+
|
|
124
|
+
// Auto scroll terminal to bottom
|
|
125
|
+
terminalBody.scrollTop = terminalBody.scrollHeight;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
document.getElementById("terminal-clear").addEventListener("click", () => {
|
|
129
|
+
terminalBody.innerHTML = '';
|
|
130
|
+
writeToTerminal("Console cleared.", "system");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// 4. Toggle Hint Box
|
|
134
|
+
const btnHint = document.getElementById("btn-hint");
|
|
135
|
+
if (btnHint) {
|
|
136
|
+
btnHint.addEventListener("click", () => {
|
|
137
|
+
const hintBox = document.getElementById("hint-box");
|
|
138
|
+
hintBox.classList.toggle("visible");
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 5. Run Code AJAX Action
|
|
143
|
+
const btnRun = document.getElementById("btn-run");
|
|
144
|
+
btnRun.addEventListener("click", function() {
|
|
145
|
+
const code = editor.getValue();
|
|
146
|
+
btnRun.disabled = true;
|
|
147
|
+
btnRun.innerHTML = `<span class="spinner"></span> Running...`;
|
|
148
|
+
|
|
149
|
+
fetch("<%= run_lesson_path(@lesson.id) %>", {
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers: {
|
|
152
|
+
"Content-Type": "application/json",
|
|
153
|
+
"X-CSRF-Token": "<%= form_authenticity_token %>"
|
|
154
|
+
},
|
|
155
|
+
body: JSON.stringify({ code: code })
|
|
156
|
+
})
|
|
157
|
+
.then(res => res.json())
|
|
158
|
+
.then(data => {
|
|
159
|
+
btnRun.disabled = false;
|
|
160
|
+
btnRun.textContent = "▶ Run Code";
|
|
161
|
+
|
|
162
|
+
if (data.stdout && data.stdout.trim() !== '') {
|
|
163
|
+
writeToTerminal(data.stdout, 'output');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (data.error) {
|
|
167
|
+
writeToTerminal(`Error: ${data.error}`, 'error');
|
|
168
|
+
} else {
|
|
169
|
+
writeToTerminal(`=> ${data.result}`, 'system');
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
.catch(err => {
|
|
173
|
+
btnRun.disabled = false;
|
|
174
|
+
btnRun.textContent = "▶ Run Code";
|
|
175
|
+
writeToTerminal("Network error while trying to run code.", "error");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// 6. Validate Code AJAX Action
|
|
180
|
+
const btnValidate = document.getElementById("btn-validate");
|
|
181
|
+
btnValidate.addEventListener("click", function() {
|
|
182
|
+
const code = editor.getValue();
|
|
183
|
+
btnValidate.disabled = true;
|
|
184
|
+
btnValidate.innerHTML = `<span class="spinner"></span> Validating...`;
|
|
185
|
+
|
|
186
|
+
fetch("<%= validate_lesson_path(@lesson.id) %>", {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: {
|
|
189
|
+
"Content-Type": "application/json",
|
|
190
|
+
"X-CSRF-Token": "<%= form_authenticity_token %>"
|
|
191
|
+
},
|
|
192
|
+
body: JSON.stringify({ code: code })
|
|
193
|
+
})
|
|
194
|
+
.then(res => res.json())
|
|
195
|
+
.then(data => {
|
|
196
|
+
btnValidate.disabled = false;
|
|
197
|
+
btnValidate.textContent = "✔ Validate Solution";
|
|
198
|
+
|
|
199
|
+
if (data.stdout && data.stdout.trim() !== '') {
|
|
200
|
+
writeToTerminal(data.stdout, 'output');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (data.success) {
|
|
204
|
+
writeToTerminal("✔ SUCCESS: All validation checks passed!", "success");
|
|
205
|
+
document.getElementById("success-banner").style.display = "flex";
|
|
206
|
+
document.getElementById("btn-next").style.display = "inline-flex";
|
|
207
|
+
// Trigger a subtle console success log
|
|
208
|
+
writeToTerminal("Nice job! Move on to the next lesson.", "system");
|
|
209
|
+
} else {
|
|
210
|
+
writeToTerminal(`❌ VALIDATION FAILED: ${data.error}`, 'error');
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
.catch(err => {
|
|
214
|
+
btnValidate.disabled = false;
|
|
215
|
+
btnValidate.textContent = "✔ Validate Solution";
|
|
216
|
+
writeToTerminal("Network error during validation.", "error");
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
</script>
|
data/config/routes.rb
ADDED