Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load custom CSS #6841

Merged
merged 16 commits into from Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/source/config_overview.md
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a follow-up (could be another PR), maybe it could be interesting to document the feature more (for example in the user guide), to show an example of such custom.css file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @jtpio I can follow this PR up with one that includes the example!


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
Expand Down
51 changes: 50 additions & 1 deletion notebook/app.py
@@ -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
Expand All @@ -32,6 +37,10 @@
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()
Expand Down Expand Up @@ -87,6 +96,7 @@ def get_page_config(self):

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():
Expand Down Expand Up @@ -203,6 +213,27 @@ def get(self, path=None):
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)


Expand All @@ -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")
Expand Down Expand Up @@ -261,6 +305,10 @@ def _default_user_settings_dir(self):
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:
Expand Down Expand Up @@ -290,6 +338,7 @@ def initialize_handlers(self):
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):
Expand Down
7 changes: 7 additions & 0 deletions notebook/custom/custom.css
@@ -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
*/
4 changes: 4 additions & 0 deletions notebook/templates/consoles.html
Expand Up @@ -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>

Expand Down
4 changes: 4 additions & 0 deletions notebook/templates/notebooks.html
Expand Up @@ -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">

Expand Down
4 changes: 4 additions & 0 deletions notebook/templates/terminals.html
Expand Up @@ -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>

Expand Down
4 changes: 4 additions & 0 deletions notebook/templates/tree.html
Expand Up @@ -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 %}
RRosio marked this conversation as resolved.
Show resolved Hide resolved
</head>
<body>

Expand Down