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
This commit is contained in:
Rosio 2023-06-08 23:39:44 -07:00 committed by GitHub
parent bdcadda22d
commit 7c1d88780f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 77 additions and 1 deletions

View File

@ -26,6 +26,10 @@ and editing settings is similar for all the Jupyter applications.
> - [traitlets](https://traitlets.readthedocs.io/en/latest/config.html#module-traitlets.config)
> provide a low-level architecture for configuration.
### Disabling Custom CSS
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`.
(configure-jupyter-server)=
## Jupyter server

View File

@ -1,11 +1,16 @@
"""Jupyter notebook application."""
import os
import re
from os.path import join as pjoin
from jupyter_client.utils import ensure_async
from jupyter_core.application import base_aliases
from jupyter_core.paths import jupyter_config_dir
from jupyter_server.base.handlers import JupyterHandler
from jupyter_server.extension.handler import ExtensionHandlerJinjaMixin, ExtensionHandlerMixin
from jupyter_server.extension.handler import (
ExtensionHandlerJinjaMixin,
ExtensionHandlerMixin,
)
from jupyter_server.serverapp import flags
from jupyter_server.utils import url_escape, url_is_absolute
from jupyter_server.utils import url_path_join as ujoin
@ -32,6 +37,10 @@ version = __version__
class NotebookBaseHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler):
"""The base notebook API handler."""
@property
def custom_css(self):
return self.settings.get("custom_css", True)
def get_page_config(self):
"""Get the page config."""
config = LabConfig()
@ -87,6 +96,7 @@ class NotebookBaseHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, Jup
page_config.setdefault("mathjaxConfig", mathjax_config)
page_config.setdefault("fullMathjaxUrl", mathjax_url)
page_config.setdefault("jupyterConfigDir", jupyter_config_dir())
# Put all our config in page_config
for name in config.trait_names():
@ -203,6 +213,27 @@ class NotebookHandler(NotebookBaseHandler):
return self.write(tpl)
class CustomCssHandler(NotebookBaseHandler):
"""A custom CSS handler."""
@web.authenticated
def get(self):
"""Get the custom css file."""
self.set_header("Content-Type", 'text/css')
page_config = self.get_page_config()
custom_css_file = f"{page_config['jupyterConfigDir']}/custom/custom.css"
if not os.path.isfile(custom_css_file):
static_path_root = re.match('^(.*?)static', page_config['staticDir'])
if static_path_root is not None:
custom_dir = static_path_root.groups()[0]
custom_css_file = f"{custom_dir}custom/custom.css"
with open(custom_css_file) as css_f:
return self.write(css_f.read())
aliases = dict(base_aliases)
@ -227,12 +258,25 @@ class JupyterNotebookApp(NotebookConfigShimMixin, LabServerApp):
help="Whether to expose the global app instance to browser via window.jupyterapp",
)
custom_css = Bool(
True,
config=True,
help="""Whether custom CSS is loaded on the page.
Defaults to True and custom CSS is loaded.
""",
)
flags = flags
flags["expose-app-in-browser"] = (
{"JupyterNotebookApp": {"expose_app_in_browser": True}},
"Expose the global app instance to browser via window.jupyterapp.",
)
flags["custom-css"] = (
{"JupyterNotebookApp": {"custom_css": True}},
"Load custom CSS in template html files. Default is True",
)
@default("static_dir")
def _default_static_dir(self):
return os.path.join(HERE, "static")
@ -261,6 +305,10 @@ class JupyterNotebookApp(NotebookConfigShimMixin, LabServerApp):
def _default_workspaces_dir(self):
return get_workspaces_dir()
def _prepare_templates(self):
super(LabServerApp, self)._prepare_templates()
self.jinja2_env.globals.update(custom_css=self.custom_css) # type:ignore
def server_extension_is_enabled(self, extension):
"""Check if server extension is enabled."""
try:
@ -290,6 +338,7 @@ class JupyterNotebookApp(NotebookConfigShimMixin, LabServerApp):
self.handlers.append(("/edit(.*)", FileHandler))
self.handlers.append(("/consoles/(.*)", ConsoleHandler))
self.handlers.append(("/terminals/(.*)", TerminalHandler))
self.handlers.append(("/custom/custom.css", CustomCssHandler))
super().initialize_handlers()
def initialize(self, argv=None):

View File

@ -0,0 +1,7 @@
/*
Placeholder for custom user CSS
mainly to be overridden in profile/static/custom/custom.css
This will always be an empty file
*/

View File

@ -7,6 +7,10 @@
{% block favicon %}
<link rel="icon" type="image/x-icon" href="{{ page_config['fullStaticUrl'] | e }}/favicons/favicon-console.ico" class="favicon">
{% endblock %}
{% if custom_css %}
<link rel="stylesheet" type="text/css" href="{{ base_url | escape }}custom/custom.css">
{% endif %}
</head>
<body>

View File

@ -7,6 +7,10 @@
{% block favicon %}
<link rel="icon" type="image/x-icon" href="{{ base_url | escape }}static/favicons/favicon-notebook.ico" class="favicon">
{% endblock %}
{% if custom_css %}
<link rel="stylesheet" type="text/css" href="{{ base_url | escape }}custom/custom.css">
{% endif %}
</head>
<body data-notebook="notebooks">

View File

@ -7,6 +7,10 @@
{% block favicon %}
<link rel="icon" type="image/x-icon" href="{{ base_url | escape }}static/favicons/favicon-terminal.ico" class="favicon">
{% endblock %}
{% if custom_css %}
<link rel="stylesheet" type="text/css" href="{{ base_url | escape }}custom/custom.css">
{% endif %}
</head>
<body>

View File

@ -7,6 +7,10 @@
{% block favicon %}
<link rel="icon" type="image/x-icon" href="{{ base_url | escape }}static/favicons/favicon.ico" class="favicon">
{% endblock %}
{% if custom_css %}
<link rel="stylesheet" type="text/css" href="{{ base_url | escape }}custom/custom.css">
{% endif %}
</head>
<body>