Skip to content

Commit 4096adb

Browse files
Merge pull request #223 from agriyakhetarpal/feat/new-tabs-for-notebooklite-directive
Add an option to open the Notebook UI and Voici apps in a new tab via the`NotebookLite` and `Voici` directives
2 parents b2f6264 + f6c4731 commit 4096adb

File tree

4 files changed

+143
-17
lines changed

4 files changed

+143
-17
lines changed

docs/directives/jupyterlite.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ You can also pass a Notebook file to open automatically:
3737
```
3838

3939
If you use the `:new_tab:` option in the directive, the Notebook will be opened in a new browser tab.
40+
The tab will render the full-fledged Lab interface, which is more complete and showcases all features
41+
of JupyterLite.
4042

4143
```rst
4244
.. jupyterlite:: my_notebook.ipynb

docs/directives/notebooklite.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,17 @@ You can also pass a Notebook file to open:
3131
:height: 600px
3232
:prompt: Try classic Notebook!
3333
```
34+
35+
If you use the `:new_tab:` option in the directive, the Notebook will be opened in a new browser tab.
36+
The tab will render the classic Notebook UI, which is more minimal and does not showcase the entire
37+
Lab interface.
38+
39+
```rst
40+
.. notebooklite:: my_notebook.ipynb
41+
:new_tab: True
42+
```
43+
44+
```{eval-rst}
45+
.. notebooklite:: my_notebook.ipynb
46+
:new_tab: True
47+
```

docs/directives/voici.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,17 @@ You can provide a notebook that will be rendered with Voici:
2727
:prompt: Try Voici!
2828
:prompt_color: #dc3545
2929
```
30+
31+
If you use the `:new_tab:` option in the directive, the Voici dashboard will execute and render
32+
the notebook in a new browser tab, instead of in the current page.
33+
34+
```rst
35+
.. voici:: my_notebook.ipynb
36+
:new_tab: True
37+
```
38+
39+
```{eval-rst}
40+
.. voici:: my_notebook.ipynb
41+
:new_tab: True
42+
```
43+

jupyterlite_sphinx/jupyterlite_sphinx.py

Lines changed: 113 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,15 @@ class JupyterLiteIframe(_LiteIframe):
204204
notebooks_path = ""
205205

206206

207-
class JupyterLiteTab(_InTab):
207+
class BaseNotebookTab(_InTab):
208+
"""Base class for notebook tab implementations. We subclass this
209+
to create more specific configurations around how tabs are rendered."""
210+
211+
lite_app = None
212+
notebooks_path = None
213+
214+
215+
class JupyterLiteTab(BaseNotebookTab):
208216
"""Appended to the doctree by the JupyterliteDirective directive
209217
210218
Renders a button that opens a Notebook with JupyterLite in a new tab.
@@ -214,6 +222,16 @@ class JupyterLiteTab(_InTab):
214222
notebooks_path = ""
215223

216224

225+
class NotebookLiteTab(BaseNotebookTab):
226+
"""Appended to the doctree by the NotebookliteDirective directive
227+
228+
Renders a button that opens a Notebook with NotebookLite in a new tab.
229+
"""
230+
231+
lite_app = "tree/"
232+
notebooks_path = "../notebooks/"
233+
234+
217235
class NotebookLiteIframe(_LiteIframe):
218236
"""Appended to the doctree by the NotebookliteDirective directive
219237
@@ -224,6 +242,21 @@ class NotebookLiteIframe(_LiteIframe):
224242
notebooks_path = "../notebooks/"
225243

226244

245+
class VoiciBase:
246+
"""Base class with common Voici application paths and URL structure"""
247+
248+
lite_app = "voici/"
249+
250+
@classmethod
251+
def get_full_path(cls, notebook=None):
252+
"""Get the complete Voici path based on whether a notebook is provided."""
253+
if notebook is not None:
254+
# For notebooks, use render path with html extension
255+
return f"{cls.lite_app}render/{notebook.replace('.ipynb', '.html')}"
256+
# Default to tree view
257+
return f"{cls.lite_app}tree"
258+
259+
227260
class VoiciIframe(_PromptedIframe):
228261
"""Appended to the doctree by the VoiciDirective directive
229262
@@ -239,20 +272,55 @@ def __init__(
239272
lite_options={},
240273
**attributes,
241274
):
242-
if notebook is not None:
243-
app_path = f"voici/render/{notebook.replace('.ipynb', '.html')}"
244-
else:
245-
app_path = "voici/tree"
246-
275+
app_path = VoiciBase.get_full_path(notebook)
247276
options = "&".join(
248277
[f"{key}={quote(value)}" for key, value in lite_options.items()]
249278
)
250279

280+
# If a notebook is provided, open it in the render view. Else, we default to the tree view.
251281
iframe_src = f'{prefix}/{app_path}{f"index.html?{options}" if options else ""}'
252282

253283
super().__init__(rawsource, *children, iframe_src=iframe_src, **attributes)
254284

255285

286+
# We do not inherit from BaseNotebookTab here because
287+
# Voici has a different URL structure.
288+
class VoiciTab(Element):
289+
"""Tabbed implementation for the Voici interface"""
290+
291+
def __init__(
292+
self,
293+
rawsource="",
294+
*children,
295+
prefix=JUPYTERLITE_DIR,
296+
notebook=None,
297+
lite_options={},
298+
**attributes,
299+
):
300+
301+
self.lab_src = f"{prefix}/"
302+
303+
app_path = VoiciBase.get_full_path(notebook)
304+
options = "&".join(
305+
[f"{key}={quote(value)}" for key, value in lite_options.items()]
306+
)
307+
308+
# If a notebook is provided, open it in a new tab. Else, we default to the tree view.
309+
self.lab_src = f'{prefix}/{app_path}{f"?{options}" if options else ""}'
310+
311+
super().__init__(
312+
rawsource,
313+
**attributes,
314+
)
315+
316+
def html(self):
317+
return (
318+
'<button class="try_examples_button" '
319+
f"onclick=\"window.open('{self.lab_src}')\">"
320+
"Open with Voici</button>"
321+
)
322+
323+
256324
class RepliteDirective(SphinxDirective):
257325
"""The ``.. replite::`` directive.
258326
@@ -400,7 +468,24 @@ def run(self):
400468
]
401469

402470

403-
class JupyterLiteDirective(_LiteDirective):
471+
class BaseJupyterViewDirective(_LiteDirective):
472+
"""Base class for jupyterlite-sphinx directives."""
473+
474+
iframe_cls = None # to be defined by subclasses
475+
newtab_cls = None # to be defined by subclasses
476+
477+
option_spec = {
478+
"width": directives.unchanged,
479+
"height": directives.unchanged,
480+
"theme": directives.unchanged,
481+
"prompt": directives.unchanged,
482+
"prompt_color": directives.unchanged,
483+
"search_params": directives.unchanged,
484+
"new_tab": directives.unchanged,
485+
}
486+
487+
488+
class JupyterLiteDirective(BaseJupyterViewDirective):
404489
"""The ``.. jupyterlite::`` directive.
405490
406491
Renders a Notebook with JupyterLite in the docs.
@@ -410,22 +495,24 @@ class JupyterLiteDirective(_LiteDirective):
410495
newtab_cls = JupyterLiteTab
411496

412497

413-
class NotebookLiteDirective(_LiteDirective):
498+
class NotebookLiteDirective(BaseJupyterViewDirective):
414499
"""The ``.. notebooklite::`` directive.
415500
416501
Renders a Notebook with NotebookLite in the docs.
417502
"""
418503

419504
iframe_cls = NotebookLiteIframe
505+
newtab_cls = NotebookLiteTab
420506

421507

422-
class VoiciDirective(_LiteDirective):
508+
class VoiciDirective(BaseJupyterViewDirective):
423509
"""The ``.. voici::`` directive.
424510
425511
Renders a Notebook with Voici in the docs.
426512
"""
427513

428514
iframe_cls = VoiciIframe
515+
newtab_cls = VoiciTab
429516

430517
def run(self):
431518
if voici is None:
@@ -810,30 +897,39 @@ def setup(app):
810897
text=(skip, None),
811898
man=(skip, None),
812899
)
900+
for node_class in [NotebookLiteTab, JupyterLiteTab]:
901+
app.add_node(
902+
node_class,
903+
html=(visit_element_html, None),
904+
latex=(skip, None),
905+
textinfo=(skip, None),
906+
text=(skip, None),
907+
man=(skip, None),
908+
)
909+
app.add_directive("jupyterlite", JupyterLiteDirective)
910+
911+
# Initialize Replite directive
813912
app.add_node(
814-
JupyterLiteTab,
913+
RepliteIframe,
815914
html=(visit_element_html, None),
816915
latex=(skip, None),
817916
textinfo=(skip, None),
818917
text=(skip, None),
819918
man=(skip, None),
820919
)
821-
app.add_directive("jupyterlite", JupyterLiteDirective)
920+
app.add_directive("replite", RepliteDirective)
822921

823-
# Initialize Replite directive
922+
# Initialize Voici directive and tabbed interface
824923
app.add_node(
825-
RepliteIframe,
924+
VoiciIframe,
826925
html=(visit_element_html, None),
827926
latex=(skip, None),
828927
textinfo=(skip, None),
829928
text=(skip, None),
830929
man=(skip, None),
831930
)
832-
app.add_directive("replite", RepliteDirective)
833-
834-
# Initialize Voici directive
835931
app.add_node(
836-
VoiciIframe,
932+
VoiciTab,
837933
html=(visit_element_html, None),
838934
latex=(skip, None),
839935
textinfo=(skip, None),

0 commit comments

Comments
 (0)