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,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAS Popup Directive
|
|
3
|
+
===================
|
|
4
|
+
|
|
5
|
+
A Sphinx directive that creates a button which opens a GeoGebra CAS (Computer Algebra System)
|
|
6
|
+
window in a popup dialog. The dialog can be resized, dragged, and positioned anywhere on the page.
|
|
7
|
+
|
|
8
|
+
Features:
|
|
9
|
+
- Customizable button text and dialog title
|
|
10
|
+
- Configurable width and height
|
|
11
|
+
- Sidebar layout option for floating the button to the side
|
|
12
|
+
- Scroll-locking when interacting with GeoGebra
|
|
13
|
+
- Responsive resizing
|
|
14
|
+
- Theme-aware styling (light/dark mode)
|
|
15
|
+
|
|
16
|
+
Dependencies:
|
|
17
|
+
- jQuery UI (for dialog functionality)
|
|
18
|
+
- GeoGebra API (loaded via geogebra-setup.js)
|
|
19
|
+
|
|
20
|
+
Usage Examples:
|
|
21
|
+
--------------
|
|
22
|
+
|
|
23
|
+
Basic usage (default 350x500 CAS window):
|
|
24
|
+
```
|
|
25
|
+
.. cas-popup::
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Custom size with 600x700 window:
|
|
29
|
+
```
|
|
30
|
+
.. cas-popup:: 600 700
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Custom button text and dialog title:
|
|
34
|
+
```
|
|
35
|
+
.. cas-popup:: 400 600 "Open Calculator" "My CAS Window"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Sidebar layout (floating button):
|
|
39
|
+
```
|
|
40
|
+
.. cas-popup:: 350 500 "CAS" "Calculator"
|
|
41
|
+
:layout: sidebar
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Author: Original implementation from matematikk_r1
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
from docutils import nodes
|
|
48
|
+
from sphinx.util.docutils import SphinxDirective
|
|
49
|
+
import uuid
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CASPopUpDirective(SphinxDirective):
|
|
53
|
+
"""
|
|
54
|
+
Directive to create a popup GeoGebra CAS window.
|
|
55
|
+
|
|
56
|
+
Arguments:
|
|
57
|
+
1. width (optional): Width of the CAS window in pixels (default: 350)
|
|
58
|
+
2. height (optional): Height of the CAS window in pixels (default: 500)
|
|
59
|
+
3. button_text (optional): Text displayed on the button (default: "Åpne CAS‑vindu")
|
|
60
|
+
4. dialog_title (optional): Title of the popup dialog (default: "CAS‑vindu")
|
|
61
|
+
|
|
62
|
+
Options:
|
|
63
|
+
:layout: Set to "sidebar" to float the button to the right side of the page
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
required_arguments = 0
|
|
67
|
+
optional_arguments = 4 # width, height, button text, dialog title
|
|
68
|
+
final_argument_whitespace = True
|
|
69
|
+
has_content = False
|
|
70
|
+
option_spec = {
|
|
71
|
+
"layout": lambda arg: arg, # e.g., "sidebar"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def run(self):
|
|
75
|
+
# 1 » parse args -----------------------------------------------------
|
|
76
|
+
width = int(self.arguments[0]) if len(self.arguments) > 0 else 350
|
|
77
|
+
height = int(self.arguments[1]) if len(self.arguments) > 1 else 500
|
|
78
|
+
|
|
79
|
+
button_text = self.arguments[2] if len(self.arguments) > 2 else "Åpne CAS‑vindu"
|
|
80
|
+
dialog_title = self.arguments[3] if len(self.arguments) > 3 else "CAS‑vindu"
|
|
81
|
+
|
|
82
|
+
# 2 » unique IDs -----------------------------------------------------
|
|
83
|
+
cid = f"ggb-cas-{uuid.uuid4().hex[:8]}"
|
|
84
|
+
dialog_id = f"dialog-{cid}"
|
|
85
|
+
button_id = f"button-{cid}"
|
|
86
|
+
|
|
87
|
+
# 3 » layout option --------------------------------------------------
|
|
88
|
+
layout = self.options.get("layout", "").strip().lower()
|
|
89
|
+
use_sidebar = layout == "sidebar"
|
|
90
|
+
|
|
91
|
+
wrapper_start = '<div class="sidebar-cas">' if use_sidebar else ""
|
|
92
|
+
wrapper_end = "</div>" if use_sidebar else ""
|
|
93
|
+
|
|
94
|
+
# 4 » HTML content ---------------------------------------------------
|
|
95
|
+
html = f"""
|
|
96
|
+
{wrapper_start}
|
|
97
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
98
|
+
|
|
99
|
+
<button id="{button_id}" class="ggb-cas-button">{button_text}</button>
|
|
100
|
+
<div id="{dialog_id}" title="{dialog_title}" style="display:none;">
|
|
101
|
+
<div id="{cid}" class="ggb-window"></div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<style>
|
|
105
|
+
.ui-resizable-handle {{ min-width:16px;min-height:16px; }}
|
|
106
|
+
.ui-dialog-content{{padding:0!important;}}
|
|
107
|
+
.ggb-window {{width:100%!important;height:100%!important;box-sizing:border-box;}}
|
|
108
|
+
.ggb-cas-button {{margin-top: 1em; margin-bottom: 1em;}}
|
|
109
|
+
</style>
|
|
110
|
+
|
|
111
|
+
<script>
|
|
112
|
+
(function() {{
|
|
113
|
+
$(function() {{
|
|
114
|
+
let ggbReady = false;
|
|
115
|
+
|
|
116
|
+
function applySize() {{
|
|
117
|
+
if (!ggbReady) return;
|
|
118
|
+
const w = $("#{cid}").width(),
|
|
119
|
+
h = $("#{cid}").height();
|
|
120
|
+
window.ggbApplet.setSize(Math.round(w), Math.round(h));
|
|
121
|
+
}}
|
|
122
|
+
|
|
123
|
+
const $dlg = $("#{dialog_id}").dialog({{
|
|
124
|
+
autoOpen: false,
|
|
125
|
+
width: {width+40}, height: {height+80},
|
|
126
|
+
resizable: true, draggable: true,
|
|
127
|
+
position: {{ my: "center", at: "center", of: window }},
|
|
128
|
+
resize: () => window.requestAnimationFrame(applySize),
|
|
129
|
+
open: function() {{
|
|
130
|
+
if (!ggbReady) {{
|
|
131
|
+
new GGBApplet({{
|
|
132
|
+
appName: "classic", id: "{cid}",
|
|
133
|
+
width: {width}, height: {height},
|
|
134
|
+
perspective: "C", language: "nb",
|
|
135
|
+
showToolBar: true, showAlgebraInput: true,
|
|
136
|
+
borderRadius: 8, enableRightClick: true, showKeyboardOnFocus: false,
|
|
137
|
+
customToolBar: "1001 | 1002 | 1007 | 1010 | 6",
|
|
138
|
+
appletOnLoad: () => {{ ggbReady = true; applySize(); }}
|
|
139
|
+
}}, true).inject("{cid}");
|
|
140
|
+
}} else {{
|
|
141
|
+
applySize();
|
|
142
|
+
}}
|
|
143
|
+
}}
|
|
144
|
+
}});
|
|
145
|
+
|
|
146
|
+
// Prevent background scroll when GeoGebra applet is active
|
|
147
|
+
let scrollLocked = false;
|
|
148
|
+
let lockedScrollTop = 0;
|
|
149
|
+
let keyHandler = null;
|
|
150
|
+
let wheelHandler = null;
|
|
151
|
+
let scrollHandler = null;
|
|
152
|
+
|
|
153
|
+
function lockScroll() {{
|
|
154
|
+
if (scrollLocked) return;
|
|
155
|
+
scrollLocked = true;
|
|
156
|
+
lockedScrollTop = window.pageYOffset || document.documentElement.scrollTop || 0;
|
|
157
|
+
|
|
158
|
+
// Instead of preventing keys, monitor and restore scroll position
|
|
159
|
+
scrollHandler = () => {{
|
|
160
|
+
if (scrollLocked) {{
|
|
161
|
+
const currentScroll = window.pageYOffset || document.documentElement.scrollTop || 0;
|
|
162
|
+
if (currentScroll !== lockedScrollTop) {{
|
|
163
|
+
window.scrollTo(0, lockedScrollTop);
|
|
164
|
+
}}
|
|
165
|
+
}}
|
|
166
|
+
}};
|
|
167
|
+
|
|
168
|
+
// Prevent wheel scrolling on the page background
|
|
169
|
+
wheelHandler = (e) => {{
|
|
170
|
+
const dialogElement = e.target.closest('.ui-dialog');
|
|
171
|
+
if (dialogElement) {{
|
|
172
|
+
// Allow scrolling within dialog elements that can scroll
|
|
173
|
+
let target = e.target;
|
|
174
|
+
while (target && target !== dialogElement) {{
|
|
175
|
+
const overflow = window.getComputedStyle(target).overflow;
|
|
176
|
+
if (overflow === 'auto' || overflow === 'scroll') {{
|
|
177
|
+
return; // Allow internal scrolling
|
|
178
|
+
}}
|
|
179
|
+
target = target.parentElement;
|
|
180
|
+
}}
|
|
181
|
+
// Prevent page scroll but don't stop the event from reaching GeoGebra
|
|
182
|
+
if (!e.target.closest('#{cid}')) {{
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
e.stopPropagation();
|
|
185
|
+
}}
|
|
186
|
+
}}
|
|
187
|
+
}};
|
|
188
|
+
|
|
189
|
+
// Monitor scroll events to maintain position
|
|
190
|
+
window.addEventListener('scroll', scrollHandler, {{ passive: true }});
|
|
191
|
+
document.addEventListener('wheel', wheelHandler, {{ capture: true, passive: false }});
|
|
192
|
+
}}
|
|
193
|
+
|
|
194
|
+
function unlockScroll() {{
|
|
195
|
+
if (!scrollLocked) return;
|
|
196
|
+
scrollLocked = false;
|
|
197
|
+
|
|
198
|
+
if (scrollHandler) {{
|
|
199
|
+
window.removeEventListener('scroll', scrollHandler);
|
|
200
|
+
scrollHandler = null;
|
|
201
|
+
}}
|
|
202
|
+
if (wheelHandler) {{
|
|
203
|
+
document.removeEventListener('wheel', wheelHandler, {{ capture: true }});
|
|
204
|
+
wheelHandler = null;
|
|
205
|
+
}}
|
|
206
|
+
}}
|
|
207
|
+
|
|
208
|
+
// Monitor GeoGebra applet focus/interaction to control scroll lock
|
|
209
|
+
function bindScrollLock() {{
|
|
210
|
+
const ggbContainer = document.getElementById('{cid}');
|
|
211
|
+
if (!ggbContainer) {{
|
|
212
|
+
setTimeout(bindScrollLock, 300);
|
|
213
|
+
return;
|
|
214
|
+
}}
|
|
215
|
+
|
|
216
|
+
// Look for the actual GeoGebra canvas/applet elements
|
|
217
|
+
const ggbApplet = ggbContainer.querySelector('canvas') ||
|
|
218
|
+
ggbContainer.querySelector('.ggbApplet') ||
|
|
219
|
+
ggbContainer.querySelector('[id^="ggbApplet"]') ||
|
|
220
|
+
ggbContainer;
|
|
221
|
+
|
|
222
|
+
if (ggbApplet) {{
|
|
223
|
+
// Lock scroll when interacting with GeoGebra
|
|
224
|
+
ggbApplet.addEventListener('mousedown', lockScroll);
|
|
225
|
+
ggbApplet.addEventListener('focus', lockScroll);
|
|
226
|
+
ggbApplet.addEventListener('click', lockScroll);
|
|
227
|
+
|
|
228
|
+
// Also monitor the container for any focus events
|
|
229
|
+
ggbContainer.addEventListener('focusin', lockScroll);
|
|
230
|
+
ggbContainer.addEventListener('mousedown', lockScroll);
|
|
231
|
+
|
|
232
|
+
// Unlock when clicking outside or losing focus
|
|
233
|
+
document.addEventListener('click', (e) => {{
|
|
234
|
+
if (!ggbContainer.contains(e.target) && !e.target.closest('.ui-dialog')) {{
|
|
235
|
+
unlockScroll();
|
|
236
|
+
}}
|
|
237
|
+
}});
|
|
238
|
+
|
|
239
|
+
ggbContainer.addEventListener('focusout', (e) => {{
|
|
240
|
+
// Small delay to check if focus moved outside the container
|
|
241
|
+
setTimeout(() => {{
|
|
242
|
+
if (!ggbContainer.contains(document.activeElement)) {{
|
|
243
|
+
unlockScroll();
|
|
244
|
+
}}
|
|
245
|
+
}}, 100);
|
|
246
|
+
}});
|
|
247
|
+
}}
|
|
248
|
+
}}
|
|
249
|
+
|
|
250
|
+
// Start monitoring after GeoGebra is loaded
|
|
251
|
+
setTimeout(bindScrollLock, 1000);
|
|
252
|
+
|
|
253
|
+
// Cleanup on dialog close
|
|
254
|
+
$dlg.on('dialogclose', unlockScroll);
|
|
255
|
+
|
|
256
|
+
$("#{button_id}").button()
|
|
257
|
+
.on("click touchend pointerup", e => {{
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
$("#{dialog_id}").dialog("open");
|
|
260
|
+
}});
|
|
261
|
+
}});
|
|
262
|
+
}})();
|
|
263
|
+
</script>
|
|
264
|
+
{wrapper_end}
|
|
265
|
+
"""
|
|
266
|
+
return [nodes.raw("", html, format="html")]
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def setup(app):
|
|
270
|
+
"""Register the directive with Sphinx."""
|
|
271
|
+
app.add_directive("cas-popup", CASPopUpDirective)
|
|
272
|
+
return {"version": "0.1", "parallel_read_safe": True, "parallel_write_safe": True}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dialogue directive for creating conversation-style content.
|
|
3
|
+
|
|
4
|
+
This directive creates a dialogue between two speakers with chat-bubble styling.
|
|
5
|
+
Each speaker's messages are aligned left or right and styled with different colors.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
```{dialogue}
|
|
9
|
+
:name1: Alice
|
|
10
|
+
:name2: Bob
|
|
11
|
+
:speaker1: left
|
|
12
|
+
:speaker2: right
|
|
13
|
+
|
|
14
|
+
Alice: Hello! How are you?
|
|
15
|
+
Bob: I'm great, thanks for asking!
|
|
16
|
+
Alice: That's wonderful to hear.
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
- name1 (required): Name of the first speaker
|
|
21
|
+
- name2 (required): Name of the second speaker
|
|
22
|
+
- speaker1 (required): Alignment for first speaker ("left" or "right")
|
|
23
|
+
- speaker2 (required): Alignment for second speaker ("left" or "right")
|
|
24
|
+
|
|
25
|
+
Content:
|
|
26
|
+
Each line should be in the format: "SpeakerName: Message text"
|
|
27
|
+
Lines not matching this format are ignored.
|
|
28
|
+
Messages can contain math notation and will be parsed as normal content.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from docutils import nodes
|
|
32
|
+
from docutils.parsers.rst import Directive, directives
|
|
33
|
+
from sphinx.directives import optional_int
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DialogueWrapperNode(nodes.General, nodes.Element):
|
|
37
|
+
"""Container node for the entire dialogue."""
|
|
38
|
+
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DialogueEntryNode(nodes.Admonition, nodes.Element):
|
|
43
|
+
"""Node for a single dialogue entry (one message from one speaker)."""
|
|
44
|
+
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def visit_dialogue_wrapper_node_html(self, node):
|
|
49
|
+
"""Open the dialogue container div."""
|
|
50
|
+
self.body.append('<div class="dialogue">')
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def depart_dialogue_wrapper_node_html(self, node):
|
|
54
|
+
"""Close the dialogue container div."""
|
|
55
|
+
self.body.append("</div>")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def visit_dialogue_entry_node_html(self, node):
|
|
59
|
+
"""Open a dialogue entry with speaker name."""
|
|
60
|
+
self.body.append(f'<div class="dialogue-entry {node["css_class"]}">')
|
|
61
|
+
self.body.append(f'<div class="speaker-name">{node["speaker_name"]}</div>')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def depart_dialogue_entry_node_html(self, node):
|
|
65
|
+
"""Close the dialogue entry div."""
|
|
66
|
+
self.body.append("</div>")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class DialogueDirective(Directive):
|
|
70
|
+
"""
|
|
71
|
+
Directive for creating dialogue between two speakers.
|
|
72
|
+
|
|
73
|
+
Creates chat-bubble styled conversations with left/right alignment.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
has_content = True
|
|
77
|
+
required_arguments = 0
|
|
78
|
+
option_spec = {
|
|
79
|
+
"name1": directives.unchanged_required,
|
|
80
|
+
"name2": directives.unchanged_required,
|
|
81
|
+
"speaker1": lambda s: directives.choice(s, ("left", "right")),
|
|
82
|
+
"speaker2": lambda s: directives.choice(s, ("left", "right")),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
def run(self):
|
|
86
|
+
"""Parse dialogue content and create dialogue nodes."""
|
|
87
|
+
name1 = self.options.get("name1")
|
|
88
|
+
name2 = self.options.get("name2")
|
|
89
|
+
class1 = "speaker1" if self.options.get("speaker1") == "left" else "speaker2"
|
|
90
|
+
class2 = "speaker1" if self.options.get("speaker2") == "left" else "speaker2"
|
|
91
|
+
|
|
92
|
+
wrapper_node = DialogueWrapperNode()
|
|
93
|
+
|
|
94
|
+
for line in self.content:
|
|
95
|
+
line = line.strip()
|
|
96
|
+
if not line or ":" not in line:
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
speaker_name, message = line.split(":", 1)
|
|
100
|
+
speaker_name = speaker_name.strip()
|
|
101
|
+
message = message.strip()
|
|
102
|
+
|
|
103
|
+
if speaker_name == name1:
|
|
104
|
+
css_class = class1
|
|
105
|
+
elif speaker_name == name2:
|
|
106
|
+
css_class = class2
|
|
107
|
+
else:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
entry_node = DialogueEntryNode()
|
|
111
|
+
entry_node["css_class"] = css_class
|
|
112
|
+
entry_node["speaker_name"] = speaker_name
|
|
113
|
+
|
|
114
|
+
# Parse the message line like normal content so math works
|
|
115
|
+
self.state.nested_parse([message], self.content_offset, entry_node)
|
|
116
|
+
wrapper_node += entry_node
|
|
117
|
+
|
|
118
|
+
return [wrapper_node]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def setup(app):
|
|
122
|
+
"""Register the dialogue directive and nodes with Sphinx."""
|
|
123
|
+
app.add_node(
|
|
124
|
+
DialogueWrapperNode,
|
|
125
|
+
html=(visit_dialogue_wrapper_node_html, depart_dialogue_wrapper_node_html),
|
|
126
|
+
)
|
|
127
|
+
app.add_node(
|
|
128
|
+
DialogueEntryNode,
|
|
129
|
+
html=(visit_dialogue_entry_node_html, depart_dialogue_entry_node_html),
|
|
130
|
+
)
|
|
131
|
+
app.add_directive("dialogue", DialogueDirective)
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
"version": "0.1.0",
|
|
135
|
+
"parallel_read_safe": True,
|
|
136
|
+
"parallel_write_safe": True,
|
|
137
|
+
}
|