From 8e89fde3c1fd6af6a5fa8ccb4c2f758518399316 Mon Sep 17 00:00:00 2001 From: a2nr Date: Tue, 21 Jul 2026 20:50:01 +0700 Subject: [PATCH] fix: docked editor panel height + quiz image parser + assets dir config - fix(layout): editor-area docked mode sekarang height:70vh (sebelumnya max-height:85vh tanpa height, menyebabkan tab Arduino/velxio menciut ke 150px karena iframe tidak punya intrinsic height) - fix(layout): tambah overflow:hidden pada velxio/flowchart/quiz panel untuk mencegah nested scrollable flex collapse - feat(quiz): parser image dari 'image:' directive dan markdown ![alt](path) di question flashcard; konversi bare filename ke /assets/ path - feat(assets): ASSETS_DIR derived dari CONTENT_DIR untuk support custom content directory (bukan hardcoded 'assets') - fix(quiz): QuizTab container gunakan flex:1+min-height:0 (bukan height:100%) - chore: hapus placeholder examples/assets/put_your_image.here --- config.py | 6 ++++++ examples/assets/demo_quiz.png | Bin 0 -> 14949 bytes examples/assets/put_your_image.here | 0 examples/content/dasar/quiz_test.md | 9 +++++++++ .../src/routes/lesson/[slug]/+page.svelte | 2 +- .../src/routes/lesson/[slug]/QuizTab.svelte | 3 ++- frontend/src/routes/lesson/[slug]/lesson.css | 11 +++++++---- routes/lessons.py | 6 +++--- services/lesson_service.py | 18 +++++++++++++++++- 9 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 examples/assets/demo_quiz.png delete mode 100644 examples/assets/put_your_image.here diff --git a/config.py b/config.py index c7e801b..36319a9 100644 --- a/config.py +++ b/config.py @@ -7,3 +7,9 @@ import os CONTENT_DIR = os.environ.get('CONTENT_DIR', 'content') TOKENS_FILE = os.environ.get('TOKENS_FILE', 'tokens.csv') + +# Assets directory: derived from parent of CONTENT_DIR +# E.g. CONTENT_DIR='content' → ASSETS_DIR='assets' +# CONTENT_DIR='examples/content' → ASSETS_DIR='examples/assets' +# CONTENT_DIR='/app/content' → ASSETS_DIR='/app/assets' +ASSETS_DIR = os.path.join(os.path.dirname(CONTENT_DIR.rstrip(os.sep)), 'assets') diff --git a/examples/assets/demo_quiz.png b/examples/assets/demo_quiz.png new file mode 100644 index 0000000000000000000000000000000000000000..4911d38cc82e27a547a90b8fc6f647ea17d90c9f GIT binary patch literal 14949 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 literal 0 HcmV?d00001 diff --git a/examples/assets/put_your_image.here b/examples/assets/put_your_image.here deleted file mode 100644 index e69de29..0000000 diff --git a/examples/content/dasar/quiz_test.md b/examples/content/dasar/quiz_test.md index 51cbf59..5a21fbf 100644 --- a/examples/content/dasar/quiz_test.md +++ b/examples/content/dasar/quiz_test.md @@ -26,6 +26,15 @@ Menandakan bahwa program telah selesai berjalan dengan sukses tanpa ada error. ### Pilih gambar yang melambangkan bahasa C: - [x] ![C Logo](https://upload.wikimedia.org/wikipedia/commons/1/18/C_Programming_Language.svg) - [] ![Wrong Logo](https://upload.wikimedia.org/wikipedia/commons/c/c3/Python-logo-notext.svg) + +### Apa warna langit pada siang hari? +image: demo_quiz.png +- [x] Biru +- [] Hijau +- [] Merah +- [] Kuning +> Penjelasan: Langit terlihat biru karena hamburan Rayleigh — cahaya matahari dihamburkan oleh atmosfer bumi. + ---END_QUIZ_FLASHCARD--- ---EXERCISE--- diff --git a/frontend/src/routes/lesson/[slug]/+page.svelte b/frontend/src/routes/lesson/[slug]/+page.svelte index 7bb6786..aee0be5 100644 --- a/frontend/src/routes/lesson/[slug]/+page.svelte +++ b/frontend/src/routes/lesson/[slug]/+page.svelte @@ -297,7 +297,7 @@ {/if} {#if mgr.data?.active_tabs?.includes('quiz')} -

+
.quiz-container { + flex: 1; + min-height: 0; padding: 1.5rem; - height: 100%; display: flex; flex-direction: column; background: var(--color-bg); diff --git a/frontend/src/routes/lesson/[slug]/lesson.css b/frontend/src/routes/lesson/[slug]/lesson.css index dc2b9e3..02dd7b6 100644 --- a/frontend/src/routes/lesson/[slug]/lesson.css +++ b/frontend/src/routes/lesson/[slug]/lesson.css @@ -80,7 +80,7 @@ display: flex; flex-direction: column; overflow: hidden; - max-height: 85vh; + height: 70vh; } .editor-area .editor-body { flex: 1; @@ -291,15 +291,18 @@ } .velxio-panel, -.flowchart-panel { +.flowchart-panel, +.quiz-panel { display: flex; flex-direction: column; flex: 1; min-height: 0; position: relative; + overflow: hidden; } .velxio-panel.tab-hidden, -.flowchart-panel.tab-hidden { +.flowchart-panel.tab-hidden, +.quiz-panel.tab-hidden { display: none; } .velxio-iframe { @@ -468,4 +471,4 @@ color: var(--color-text-muted); margin-bottom: 1.5rem; line-height: 1.4; -} \ No newline at end of file +} diff --git a/routes/lessons.py b/routes/lessons.py index 48c7d48..30797b8 100644 --- a/routes/lessons.py +++ b/routes/lessons.py @@ -8,7 +8,7 @@ from flask import Blueprint, request, jsonify, send_from_directory from werkzeug.utils import secure_filename from compiler import compiler_factory -from config import CONTENT_DIR +from config import CONTENT_DIR, ASSETS_DIR from services.lesson_service import ( get_ordered_lessons_with_learning_objectives, render_markdown_content, @@ -225,5 +225,5 @@ def get_key_text(filename): @lessons_bp.route('/assets/') def send_assets(path): - """Serve asset files (images, etc.).""" - return send_from_directory('assets', path) + """Serve asset files (images, etc.) from ASSETS_DIR.""" + return send_from_directory(ASSETS_DIR, path) diff --git a/services/lesson_service.py b/services/lesson_service.py index f4a6b94..b9f4efc 100644 --- a/services/lesson_service.py +++ b/services/lesson_service.py @@ -481,14 +481,30 @@ def _parse_flashcards(text): if not question: continue + # Initialize image_url - will be set from markdown image or 'image:' directive + image_url = "" + + # Extract image from markdown syntax ![...](path) in question + md_image_match = re.search(r'!\[.*?\]\(([^)]+)\)', question) + if md_image_match: + md_image_path = md_image_match.group(1).strip() + # Jika path lokal (/assets/ atau bare filename) dan belum ada image dari 'image:' directive + if md_image_path.startswith('/assets/') or (not md_image_path.startswith(('http://', 'https://')) and not image_url): + image_url = md_image_path + # Check for MCQ options: - [ ] or - [x] option_pattern = re.compile(r'^\s*-\s*\[([ xX]?)\]\s*(.*)$', re.MULTILINE) options = option_pattern.findall(body) # Check for image: URL image_match = re.search(r'^\s*image:\s*(.*)$', body, re.MULTILINE) - image_url = image_match.group(1).strip() if image_match else "" if image_match: + image_url = image_match.group(1).strip() # Override jika ada 'image:' directive + if image_url.startswith('/assets/'): + # Path sudah lengkap, tidak perlu konversi + pass + elif not image_url.startswith(('http://', 'https://', '/')): + image_url = f'/assets/{image_url}' body = re.sub(r'^\s*image:\s*.*$', '', body, flags=re.MULTILINE).strip() # Check for explanation (blockquote starting with >)