Skip to content

Commit d286a80

Browse files
Add a new-tabbed variant for the Replite directive and clean up new tab button text configuration (#228)
* Add Replite tabbed variant * Add docs on using `:new_tab:` for Replite * Add docs on global value and overrides for Replite buttons * Rename `:button_text:` to `:new_tab_button_text:` * Add "_new_tab_" to button text config options
1 parent aab1271 commit d286a80

File tree

6 files changed

+202
-35
lines changed

6 files changed

+202
-35
lines changed

docs/configuration.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,22 @@ jupyterlite_config = "jupyter_lite_config.json"
6767
jupyterlite_overrides = "overrides.json"
6868
```
6969

70-
# Setting default button texts for the `JupyterLite`, `NotebookLite`, and `Voici` directives
70+
# Setting default button texts for the `JupyterLite`, `NotebookLite`, `Replite`, and `Voici` directives
7171

72-
When using the `:new_tab:` option in the `JupyterLite`, `NotebookLite`, and `Voici` directives,
73-
the button text defaults to "Open as a notebook" and "Open with Voici", respectively.
72+
When using the `:new_tab:` option in the `JupyterLite`, `NotebookLite`, `Replite`, and `Voici` directives,
73+
the button text defaults to "Open as a notebook", "Open in a REPL", and "Open with Voici", respectively.
7474

7575
You can optionally the button text on a global level for these directives by setting the
7676
following values in your `conf.py` file:
7777

7878
```python
79-
jupyterlite_button_text = "My custom JupyterLite button text"
80-
notebooklite_button_text = "My custom NotebookLite button text"
81-
voici_button_text = "My custom Voici button text"
79+
jupyterlite_new_tab_button_text = "My custom JupyterLite button text"
80+
notebooklite_new_tab_button_text = "My custom NotebookLite button text"
81+
replite_new_tab_button_text = "My custom Replite button text"
82+
voici_new_tab_button_text = "My custom Voici button text"
8283
```
8384

84-
You can override this text on a per-directive basis by passing the `:button_text:` option
85+
You can override this text on a per-directive basis by passing the `:new_tab_button_text:` option
8586
to the directive. Note that this is compatible only if `:new_tab:` is also provided.
8687

8788
## Strip particular tagged cells from IPython Notebooks

docs/directives/jupyterlite.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,18 @@ of JupyterLite.
5151
```
5252

5353
When using this option, it is also possible to customise the button text, overriding the
54-
global value using an additional `:button_text:` parameter:
54+
global value using an additional `:new_tab_button_text:` parameter:
5555

5656
```rst
5757
.. jupyterlite:: my_notebook.ipynb
5858
:new_tab: True
59-
:button_text: My custom JupyterLite button text
59+
:new_tab_button_text: My custom JupyterLite button text
6060
```
6161

6262
```{eval-rst}
6363
.. jupyterlite:: my_notebook.ipynb
6464
:new_tab: True
65-
:button_text: My custom JupyterLite button text
65+
:new_tab_button_text: My custom JupyterLite button text
6666
```
6767

6868
## Search parameters

docs/directives/notebooklite.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,16 @@ Lab interface.
4747
```
4848

4949
When using this option, it is also possible to customise the button text, overriding the
50-
global value using an additional `:button_text:` parameter:
50+
global value using an additional `:new_tab_button_text:` parameter:
5151

5252
```rst
5353
.. notebooklite:: my_notebook.ipynb
5454
:new_tab: True
55-
:button_text: My custom NotebookLite button text
55+
:new_tab_button_text: My custom NotebookLite button text
5656
```
5757

5858
```{eval-rst}
5959
.. notebooklite:: my_notebook.ipynb
6060
:new_tab: True
61-
:button_text: My custom NotebookLite button text
61+
:new_tab_button_text: My custom NotebookLite button text
6262
```

docs/directives/replite.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,75 @@ This directive takes extra options which are the same options as the `replite` p
3838
ax.plot(x, y)
3939
plt.show()
4040
```
41+
42+
If you use the `:new_tab:` option in the directive, the Replite console will be opened in a new browser tab
43+
with the code pre-filled.
44+
45+
```rst
46+
.. replite::
47+
:kernel: xeus-python
48+
:new_tab: True
49+
50+
import matplotlib.pyplot as plt
51+
import numpy as np
52+
53+
x = np.linspace(0, 2 * np.pi, 200)
54+
y = np.sin(x)
55+
56+
fig, ax = plt.subplots()
57+
ax.plot(x, y)
58+
plt.show()
59+
```
60+
61+
```{eval-rst}
62+
.. replite::
63+
:kernel: xeus-python
64+
:new_tab: True
65+
66+
import matplotlib.pyplot as plt
67+
import numpy as np
68+
69+
x = np.linspace(0, 2 * np.pi, 200)
70+
y = np.sin(x)
71+
72+
fig, ax = plt.subplots()
73+
ax.plot(x, y)
74+
plt.show()
75+
```
76+
77+
When using this option, it is also possible to customise the button text, overriding the
78+
global value using an additional `:new_tab_button_text:` parameter:
79+
80+
```rst
81+
.. replite::
82+
:kernel: xeus-python
83+
:new_tab: True
84+
:new_tab_button_text: My custom Replite button text
85+
86+
import matplotlib.pyplot as plt
87+
import numpy as np
88+
89+
x = np.linspace(0, 2 * np.pi, 200)
90+
y = np.sin(x)
91+
92+
fig, ax = plt.subplots()
93+
ax.plot(x, y)
94+
plt.show()
95+
```
96+
97+
```{eval-rst}
98+
.. replite::
99+
:kernel: xeus-python
100+
:new_tab: True
101+
:new_tab_button_text: My custom Replite button text
102+
103+
import matplotlib.pyplot as plt
104+
import numpy as np
105+
106+
x = np.linspace(0, 2 * np.pi, 200)
107+
y = np.sin(x)
108+
109+
fig, ax = plt.subplots()
110+
ax.plot(x, y)
111+
plt.show()
112+
```

docs/directives/voici.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@ the notebook in a new browser tab, instead of in the current page.
4242
```
4343

4444
When using this option, it is also possible to customise the button text, overriding the
45-
global value using an additional `:button_text:` parameter:
45+
global value using an additional `:new_tab_button_text:` parameter:
4646

4747
```rst
4848
.. voici:: my_notebook.ipynb
4949
:new_tab: True
50-
:button_text: My custom Voici button text
50+
:new_tab_button_text: My custom Voici button text
5151
```
5252

5353
```{eval-rst}
5454
.. voici:: my_notebook.ipynb
5555
:new_tab: True
56-
:button_text: My custom Voici button text
56+
:new_tab_button_text: My custom Voici button text
5757
```

jupyterlite_sphinx/jupyterlite_sphinx.py

Lines changed: 113 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,66 @@ class NotebookLiteTab(BaseNotebookTab):
236236
notebooks_path = "../notebooks/"
237237

238238

239+
# We do not inherit from _InTab here because Replite
240+
# has a different URL structure and we need to ensure
241+
# that the code is serialised to be passed to the URL.
242+
class RepliteTab(Element):
243+
"""Appended to the doctree by the RepliteDirective directive
244+
245+
Renders a button that opens a REPL with JupyterLite in a new tab.
246+
"""
247+
248+
lite_app = "repl/"
249+
notebooks_path = ""
250+
251+
def __init__(
252+
self,
253+
rawsource="",
254+
*children,
255+
prefix=JUPYTERLITE_DIR,
256+
content=[],
257+
notebook=None,
258+
lite_options={},
259+
button_text=None,
260+
**attributes,
261+
):
262+
# For a new-tabbed variant, we need to ensure we process the content
263+
# into properly encoded code for passing it to the URL.
264+
if content:
265+
code_lines: list[str] = [
266+
"" if not line.strip() else line for line in content
267+
]
268+
code = "\n".join(code_lines)
269+
lite_options["code"] = code
270+
271+
app_path = self.lite_app
272+
if notebook is not None:
273+
lite_options["path"] = notebook
274+
app_path = f"{self.lite_app}{self.notebooks_path}"
275+
276+
options = "&".join(
277+
[f"{key}={quote(value)}" for key, value in lite_options.items()]
278+
)
279+
280+
self.lab_src = (
281+
f'{prefix}/{app_path}{f"index.html?{options}" if options else ""}'
282+
)
283+
284+
self.button_text = button_text
285+
286+
super().__init__(
287+
rawsource,
288+
**attributes,
289+
)
290+
291+
def html(self):
292+
return (
293+
'<button class="try_examples_button" '
294+
f"onclick=\"window.open('{self.lab_src}')\">"
295+
f"{self.button_text}</button>"
296+
)
297+
298+
239299
class NotebookLiteIframe(_LiteIframe):
240300
"""Appended to the doctree by the NotebookliteDirective directive
241301
@@ -345,6 +405,8 @@ class RepliteDirective(SphinxDirective):
345405
"prompt": directives.unchanged,
346406
"prompt_color": directives.unchanged,
347407
"search_params": directives.unchanged,
408+
"new_tab": directives.unchanged,
409+
"new_tab_button_text": directives.unchanged,
348410
}
349411

350412
def run(self):
@@ -356,19 +418,45 @@ def run(self):
356418

357419
search_params = search_params_parser(self.options.pop("search_params", False))
358420

421+
new_tab = self.options.pop("new_tab", False)
422+
423+
content = self.content
424+
425+
button_text = None
426+
359427
prefix = os.path.relpath(
360428
os.path.join(self.env.app.srcdir, JUPYTERLITE_DIR),
361429
os.path.dirname(self.get_source_info()[0]),
362430
)
363431

432+
if new_tab:
433+
directive_button_text = self.options.pop("new_tab_button_text", None)
434+
if directive_button_text is not None:
435+
button_text = directive_button_text
436+
else:
437+
button_text = self.env.config.replite_new_tab_button_text
438+
return [
439+
RepliteTab(
440+
prefix=prefix,
441+
width=width,
442+
height=height,
443+
prompt=prompt,
444+
prompt_color=prompt_color,
445+
content=content,
446+
search_params=search_params,
447+
lite_options=self.options,
448+
button_text=button_text,
449+
)
450+
]
451+
364452
return [
365453
RepliteIframe(
366454
prefix=prefix,
367455
width=width,
368456
height=height,
369457
prompt=prompt,
370458
prompt_color=prompt_color,
371-
content=self.content,
459+
content=content,
372460
search_params=search_params,
373461
lite_options=self.options,
374462
)
@@ -387,7 +475,7 @@ class _LiteDirective(SphinxDirective):
387475
"prompt_color": directives.unchanged,
388476
"search_params": directives.unchanged,
389477
"new_tab": directives.unchanged,
390-
"button_text": directives.unchanged,
478+
"new_tab_button_text": directives.unchanged,
391479
}
392480

393481
def run(self):
@@ -451,24 +539,19 @@ def run(self):
451539
notebook_name = None
452540

453541
if new_tab:
454-
directive_button_text = self.options.pop("button_text", None)
542+
directive_button_text = self.options.pop("new_tab_button_text", None)
455543
if directive_button_text is not None:
456544
button_text = directive_button_text
457545
else:
458546
# If none, we use the appropriate global config based on
459547
# the type of directive passed.
460548
if isinstance(self, JupyterLiteDirective):
461-
button_text = self.env.config.jupyterlite_button_text
549+
button_text = self.env.config.jupyterlite_new_tab_button_text
462550
elif isinstance(self, NotebookLiteDirective):
463-
button_text = self.env.config.notebooklite_button_text
551+
button_text = self.env.config.notebooklite_new_tab_button_text
464552
elif isinstance(self, VoiciDirective):
465-
button_text = self.env.config.voici_button_text
466-
elif "button_text" in self.options:
467-
raise ValueError(
468-
"'button_text' is only valid if 'new_tab' is True. To modify the prompt text, use 'prompt' and 'prompt_color'."
469-
)
553+
button_text = self.env.config.voici_new_tab_button_text
470554

471-
if new_tab:
472555
return [
473556
self.newtab_cls(
474557
prefix=prefix,
@@ -511,9 +594,9 @@ class BaseJupyterViewDirective(_LiteDirective):
511594
"prompt_color": directives.unchanged,
512595
"search_params": directives.unchanged,
513596
"new_tab": directives.unchanged,
514-
# "button_text" below is valid only if "new_tab" is True, otherwise
597+
# "new_tab_button_text" below is useful only if "new_tab" is True, otherwise
515598
# we have "prompt" and "prompt_color" as options already.
516-
"button_text": directives.unchanged,
599+
"new_tab_button_text": directives.unchanged,
517600
}
518601

519602

@@ -927,15 +1010,18 @@ def setup(app):
9271010
rebuild="html",
9281011
)
9291012

930-
# Allow customising the button text for each directive (only when "new_tab" is True,
931-
# error otherwise)
1013+
# Allow customising the button text for each directive (this is useful
1014+
# only when "new_tab" is set to True)
1015+
app.add_config_value(
1016+
"jupyterlite_new_tab_button_text", "Open as a notebook", rebuild="html"
1017+
)
9321018
app.add_config_value(
933-
"jupyterlite_button_text", "Open as a notebook", rebuild="html"
1019+
"notebooklite_new_tab_button_text", "Open as a notebook", rebuild="html"
9341020
)
1021+
app.add_config_value("voici_new_tab_button_text", "Open with Voici", rebuild="html")
9351022
app.add_config_value(
936-
"notebooklite_button_text", "Open as a notebook", rebuild="html"
1023+
"replite_new_tab_button_text", "Open in a REPL", rebuild="html"
9371024
)
938-
app.add_config_value("voici_button_text", "Open with Voici", rebuild="html")
9391025

9401026
# Initialize NotebookLite and JupyterLite directives
9411027
app.add_node(
@@ -968,7 +1054,7 @@ def setup(app):
9681054
)
9691055
app.add_directive("jupyterlite", JupyterLiteDirective)
9701056

971-
# Initialize Replite directive
1057+
# Initialize Replite directive and tab
9721058
app.add_node(
9731059
RepliteIframe,
9741060
html=(visit_element_html, None),
@@ -977,6 +1063,14 @@ def setup(app):
9771063
text=(skip, None),
9781064
man=(skip, None),
9791065
)
1066+
app.add_node(
1067+
RepliteTab,
1068+
html=(visit_element_html, None),
1069+
latex=(skip, None),
1070+
textinfo=(skip, None),
1071+
text=(skip, None),
1072+
man=(skip, None),
1073+
)
9801074
app.add_directive("replite", RepliteDirective)
9811075

9821076
# Initialize Voici directive and tabbed interface

0 commit comments

Comments
 (0)