Team Ai
Apppublic

chansung/code-explainer-pilot

sourceHugging Faceapache-2.0updated 1y agoView on Hugging Face
2likes
script.js365 linesDownload Raw Back to root
1document.addEventListener('DOMContentLoaded', () => {2        const codeSnippet = `// HTML structure for this example:3//  <button id="counterBtn">Click me: 0</button>4//  <p id="message"></p>5 6const button = document.getElementById('counterBtn');7let count = 0;8 9button.addEventListener('click', () => {10  count++;11  button.textContent = \`Click me: \${count}\`;12 13  if (count === 5) {14    const messageEl = document.getElementById('message');15    messageEl.textContent = "You've reached 5 clicks!";16  } else if (count > 5) {17    const msgEl = document.getElementById('message');18    msgEl.textContent = "Keep clicking!";19  }20});`;21 22        const journeyStructure = [23          {24            groupTitle: "Setup & Initialization",25            steps: [26              { category: "Setup", title: "HTML Structure", lineRanges: [[0, 3]], explanation: "This JavaScript interacts with specific HTML elements. We're assuming a button with <code>id=\"counterBtn\"</code> and a paragraph with <code>id=\"message\"</code> exist on the page. The comments at the top outline this structure." },27              { category: "DOM Query", title: "Access Button Element", lineRanges: [[4, 4]], explanation: "<code>const button = document.getElementById('counterBtn');</code><br>This line uses the DOM API to find the HTML button element with ID 'counterBtn' and stores it in the 'button' constant." },28              { category: "Variable Declaration", title: "Initialize Click Counter", lineRanges: [[5, 5]], explanation: "<code>let count = 0;</code><br>A variable 'count' is declared and initialized to 0. This variable will track clicks." }29            ]30          },31          {32            groupTitle: "Core Interaction Logic",33            steps: [34              { category: "Event Handling", title: "Attach Click Listener", lineRanges: [[7, 7], [16,16]], explanation: "<code>button.addEventListener('click', () => { ... });</code><br>An event listener is attached to the 'button'. The arrow function <code>() => { ... }</code> executes on each click." },35              { category: "State Update", title: "Increment Counter", lineRanges: [[8, 8]], explanation: "<code>count++;</code><br>Inside the click handler, 'count' is incremented by 1." },36              { category: "UI Update", title: "Update Button Text", lineRanges: [[9, 9]], explanation: "<code>button.textContent = \`Click me: \${count}\`;</code><br>The button's text is updated to show the new 'count' using template literals." }37            ]38          },39          {40            groupTitle: "Conditional Feedback",41            steps: [42              { category: "Conditional Logic", title: "Check 5-Click Milestone", lineRanges: [[11, 11],[12,12],[13,13]], explanation: "<code>if (count === 5) { ... }</code><br>If 'count' reaches 5, a special message is displayed in the 'message' paragraph. Note that line 13 is the closing brace of this `if` block." },43              { category: "Conditional Logic", title: "Handle Subsequent Clicks", lineRanges: [[13,13],[14,14],[15,15]], explanation: "<code>else if (count > 5) { ... }</code><br>If 'count' is over 5, a different message encourages continued clicking. Note that line 13 is also the start of this `else if`." }44            ]45          },46          {47            groupTitle: "Conclusion",48            steps: [49              { category: "Summary", title: "Tour Completed!", lineRanges: [], explanation: "You've completed the tour! This example showed DOM manipulation, events, variables, and conditional logic. Click 'Reset' to restart." }50            ]51          }52        ];53 54        const categoryStyles = {55            "Setup": "bg-slate-200 text-slate-700",56            "DOM Query": "bg-sky-100 text-sky-700",57            "Variable Declaration": "bg-emerald-100 text-emerald-700",58            "Event Handling": "bg-violet-100 text-violet-700",59            "State Update": "bg-amber-100 text-amber-700",60            "UI Update": "bg-indigo-100 text-indigo-700",61            "Conditional Logic": "bg-rose-100 text-rose-700",62            "Summary": "bg-cyan-100 text-cyan-700",63            "Default": "bg-gray-200 text-gray-700" // Fallback64        };65 66        const codeLinesContainer = document.getElementById('codeLinesContainer');67        const explanationStepTitle = document.getElementById('explanationStepTitle');68        const explanationMeta = document.getElementById('explanationMeta');69        const explanationText = document.getElementById('explanationText');70        const explanationStepCategoryContainer = document.getElementById('explanationStepCategoryContainer');71        const prevBtn = document.getElementById('prevBtn');72        const nextBtn = document.getElementById('nextBtn');73        const resetBtn = document.getElementById('resetBtn');74        const progressIndicator = document.getElementById('progressIndicator');75        const stepsOutlineUl = document.getElementById('stepsOutline');76        const mobileCurrentGroupTextEl = document.getElementById('mobileCurrentGroupText');77        const mobileCurrentStepTextEl = document.getElementById('mobileCurrentStepText'); // New element78 79        let currentStepIndex = 0;80        let lineElements = [];81        let flatCodeSteps = [];82 83        // Flatten the journeyStructure for sequential navigation84        journeyStructure.forEach(group => {85          group.steps.forEach(step => {86            flatCodeSteps.push(step);87          });88        });89 90        function handleCodeLineClick(event) {91            const clickedLineDiv = event.currentTarget;92            const clickedLineIndex = parseInt(clickedLineDiv.dataset.lineIndex, 10);93 94            for (let i = 0; i < flatCodeSteps.length; i++) {95                const step = flatCodeSteps[i];96                if (step.lineRanges && step.lineRanges.length > 0) {97                    for (const range of step.lineRanges) {98                        if (clickedLineIndex >= range[0] && clickedLineIndex <= range[1]) {99                            currentStepIndex = i;100                            updateStep();101                            return;102                        }103                    }104                }105            }106        }107 108        function renderCode() {109            codeLinesContainer.innerHTML = '';110            lineElements = [];111 112            const tempCodeElement = document.createElement('code');113            tempCodeElement.className = 'language-javascript';114            tempCodeElement.textContent = codeSnippet;115 116            if (typeof Prism !== 'undefined' && Prism.highlightElement) {117                Prism.highlightElement(tempCodeElement);118            } else {119                console.warn("Prism.js not available. Syntax highlighting will be plain.");120            }121 122            const highlightedHtml = tempCodeElement.innerHTML;123            const linesHtml = highlightedHtml.split('\n');124 125            linesHtml.forEach((lineHtml, index) => {126                const lineDiv = document.createElement('div');127                lineDiv.classList.add('code-line');128                lineDiv.innerHTML = lineHtml || '&nbsp;';129                lineDiv.dataset.lineIndex = index;130                lineDiv.addEventListener('click', handleCodeLineClick);131                codeLinesContainer.appendChild(lineDiv);132                lineElements.push(lineDiv);133            });134        }135 136        function renderOutline() {137            stepsOutlineUl.innerHTML = '';138            let globalStepIndex = 0;139 140            journeyStructure.forEach((group, groupIndex) => {141                const groupLi = document.createElement('li');142                groupLi.className = `pt-3 pb-1 px-1 ${groupIndex > 0 ? 'mt-1' : ''}`;143 144                const groupTitleEl = document.createElement('h4');145                groupTitleEl.className = 'text-xs font-semibold text-slate-500 uppercase tracking-wider select-none';146                groupTitleEl.textContent = group.groupTitle;147                groupLi.appendChild(groupTitleEl);148                stepsOutlineUl.appendChild(groupLi);149 150                group.steps.forEach((step, stepIndexInGroup) => {151                    const li = document.createElement('li');152                    li.className = 'outline-step-item rounded-md ml-1';153                    li.dataset.stepIndex = globalStepIndex;154 155                    const button = document.createElement('button');156                    button.className = 'group flex items-start w-full text-left p-2 focus:outline-none focus:ring-2 focus:ring-sky-300 focus:z-10 rounded-md transition-colors hover:bg-slate-100';157 158                    const dotAndLineContainer = document.createElement('div');159                    dotAndLineContainer.className = 'flex flex-col items-center mr-3 mt-1 flex-shrink-0';160 161                    const dot = document.createElement('div');162                    dot.className = 'outline-dot w-3 h-3 rounded-full bg-slate-300 border-2 border-white group-hover:bg-sky-400 transition-all duration-150 ease-in-out';163                    dotAndLineContainer.appendChild(dot);164 165                    if (globalStepIndex < flatCodeSteps.length - 1) {166                        const isLastInGroup = stepIndexInGroup === group.steps.length - 1;167                        const isLastGroup = groupIndex === journeyStructure.length - 1;168                        if (isLastInGroup && !(isLastGroup && isLastInGroup) ) {169                            const line = document.createElement('div');170                            line.className = 'outline-connector w-0.5 h-2 bg-slate-300 mt-1 group-hover:bg-slate-400 transition-colors duration-150 ease-in-out';171                            dotAndLineContainer.appendChild(line);172                        } else if (!(isLastGroup && isLastInGroup)){173                            const line = document.createElement('div');174                            line.className = 'outline-connector w-0.5 h-5 bg-slate-300 mt-1 group-hover:bg-slate-400 transition-colors duration-150 ease-in-out';175                            dotAndLineContainer.appendChild(line);176                        } else {177                            const placeholder = document.createElement('div');178                            placeholder.className = 'h-5 mt-1';179                            dotAndLineContainer.appendChild(placeholder);180                        }181                    } else {182                        const placeholder = document.createElement('div');183                        placeholder.className = 'h-5 mt-1';184                        dotAndLineContainer.appendChild(placeholder);185                    }186 187 188                    const titleSpan = document.createElement('span');189                    titleSpan.className = 'outline-title text-sm text-slate-600 group-hover:text-sky-600 transition-colors duration-150 ease-in-out';190                    titleSpan.textContent = step.title;191 192                    button.appendChild(dotAndLineContainer);193                    button.appendChild(titleSpan);194                    li.appendChild(button);195 196                    button.addEventListener('click', () => {197                        currentStepIndex = parseInt(li.dataset.stepIndex, 10);198                        updateStep();199                    });200                    stepsOutlineUl.appendChild(li);201                    globalStepIndex++;202                });203            });204        }205 206        function updateOutlineActiveState() {207            const outlineItems = stepsOutlineUl.querySelectorAll('.outline-step-item[data-step-index]');208            const outlineNav = stepsOutlineUl.parentElement;209 210            outlineItems.forEach((itemLi) => {211                const idx = parseInt(itemLi.dataset.stepIndex, 10);212                const button = itemLi.querySelector('button');213                const dot = itemLi.querySelector('.outline-dot');214                const title = itemLi.querySelector('.outline-title');215                const connector = itemLi.querySelector('.outline-connector');216 217                if (idx === currentStepIndex) {218                    button.classList.add('bg-sky-50');219                    dot.classList.remove('bg-slate-300');220                    dot.classList.add('bg-sky-500', 'scale-125');221                     if (connector) connector.classList.add('bg-sky-400');222                    title.classList.remove('text-slate-600');223                    title.classList.add('text-sky-700', 'font-semibold');224 225                    if (outlineNav.scrollHeight > outlineNav.clientHeight && !outlineNav.classList.contains('hidden')) { // Only scroll if scrollable and visible226                        const itemRect = itemLi.getBoundingClientRect();227                        const navRect = outlineNav.getBoundingClientRect();228                        if (itemRect.top < navRect.top || itemRect.bottom > navRect.bottom) {229                           itemLi.scrollIntoView({ behavior: 'smooth', block: 'nearest' });230                        }231                    }232                } else {233                    button.classList.remove('bg-sky-50');234                    dot.classList.add('bg-slate-300');235                    dot.classList.remove('bg-sky-500', 'scale-125');236                    if (connector) connector.classList.remove('bg-sky-400');237                    title.classList.add('text-slate-600');238                    title.classList.remove('text-sky-700', 'font-semibold');239                }240            });241        }242 243        function updateStep() {244          if (currentStepIndex < 0 || currentStepIndex >= flatCodeSteps.length) return;245 246          const step = flatCodeSteps[currentStepIndex];247          const hasLineFocus = step.lineRanges && step.lineRanges.length > 0;248 249          // Update mobile current group and step titles250          if (mobileCurrentGroupTextEl && mobileCurrentStepTextEl) {251            let currentGroupTitleForMobile = "Loading...";252            let stepsProcessed = 0;253            for (const group of journeyStructure) {254                const stepsInGroup = group.steps.length;255                if (currentStepIndex >= stepsProcessed && currentStepIndex < stepsProcessed + stepsInGroup) {256                    currentGroupTitleForMobile = group.groupTitle;257                    break;258                }259                stepsProcessed += stepsInGroup;260            }261            mobileCurrentGroupTextEl.textContent = currentGroupTitleForMobile;262            mobileCurrentStepTextEl.textContent = step.title;263          }264 265 266          explanationStepCategoryContainer.innerHTML = '';267          if (step.category) {268            const badge = document.createElement('span');269            const styleClasses = categoryStyles[step.category] || categoryStyles["Default"];270            badge.className = `${styleClasses} text-xs font-medium mr-2 px-2.5 py-0.5 rounded-full inline-block`;271            badge.textContent = step.category;272            explanationStepCategoryContainer.appendChild(badge);273            explanationStepCategoryContainer.classList.remove('hidden');274          } else {275            explanationStepCategoryContainer.classList.add('hidden');276          }277 278          explanationStepTitle.textContent = step.title;279          explanationText.innerHTML = step.explanation;280 281          lineElements.forEach(el => {282            el.classList.remove('highlighted', 'code-line-dimmed');283          });284 285          let firstHighlightedLineElement = null;286          let highlightedLineNumbersForMeta = [];287 288          if (hasLineFocus) {289            const focusedIndices = new Set();290            step.lineRanges.forEach(range => {291              for (let i = range[0]; i <= range[1]; i++) {292                focusedIndices.add(i);293                if (lineElements[i]) {294                   if (!highlightedLineNumbersForMeta.includes(i + 1)) {295                       highlightedLineNumbersForMeta.push(i + 1);296                   }297                   if (!firstHighlightedLineElement) firstHighlightedLineElement = lineElements[i];298                }299              }300            });301 302            lineElements.forEach((el, idx) => {303                if (focusedIndices.has(idx)) {304                    el.classList.add('highlighted');305                } else {306                    el.classList.add('code-line-dimmed');307                }308            });309            highlightedLineNumbersForMeta.sort((a,b) => a - b);310            const rangesSummary = highlightedLineNumbersForMeta.length === 1 ? `Line ${highlightedLineNumbersForMeta[0]}` : `Lines ${highlightedLineNumbersForMeta.join(', ')}`;311            explanationMeta.textContent = `Focus: ${rangesSummary}`;312 313          } else {314             explanationMeta.textContent = "General overview for this step.";315          }316 317          if (firstHighlightedLineElement) {318            const codePane = codeLinesContainer.parentElement;319            if (codePane.scrollHeight > codePane.clientHeight) {320                const lineRect = firstHighlightedLineElement.getBoundingClientRect();321                const paneRect = codePane.getBoundingClientRect();322                if (lineRect.top < paneRect.top || lineRect.bottom > paneRect.bottom) {323                    firstHighlightedLineElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });324                }325            }326          }327 328 329          prevBtn.disabled = currentStepIndex === 0;330          nextBtn.disabled = currentStepIndex === flatCodeSteps.length - 1;331          progressIndicator.textContent = `Step ${currentStepIndex + 1} / ${flatCodeSteps.length}`;332          updateOutlineActiveState();333        }334 335        nextBtn.addEventListener('click', () => {336          if (currentStepIndex < flatCodeSteps.length - 1) {337            currentStepIndex++;338            updateStep();339          }340        });341 342        prevBtn.addEventListener('click', () => {343          if (currentStepIndex > 0) {344            currentStepIndex--;345            updateStep();346          }347        });348 349        resetBtn.addEventListener('click', () => {350          currentStepIndex = 0;351          updateStep();352          const codePane = codeLinesContainer.parentElement;353          if (codePane) {354            codePane.scrollTop = 0;355          }356          const outlineNav = stepsOutlineUl.parentElement;357          if (outlineNav && !outlineNav.classList.contains('hidden')) { // Check if outlineNav is visible358            outlineNav.scrollTop = 0;359          }360        });361 362        renderCode();363        renderOutline();364        updateStep();365      });