Skip to content

Commit 3db7762

Browse files
committed
Allow custom css for try_examples directive
- and update how try examples config is specified
1 parent 661eafc commit 3db7762

File tree

3 files changed

+47
-114
lines changed

3 files changed

+47
-114
lines changed

docs/_static/try_examples.css

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.try_examples_button {
2+
background-color: #f7dc1e;
3+
border: none;
4+
padding: 5px 10px;
5+
border-radius: 15px;
6+
font-family: vibur;
7+
font-size: larger;
8+
box-shadow: 0 2px 5px rgba(108,108,108,0.2);
9+
}
10+
11+
.try_examples_button:hover {
12+
background-color: #fff221;
13+
transform: scale(1.02);
14+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
15+
cursor: pointer;
16+
}
17+
18+
.try_examples_button_container {
19+
display: flex;
20+
justify-content: flex-end;
21+
}

jupyterlite_sphinx/jupyterlite_sphinx.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,22 +78,22 @@ window.tryExamplesHideIframe = (examplesContainerId, iframeParentContainerId) =>
7878
}
7979

8080

81-
window.loadTryExamplesConfig = async (ignoreFilePath) => {
81+
window.loadTryExamplesConfig = async (configFilePath) => {
8282
try {
8383
// Add a timestamp as query parameter to ensure a cached version of the
8484
// file is not used.
8585
const timestamp = new Date().getTime();
86-
const ignoreFileUrl = `${ignoreFilePath}?cb=${timestamp}`;
86+
const configFileUrl = `${configFilePath}?cb=${timestamp}`;
8787
const currentPageUrl = window.location.pathname;
8888

89-
const response = await fetch(ignoreFileUrl);
89+
const response = await fetch(configFileUrl);
9090
if (!response.ok) {
9191
if (response.status === 404) {
9292
// Try examples ignore file is not present.
93-
console.log('Ignore file not found.');
93+
console.log('try_examples config file not found.');
9494
return;
9595
}
96-
throw new Error(`Error fetching ${ignoreFilePath}`);
96+
throw new Error(`Error fetching ${configFilePath}`);
9797
}
9898

9999
const data = await response.json();

jupyterlite_sphinx/jupyterlite_sphinx.py

Lines changed: 21 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22
import json
33
from uuid import uuid4
44
import shutil
5-
import tempfile
6-
from warnings import warn
75
import glob
86
import re
9-
import nbformat as nbf
107

118
from pathlib import Path
129

@@ -19,7 +16,6 @@
1916
from docutils import nodes
2017

2118
from sphinx.application import Sphinx
22-
from sphinx.ext.doctest import DoctestDirective
2319
from sphinx.util.docutils import SphinxDirective
2420
from sphinx.util.fileutil import copy_asset
2521
from sphinx.parsers import RSTParser
@@ -370,10 +366,7 @@ class TryExamplesDirective(SphinxDirective):
370366
option_spec = {
371367
"theme": directives.unchanged,
372368
"button_text": directives.unchanged,
373-
"button_css": directives.unchanged,
374-
"button_hover_css": directives.unchanged,
375-
"button_horizontal_position": directives.unchanged,
376-
"button_vertical_position": directives.unchanged,
369+
"example_class": directives.unchanged,
377370
"min_height": directives.unchanged,
378371
}
379372

@@ -387,26 +380,9 @@ def run(self):
387380
)
388381

389382
button_text = self.options.pop("button_text", "Try it with Jupyterlite!")
390-
button_css = self.options.pop("button_css", "")
391-
button_hover_css = self.options.pop("button_hover_css", "")
392-
button_horizontal_position = self.options.pop(
393-
"button_horizontal_position", "right"
394-
)
395-
button_vertical_position = self.options.pop("button_vertical_position", "top")
383+
example_class = self.options.pop("example_class", "")
396384
min_height = self.options.pop("min_height", "200px")
397385

398-
if button_horizontal_position not in ["left", "center", "right"]:
399-
raise RuntimeError(
400-
"try_examples directive expects button_horizontal_position to be one of"
401-
f" 'left', 'center', or 'right', received {button_horizontal_position}."
402-
)
403-
404-
if button_vertical_position not in ["bottom", "top"]:
405-
raise RuntimeError(
406-
"try_examples directive expects button_vertical_position to be one of"
407-
f" 'top' or 'bottom', received {button_vertical_position}."
408-
)
409-
410386
# We need to get the relative path back to the documentation root from
411387
# whichever file the docstring content is in.
412388
docname = self.env.docname
@@ -454,7 +430,7 @@ def run(self):
454430
# Parent container (initially hidden)
455431
iframe_parent_container_div_start = (
456432
f'<div id="{iframe_parent_div_id}" '
457-
f'class="try_examples_outer_container hidden">'
433+
f'class="try_examples_outer_container {example_class} hidden">'
458434
)
459435

460436
iframe_parent_container_div_end = "</div>"
@@ -487,51 +463,19 @@ def run(self):
487463
try_it_button_node = nodes.raw("", try_it_button_html, format="html")
488464

489465
# Combine everything
490-
if button_vertical_position == "top":
491-
notebook_container_html = (
492-
iframe_parent_container_div_start
493-
+ go_back_button_html
494-
+ iframe_container_div
495-
+ iframe_parent_container_div_end
496-
)
497-
content_container_node += try_it_button_node
498-
content_container_node += content_node
499-
else:
500-
notebook_container_html = (
501-
iframe_parent_container_div_start
502-
+ iframe_container_div
503-
+ go_back_button_html
504-
+ iframe_parent_container_div_end
505-
)
506-
content_container_node += content_node
507-
content_container_node += try_it_button_node
508-
509-
notebook_container = nodes.raw("", notebook_container_html, format="html")
510-
511-
# Generate css for button based on options.
512-
if button_css:
513-
button_css = f".try_examples_button {{{button_css}}}"
514-
if button_hover_css:
515-
button_hover_css = f".try_examples_button:hover {{{button_hover_css}}}"
516-
517-
justify = {"left": "flex-start", "center": "center", "right": "flex-end"}[
518-
button_horizontal_position
519-
]
520-
521-
button_container_css = (
522-
".try_examples_button_container {"
523-
f"display: flex; justify-content: {justify}"
524-
"}"
466+
notebook_container_html = (
467+
iframe_parent_container_div_start
468+
+ go_back_button_html
469+
+ iframe_container_div
470+
+ iframe_parent_container_div_end
525471
)
472+
content_container_node += try_it_button_node
473+
content_container_node += content_node
526474

527-
complete_button_css = button_css + button_hover_css + button_container_css
528-
529-
style_tag = nodes.raw(
530-
"", f"<style>{complete_button_css}</style>", format="html"
531-
)
475+
notebook_container = nodes.raw("", notebook_container_html, format="html")
532476

533-
# Search cnofig file allowing for config changes without rebuilding docs.
534-
config_path = os.path.join(relative_path_to_root, ".try_examples.json")
477+
# Search config file allowing for config changes without rebuilding docs.
478+
config_path = os.path.join(relative_path_to_root, "try_examples.json")
535479
script_html = (
536480
"<script>"
537481
'document.addEventListener("DOMContentLoaded", function() {'
@@ -541,7 +485,7 @@ def run(self):
541485
)
542486
script_node = nodes.raw("", script_html, format="html")
543487

544-
return [content_container_node, notebook_container, style_tag, script_node]
488+
return [content_container_node, notebook_container, script_node]
545489

546490

547491
def _process_docstring_examples(app, docname, source):
@@ -554,10 +498,7 @@ def _process_autodoc_docstrings(app, what, name, obj, options, lines):
554498
try_examples_options = {
555499
"theme": app.config.try_examples_global_theme,
556500
"button_text": app.config.try_examples_global_button_text,
557-
"button_css": app.config.try_examples_global_button_css,
558-
"button_hover_css": app.config.try_examples_global_button_hover_css,
559-
"button_horizontal_position": app.config.try_examples_global_button_horizontal_position,
560-
"button_vertical_position": app.config.try_examples_global_button_vertical_position,
501+
"example_class": app.config.try_examples_global_example_class,
561502
"min_height": app.config.try_examples_global_min_height,
562503
}
563504
try_examples_options = {
@@ -574,25 +515,6 @@ def conditional_process_examples(app, config):
574515
app.connect("autodoc-process-docstring", _process_autodoc_docstrings)
575516

576517

577-
def write_try_examples_runtime_config(app, exception):
578-
"""Add default runtime configuration file .try_examples.json.
579-
580-
These configuration options can be changed in the deployed docs without
581-
rebuilding by replacing this file.
582-
"""
583-
if exception is not None:
584-
return
585-
586-
config = app.config.try_examples_default_runtime_config
587-
if config is None:
588-
return
589-
590-
output_dir = app.outdir
591-
config_path = os.path.join(output_dir, ".try_examples.json")
592-
with open(config_path, "w") as f:
593-
json.dump(config, f, indent=4)
594-
595-
596518
def inited(app: Sphinx, config):
597519
# Create the content dir
598520
os.makedirs(os.path.join(app.srcdir, CONTENT_DIR), exist_ok=True)
@@ -678,9 +600,6 @@ def setup(app):
678600
# We need to build JupyterLite at the end, when all the content was created
679601
app.connect("build-finished", jupyterlite_build)
680602

681-
# Write default .try_examples.json after build finishes.
682-
app.connect("build-finished", write_try_examples_runtime_config)
683-
684603
# Config options
685604
app.add_config_value("jupyterlite_config", None, rebuild="html")
686605
app.add_config_value("jupyterlite_dir", app.srcdir, rebuild="html")
@@ -689,27 +608,15 @@ def setup(app):
689608

690609
app.add_config_value("global_enable_try_examples", default=False, rebuild=True)
691610
app.add_config_value("try_examples_global_theme", default=None, rebuild=True)
692-
app.add_config_value("try_examples_global_button_css", default=None, rebuild="html")
693-
app.add_config_value(
694-
"try_examples_global_button_hover_css", default=None, rebuild="html"
695-
)
696-
app.add_config_value(
697-
"try_examples_global_button_horizontal_position", default=None, rebuild="html"
698-
)
699611
app.add_config_value(
700-
"try_examples_global_button_vertical_position", default=None, rebuild="html"
612+
"try_examples_global_example_class", default=None, rebuild="html"
701613
)
702614
app.add_config_value("try_examples_global_min_height", default=None, rebuild="html")
703615
app.add_config_value(
704616
"try_examples_global_button_text",
705617
default=None,
706618
rebuild="html",
707619
)
708-
app.add_config_value(
709-
"try_examples_default_runtime_config",
710-
default=None,
711-
rebuild=None,
712-
)
713620

714621
# Initialize NotebookLite and JupyterLite directives
715622
app.add_node(
@@ -768,6 +675,11 @@ def setup(app):
768675

769676
app.add_js_file("jupyterlite_sphinx.js")
770677

678+
# Copy optional try examples runtime config if it exists.
679+
try_examples_config_path = Path(app.srcdir) / "try_examples.json"
680+
if try_examples_config_path.exists():
681+
copy_asset(str(try_examples_config_path), app.outdir)
682+
771683

772684
def search_params_parser(search_params: str) -> str:
773685
pattern = re.compile(r"^\[(?:\s*[\"']{1}([^=\s\,&=\?\/]+)[\"']{1}\s*\,?)+\]$")

0 commit comments

Comments
 (0)