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')
|
CONTENT_DIR = os.environ.get('CONTENT_DIR', 'content')
|
||||||
TOKENS_FILE = os.environ.get('TOKENS_FILE', 'tokens.csv')
|
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:
|
### Pilih gambar yang melambangkan bahasa C:
|
||||||
- [x] 
|
- [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---
|
---END_QUIZ_FLASHCARD---
|
||||||
|
|
||||||
---EXERCISE---
|
---EXERCISE---
|
||||||
|
|
|
||||||
|
|
@ -297,7 +297,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if mgr.data?.active_tabs?.includes('quiz')}
|
{#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
|
<QuizTab
|
||||||
quizData={mgr.data.quiz_data ?? []}
|
quizData={mgr.data.quiz_data ?? []}
|
||||||
bind:isQuizMode={mgr.isQuizMode}
|
bind:isQuizMode={mgr.isQuizMode}
|
||||||
|
|
|
||||||
|
|
@ -290,8 +290,9 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.quiz-container {
|
.quiz-container {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: var(--color-bg);
|
background: var(--color-bg);
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 85vh;
|
height: 70vh;
|
||||||
}
|
}
|
||||||
.editor-area .editor-body {
|
.editor-area .editor-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
@ -291,15 +291,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.velxio-panel,
|
.velxio-panel,
|
||||||
.flowchart-panel {
|
.flowchart-panel,
|
||||||
|
.quiz-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.velxio-panel.tab-hidden,
|
.velxio-panel.tab-hidden,
|
||||||
.flowchart-panel.tab-hidden {
|
.flowchart-panel.tab-hidden,
|
||||||
|
.quiz-panel.tab-hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.velxio-iframe {
|
.velxio-iframe {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from flask import Blueprint, request, jsonify, send_from_directory
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from compiler import compiler_factory
|
from compiler import compiler_factory
|
||||||
from config import CONTENT_DIR
|
from config import CONTENT_DIR, ASSETS_DIR
|
||||||
from services.lesson_service import (
|
from services.lesson_service import (
|
||||||
get_ordered_lessons_with_learning_objectives,
|
get_ordered_lessons_with_learning_objectives,
|
||||||
render_markdown_content,
|
render_markdown_content,
|
||||||
|
|
@ -225,5 +225,5 @@ def get_key_text(filename):
|
||||||
|
|
||||||
@lessons_bp.route('/assets/<path:path>')
|
@lessons_bp.route('/assets/<path:path>')
|
||||||
def send_assets(path):
|
def send_assets(path):
|
||||||
"""Serve asset files (images, etc.)."""
|
"""Serve asset files (images, etc.) from ASSETS_DIR."""
|
||||||
return send_from_directory('assets', path)
|
return send_from_directory(ASSETS_DIR, path)
|
||||||
|
|
|
||||||
|
|
@ -481,14 +481,30 @@ def _parse_flashcards(text):
|
||||||
if not question:
|
if not question:
|
||||||
continue
|
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]
|
# Check for MCQ options: - [ ] or - [x]
|
||||||
option_pattern = re.compile(r'^\s*-\s*\[([ xX]?)\]\s*(.*)$', re.MULTILINE)
|
option_pattern = re.compile(r'^\s*-\s*\[([ xX]?)\]\s*(.*)$', re.MULTILINE)
|
||||||
options = option_pattern.findall(body)
|
options = option_pattern.findall(body)
|
||||||
|
|
||||||
# Check for image: URL
|
# Check for image: URL
|
||||||
image_match = re.search(r'^\s*image:\s*(.*)$', body, re.MULTILINE)
|
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:
|
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()
|
body = re.sub(r'^\s*image:\s*.*$', '', body, flags=re.MULTILINE).strip()
|
||||||
|
|
||||||
# Check for explanation (blockquote starting with >)
|
# Check for explanation (blockquote starting with >)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue