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  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
This commit is contained in:
parent
156480b26f
commit
8e89fde3c1
|
|
@ -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')
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -26,6 +26,15 @@ Menandakan bahwa program telah selesai berjalan dengan sukses tanpa ada error.
|
|||
### Pilih gambar yang melambangkan bahasa C:
|
||||
- [x] 
|
||||
- [] 
|
||||
|
||||
### 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---
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@
|
|||
{/if}
|
||||
|
||||
{#if mgr.data?.active_tabs?.includes('quiz')}
|
||||
<div class="tab-panel" class:tab-hidden={mgr.activeTab !== 'quiz'}>
|
||||
<div class="tab-panel quiz-panel" class:tab-hidden={mgr.activeTab !== 'quiz'}>
|
||||
<QuizTab
|
||||
quizData={mgr.data.quiz_data ?? []}
|
||||
bind:isQuizMode={mgr.isQuizMode}
|
||||
|
|
|
|||
|
|
@ -290,8 +290,9 @@
|
|||
|
||||
<style>
|
||||
.quiz-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 1.5rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-bg);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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/<path:path>')
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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  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 >)
|
||||
|
|
|
|||
Loading…
Reference in New Issue