Skip to content

Commit 7c1d887

Browse files
authored
Load custom CSS (#6841)
* wip loading custom css * read css file from profile directory and apply * update css handler, add CLI flag to disable custom CSS, documentation * import ExtensionAppJinjaMixin * remove ExtensionAppJinjaMixin as a JupyterNotebookApp base class * add type hint to ignore jinja2_env type * fix prettier issue in docs * add empty line; trigger new docs build * new line caused prettier error, remove * move custom css to different directory and update handler * modify regex * satisfy mypy: check for match in regex before grabbing the directory path * load custom css in consoles page
1 parent bdcadda commit 7c1d887

File tree

7 files changed

+77
-1
lines changed

7 files changed

+77
-1
lines changed

docs/source/config_overview.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ and editing settings is similar for all the Jupyter applications.
2626
> - [traitlets](https://traitlets.readthedocs.io/en/latest/config.html#module-traitlets.config)
2727
> provide a low-level architecture for configuration.
2828
29+
### Disabling Custom CSS
30+
31+
Custom CSS is loaded by default as was done with Jupyter Notebook 6. In the jupyter configuration directory, the `/.jupyter/custom/custom.css` file will be loaded unless the the application is initialized with the `custom_css` flag with the argument set to `False` as in `--JupyterNotebookApp.custom_css=False`.
32+
2933
(configure-jupyter-server)=
3034

3135
## Jupyter server

notebook/app.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
"""Jupyter notebook application."""
22
import os
3+
import re
34
from os.path import join as pjoin
45

56
from jupyter_client.utils import ensure_async
67
from jupyter_core.application import base_aliases
8+
from jupyter_core.paths import jupyter_config_dir
79
from jupyter_server.base.handlers import JupyterHandler
8-
from jupyter_server.extension.handler import ExtensionHandlerJinjaMixin, ExtensionHandlerMixin
10+
from jupyter_server.extension.handler import (
11+
ExtensionHandlerJinjaMixin,
12+
ExtensionHandlerMixin,
13+
)
914
from jupyter_server.serverapp import flags
1015
from jupyter_server.utils import url_escape, url_is_absolute
1116
from jupyter_server.utils import url_path_join as ujoin
@@ -32,6 +37,10 @@
3237
class NotebookBaseHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler):
3338
"""The base notebook API handler."""
3439

40+
@property
41+
def custom_css(self):
42+
return self.settings.get("custom_css", True)
43+
3544
def get_page_config(self):
3645
"""Get the page config."""
3746
config = LabConfig()
@@ -87,6 +96,7 @@ def get_page_config(self):
8796

8897
page_config.setdefault("mathjaxConfig", mathjax_config)
8998
page_config.setdefault("fullMathjaxUrl", mathjax_url)
99+
page_config.setdefault("jupyterConfigDir", jupyter_config_dir())
90100

91101
# Put all our config in page_config
92102
for name in config.trait_names():
@@ -203,6 +213,27 @@ def get(self, path=None):
203213
return self.write(tpl)
204214

205215

216+
class CustomCssHandler(NotebookBaseHandler):
217+
"""A custom CSS handler."""
218+
219+
@web.authenticated
220+
def get(self):
221+
"""Get the custom css file."""
222+
223+
self.set_header("Content-Type", 'text/css')
224+
page_config = self.get_page_config()
225+
custom_css_file = f"{page_config['jupyterConfigDir']}/custom/custom.css"
226+
227+
if not os.path.isfile(custom_css_file):
228+
static_path_root = re.match('^(.*?)static', page_config['staticDir'])
229+
if static_path_root is not None:
230+
custom_dir = static_path_root.groups()[0]
231+
custom_css_file = f"{custom_dir}custom/custom.css"
232+
233+
with open(custom_css_file) as css_f:
234+
return self.write(css_f.read())
235+
236+
206237
aliases = dict(base_aliases)
207238

208239

@@ -227,12 +258,25 @@ class JupyterNotebookApp(NotebookConfigShimMixin, LabServerApp):
227258
help="Whether to expose the global app instance to browser via window.jupyterapp",
228259
)
229260

261+
custom_css = Bool(
262+
True,
263+
config=True,
264+
help="""Whether custom CSS is loaded on the page.
265+
Defaults to True and custom CSS is loaded.
266+
""",
267+
)
268+
230269
flags = flags
231270
flags["expose-app-in-browser"] = (
232271
{"JupyterNotebookApp": {"expose_app_in_browser": True}},
233272
"Expose the global app instance to browser via window.jupyterapp.",
234273
)
235274

275+
flags["custom-css"] = (
276+
{"JupyterNotebookApp": {"custom_css": True}},
277+
"Load custom CSS in template html files. Default is True",
278+
)
279+
236280
@default("static_dir")
237281
def _default_static_dir(self):
238282
return os.path.join(HERE, "static")
@@ -261,6 +305,10 @@ def _default_user_settings_dir(self):
261305
def _default_workspaces_dir(self):
262306
return get_workspaces_dir()
263307

308+
def _prepare_templates(self):
309+
super(LabServerApp, self)._prepare_templates()
310+
self.jinja2_env.globals.update(custom_css=self.custom_css) # type:ignore
311+
264312
def server_extension_is_enabled(self, extension):
265313
"""Check if server extension is enabled."""
266314
try:
@@ -290,6 +338,7 @@ def initialize_handlers(self):
290338
self.handlers.append(("/edit(.*)", FileHandler))
291339
self.handlers.append(("/consoles/(.*)", ConsoleHandler))
292340
self.handlers.append(("/terminals/(.*)", TerminalHandler))
341+
self.handlers.append(("/custom/custom.css", CustomCssHandler))
293342
super().initialize_handlers()
294343

295344
def initialize(self, argv=None):

notebook/custom/custom.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
Placeholder for custom user CSS
3+
4+
mainly to be overridden in profile/static/custom/custom.css
5+
6+
This will always be an empty file
7+
*/

notebook/templates/consoles.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
{% block favicon %}
88
<link rel="icon" type="image/x-icon" href="{{ page_config['fullStaticUrl'] | e }}/favicons/favicon-console.ico" class="favicon">
99
{% endblock %}
10+
11+
{% if custom_css %}
12+
<link rel="stylesheet" type="text/css" href="{{ base_url | escape }}custom/custom.css">
13+
{% endif %}
1014
</head>
1115
<body>
1216

notebook/templates/notebooks.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
{% block favicon %}
88
<link rel="icon" type="image/x-icon" href="{{ base_url | escape }}static/favicons/favicon-notebook.ico" class="favicon">
99
{% endblock %}
10+
11+
{% if custom_css %}
12+
<link rel="stylesheet" type="text/css" href="{{ base_url | escape }}custom/custom.css">
13+
{% endif %}
1014
</head>
1115
<body data-notebook="notebooks">
1216

notebook/templates/terminals.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
{% block favicon %}
88
<link rel="icon" type="image/x-icon" href="{{ base_url | escape }}static/favicons/favicon-terminal.ico" class="favicon">
99
{% endblock %}
10+
11+
{% if custom_css %}
12+
<link rel="stylesheet" type="text/css" href="{{ base_url | escape }}custom/custom.css">
13+
{% endif %}
1014
</head>
1115
<body>
1216

notebook/templates/tree.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
{% block favicon %}
88
<link rel="icon" type="image/x-icon" href="{{ base_url | escape }}static/favicons/favicon.ico" class="favicon">
99
{% endblock %}
10+
11+
{% if custom_css %}
12+
<link rel="stylesheet" type="text/css" href="{{ base_url | escape }}custom/custom.css">
13+
{% endif %}
1014
</head>
1115
<body>
1216

0 commit comments

Comments
 (0)