feat: update to support folder inside content folder to easy to manage

This commit is contained in:
a2nr 2026-05-14 19:45:10 +07:00
parent 8485425d0f
commit bc49877123
15 changed files with 68 additions and 27 deletions

View File

@ -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/

View File

@ -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 <stdio.h>
int main() {
printf("Uji coba slide\n");
return 0;
}
```

View File

@ -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)

View File

@ -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/<filename>.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/<filename>')
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)

View File

@ -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: