Skip to content

Commit 1b669c2

Browse files
committed
Add hardening to run button
1 parent a16d030 commit 1b669c2

1 file changed

Lines changed: 73 additions & 1 deletion

File tree

‎ui/src/components/lesson/RunButton.astro‎

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,43 @@ const initialLabel = isDefaultCodespaces
148148
</div>
149149
</div>
150150

151+
<script is:inline>
152+
(function () {
153+
try {
154+
var env = localStorage.getItem('ultimate_python_cloud_env');
155+
if (env === 'replit') {
156+
var btn = document.getElementById('run-primary-btn');
157+
var iCodespaces = document.getElementById('run-icon-codespaces');
158+
var iReplit = document.getElementById('run-icon-replit');
159+
var tFull = document.getElementById('run-text-full');
160+
var tShort = document.getElementById('run-text-short');
161+
var mCodespaces = document.querySelector(
162+
'#run-dropdown-menu [data-env="codespaces"]'
163+
);
164+
var mReplit = document.querySelector(
165+
'#run-dropdown-menu [data-env="replit"]'
166+
);
167+
if (btn && btn.getAttribute('data-replit-url')) {
168+
btn.href = btn.getAttribute('data-replit-url');
169+
btn.setAttribute('aria-label', 'Run on Replit');
170+
}
171+
if (iCodespaces) iCodespaces.style.display = 'none';
172+
if (iReplit) iReplit.style.display = 'inline-flex';
173+
if (tFull) tFull.textContent = 'Run on Replit';
174+
if (tShort) tShort.textContent = 'Run';
175+
if (mCodespaces) {
176+
mCodespaces.classList.remove('active');
177+
mCodespaces.setAttribute('aria-selected', 'false');
178+
}
179+
if (mReplit) {
180+
mReplit.classList.add('active');
181+
mReplit.setAttribute('aria-selected', 'true');
182+
}
183+
}
184+
} catch (e) {}
185+
})();
186+
</script>
187+
151188
<style>
152189
.run-split-container {
153190
position: relative;
@@ -395,11 +432,23 @@ const initialLabel = isDefaultCodespaces
395432
}
396433
}
397434

435+
function openDropdown() {
436+
container!.classList.add('open');
437+
toggleBtn!.setAttribute('aria-expanded', 'true');
438+
menu!.setAttribute('aria-hidden', 'false');
439+
}
440+
398441
function toggleDropdown(e: Event) {
399442
e.stopPropagation();
400443
const isOpen = container!.classList.toggle('open');
401444
toggleBtn!.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
402445
menu!.setAttribute('aria-hidden', isOpen ? 'false' : 'true');
446+
if (isOpen && items.length > 0) {
447+
const activeItem =
448+
menu!.querySelector<HTMLButtonElement>('.dropdown-item.active') ||
449+
items[0];
450+
activeItem?.focus();
451+
}
403452
}
404453

405454
// Bind event listeners if not already bound
@@ -408,7 +457,16 @@ const initialLabel = isDefaultCodespaces
408457

409458
toggleBtn.addEventListener('click', toggleDropdown);
410459

411-
items.forEach((item) => {
460+
toggleBtn.addEventListener('keydown', (e) => {
461+
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
462+
e.preventDefault();
463+
openDropdown();
464+
const targetIndex = e.key === 'ArrowDown' ? 0 : items.length - 1;
465+
items[targetIndex]?.focus();
466+
}
467+
});
468+
469+
items.forEach((item, index) => {
412470
item.addEventListener('click', (e) => {
413471
e.stopPropagation();
414472
const selectedEnv = item.getAttribute('data-env') as
@@ -423,6 +481,20 @@ const initialLabel = isDefaultCodespaces
423481
closeDropdown(true);
424482
}
425483
});
484+
485+
item.addEventListener('keydown', (e) => {
486+
if (e.key === 'ArrowDown') {
487+
e.preventDefault();
488+
const next = items[(index + 1) % items.length];
489+
next?.focus();
490+
} else if (e.key === 'ArrowUp') {
491+
e.preventDefault();
492+
const prev = items[(index - 1 + items.length) % items.length];
493+
prev?.focus();
494+
} else if (e.key === 'Tab') {
495+
closeDropdown(false);
496+
}
497+
});
426498
});
427499

428500
document.addEventListener('click', (e) => {

0 commit comments

Comments
 (0)