Skip to content

Commit a6e9e8e

Browse files
author
Release Manager
committed
sagemathgh-36144: Revive sage live doc using jupyter-sphinx <!-- ^^^^^ Please provide a concise, informative and self-explanatory title. Don't put issue numbers in there, do this in the PR body below. For example, instead of "Fixes sagemath#1234" use "Introduce new method to calculate 1+1" --> <!-- Describe your changes here in detail --> We fix sagemath#33320 and sagemath#24367. We also upgrade jupyter-sphinx standard package to the latest version 0.4.0: https://github.com/jupyter/jupyter-sphinx/releases/tag/v0.4.0, and patch it to facilitate live doc. By default, live doc depends on https://github.com/sagemath/sage-binder- env for binder (remote jupyter server). To showcase the live doc, we edited "A Tour of Sage" doc and some parts of sage doc. Visit - https://deploy-preview-36144--sagemath- tobias.netlify.app/a_tour_of_sage or - https://deploy-preview-36144--sagemath- tobias.netlify.app/tutorial/latex (edited to resolve sagemath#24367) **Click the activate button to activate live doc** The button is hidden to the right middle side of the browser. After clicked, the activate button shows the status of the launching kernel. **Be patient if the kernel loads slow. It may take several minutes.** Sometimes it may be stuck in the "launching" state. In that case, reload the page and try again. Note that `%display latex` is the default. You should use `%display plain` to get plain textual output. ## Preparation ``` ./sage -i jupyterlab ``` If things do not work as expected, first remove the existing documentation by ``` make doc-clean doc-uninstall ``` ## Build static doc ``` export SAGE_LIVE_DOC=no # default make -j4 ``` ## Build live doc with the default Binder repo ``` export SAGE_LIVE_DOC=yes export SAGE_JUPYTER_SERVER=binder # default make -j4 ``` ## Build live doc with a custom Binder repo ``` export SAGE_LIVE_DOC=yes export SAGE_JUPYTER_SERVER=binder:sagemath/sage-binder-env # default make -j4 ``` The kernel is "sagemath". ## Build live doc with a local server ``` export SAGE_LIVE_DOC=yes export SAGE_JUPYTER_SERVER=http://localhost:8889 export SAGE_JUPYTER_SERVER_TOKEN=secret # default make -j4 ``` and run a local jupyter server before browsing the documentation. ``` ./sage --notebook=jupyterlab \ --ServerApp.token='secret' \ --ServerApp.allow_origin='null' \ --ServerApp.disable_check_xsrf=true \ --ServerApp.port=8889 \ --ServerApp.open_browser=false ``` ## Build live doc selectively First build static doc ``` make -j4 ``` and then build live doc selectively either by ``` ./sage --docbuild --live-doc a_tour_of_sage html ``` or ``` export SAGE_JUPYTER_SERVER=http://localhost:8889 export SAGE_JUPYTER_SERVER_TOKEN=secret # default ./sage --docbuild --live-doc a_tour_of_sage html ``` with a local server. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> <!-- If your change requires a documentation PR, please link it appropriately --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> <!-- Feel free to remove irrelevant items. --> - [x] The title is concise, informative, and self-explanatory. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [ ] I have created tests covering the changes. - [x] I have updated the documentation accordingly. Related upstream issues: - jupyter/jupyter-sphinx#201 - jupyter/jupyter-sphinx#231 URL: sagemath#36144 Reported by: Kwankyu Lee Reviewer(s): github-actions[bot], Kwankyu Lee, Matthias Köppe
2 parents e8397c1 + 857741c commit a6e9e8e

File tree

22 files changed

+915
-964
lines changed

22 files changed

+915
-964
lines changed
+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
tarball=jupyter_sphinx-VERSION.tar.gz
2-
sha1=241f6dfcd3aae4f44f330e2ba76480011b382d3d
3-
md5=e7ab370d9793be5b20bce5447ccbd45b
4-
cksum=2021246952
2+
sha1=fb2abdd5e35da0886b12d45a6373c4dbcc24b244
3+
md5=130daa6be810976c9f8e30aa04011e50
4+
cksum=2882523000
55
upstream_url=https://pypi.io/packages/source/j/jupyter_sphinx/jupyter_sphinx-VERSION.tar.gz
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.2
1+
0.4.0.p0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
From 5bffbe38302c695123779f87300d84090b4bd118 Mon Sep 17 00:00:00 2001
2+
From: Kwankyu Lee <[email protected]>
3+
Date: Mon, 28 Aug 2023 00:18:59 +0900
4+
Subject: [PATCH] Patch for sage live doc
5+
6+
---
7+
jupyter_sphinx/__init__.py | 4 ++--
8+
jupyter_sphinx/execute.py | 11 +++++++++++
9+
2 files changed, 13 insertions(+), 2 deletions(-)
10+
11+
diff --git a/jupyter_sphinx/__init__.py b/jupyter_sphinx/__init__.py
12+
index 34af884..b7ca8ee 100644
13+
--- a/jupyter_sphinx/__init__.py
14+
+++ b/jupyter_sphinx/__init__.py
15+
@@ -31,7 +31,7 @@ from .thebelab import ThebeButton, ThebeButtonNode, ThebeOutputNode, ThebeSource
16+
REQUIRE_URL_DEFAULT = (
17+
"https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js"
18+
)
19+
-THEBELAB_URL_DEFAULT = "https://unpkg.com/thebelab@^0.4.0"
20+
+THEBELAB_URL_DEFAULT = "https://unpkg.com/thebe@latest/lib/index.js"
21+
22+
logger = logging.getLogger(__name__)
23+
24+
@@ -186,7 +186,7 @@ def setup(app):
25+
app.add_config_value("jupyter_sphinx_embed_url", None, "html")
26+
27+
# thebelab config, can be either a filename or a dict
28+
- app.add_config_value("jupyter_sphinx_thebelab_config", None, "html")
29+
+ app.add_config_value("jupyter_sphinx_thebelab_config", None, "env")
30+
app.add_config_value("jupyter_sphinx_thebelab_url", THEBELAB_URL_DEFAULT, "html")
31+
32+
# linenos config
33+
diff --git a/jupyter_sphinx/execute.py b/jupyter_sphinx/execute.py
34+
index 558a26b..de44455 100644
35+
--- a/jupyter_sphinx/execute.py
36+
+++ b/jupyter_sphinx/execute.py
37+
@@ -152,6 +152,17 @@ class ExecuteJupyterCells(SphinxTransform):
38+
kernel_name = default_kernel
39+
file_name = next(default_names)
40+
41+
+ # Save time when jupyter notebook execution is not necessary
42+
+ if not any(not "execute" in node or node["execute"] for node in nodes):
43+
+ # mimics empty cell output for each node
44+
+ for node in nodes:
45+
+ source = node.children[0]
46+
+ source.attributes["classes"].append("code_cell")
47+
+ node.attributes["cm_language"] = kernel_name
48+
+ node += CellOutputNode(classes=["cell_output"])
49+
+ apply_styling(node, thebe_config)
50+
+ continue
51+
+
52+
# Add empty placeholder cells for non-executed nodes so nodes
53+
# and cells can be zipped and the provided input/output
54+
# can be inserted later
55+
--
56+
2.42.0
57+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* from https://codemirror.net/5/theme/monokai.css */
2+
/* Based on Sublime Text's Monokai theme */
3+
4+
.cm-s-monokai.CodeMirror { background: #272822; color: #f8f8f2; }
5+
.cm-s-monokai div.CodeMirror-selected { background: #49483E; }
6+
.cm-s-monokai .CodeMirror-line::selection, .cm-s-monokai .CodeMirror-line > span::selection, .cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99); }
7+
.cm-s-monokai .CodeMirror-line::-moz-selection, .cm-s-monokai .CodeMirror-line > span::-moz-selection, .cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99); }
8+
.cm-s-monokai .CodeMirror-gutters { background: #272822; border-right: 0px; }
9+
.cm-s-monokai .CodeMirror-guttermarker { color: white; }
10+
.cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
11+
.cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0; }
12+
.cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
13+
14+
.cm-s-monokai span.cm-comment { color: #75715e; }
15+
.cm-s-monokai span.cm-atom { color: #ae81ff; }
16+
.cm-s-monokai span.cm-number { color: #ae81ff; }
17+
18+
.cm-s-monokai span.cm-comment.cm-attribute { color: #97b757; }
19+
.cm-s-monokai span.cm-comment.cm-def { color: #bc9262; }
20+
.cm-s-monokai span.cm-comment.cm-tag { color: #bc6283; }
21+
.cm-s-monokai span.cm-comment.cm-type { color: #5998a6; }
22+
23+
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; }
24+
.cm-s-monokai span.cm-keyword { color: #f92672; }
25+
.cm-s-monokai span.cm-builtin { color: #66d9ef; }
26+
.cm-s-monokai span.cm-string { color: #e6db74; }
27+
28+
.cm-s-monokai span.cm-variable { color: #f8f8f2; }
29+
.cm-s-monokai span.cm-variable-2 { color: #9effff; }
30+
.cm-s-monokai span.cm-variable-3, .cm-s-monokai span.cm-type { color: #66d9ef; }
31+
.cm-s-monokai span.cm-def { color: #fd971f; }
32+
.cm-s-monokai span.cm-bracket { color: #f8f8f2; }
33+
.cm-s-monokai span.cm-tag { color: #f92672; }
34+
.cm-s-monokai span.cm-header { color: #ae81ff; }
35+
.cm-s-monokai span.cm-link { color: #ae81ff; }
36+
.cm-s-monokai span.cm-error { background: #f92672; color: #f8f8f0; }
37+
38+
.cm-s-monokai .CodeMirror-activeline-background { background: #373831; }
39+
.cm-s-monokai .CodeMirror-matchingbracket {
40+
text-decoration: underline;
41+
color: white !important;
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
div.jupyter_container {
2+
margin: .5rem 0;
3+
}
4+
5+
div.jupyter_container + div.jupyter_container {
6+
margin: 0 0 .5rem 0;
7+
}
8+
9+
div.jupyter_container div.cell_input pre {
10+
margin: .5rem;
11+
}
12+
13+
div.jupyter_container div.cell_output div.output {
14+
margin: .5rem;
15+
}
16+
17+
.thebelab-cell .jp-OutputArea {
18+
margin: 0 .5rem;
19+
}
20+
21+
.thebelab-cell .jp-OutputArea-output {
22+
margin: 0 0 .5rem 0;
23+
overflow-x: auto;
24+
}
25+
26+
.thebelab-button {
27+
margin: .5rem 0 .5rem .5rem;
28+
padding: 0 .5rem;
29+
min-width: 2rem;
30+
}
31+
32+
.thebelab-button:active {
33+
color: #0f0fff;
34+
}
35+
36+
.thebelab-busy {
37+
margin-left: .5rem;
38+
}
39+
40+
#thebelab-activate-button {
41+
position: fixed;
42+
right: -5.1rem;
43+
top: calc(50% - 1.5rem);
44+
width: 6rem;
45+
border-radius: 1rem;
46+
transition-property: right;
47+
transition-duration: 300ms;
48+
transition-timing-function: ease-out;
49+
z-index: 100;
50+
}
51+
52+
#thebelab-activate-button:hover {
53+
right: .4rem;
54+
}
55+
56+
#thebelab-activate-button:active {
57+
color: #0f0fff;
58+
}
59+
60+
body[data-theme="dark"] {
61+
.jupyter_container {
62+
color: white;
63+
background-color: black;
64+
}
65+
66+
.jupyter_container .highlight {
67+
background-color: black;
68+
}
69+
70+
.thebelab-button {
71+
color: #d0d0d0;
72+
background-color: #383838;
73+
}
74+
75+
.thebelab-button:active {
76+
color: #368ce2;
77+
}
78+
79+
#thebelab-activate-button {
80+
background-color: #383838;
81+
}
82+
83+
#thebelab-activate-button:active {
84+
color: #368ce2;
85+
}
86+
87+
.thebelab-cell .jp-OutputArea-output {
88+
color: white;
89+
background-color: black;
90+
}
91+
92+
.thebelab-cell .jp-OutputArea-output pre {
93+
color: white;
94+
background-color: black;
95+
}
96+
}
97+
98+
@media (prefers-color-scheme: dark) {
99+
body[data-theme="auto"] { /* the same styles with body[data-theme="dark"] */
100+
.jupyter_container {
101+
color: white;
102+
background-color: black;
103+
}
104+
105+
.jupyter_container .highlight {
106+
background-color: black;
107+
}
108+
109+
.thebelab-button {
110+
color: #d0d0d0;
111+
background-color: #383838;
112+
}
113+
114+
.thebelab-button:active {
115+
color: #368ce2;
116+
}
117+
118+
#thebelab-activate-button {
119+
background-color: #383838;
120+
}
121+
122+
#thebelab-activate-button:active {
123+
color: #368ce2;
124+
}
125+
126+
.thebelab-cell .jp-OutputArea-output {
127+
color: white;
128+
background-color: black;
129+
}
130+
131+
.thebelab-cell .jp-OutputArea-output pre {
132+
color: white;
133+
background-color: black;
134+
}
135+
}
136+
}
137+
138+
139+
140+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Change the editor theme according to the furo light/dark/auto mode
2+
function changeTheme(editor, theme) {
3+
if (theme === 'dark') {
4+
editor.setOption('theme', 'monokai'); // the same with pygments dark style in conf.py
5+
} else if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
6+
editor.setOption('theme', 'monokai');
7+
} else {
8+
editor.setOption('theme', 'default');
9+
}
10+
}
11+
12+
// Change the editor theme of all CodeMirror cells
13+
function changeThemeAll(theme) {
14+
const querySet = document.querySelectorAll('.CodeMirror');
15+
for (var i = 0; i < querySet.length; i++) {
16+
changeTheme(querySet[i].CodeMirror, theme);
17+
}
18+
}
19+
20+
// Use the theme data of the body element set by setTheme function
21+
// defined in https://github.com/pradyunsg/furo/blob/main/src/furo/assets/scripts/furo.js
22+
const body = document.body;
23+
const observer1 = new MutationObserver((mutationsList) => {
24+
for (let mutation of mutationsList) {
25+
if (mutation.type === 'attributes' && mutation.attributeName === 'data-theme') {
26+
const theme = body.dataset.theme;
27+
changeThemeAll(theme);
28+
}
29+
}
30+
});
31+
observer1.observe(body, { attributes: true });
32+
33+
34+
// In the furo auto mode, we watch prefers-color-scheme and use the theme data
35+
// of the body element to change the CodeMirror editor theme
36+
const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)');
37+
38+
function handlePrefersColorSchemeChange(e) {
39+
const theme = body.dataset.theme;
40+
if (theme === 'auto') {
41+
changeThemeAll(theme);
42+
}
43+
}
44+
45+
prefersDarkMode.addEventListener('change', handlePrefersColorSchemeChange);
46+
47+
48+
// Change the editor theme of a new CodeMirror cell.
49+
const callback = function(mutationsList, observer) {
50+
for(const mutation of mutationsList) {
51+
if (mutation.type === 'childList') {
52+
const theme = body.dataset.theme;
53+
for (const addedNode of mutation.addedNodes) {
54+
if (addedNode.classList && addedNode.classList.contains('CodeMirror')) {
55+
changeTheme(addedNode.CodeMirror, theme);
56+
}}}}};
57+
const observer2 = new MutationObserver(callback);
58+
observer2.observe(document.getElementsByClassName("content")[0], { childList: true, subtree: true });
59+
60+
61+
// Listen to the kernel status changes
62+
// https://thebe.readthedocs.io/en/stable/events.html
63+
thebelab.on("status", function (evt, data) {
64+
if (data.status === 'building') {
65+
const elements = document.querySelectorAll('.thebelab-cell');
66+
elements.forEach(element => {
67+
element.style.filter = 'opacity(50%)';
68+
});
69+
const element = document.getElementById("thebelab-activate-button");
70+
element.innerHTML = "Building";
71+
element.style.right = '.4rem';
72+
}
73+
else if (data.status === 'built') {
74+
const elements = document.querySelectorAll('.thebelab-cell');
75+
elements.forEach(element => {
76+
element.style.filter = 'opacity(60%)';
77+
});
78+
const element = document.getElementById("thebelab-activate-button");
79+
element.innerHTML = "Built";
80+
element.style.right = '.4rem';
81+
}
82+
else if (data.status === 'launching') {
83+
const elements = document.querySelectorAll('.thebelab-cell');
84+
elements.forEach(element => {
85+
element.style.filter = 'opacity(70%)';
86+
});
87+
const element = document.getElementById("thebelab-activate-button");
88+
element.innerHTML = "Launching";
89+
element.style.right = '.4rem';
90+
}
91+
else if (data.status === 'failed') {
92+
const elements = document.querySelectorAll('.thebelab-cell');
93+
elements.forEach(element => {
94+
element.style.filter = 'opacity(50%)';
95+
});
96+
const element = document.getElementById("thebelab-activate-button");
97+
element.innerHTML = 'Failed: ' + data.message;
98+
element.style.right = '.4rem';
99+
element.style.width = 'auto';
100+
element.style.color = 'red';
101+
}
102+
else if (data.status === 'ready') {
103+
const elements = document.querySelectorAll('.thebelab-cell');
104+
elements.forEach(element => {
105+
element.style.filter = 'opacity(100%)';
106+
});
107+
const element = document.getElementById("thebelab-activate-button");
108+
element.innerHTML = "Ready";
109+
element.style.right = null;
110+
// Run custom code when the kernel is ready
111+
const kernel = data.kernel;
112+
kernel.requestExecute({code: "%display latex"});
113+
}
114+
});

0 commit comments

Comments
 (0)