munchboka-edutools 0.1.13__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.

Potentially problematic release.


This version of munchboka-edutools might be problematic. Click here for more details.

Files changed (149) hide show
  1. munchboka_edutools/__init__.py +184 -0
  2. munchboka_edutools/_plotmath_shim.py +126 -0
  3. munchboka_edutools/_version.py +2 -0
  4. munchboka_edutools/directives/__init__.py +1 -0
  5. munchboka_edutools/directives/admonitions.py +389 -0
  6. munchboka_edutools/directives/cas_popup.py +272 -0
  7. munchboka_edutools/directives/clear.py +103 -0
  8. munchboka_edutools/directives/dialogue.py +137 -0
  9. munchboka_edutools/directives/escape_room.py +296 -0
  10. munchboka_edutools/directives/factor_tree.py +549 -0
  11. munchboka_edutools/directives/ggb.py +209 -0
  12. munchboka_edutools/directives/ggb_icon.py +105 -0
  13. munchboka_edutools/directives/ggb_popup.py +165 -0
  14. munchboka_edutools/directives/horner.py +324 -0
  15. munchboka_edutools/directives/interactive_code.py +75 -0
  16. munchboka_edutools/directives/jeopardy.py +252 -0
  17. munchboka_edutools/directives/multi_plot.py +1126 -0
  18. munchboka_edutools/directives/pair_puzzle.py +191 -0
  19. munchboka_edutools/directives/parsons.py +109 -0
  20. munchboka_edutools/directives/plot.py +3105 -0
  21. munchboka_edutools/directives/poly_icon.py +111 -0
  22. munchboka_edutools/directives/polydiv.py +344 -0
  23. munchboka_edutools/directives/popup.py +245 -0
  24. munchboka_edutools/directives/quiz.py +291 -0
  25. munchboka_edutools/directives/signchart.py +516 -0
  26. munchboka_edutools/directives/timed_quiz.py +436 -0
  27. munchboka_edutools/directives/turtle.py +157 -0
  28. munchboka_edutools/static/css/admonitions.css +2012 -0
  29. munchboka_edutools/static/css/cas_popup.css +242 -0
  30. munchboka_edutools/static/css/code_mirror_themes/github_dark_cm.css +112 -0
  31. munchboka_edutools/static/css/code_mirror_themes/github_dark_default_cm.css +40 -0
  32. munchboka_edutools/static/css/code_mirror_themes/github_dark_high_contrast_cm.css +141 -0
  33. munchboka_edutools/static/css/code_mirror_themes/github_light_cm.css +120 -0
  34. munchboka_edutools/static/css/code_mirror_themes/github_light_default_cm.css +108 -0
  35. munchboka_edutools/static/css/code_mirror_themes/one_dark_cm.css +121 -0
  36. munchboka_edutools/static/css/dialogue.css +92 -0
  37. munchboka_edutools/static/css/escapeRoom/escape-room.css +223 -0
  38. munchboka_edutools/static/css/figures.css +274 -0
  39. munchboka_edutools/static/css/general_style.css +74 -0
  40. munchboka_edutools/static/css/github-dark-high-contrast.css +141 -0
  41. munchboka_edutools/static/css/github-dark.css +112 -0
  42. munchboka_edutools/static/css/github-light.css +120 -0
  43. munchboka_edutools/static/css/interactive_code/style.css +575 -0
  44. munchboka_edutools/static/css/interactive_code.css +582 -0
  45. munchboka_edutools/static/css/jeopardy.css +529 -0
  46. munchboka_edutools/static/css/pairPuzzle/style.css +177 -0
  47. munchboka_edutools/static/css/parsons/parsonsPuzzle.css +331 -0
  48. munchboka_edutools/static/css/popup.css +115 -0
  49. munchboka_edutools/static/css/quiz.css +312 -0
  50. munchboka_edutools/static/css/timedQuiz.css +375 -0
  51. munchboka_edutools/static/icons/ggb/mode_evaluate.svg +1 -0
  52. munchboka_edutools/static/icons/ggb/mode_extremum.svg +1 -0
  53. munchboka_edutools/static/icons/ggb/mode_intersect.svg +1 -0
  54. munchboka_edutools/static/icons/ggb/mode_nsolve.svg +1 -0
  55. munchboka_edutools/static/icons/ggb/mode_numeric.svg +1 -0
  56. munchboka_edutools/static/icons/ggb/mode_point.svg +1 -0
  57. munchboka_edutools/static/icons/ggb/mode_solve.svg +1 -0
  58. munchboka_edutools/static/icons/misc/windows-logo.svg +1 -0
  59. munchboka_edutools/static/icons/outline/dark_mode/academic.svg +3 -0
  60. munchboka_edutools/static/icons/outline/dark_mode/backward.svg +3 -0
  61. munchboka_edutools/static/icons/outline/dark_mode/book.svg +3 -0
  62. munchboka_edutools/static/icons/outline/dark_mode/chat_bubble.svg +3 -0
  63. munchboka_edutools/static/icons/outline/dark_mode/check.svg +3 -0
  64. munchboka_edutools/static/icons/outline/dark_mode/cmd_line.svg +3 -0
  65. munchboka_edutools/static/icons/outline/dark_mode/file.svg +1 -0
  66. munchboka_edutools/static/icons/outline/dark_mode/fire.svg +4 -0
  67. munchboka_edutools/static/icons/outline/dark_mode/key.svg +3 -0
  68. munchboka_edutools/static/icons/outline/dark_mode/magnifying.svg +3 -0
  69. munchboka_edutools/static/icons/outline/dark_mode/pencil_square.svg +3 -0
  70. munchboka_edutools/static/icons/outline/dark_mode/play.svg +3 -0
  71. munchboka_edutools/static/icons/outline/dark_mode/question.svg +3 -0
  72. munchboka_edutools/static/icons/outline/dark_mode/square_check.svg +1 -0
  73. munchboka_edutools/static/icons/outline/dark_mode/stop.svg +3 -0
  74. munchboka_edutools/static/icons/outline/dark_mode/summary.svg +3 -0
  75. munchboka_edutools/static/icons/outline/dark_mode/undo.svg +3 -0
  76. munchboka_edutools/static/icons/outline/dark_mode/unlock.svg +3 -0
  77. munchboka_edutools/static/icons/outline/light_mode/academic.svg +3 -0
  78. munchboka_edutools/static/icons/outline/light_mode/backward.svg +3 -0
  79. munchboka_edutools/static/icons/outline/light_mode/book.svg +3 -0
  80. munchboka_edutools/static/icons/outline/light_mode/chat_bubble.svg +3 -0
  81. munchboka_edutools/static/icons/outline/light_mode/check.svg +3 -0
  82. munchboka_edutools/static/icons/outline/light_mode/cmd_line.svg +3 -0
  83. munchboka_edutools/static/icons/outline/light_mode/file.svg +1 -0
  84. munchboka_edutools/static/icons/outline/light_mode/fire.svg +4 -0
  85. munchboka_edutools/static/icons/outline/light_mode/key.svg +3 -0
  86. munchboka_edutools/static/icons/outline/light_mode/magnifying.svg +3 -0
  87. munchboka_edutools/static/icons/outline/light_mode/pencil_square.svg +3 -0
  88. munchboka_edutools/static/icons/outline/light_mode/play.svg +3 -0
  89. munchboka_edutools/static/icons/outline/light_mode/question.svg +3 -0
  90. munchboka_edutools/static/icons/outline/light_mode/square_check.svg +1 -0
  91. munchboka_edutools/static/icons/outline/light_mode/stop.svg +3 -0
  92. munchboka_edutools/static/icons/outline/light_mode/summary.svg +3 -0
  93. munchboka_edutools/static/icons/outline/light_mode/undo.svg +3 -0
  94. munchboka_edutools/static/icons/outline/light_mode/unlock.svg +3 -0
  95. munchboka_edutools/static/icons/polyicons/cubicdown.svg +3 -0
  96. munchboka_edutools/static/icons/polyicons/cubicup.svg +3 -0
  97. munchboka_edutools/static/icons/polyicons/frown.svg +3 -0
  98. munchboka_edutools/static/icons/polyicons/smile.svg +3 -0
  99. munchboka_edutools/static/icons/solid/dark_mode/academic.svg +5 -0
  100. munchboka_edutools/static/icons/solid/dark_mode/backward.svg +3 -0
  101. munchboka_edutools/static/icons/solid/dark_mode/book.svg +3 -0
  102. munchboka_edutools/static/icons/solid/dark_mode/brain.svg +1 -0
  103. munchboka_edutools/static/icons/solid/dark_mode/file.svg +1 -0
  104. munchboka_edutools/static/icons/solid/dark_mode/fire.svg +3 -0
  105. munchboka_edutools/static/icons/solid/dark_mode/key.svg +3 -0
  106. munchboka_edutools/static/icons/solid/dark_mode/pencil_square.svg +4 -0
  107. munchboka_edutools/static/icons/solid/dark_mode/play.svg +3 -0
  108. munchboka_edutools/static/icons/solid/dark_mode/python.svg +1 -0
  109. munchboka_edutools/static/icons/solid/dark_mode/scroll.svg +1 -0
  110. munchboka_edutools/static/icons/solid/dark_mode/stop.svg +3 -0
  111. munchboka_edutools/static/icons/solid/light_mode/academic.svg +5 -0
  112. munchboka_edutools/static/icons/solid/light_mode/backward.svg +3 -0
  113. munchboka_edutools/static/icons/solid/light_mode/book.svg +3 -0
  114. munchboka_edutools/static/icons/solid/light_mode/brain.svg +1 -0
  115. munchboka_edutools/static/icons/solid/light_mode/file.svg +1 -0
  116. munchboka_edutools/static/icons/solid/light_mode/fire.svg +3 -0
  117. munchboka_edutools/static/icons/solid/light_mode/key.svg +3 -0
  118. munchboka_edutools/static/icons/solid/light_mode/pencil_square.svg +4 -0
  119. munchboka_edutools/static/icons/solid/light_mode/play.svg +3 -0
  120. munchboka_edutools/static/icons/solid/light_mode/python.svg +1 -0
  121. munchboka_edutools/static/icons/solid/light_mode/scroll.svg +1 -0
  122. munchboka_edutools/static/icons/solid/light_mode/stop.svg +3 -0
  123. munchboka_edutools/static/icons/stickers/edit.svg +1 -0
  124. munchboka_edutools/static/icons/stickers/pencil_square.svg +3 -0
  125. munchboka_edutools/static/js/casThemeManager.js +99 -0
  126. munchboka_edutools/static/js/escapeRoom/escape-room.js +219 -0
  127. munchboka_edutools/static/js/geogebra-setup.js +6 -0
  128. munchboka_edutools/static/js/highlight-init.js +6 -0
  129. munchboka_edutools/static/js/interactiveCode/codeEditor.js +632 -0
  130. munchboka_edutools/static/js/interactiveCode/interactiveCodeSetup.js +348 -0
  131. munchboka_edutools/static/js/interactiveCode/pythonRunner.js +336 -0
  132. munchboka_edutools/static/js/interactiveCode/turtleCode.js +203 -0
  133. munchboka_edutools/static/js/interactiveCode/workerManager.js +353 -0
  134. munchboka_edutools/static/js/jeopardy.js +523 -0
  135. munchboka_edutools/static/js/pairPuzzle/draggableItem.js +64 -0
  136. munchboka_edutools/static/js/pairPuzzle/dropZone.js +119 -0
  137. munchboka_edutools/static/js/pairPuzzle/game.js +160 -0
  138. munchboka_edutools/static/js/parsons/parsonsPuzzle.js +641 -0
  139. munchboka_edutools/static/js/popup.js +85 -0
  140. munchboka_edutools/static/js/quiz.js +422 -0
  141. munchboka_edutools/static/js/skulpt/skulpt.js +35721 -0
  142. munchboka_edutools/static/js/timedQuiz/multipleChoiceQuestion.js +184 -0
  143. munchboka_edutools/static/js/timedQuiz/timedMultipleChoiceQuiz.js +244 -0
  144. munchboka_edutools/static/js/timedQuiz/utils.js +6 -0
  145. munchboka_edutools/static/js/utils.js +3 -0
  146. munchboka_edutools-0.1.13.dist-info/METADATA +108 -0
  147. munchboka_edutools-0.1.13.dist-info/RECORD +149 -0
  148. munchboka_edutools-0.1.13.dist-info/WHEEL +4 -0
  149. munchboka_edutools-0.1.13.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,103 @@
1
+ """
2
+ Clear Directive for Munchboka Edutools
3
+ ======================================
4
+
5
+ This directive inserts a CSS clear:both element to clear floating elements.
6
+ Useful after floated images or other content to ensure proper layout flow.
7
+
8
+ Usage in MyST Markdown:
9
+ ```{clear}
10
+ ```
11
+
12
+ Purpose:
13
+ The clear directive is commonly used in web layouts to clear CSS floats.
14
+ When you have floated elements (like images with float:left or float:right),
15
+ subsequent content can wrap around them. The clear directive ensures that
16
+ content after it starts on a new line below all floated elements.
17
+
18
+ Common Use Cases:
19
+ - After floating images to prevent text wrapping
20
+ - Between sections to ensure proper spacing
21
+ - To reset layout flow after floated elements
22
+
23
+ Features:
24
+ - No arguments or options needed
25
+ - Simply inserts a clearing div
26
+ - Works with all CSS float properties
27
+
28
+ Example:
29
+ ![Floating Image](image.png){.float-left}
30
+
31
+ Some text that wraps around the image...
32
+
33
+ ```{clear}
34
+ ```
35
+
36
+ This text starts below the image, not wrapped around it.
37
+
38
+ Author: René Aasen (ported from matematikk_r1)
39
+ Date: November 2025
40
+ """
41
+
42
+ from docutils import nodes
43
+ from docutils.parsers.rst import Directive
44
+
45
+
46
+ class ClearDirective(Directive):
47
+ """
48
+ Sphinx directive for clearing CSS floats.
49
+
50
+ This directive inserts a div with `clear: both` CSS property,
51
+ which forces content to start below any floated elements.
52
+
53
+ Arguments:
54
+ None
55
+
56
+ Options:
57
+ None
58
+
59
+ Example:
60
+ After a floated image:
61
+ ```{clear}
62
+ ```
63
+
64
+ Or between sections:
65
+ ```{clear}
66
+ ```
67
+ """
68
+
69
+ has_content = False
70
+ required_arguments = 0
71
+ optional_arguments = 0
72
+
73
+ def run(self):
74
+ """
75
+ Generate the HTML for the clear element.
76
+
77
+ Returns:
78
+ list: List of docutils nodes (raw HTML node with clear div)
79
+ """
80
+ html = '<div style="clear: both;"></div>'
81
+ return [nodes.raw("", html, format="html")]
82
+
83
+
84
+ def setup(app):
85
+ """
86
+ Setup function to register the directive with Sphinx.
87
+
88
+ This function is called automatically by Sphinx when the extension is loaded.
89
+ It registers the 'clear' directive for use in documentation.
90
+
91
+ Args:
92
+ app: The Sphinx application instance
93
+
94
+ Returns:
95
+ dict: Extension metadata including version and parallel processing flags
96
+ """
97
+ app.add_directive("clear", ClearDirective)
98
+
99
+ return {
100
+ "version": "0.1",
101
+ "parallel_read_safe": True,
102
+ "parallel_write_safe": True,
103
+ }
@@ -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
+ }