@@ -242,6 +242,21 @@ class NotebookLiteIframe(_LiteIframe):
242
242
notebooks_path = "../notebooks/"
243
243
244
244
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
+
245
260
class VoiciIframe (_PromptedIframe ):
246
261
"""Appended to the doctree by the VoiciDirective directive
247
262
@@ -257,20 +272,55 @@ def __init__(
257
272
lite_options = {},
258
273
** attributes ,
259
274
):
260
- if notebook is not None :
261
- app_path = f"voici/render/{ notebook .replace ('.ipynb' , '.html' )} "
262
- else :
263
- app_path = "voici/tree"
264
-
275
+ app_path = VoiciBase .get_full_path (notebook )
265
276
options = "&" .join (
266
277
[f"{ key } ={ quote (value )} " for key , value in lite_options .items ()]
267
278
)
268
279
280
+ # If a notebook is provided, open it in the render view. Else, we default to the tree view.
269
281
iframe_src = f'{ prefix } /{ app_path } { f"index.html?{ options } " if options else "" } '
270
282
271
283
super ().__init__ (rawsource , * children , iframe_src = iframe_src , ** attributes )
272
284
273
285
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
+
274
324
class RepliteDirective (SphinxDirective ):
275
325
"""The ``.. replite::`` directive.
276
326
@@ -418,8 +468,8 @@ def run(self):
418
468
]
419
469
420
470
421
- class BaseNotebookDirective (_LiteDirective ):
422
- """Base class for notebook directives."""
471
+ class BaseJupyterViewDirective (_LiteDirective ):
472
+ """Base class for jupyterlite-sphinx directives."""
423
473
424
474
iframe_cls = None # to be defined by subclasses
425
475
newtab_cls = None # to be defined by subclasses
@@ -435,7 +485,7 @@ class BaseNotebookDirective(_LiteDirective):
435
485
}
436
486
437
487
438
- class JupyterLiteDirective (BaseNotebookDirective ):
488
+ class JupyterLiteDirective (BaseJupyterViewDirective ):
439
489
"""The ``.. jupyterlite::`` directive.
440
490
441
491
Renders a Notebook with JupyterLite in the docs.
@@ -445,7 +495,7 @@ class JupyterLiteDirective(BaseNotebookDirective):
445
495
newtab_cls = JupyterLiteTab
446
496
447
497
448
- class NotebookLiteDirective (BaseNotebookDirective ):
498
+ class NotebookLiteDirective (BaseJupyterViewDirective ):
449
499
"""The ``.. notebooklite::`` directive.
450
500
451
501
Renders a Notebook with NotebookLite in the docs.
@@ -455,13 +505,14 @@ class NotebookLiteDirective(BaseNotebookDirective):
455
505
newtab_cls = NotebookLiteTab
456
506
457
507
458
- class VoiciDirective (_LiteDirective ):
508
+ class VoiciDirective (BaseJupyterViewDirective ):
459
509
"""The ``.. voici::`` directive.
460
510
461
511
Renders a Notebook with Voici in the docs.
462
512
"""
463
513
464
514
iframe_cls = VoiciIframe
515
+ newtab_cls = VoiciTab
465
516
466
517
def run (self ):
467
518
if voici is None :
@@ -868,7 +919,7 @@ def setup(app):
868
919
)
869
920
app .add_directive ("replite" , RepliteDirective )
870
921
871
- # Initialize Voici directive
922
+ # Initialize Voici directive and tabbed interface
872
923
app .add_node (
873
924
VoiciIframe ,
874
925
html = (visit_element_html , None ),
@@ -877,6 +928,14 @@ def setup(app):
877
928
text = (skip , None ),
878
929
man = (skip , None ),
879
930
)
931
+ app .add_node (
932
+ VoiciTab ,
933
+ html = (visit_element_html , None ),
934
+ latex = (skip , None ),
935
+ textinfo = (skip , None ),
936
+ text = (skip , None ),
937
+ man = (skip , None ),
938
+ )
880
939
app .add_directive ("voici" , VoiciDirective )
881
940
882
941
# Initialize TryExamples directive
0 commit comments