munchboka-edutools 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- munchboka_edutools/__init__.py +182 -0
- munchboka_edutools/_plotmath_shim.py +126 -0
- munchboka_edutools/_version.py +2 -0
- munchboka_edutools/directives/__init__.py +1 -0
- munchboka_edutools/directives/admonitions.py +356 -0
- munchboka_edutools/directives/cas_popup.py +272 -0
- munchboka_edutools/directives/dialogue.py +137 -0
- munchboka_edutools/directives/escape_room.py +296 -0
- munchboka_edutools/directives/factor_tree.py +549 -0
- munchboka_edutools/directives/ggb.py +209 -0
- munchboka_edutools/directives/ggb_icon.py +62 -0
- munchboka_edutools/directives/ggb_popup.py +165 -0
- munchboka_edutools/directives/horner.py +324 -0
- munchboka_edutools/directives/interactive_code.py +75 -0
- munchboka_edutools/directives/jeopardy.py +252 -0
- munchboka_edutools/directives/multi_plot.py +1126 -0
- munchboka_edutools/directives/pair_puzzle.py +191 -0
- munchboka_edutools/directives/parsons.py +109 -0
- munchboka_edutools/directives/plot.py +3012 -0
- munchboka_edutools/directives/poly_icon.py +91 -0
- munchboka_edutools/directives/polydiv.py +344 -0
- munchboka_edutools/directives/quiz.py +291 -0
- munchboka_edutools/directives/signchart.py +474 -0
- munchboka_edutools/directives/timed_quiz.py +436 -0
- munchboka_edutools/directives/turtle.py +157 -0
- munchboka_edutools/static/css/admonitions.css +2012 -0
- munchboka_edutools/static/css/cas_popup.css +242 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_cm.css +112 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_default_cm.css +40 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_high_contrast_cm.css +141 -0
- munchboka_edutools/static/css/code_mirror_themes/github_light_cm.css +120 -0
- munchboka_edutools/static/css/code_mirror_themes/github_light_default_cm.css +108 -0
- munchboka_edutools/static/css/code_mirror_themes/one_dark_cm.css +121 -0
- munchboka_edutools/static/css/dialogue.css +92 -0
- munchboka_edutools/static/css/escapeRoom/escape-room.css +223 -0
- munchboka_edutools/static/css/figures.css +274 -0
- munchboka_edutools/static/css/general_style.css +74 -0
- munchboka_edutools/static/css/github-dark-high-contrast.css +141 -0
- munchboka_edutools/static/css/github-dark.css +112 -0
- munchboka_edutools/static/css/github-light.css +120 -0
- munchboka_edutools/static/css/interactive_code/style.css +575 -0
- munchboka_edutools/static/css/interactive_code.css +582 -0
- munchboka_edutools/static/css/jeopardy.css +476 -0
- munchboka_edutools/static/css/pairPuzzle/style.css +177 -0
- munchboka_edutools/static/css/parsons/parsonsPuzzle.css +331 -0
- munchboka_edutools/static/css/quiz.css +312 -0
- munchboka_edutools/static/css/timedQuiz.css +375 -0
- munchboka_edutools/static/icons/ggb/mode_evaluate.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_extremum.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_intersect.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_nsolve.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_numeric.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_point.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_solve.svg +1 -0
- munchboka_edutools/static/icons/misc/windows-logo.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/academic.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/book.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/chat_bubble.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/check.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/cmd_line.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/file.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/fire.svg +4 -0
- munchboka_edutools/static/icons/outline/dark_mode/key.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/magnifying.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/pencil_square.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/play.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/question.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/square_check.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/summary.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/undo.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/unlock.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/academic.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/book.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/chat_bubble.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/check.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/cmd_line.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/file.svg +1 -0
- munchboka_edutools/static/icons/outline/light_mode/fire.svg +4 -0
- munchboka_edutools/static/icons/outline/light_mode/key.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/magnifying.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/pencil_square.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/play.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/question.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/square_check.svg +1 -0
- munchboka_edutools/static/icons/outline/light_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/summary.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/undo.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/unlock.svg +3 -0
- munchboka_edutools/static/icons/polyicons/cubicdown.svg +3 -0
- munchboka_edutools/static/icons/polyicons/cubicup.svg +3 -0
- munchboka_edutools/static/icons/polyicons/frown.svg +3 -0
- munchboka_edutools/static/icons/polyicons/smile.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/academic.svg +5 -0
- munchboka_edutools/static/icons/solid/dark_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/book.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/brain.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/file.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/fire.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/key.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/pencil_square.svg +4 -0
- munchboka_edutools/static/icons/solid/dark_mode/play.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/python.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/scroll.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/academic.svg +5 -0
- munchboka_edutools/static/icons/solid/light_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/book.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/brain.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/file.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/fire.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/key.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/pencil_square.svg +4 -0
- munchboka_edutools/static/icons/solid/light_mode/play.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/python.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/scroll.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/stickers/edit.svg +1 -0
- munchboka_edutools/static/icons/stickers/pencil_square.svg +3 -0
- munchboka_edutools/static/js/casThemeManager.js +99 -0
- munchboka_edutools/static/js/escapeRoom/escape-room.js +219 -0
- munchboka_edutools/static/js/geogebra-setup.js +6 -0
- munchboka_edutools/static/js/highlight-init.js +6 -0
- munchboka_edutools/static/js/interactiveCode/codeEditor.js +632 -0
- munchboka_edutools/static/js/interactiveCode/interactiveCodeSetup.js +348 -0
- munchboka_edutools/static/js/interactiveCode/pythonRunner.js +336 -0
- munchboka_edutools/static/js/interactiveCode/turtleCode.js +203 -0
- munchboka_edutools/static/js/interactiveCode/workerManager.js +353 -0
- munchboka_edutools/static/js/interactive_code/codeEditor.js +662 -0
- munchboka_edutools/static/js/interactive_code/interactiveCodeSetup.js +252 -0
- munchboka_edutools/static/js/interactive_code/pythonRunner.js +145 -0
- munchboka_edutools/static/js/interactive_code/turtleCode.js +56 -0
- munchboka_edutools/static/js/interactive_code/workerManager.js +204 -0
- munchboka_edutools/static/js/jeopardy.js +457 -0
- munchboka_edutools/static/js/pairPuzzle/draggableItem.js +64 -0
- munchboka_edutools/static/js/pairPuzzle/dropZone.js +119 -0
- munchboka_edutools/static/js/pairPuzzle/game.js +160 -0
- munchboka_edutools/static/js/parsons/parsonsPuzzle.js +641 -0
- munchboka_edutools/static/js/quiz.js +422 -0
- munchboka_edutools/static/js/skulpt/skulpt.js +35721 -0
- munchboka_edutools/static/js/timedQuiz/multipleChoiceQuestion.js +184 -0
- munchboka_edutools/static/js/timedQuiz/timedMultipleChoiceQuiz.js +244 -0
- munchboka_edutools/static/js/timedQuiz/utils.js +6 -0
- munchboka_edutools/static/js/utils.js +3 -0
- munchboka_edutools-0.1.0.dist-info/METADATA +107 -0
- munchboka_edutools-0.1.0.dist-info/RECORD +150 -0
- munchboka_edutools-0.1.0.dist-info/WHEEL +4 -0
- munchboka_edutools-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
from docutils import nodes
|
|
2
|
+
from docutils.parsers.rst import Directive, directives
|
|
3
|
+
from sphinx.util.docutils import SphinxDirective
|
|
4
|
+
import json
|
|
5
|
+
import uuid
|
|
6
|
+
import re
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class QuizDirective(SphinxDirective):
|
|
11
|
+
"""Directive for embedding interactive quizzes."""
|
|
12
|
+
|
|
13
|
+
has_content = True
|
|
14
|
+
required_arguments = 0
|
|
15
|
+
optional_arguments = 0
|
|
16
|
+
final_argument_whitespace = True
|
|
17
|
+
option_spec = {
|
|
18
|
+
"shuffle": directives.flag,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def run(self):
|
|
22
|
+
|
|
23
|
+
# Parse the content
|
|
24
|
+
# self.state.nested_parse(self.content, self.content_offset, admonition_node)
|
|
25
|
+
self.quiz_id = uuid.uuid4().hex
|
|
26
|
+
container_id = f"quiz-container-{self.quiz_id}"
|
|
27
|
+
|
|
28
|
+
# Parse quiz content
|
|
29
|
+
quiz_data = self._parse_quiz_content()
|
|
30
|
+
|
|
31
|
+
# Create the HTML output
|
|
32
|
+
html = f"""
|
|
33
|
+
<!-- Container for the quiz -->
|
|
34
|
+
<div id="{container_id}" class="quiz-main-container"></div>
|
|
35
|
+
<!-- Include KaTeX for LaTeX rendering -->
|
|
36
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css">
|
|
37
|
+
<script defer src="https://cdn.jsdelivr.net/npm/katex/dist/katex.min.js"></script>
|
|
38
|
+
<script defer src="https://cdn.jsdelivr.net/npm/katex/dist/contrib/auto-render.min.js"></script>
|
|
39
|
+
|
|
40
|
+
<script type="text/javascript">
|
|
41
|
+
document.addEventListener("DOMContentLoaded", () => {{
|
|
42
|
+
// Define your questions and answers
|
|
43
|
+
const questionsData = {json.dumps(quiz_data, ensure_ascii=False)};
|
|
44
|
+
|
|
45
|
+
// Initialize the multiple-choice quiz
|
|
46
|
+
const quiz = new SequentialMultipleChoiceQuiz('{container_id}', questionsData);
|
|
47
|
+
}});
|
|
48
|
+
</script>
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
raw_node = nodes.raw("", html, format="html")
|
|
52
|
+
|
|
53
|
+
return [raw_node]
|
|
54
|
+
|
|
55
|
+
def _parse_quiz_content(self):
|
|
56
|
+
"""Parse the directive content into quiz questions data."""
|
|
57
|
+
questions = []
|
|
58
|
+
current_question = None
|
|
59
|
+
current_answers = []
|
|
60
|
+
|
|
61
|
+
for line in self.content:
|
|
62
|
+
line = self._process_figures(line)
|
|
63
|
+
line = line.strip()
|
|
64
|
+
|
|
65
|
+
# Skip empty lines
|
|
66
|
+
if not line:
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
# New question starts with Q:
|
|
70
|
+
if line.startswith("Q:"):
|
|
71
|
+
# Save previous question if exists
|
|
72
|
+
if current_question:
|
|
73
|
+
questions.append({"content": current_question, "answers": current_answers})
|
|
74
|
+
|
|
75
|
+
# Start new question and process newlines
|
|
76
|
+
question_text = line[2:].strip()
|
|
77
|
+
# Replace \\n with actual newlines for code blocks
|
|
78
|
+
question_text = self._process_code_blocks(question_text)
|
|
79
|
+
current_question = question_text
|
|
80
|
+
current_answers = []
|
|
81
|
+
|
|
82
|
+
# Correct answer starts with +
|
|
83
|
+
elif line.startswith("+"):
|
|
84
|
+
answer_text = line[1:].strip()
|
|
85
|
+
# Process code blocks in answers too
|
|
86
|
+
answer_text = self._process_code_blocks(answer_text)
|
|
87
|
+
current_answers.append({"content": answer_text, "isCorrect": True})
|
|
88
|
+
|
|
89
|
+
# Incorrect answer starts with -
|
|
90
|
+
elif line.startswith("-"):
|
|
91
|
+
answer_text = line[1:].strip()
|
|
92
|
+
# Process code blocks in answers too
|
|
93
|
+
answer_text = self._process_code_blocks(answer_text)
|
|
94
|
+
current_answers.append({"content": answer_text, "isCorrect": False})
|
|
95
|
+
|
|
96
|
+
# Add the last question
|
|
97
|
+
if current_question:
|
|
98
|
+
questions.append({"content": current_question, "answers": current_answers})
|
|
99
|
+
|
|
100
|
+
return questions
|
|
101
|
+
|
|
102
|
+
def _process_code_blocks(self, text):
|
|
103
|
+
"""Process code blocks to handle newlines properly."""
|
|
104
|
+
|
|
105
|
+
# Helper function to process code blocks by type
|
|
106
|
+
def replace_newlines(match):
|
|
107
|
+
code = match.group(2) # The actual code content
|
|
108
|
+
lang = match.group(1) # The language class (python or console)
|
|
109
|
+
|
|
110
|
+
# Replace escaped newlines with actual newlines
|
|
111
|
+
code = code.replace("\\n", "\n")
|
|
112
|
+
return f'<pre><code class="{lang}">{code}</code></pre>'
|
|
113
|
+
|
|
114
|
+
# Find all code blocks with any class and process them
|
|
115
|
+
pattern = r'<pre><code class="([\w-]+)">(.*?)</code></pre>'
|
|
116
|
+
text = re.sub(pattern, replace_newlines, text, flags=re.DOTALL)
|
|
117
|
+
|
|
118
|
+
return text
|
|
119
|
+
|
|
120
|
+
def _process_figures(self, text):
|
|
121
|
+
"""Replace Markdown images with HTML <img> tags, copy figures, and fix path."""
|
|
122
|
+
import shutil
|
|
123
|
+
import re
|
|
124
|
+
import json
|
|
125
|
+
|
|
126
|
+
# Add a counter to track images within this quiz
|
|
127
|
+
if not hasattr(self, "_image_counter"):
|
|
128
|
+
self._image_counter = 0
|
|
129
|
+
|
|
130
|
+
def replace(match):
|
|
131
|
+
alt_or_options = match.group(1).strip() # Alt text or options
|
|
132
|
+
raw_src = match.group(2) # Image source path
|
|
133
|
+
|
|
134
|
+
# Increment image counter for unique naming
|
|
135
|
+
self._image_counter += 1
|
|
136
|
+
|
|
137
|
+
# Parse options from alt text
|
|
138
|
+
options = self._parse_figure_options(alt_or_options)
|
|
139
|
+
|
|
140
|
+
# Path of the source .md/.rst file
|
|
141
|
+
source_file = self.state.document["source"]
|
|
142
|
+
source_dir = os.path.dirname(source_file)
|
|
143
|
+
app_src_dir = self.env.srcdir # Root of source directory
|
|
144
|
+
|
|
145
|
+
# Absolute source path of the image file
|
|
146
|
+
abs_fig_src = os.path.normpath(os.path.join(source_dir, raw_src))
|
|
147
|
+
|
|
148
|
+
if not os.path.exists(abs_fig_src):
|
|
149
|
+
print(f"⚠️ QuizDirective: Figure not found: {abs_fig_src}")
|
|
150
|
+
return f'<img src="{raw_src}" class="quiz-image adaptive-figure" alt="Quiz figure (missing)">'
|
|
151
|
+
|
|
152
|
+
# Determine quiz-local static path: _static/figurer/<path to .md>/<filename>
|
|
153
|
+
relative_doc_path = os.path.relpath(source_dir, app_src_dir)
|
|
154
|
+
figure_dest_dir = os.path.join(app_src_dir, "_static", "figurer", relative_doc_path)
|
|
155
|
+
os.makedirs(figure_dest_dir, exist_ok=True)
|
|
156
|
+
|
|
157
|
+
# Create unique filename using the full relative path to avoid conflicts
|
|
158
|
+
# Convert the relative path from source to a safe filename part
|
|
159
|
+
rel_path_from_source = os.path.relpath(abs_fig_src, source_dir)
|
|
160
|
+
safe_path = rel_path_from_source.replace(os.sep, "_").replace("/", "_")
|
|
161
|
+
base_name, ext = os.path.splitext(safe_path)
|
|
162
|
+
|
|
163
|
+
# Use quiz_id, image counter, and the safe path for uniqueness
|
|
164
|
+
fig_filename = f"{self.quiz_id}_img{self._image_counter}_{base_name}{ext}"
|
|
165
|
+
fig_dest_path = os.path.join(figure_dest_dir, fig_filename)
|
|
166
|
+
|
|
167
|
+
# Copy image
|
|
168
|
+
shutil.copy2(abs_fig_src, fig_dest_path)
|
|
169
|
+
|
|
170
|
+
# Now calculate relative path from output HTML to _static
|
|
171
|
+
depth = os.path.relpath(source_dir, app_src_dir).count(os.sep)
|
|
172
|
+
rel_prefix = "../" * (depth + 1)
|
|
173
|
+
|
|
174
|
+
html_img_path = f"{rel_prefix}_static/figurer/{relative_doc_path}/{fig_filename}"
|
|
175
|
+
|
|
176
|
+
# Build HTML with options
|
|
177
|
+
html_img = self._build_figure_html(html_img_path, options)
|
|
178
|
+
|
|
179
|
+
return html_img
|
|
180
|
+
|
|
181
|
+
# Updated regex to capture both alt text and source
|
|
182
|
+
return re.sub(r"!\[([^\]]*)\]\(([^)]+)\)", replace, text)
|
|
183
|
+
|
|
184
|
+
def _parse_figure_options(self, alt_text):
|
|
185
|
+
"""Parse figure options from alt text."""
|
|
186
|
+
options = {}
|
|
187
|
+
|
|
188
|
+
# Method 1: JSON-like syntax {width: 60%, class: adaptive-figure}
|
|
189
|
+
if alt_text.startswith("{") and alt_text.endswith("}"):
|
|
190
|
+
try:
|
|
191
|
+
# Clean up the syntax for JSON parsing
|
|
192
|
+
json_str = alt_text
|
|
193
|
+
# Add quotes to keys: width: -> "width":
|
|
194
|
+
json_str = re.sub(r"(\w+):", r'"\1":', json_str)
|
|
195
|
+
# Add quotes to unquoted values
|
|
196
|
+
json_str = re.sub(r':\s*([^",}]+)', r': "\1"', json_str)
|
|
197
|
+
options = json.loads(json_str)
|
|
198
|
+
except:
|
|
199
|
+
# Fallback to simple parsing
|
|
200
|
+
options = self._parse_simple_options(alt_text[1:-1])
|
|
201
|
+
|
|
202
|
+
# Method 2: HTML-style attributes: width="60%" class="adaptive-figure"
|
|
203
|
+
elif "=" in alt_text:
|
|
204
|
+
options = self._parse_html_style_options(alt_text)
|
|
205
|
+
|
|
206
|
+
# Method 3: Plain alt text (traditional)
|
|
207
|
+
else:
|
|
208
|
+
if alt_text:
|
|
209
|
+
options["alt"] = alt_text
|
|
210
|
+
|
|
211
|
+
return options
|
|
212
|
+
|
|
213
|
+
def _parse_html_style_options(self, alt_text):
|
|
214
|
+
"""Parse HTML-style attributes: width="60%" class="adaptive-figure" """
|
|
215
|
+
options = {}
|
|
216
|
+
# Match attribute="value" or attribute='value'
|
|
217
|
+
pattern = r'(\w+)=(["\'])([^"\']*)\2'
|
|
218
|
+
|
|
219
|
+
for match in re.finditer(pattern, alt_text):
|
|
220
|
+
key = match.group(1)
|
|
221
|
+
value = match.group(3)
|
|
222
|
+
options[key] = value
|
|
223
|
+
|
|
224
|
+
return options
|
|
225
|
+
|
|
226
|
+
def _parse_simple_options(self, options_str):
|
|
227
|
+
"""Parse simple key: value syntax"""
|
|
228
|
+
options = {}
|
|
229
|
+
pairs = options_str.split(",")
|
|
230
|
+
|
|
231
|
+
for pair in pairs:
|
|
232
|
+
if ":" in pair:
|
|
233
|
+
key, value = pair.split(":", 1)
|
|
234
|
+
key = key.strip()
|
|
235
|
+
value = value.strip()
|
|
236
|
+
# Remove quotes if present
|
|
237
|
+
if (value.startswith('"') and value.endswith('"')) or (
|
|
238
|
+
value.startswith("'") and value.endswith("'")
|
|
239
|
+
):
|
|
240
|
+
value = value[1:-1]
|
|
241
|
+
options[key] = value
|
|
242
|
+
|
|
243
|
+
return options
|
|
244
|
+
|
|
245
|
+
def _build_figure_html(self, html_img_path, options):
|
|
246
|
+
"""Build the HTML for the figure with options."""
|
|
247
|
+
|
|
248
|
+
# Default options
|
|
249
|
+
default_options = {"class": "quiz-image adaptive-figure", "alt": "Quiz figure"}
|
|
250
|
+
|
|
251
|
+
# Merge with user options (user options override defaults)
|
|
252
|
+
final_options = {**default_options, **options}
|
|
253
|
+
|
|
254
|
+
# Build img attributes
|
|
255
|
+
img_attrs = []
|
|
256
|
+
img_attrs.append(f'src="{html_img_path}"')
|
|
257
|
+
|
|
258
|
+
for key, value in final_options.items():
|
|
259
|
+
if key == "width":
|
|
260
|
+
img_attrs.append(f'style="width: {value};"')
|
|
261
|
+
elif key == "height":
|
|
262
|
+
img_attrs.append(f'style="height: {value};"')
|
|
263
|
+
elif key in ["class", "alt", "title"]:
|
|
264
|
+
img_attrs.append(f'{key}="{value}"')
|
|
265
|
+
|
|
266
|
+
img_tag = f'<img {" ".join(img_attrs)}>'
|
|
267
|
+
|
|
268
|
+
# Wrap in container
|
|
269
|
+
html_img = f"""<div class="quiz-image-container">
|
|
270
|
+
{img_tag}
|
|
271
|
+
</div>
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
return html_img
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def setup(app):
|
|
278
|
+
app.add_directive("quiz", QuizDirective)
|
|
279
|
+
# Also link shared figure styles in case only this submodule is loaded
|
|
280
|
+
try:
|
|
281
|
+
app.add_css_file("munchboka/css/general_style.css")
|
|
282
|
+
app.add_css_file("munchboka/css/quiz.css")
|
|
283
|
+
app.add_js_file("munchboka/js/quiz.js")
|
|
284
|
+
except Exception:
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
"version": "0.1",
|
|
289
|
+
"parallel_read_safe": True,
|
|
290
|
+
"parallel_write_safe": True,
|
|
291
|
+
}
|