CSS
basic selectors.html
●
css/1. selectors/basic selectors.html
Auto
⟲ Reset
▶ Render
⌘↵
basic selectors.html
HTML + CSS
Preview
◐ BG
↗ Pop out
Loading…
×
Embedded from
source ↗
{ b.classList.toggle('active', b.dataset.file === name); }); fileNameEl.firstChild.textContent = name; if(!state.loaded[name]){ editor.setValue('// Loading ' + name + '…'); await loadFile(name); } // Also pre-load any companion files declared via `related` const entry = FILES.find(f => f.file === name); const related = (entry && entry.related) || []; for(const rel of related){ if(!state.loaded[rel]) await loadFile(rel); } // (Re)compute steps from the current "All" buffer (no-op for HTML lessons) state.steps[name] = parseSteps(state.buffers[name] ?? state.originals[name] ?? ''); if(state.activeStep[name] == null){ const ss = state.steps[name]; state.activeStep[name] = (ss && ss.length >= 2) ? ss[ss.length - 1].n : 0; } if(state.activeStep[name] !== 0 && !(state.steps[name] || []).some(s => s.n === state.activeStep[name])){ const ss = state.steps[name]; state.activeStep[name] = (ss && ss.length) ? ss[ss.length - 1].n : 0; } // Default the sub-file selector to the lesson itself if(!state.activeSubfile[name]) state.activeSubfile[name] = name; loadSubfileIntoEditor(); renderStepTabs(); renderFileTabs(); renderFileLinks(); refreshDirty(name); editor.focus(); // Refresh the iframe preview for the newly-active file if(typeof renderPreview === 'function') renderPreview(); } function refreshDirty(name){ // "Dirty" = the lesson's HTML buffer OR any of its related-file buffers // differs from the originals on disk. Switching sub-file tabs is just a // view change, so the editor's current text isn't read here. if(!state.loaded[name]) return; const entry = FILES.find(f => f.file === name); const all = [name, ...((entry && entry.related) || [])]; let dirty = false; for(const p of all){ if(state.buffers[p] != null && state.buffers[p] !== state.originals[p]){ dirty = true; break; } } const btn = document.querySelector(`.file-btn[data-file="${name}"]`); if(btn) btn.classList.toggle('dirty', dirty); if(name === state.active){ fileNameEl.classList.toggle('dirty', dirty); } } /* Sub-file tabs (when a lesson has companion .css/.js files) */ function saveCurrentSubEditor(){ const lesson = state.active; if(!lesson) return; const sub = state.activeSubfile[lesson] || lesson; state.buffers[sub] = editor.getValue(); } function loadSubfileIntoEditor(){ const lesson = state.active; if(!lesson) return; const sub = state.activeSubfile[lesson] || lesson; const content = state.buffers[sub] ?? state.originals[sub] ?? ''; // Match syntax mode to the file's extension const mode = sub.endsWith('.css') ? 'css' : sub.endsWith('.js') ? 'javascript' : 'htmlmixed'; editor.setOption('mode', mode); editor.setValue(content); editor.clearHistory(); // Update chrome name + sub-path display chromeNameEl.textContent = sub.split('/').pop(); fileSubEl.textContent = 'css/' + sub; } function renderFileTabs(){ const tabsEl = document.getElementById('fileTabs'); const lesson = state.active; if(!lesson){ tabsEl.innerHTML = ''; chromeNameEl.style.display = ''; return; } const entry = FILES.find(f => f.file === lesson); const related = (entry && entry.related) || []; const all = [lesson, ...related]; if(all.length < 2){ tabsEl.innerHTML = ''; chromeNameEl.style.display = ''; return; } // Tabs replace the static filename in the chrome chromeNameEl.style.display = 'none'; const cur = state.activeSubfile[lesson] || lesson; const esc = (s) => String(s).replace(/&/g,'&').replace(/ { const name = p.split('/').pop(); const cls = 'file-tab' + (p === cur ? ' active' : ''); return `
${esc(name)}
`; }).join(''); tabsEl.querySelectorAll('.file-tab').forEach(b => { b.addEventListener('click', () => switchSubfile(b.dataset.path)); }); } function switchSubfile(path){ const lesson = state.active; if(!lesson) return; saveCurrentSubEditor(); state.activeSubfile[lesson] = path; loadSubfileIntoEditor(); renderFileTabs(); refreshDirty(lesson); editor.focus(); } editor.on('change', ()=>{ if(!state.active) return; const lesson = state.active; const sub = state.activeSubfile[lesson] || lesson; state.buffers[sub] = editor.getValue(); refreshDirty(lesson); // If the user just edited the lesson HTML, the set of
links // can change — refresh the chip strip (debounced so we don't churn DOM // on every keystroke). if(sub === lesson){ clearTimeout(renderFileLinks._t); renderFileLinks._t = setTimeout(renderFileLinks, 250); } }); /* ========================================================== Reset / Render / Preview ========================================================== */ document.getElementById('resetBtn').addEventListener('click', ()=>{ const lesson = state.active; if(!lesson || state.originals[lesson] == null) return; // Reset both the lesson's HTML and every related companion file const entry = FILES.find(f => f.file === lesson); const all = [lesson, ...((entry && entry.related) || [])]; for(const p of all){ if(state.originals[p] != null) state.buffers[p] = state.originals[p]; } loadSubfileIntoEditor(); refreshDirty(lesson); renderPreview(); toast('Reset ' + lesson); }); document.getElementById('runBtn').addEventListener('click', () => renderPreview()); window.addEventListener('keydown', (e)=>{ if((e.metaKey || e.ctrlKey) && e.key === 'Enter'){ e.preventDefault(); renderPreview(); } }); /* ========================================================== Live preview ========================================================== */ const previewEl = document.getElementById('preview'); const previewCardEl = document.getElementById('previewCard'); const previewStatusEl = document.getElementById('previewStatus'); const autoChk = document.getElementById('autoChk'); /* Compose the lesson's HTML with each companion file inlined, so the user's in-editor edits to a sibling .css / .js show up in the preview. */ function buildPreviewHtml(){ const lesson = state.active; if(!lesson) return ''; // Make sure the active sub-file's editor content is captured before we read // the buffers (the change handler usually does this, but be explicit). saveCurrentSubEditor(); let html = state.buffers[lesson] ?? state.originals[lesson] ?? ''; const entry = FILES.find(f => f.file === lesson); const related = (entry && entry.related) || []; for(const rel of related){ const buf = state.buffers[rel] ?? state.originals[rel] ?? ''; const fname = rel.split('/').pop(); const escName = fname.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); if(rel.endsWith('.css')){ const re = new RegExp(`
]*\\bhref=(?:"${escName}"|'${escName}')[^>]*/?>`, 'i'); html = html.replace(re, `