From bc498771239bf9a788bf2004b4bb29c56765e11a Mon Sep 17 00:00:00 2001 From: a2nr Date: Thu, 14 May 2026 19:45:10 +0700 Subject: [PATCH] feat: update to support folder inside content folder to easy to manage --- elemes.sh | 4 +-- .../{ => arduino}/button_input_arduino.md | 0 .../{ => arduino}/hello_serial_arduino.md | 0 .../{ => arduino}/led_blink_arduino.md | 0 .../{ => arduino}/traffic_light_arduino.md | 0 .../content/{ => circuit}/rangkaian_dasar.md | 0 .../content/{ => dasar}/flowchart_test.md | 0 examples/content/{ => dasar}/hello_world.md | 0 examples/content/{ => dasar}/latex_test.md | 0 examples/content/{ => dasar}/quiz_test.md | 0 examples/content/dasar/test_slides.md | 26 ++++++++++++++++++ examples/content/{ => dasar}/variabel.md | 0 examples/content/home.md | 27 ++++++++++--------- routes/lessons.py | 13 +++++---- services/lesson_service.py | 25 ++++++++++++----- 15 files changed, 68 insertions(+), 27 deletions(-) rename examples/content/{ => arduino}/button_input_arduino.md (100%) rename examples/content/{ => arduino}/hello_serial_arduino.md (100%) rename examples/content/{ => arduino}/led_blink_arduino.md (100%) rename examples/content/{ => arduino}/traffic_light_arduino.md (100%) rename examples/content/{ => circuit}/rangkaian_dasar.md (100%) rename examples/content/{ => dasar}/flowchart_test.md (100%) rename examples/content/{ => dasar}/hello_world.md (100%) rename examples/content/{ => dasar}/latex_test.md (100%) rename examples/content/{ => dasar}/quiz_test.md (100%) create mode 100644 examples/content/dasar/test_slides.md rename examples/content/{ => dasar}/variabel.md (100%) diff --git a/elemes.sh b/elemes.sh index ee7befd..99c07e8 100755 --- a/elemes.sh +++ b/elemes.sh @@ -43,8 +43,8 @@ init) echo "✅ [Skip] Folder content/ sudah ada" else mkdir -p "$PARENT_DIR/content" - cp -n "$EXAMPLES_DIR/content/"*.md "$PARENT_DIR/content/" - echo "📁 [Buat] Folder content/ ($(ls "$PARENT_DIR/content/"*.md 2>/dev/null | wc -l) materi contoh ditambahkan)" + cp -rn "$EXAMPLES_DIR/content/"* "$PARENT_DIR/content/" + echo "📁 [Buat] Folder content/ ($(find "$PARENT_DIR/content/" -name "*.md" 2>/dev/null | wc -l) materi contoh ditambahkan)" fi # assets/ diff --git a/examples/content/button_input_arduino.md b/examples/content/arduino/button_input_arduino.md similarity index 100% rename from examples/content/button_input_arduino.md rename to examples/content/arduino/button_input_arduino.md diff --git a/examples/content/hello_serial_arduino.md b/examples/content/arduino/hello_serial_arduino.md similarity index 100% rename from examples/content/hello_serial_arduino.md rename to examples/content/arduino/hello_serial_arduino.md diff --git a/examples/content/led_blink_arduino.md b/examples/content/arduino/led_blink_arduino.md similarity index 100% rename from examples/content/led_blink_arduino.md rename to examples/content/arduino/led_blink_arduino.md diff --git a/examples/content/traffic_light_arduino.md b/examples/content/arduino/traffic_light_arduino.md similarity index 100% rename from examples/content/traffic_light_arduino.md rename to examples/content/arduino/traffic_light_arduino.md diff --git a/examples/content/rangkaian_dasar.md b/examples/content/circuit/rangkaian_dasar.md similarity index 100% rename from examples/content/rangkaian_dasar.md rename to examples/content/circuit/rangkaian_dasar.md diff --git a/examples/content/flowchart_test.md b/examples/content/dasar/flowchart_test.md similarity index 100% rename from examples/content/flowchart_test.md rename to examples/content/dasar/flowchart_test.md diff --git a/examples/content/hello_world.md b/examples/content/dasar/hello_world.md similarity index 100% rename from examples/content/hello_world.md rename to examples/content/dasar/hello_world.md diff --git a/examples/content/latex_test.md b/examples/content/dasar/latex_test.md similarity index 100% rename from examples/content/latex_test.md rename to examples/content/dasar/latex_test.md diff --git a/examples/content/quiz_test.md b/examples/content/dasar/quiz_test.md similarity index 100% rename from examples/content/quiz_test.md rename to examples/content/dasar/quiz_test.md diff --git a/examples/content/dasar/test_slides.md b/examples/content/dasar/test_slides.md new file mode 100644 index 0000000..b3add8b --- /dev/null +++ b/examples/content/dasar/test_slides.md @@ -0,0 +1,26 @@ +# Test Slide Feature + +Selamat datang di materi uji coba fitur slide. + +---slide-start--- +# Slide 1: Pengenalan +Ini adalah konten slide pertama. +--- +# Slide 2: Keuntungan +- Mudah digunakan +- Berbasis Markdown +- Interaktif +--- +# Slide 3: Selesai +Terima kasih telah mencoba! +---slide-end--- + +Setelah slide di atas, ini adalah konten materi biasa. + +```c +#include +int main() { + printf("Uji coba slide\n"); + return 0; +} +``` diff --git a/examples/content/variabel.md b/examples/content/dasar/variabel.md similarity index 100% rename from examples/content/variabel.md rename to examples/content/dasar/variabel.md diff --git a/examples/content/home.md b/examples/content/home.md index d5458d7..b2ef449 100644 --- a/examples/content/home.md +++ b/examples/content/home.md @@ -18,15 +18,16 @@ Semua materi bisa dikerjakan langsung di browser! 3. [Uji Coba Flowchart](lesson/flowchart_test.md) 4. [Uji Coba Kuis](lesson/quiz_test.md) 5. [Uji Coba LaTeX](lesson/latex_test.md) +6. [Uji Coba Slide](lesson/test_slides.md) -### Elektronika (Hybrid) -6. [Rangkaian Dasar](lesson/rangkaian_dasar.md) +### Elektronika & Sirkuit (Hybrid) +7. [Rangkaian Dasar](lesson/rangkaian_dasar.md) -### Arduino (Velxio) -7. [LED Blink](lesson/led_blink_arduino.md) -8. [Hello Serial](lesson/hello_serial_arduino.md) -9. [Button Input](lesson/button_input_arduino.md) -10. [Traffic Light](lesson/traffic_light_arduino.md) +### Arduino & Robotika (Velxio) +8. [LED Blink](lesson/led_blink_arduino.md) +9. [Hello Serial](lesson/hello_serial_arduino.md) +10. [Button Input](lesson/button_input_arduino.md) +11. [Traffic Light](lesson/traffic_light_arduino.md) ----Available_Lessons---- @@ -34,8 +35,10 @@ Semua materi bisa dikerjakan langsung di browser! 2. [Variabel](lesson/variabel.md) 3. [Uji Coba Flowchart](lesson/flowchart_test.md) 4. [Uji Coba Kuis](lesson/quiz_test.md) -5. [Rangkaian Dasar](lesson/rangkaian_dasar.md) -6. [LED Blink](lesson/led_blink_arduino.md) -7. [Hello Serial](lesson/hello_serial_arduino.md) -8. [Button Input](lesson/button_input_arduino.md) -9. [Traffic Light](lesson/traffic_light_arduino.md) +5. [Uji Coba LaTeX](lesson/latex_test.md) +6. [Uji Coba Slide](lesson/test_slides.md) +7. [Rangkaian Dasar](lesson/rangkaian_dasar.md) +8. [LED Blink](lesson/led_blink_arduino.md) +9. [Hello Serial](lesson/hello_serial_arduino.md) +10. [Button Input](lesson/button_input_arduino.md) +11. [Traffic Light](lesson/traffic_light_arduino.md) diff --git a/routes/lessons.py b/routes/lessons.py index bb482bc..48c7d48 100644 --- a/routes/lessons.py +++ b/routes/lessons.py @@ -13,6 +13,7 @@ from services.lesson_service import ( get_ordered_lessons_with_learning_objectives, render_markdown_content, render_home_content, + find_lesson_file, ) from services.token_service import get_student_progress @@ -52,10 +53,9 @@ def api_lessons(): @lessons_bp.route('/lesson/.json') def api_lesson(filename): """Return single lesson data as JSON.""" - safe_filename = secure_filename(filename) - full_filename = safe_filename if safe_filename.endswith('.md') else f'{safe_filename}.md' - file_path = os.path.join(CONTENT_DIR, full_filename) - if not os.path.exists(file_path): + full_filename = filename if filename.endswith('.md') else f'{filename}.md' + file_path = find_lesson_file(full_filename) + if not file_path: return jsonify({'error': 'Lesson not found'}), 404 parsed_data = render_markdown_content(file_path) @@ -215,9 +215,8 @@ def api_lesson(filename): @lessons_bp.route('/get-key-text/') def get_key_text(filename): """Get the key text for a specific lesson.""" - safe_filename = secure_filename(filename) - file_path = os.path.join(CONTENT_DIR, safe_filename) - if not os.path.exists(file_path): + file_path = find_lesson_file(filename) + if not file_path: return jsonify({'success': False, 'error': 'Lesson not found'}), 404 parsed_data = render_markdown_content(file_path) diff --git a/services/lesson_service.py b/services/lesson_service.py index 5cd2c99..967ebb0 100644 --- a/services/lesson_service.py +++ b/services/lesson_service.py @@ -43,6 +43,19 @@ def _parse_lesson_links(home_content): # Lesson listing # --------------------------------------------------------------------------- +@lru_cache(maxsize=128) +def find_lesson_file(filename): + """Recursively search for filename in CONTENT_DIR and return its full path.""" + # Security: Prevent directory traversal + if '/' in filename or '\\' in filename: + return None + + for root, _, files in os.walk(CONTENT_DIR): + if filename in files: + return os.path.join(root, filename) + return None + + @lru_cache(maxsize=32) def get_lessons(): """Get lessons from the Available_Lessons section in home.md.""" @@ -52,8 +65,8 @@ def get_lessons(): return lessons for link_text, filename in _parse_lesson_links(home_content): - file_path = os.path.join(CONTENT_DIR, filename) - if not os.path.exists(file_path): + file_path = find_lesson_file(filename) + if not file_path: continue with open(file_path, 'r', encoding='utf-8') as f: @@ -92,8 +105,8 @@ def get_lesson_names(): names = [] for _link_text, filename in _parse_lesson_links(home_content): - file_path = os.path.join(CONTENT_DIR, filename) - if os.path.exists(file_path): + file_path = find_lesson_file(filename) + if file_path: names.append(filename.replace('.md', '')) return names @@ -107,8 +120,8 @@ def get_lessons_with_learning_objectives(): return lessons for link_text, filename in _parse_lesson_links(home_content): - file_path = os.path.join(CONTENT_DIR, filename) - if not os.path.exists(file_path): + file_path = find_lesson_file(filename) + if not file_path: continue with open(file_path, 'r', encoding='utf-8') as f: