script.js

8.97 KB
23/10/2025 03:13
JS
script.js
// รายการบทเรียนและไฟล์ที่สอดคล้องกัน
const lessons = [
  {id: 'intro', title: 'บทนำ', file: 'lessons/00-intro.html'},
  {id: 'html', title: 'HTML', file: 'lessons/01-html.html'},
  {id: 'css', title: 'CSS', file: 'lessons/02-css.html'},
  {id: 'javascript', title: 'JavaScript', file: 'lessons/03-javascript.html'},
  {id: 'tools', title: 'Tools', file: 'lessons/04-tools.html'},
  {id: 'database', title: 'ฐานข้อมูล', file: 'lessons/05-database.html'},
  {id: 'responsive', title: 'Responsive Design', file: 'lessons/06-responsive.html'},
  {id: 'next-steps', title: 'ก้าวต่อไป', file: 'lessons/07-next-steps.html'},
  {id: 'api-fetch', title: 'API & Fetch', file: 'lessons/13-api-fetch.html'},
  {id: 'accessibility', title: 'Accessibility', file: 'lessons/14-accessibility.html'}
];

// ฟังก์ชันสำหรับโหลดเนื้อหา
/* eslint-env browser */
/* global SyntaxHighlighterComponent, QuizEngine */
async function loadLesson(sectionId) {
  const lesson = lessons.find(l => l.id === sectionId);

  if (!lesson) {
    console.error('ไม่พบบทเรียน:', sectionId);
    return;
  }

  try {
    const response = await fetch(lesson.file);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const content = await response.text();

    // โหลดเนื้อหา
    const contentLoader = document.getElementById('content-loader');
    contentLoader.innerHTML = content;
    // store the lesson file as base for resolving relative URLs inside the lesson
    contentLoader.dataset.lessonFile = lesson.file || '';

    // Playground: extract any <style> blocks from the lesson content and move them to <head>
    try {
      // remove previous lesson style if present
      const prev = document.getElementById('lesson-inline-style');
      if (prev) prev.remove();

      const styleBlocks = contentLoader.querySelectorAll('style');
      if (styleBlocks && styleBlocks.length) {
        const combined = Array.from(styleBlocks).map(s => s.textContent).join('\n\n');
        const styleEl = document.createElement('style');
        styleEl.id = 'lesson-inline-style';
        styleEl.dataset.lesson = lesson.id || '';
        styleEl.appendChild(document.createTextNode(combined));
        document.head.appendChild(styleEl);

        // remove original style tags from content area to avoid duplication
        styleBlocks.forEach(s => s.parentNode && s.parentNode.removeChild(s));
      }
    } catch (e) {
      console.warn('Error extracting lesson styles:', e);
    }

    // อัปเดต Sidebar
    updateSidebar(sectionId);

    // อัปเดต Progress Bar
    updateProgressBar(sectionId);

    // Scroll ไปที่ด้านบน
    document.querySelector('.main-content').scrollTop = 0;

    // เพิ่ม Event Listener สำหรับปุ่มนำทาง
    attachNavigationListeners();

    // Initialize SyntaxHighlighterComponent for loaded content if available
    if (window.SyntaxHighlighterComponent && typeof SyntaxHighlighterComponent.init === 'function') {
      // small delay to ensure DOM elements are parsed
      setTimeout(() => {
        SyntaxHighlighterComponent.init();
      }, 20);
    }

    // Inject Open-in-Editor / Run buttons under code blocks and quiz placeholders
    try {
      const codeBlocks = document.querySelectorAll('#content-loader pre > code');
      codeBlocks.forEach(codeEl => {
        const pre = codeEl.parentNode;
        // Avoid adding duplicate buttons
        if (pre.nextElementSibling && pre.nextElementSibling.classList.contains('code-actions')) return;

        const actions = document.createElement('div');
        actions.className = 'code-actions';

        // If the code block has data-quiz attribute, create a holder for the quiz
        if (codeEl.dataset.quiz) {
          const quizHolder = document.createElement('div');
          quizHolder.className = 'quiz-holder';
          quizHolder.setAttribute('data-quiz-src', codeEl.dataset.quiz);
          actions.appendChild(quizHolder);
        }

        pre.parentNode.insertBefore(actions, pre.nextSibling);
      });

      // Render quizzes for any quiz placeholders (fetch JSON and call renderQuiz)
      const holders = document.querySelectorAll('#content-loader [data-quiz-src]');
      holders.forEach(holder => {
        const src = holder.getAttribute('data-quiz-src');
        if (!src) return;
        if (!window.QuizEngine || typeof QuizEngine.renderQuiz !== 'function') {
          holder.innerHTML = '<div class="quiz-error">Quiz engine not available</div>';
          return;
        }
        // resolve relative quiz paths against the lesson file
        const baseFile = contentLoader.dataset.lessonFile || window.location.href;
        let resolved;
        try {
          resolved = new URL(src, new URL(baseFile, window.location.href)).href;
        } catch (e) {
          // fallback to simple resolution
          resolved = src;
        }
        console.debug('Loading quiz', src, 'resolved->', resolved);
        fetch(resolved).then(r => {
          if (!r.ok) throw new Error(`Failed to load quiz (${r.status})`);
          return r.json();
        }).then(data => {
          QuizEngine.renderQuiz(holder, data);
        }).catch(err => {
          console.warn('Quiz load failed', err);
          holder.innerHTML = '<div class="quiz-error">ไม่สามารถโหลดแบบทดสอบได้</div>';
        });
      });
    } catch (e) {
      console.warn('Error injecting editor buttons or quizzes', e);
    }
  } catch (error) {
    console.error('Error loading lesson:', error);
    document.getElementById('content-loader').innerHTML = `
            <div class="warning">
                <strong>⚠️ ข้อผิดพลาด:</strong> ไม่สามารถโหลดบทเรียนได้ กรุณาลองใหม่อีกครั้ง
            </div>
        `;
  }
}

// ฟังก์ชันสำหรับอัปเดต Sidebar
function updateSidebar(sectionId) {
  const sidebarItems = document.querySelectorAll('.sidebar-item');
  sidebarItems.forEach(item => {
    item.classList.remove('active');
  });
  const activeItem = document.querySelector(`[data-section="${sectionId}"]`);
  if (activeItem) {
    activeItem.classList.add('active');
  }
}

// ฟังก์ชันสำหรับอัปเดต Progress Bar
function updateProgressBar(sectionId) {
  const currentIndex = lessons.findIndex(l => l.id === sectionId);
  const progress = ((currentIndex + 1) / lessons.length) * 100;
  const progressFill = document.getElementById('progressFill');
  progressFill.style.width = progress + '%';
}

// ฟังก์ชันสำหรับเพิ่ม Event Listener สำหรับปุ่มนำทาง
function attachNavigationListeners() {
  const prevBtn = document.getElementById('prev-btn');
  const nextBtn = document.getElementById('next-btn');

  if (prevBtn) {
    prevBtn.onclick = function(e) {
      e.preventDefault();
      const currentId = this.getAttribute('data-current');
      const currentIndex = lessons.findIndex(l => l.id === currentId);
      if (currentIndex > 0) {
        loadLesson(lessons[currentIndex - 1].id);
      }
    };
  }

  if (nextBtn) {
    nextBtn.onclick = function(e) {
      e.preventDefault();
      const currentId = this.getAttribute('data-current');
      const currentIndex = lessons.findIndex(l => l.id === currentId);
      if (currentIndex < lessons.length - 1) {
        loadLesson(lessons[currentIndex + 1].id);
      }
    };
  }
}

// Render sidebar dynamically from `lessons` array and attach handlers
function renderSidebar(activeId) {
  const sidebar = document.getElementById('sidebar');
  if (!sidebar) return;
  sidebar.innerHTML = '';

  lessons.forEach(lesson => {
    const el = document.createElement('div');
    el.className = 'sidebar-item';
    el.setAttribute('data-section', lesson.id);
    el.setAttribute('role', 'button');
    el.setAttribute('tabindex', '0');
    el.textContent = lesson.title || lesson.id;
    if (lesson.id === activeId) el.classList.add('active');

    // click handler
    el.addEventListener('click', () => loadLesson(lesson.id));
    // keyboard accessibility (Enter / Space)
    el.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' || e.key === ' ') {
        e.preventDefault();
        loadLesson(lesson.id);
      }
    });

    sidebar.appendChild(el);
  });
}

// โหลดบทนำเมื่อหน้าเพจโหลด
document.addEventListener('DOMContentLoaded', function() {
  // render the sidebar dynamically and load the intro lesson
  renderSidebar('intro');
  loadLesson('intro');

  // Initialize SyntaxHighlighterComponent globally if available
  if (window.SyntaxHighlighterComponent && typeof SyntaxHighlighterComponent.init === 'function') {
    SyntaxHighlighterComponent.init();
  }
});