Skip to content

Commit

Permalink
feat: add docsearch (#597)
Browse files Browse the repository at this point in the history
* feat: docsearch

* fix: mypy

* fix: python version for netlify

* fix: docsearch on all pages

Change the config options for DocSearch and add them both to the
local context and global context.

I think, it didn't work before because the `app.builder.globalcontext`
is only initialized after `html-page-context` for the first page.

* fix: update packages

* fix: mypy

* fix: link styles

* fix: no jquery

When using DocSearch, don't include `jquery` and `underscore` and
all the other JavaScript libraries from Sphinx.

* fix: scrollspy and search JS

* fix: simplify templates

- add 'sr-only' labels to buttons
- remove unused 'search-pane' button and remove extra 'buttons' file
  now that the close button is only used once.
  • Loading branch information
kai687 authored Oct 15, 2021
1 parent b537268 commit 4a815f1
Show file tree
Hide file tree
Showing 38 changed files with 727 additions and 392 deletions.
15 changes: 13 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
html_last_updated_fmt = ""
html_use_index = False # Don't create index
html_domain_indices = False # Don't need module indices
html_copy_source = True
html_copy_source = False # Don't need sources
html_logo = "assets/auto_awesome.svg"
html_favicon = "assets/favicon-128x128.png"
html_permalinks_icon = (
Expand All @@ -86,8 +86,19 @@
# templates_path = ["_templates"]
# html_additional_pages = {"about": "about.html"}

# extra option from the sphinxawesome_theme
# extra options from the sphinxawesome_theme
html_collapsible_definitions = True
html_awesome_docsearch = True

# DocSearch is configured via an `.env` key here.
# You can also use the following dictionary
#
# docsearch_config = {
# "api_key": "",
# "container": "",
# "index_name": "",
# }

html_theme_options = {
"show_scrolltop": True,
"extra_header_links": {
Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def tests(session: Session) -> None:
@nox.session(python=Versions.all())
def docs(session: Session) -> None:
"""Build the docs."""
args = session.posargs or ["-b", "dirhtml", "-aqWTE", "docs", "docs/public"]
args = session.posargs or ["-b", "dirhtml", "-aWTE", "docs", "docs/public"]
session.run("poetry", "install", "--no-dev", external=True)
install_constrained_version(session, "myst-parser", "sphinx-sitemap")
session.run("sphinx-build", *args)
Expand Down
305 changes: 152 additions & 153 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ python = ">=3.6.2,<4.0.0"
sphinx = ">4"
importlib_metadata = {version = ">=1.6.1,<5.0.0", python = "<3.8"}
beautifulsoup4 = "^4.9.1"
python-dotenv = "^0.19.0"

[tool.poetry.dev-dependencies]
flake8 = "^3.9.2"
Expand Down Expand Up @@ -64,7 +65,7 @@ show_error_codes = true
pretty = true

[[tool.mypy.overrides]]
module = ["pygments.*", "bs4", "sphinxcontrib.serializinghtml"]
module = ["pygments.*", "bs4", "dotenv", "sphinxcontrib.serializinghtml"]
ignore_missing_imports = true

[tool.poetry.plugins."sphinx.html_themes"]
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ markupsafe==2.0.1; python_version >= "3.6"
packaging==21.0; python_version >= "3.6"
pygments==2.10.0; python_version >= "3.6"
pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6"
python-dotenv==0.19.0; python_version >= "3.5"
pytz==2021.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
requests==2.26.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
snowballstemmer==2.1.0; python_version >= "3.6"
Expand Down
1 change: 1 addition & 0 deletions runtime.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.8
40 changes: 37 additions & 3 deletions src/sphinxawesome_theme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from typing import Any, Dict

from sphinx.application import Sphinx
from sphinx.config import Config
from sphinxcontrib.serializinghtml import JSONHTMLBuilder

from . import jsonimpl
Expand All @@ -27,14 +28,47 @@
__version__ = "unknown"


def conditional_setup(app: Sphinx, config: Config) -> None:
"""Set up extensions if configuration is ready."""
if config.html_awesome_docsearch:
app.setup_extension("sphinxawesome_theme.docsearch")

if config.html_awesome_html_translator:
app.setup_extension("sphinxawesome_theme.html_translator")

if config.html_awesome_postprocessing:
app.setup_extension("sphinxawesome_theme.postprocess")


def setup(app: "Sphinx") -> Dict[str, Any]:
"""Register the theme and its extensions wih Sphinx."""
app.add_html_theme("sphinxawesome_theme", path.abspath(path.dirname(__file__)))
app.add_config_value("html_awesome_postprocessing", True, "html")
app.add_config_value("html_awesome_html_translator", True, "html")
app.add_html_theme(
name="sphinxawesome_theme", theme_path=path.abspath(path.dirname(__file__))
)
app.add_config_value(
name="html_awesome_postprocessing", default=True, rebuild="html", types=(bool)
)
app.add_config_value(
name="html_awesome_html_translator", default=True, rebuild="html", types=(bool)
)
app.add_config_value(
name="html_awesome_docsearch", default=False, rebuild="html", types=(bool)
)
app.add_config_value(
name="docsearch_config", default={}, rebuild="html", types=(dict)
)
app.add_config_value(
name="html_collapsible_definitions", default=False, rebuild="html", types=(str)
)
app.add_config_value(
name="html_awesome_headerlinks", default=True, rebuild="html", types=(str)
)

app.setup_extension("sphinxawesome_theme.highlighting")
app.setup_extension("sphinxawesome_theme.jinja_functions")

app.connect("config-inited", conditional_setup)

JSONHTMLBuilder.out_suffix = ".json"
JSONHTMLBuilder.implementation = jsonimpl
JSONHTMLBuilder.indexer_format = jsonimpl
Expand Down
24 changes: 0 additions & 24 deletions src/sphinxawesome_theme/buttons.html

This file was deleted.

70 changes: 70 additions & 0 deletions src/sphinxawesome_theme/docsearch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Add Algolia DocSearch to Sphinx.
This Sphinx extension adds DocSearch to your Sphinx project.
:copyright: Kai Welke.
:license: MIT
"""
import os
from pathlib import Path
from typing import Any, Dict

from docutils.nodes import Node
from dotenv import load_dotenv
from sphinx.application import Sphinx
from sphinx.util import progress_message

from . import __version__


@progress_message("Set up DocSearch")
def setup_docsearch(
app: Sphinx,
pagename: str,
templatename: str,
context: Dict[str, Any],
doctree: Node,
) -> None:
"""Set up DocSearch.
Config can be provided via environment variables, a `.env.` file,
or the Sphinx configuration file.
"""
# load `.env` file into environment
load_dotenv(dotenv_path=(Path(app.confdir) / ".env"))

docsearch_config = {
"container": (
os.getenv("DOCSEARCH_CONTAINER")
or app.config.docsearch_config.get("container", "#docsearch")
),
"api_key": (
os.getenv("DOCSEARCH_API_KEY") or app.config.docsearch_config.get("api_key")
),
"index_name": (
os.getenv("DOCSEARCH_INDEX_NAME")
or app.config.docsearch_config.get("index_name")
),
}

# if we want to use `docsearch` we don't need any other JS file from Sphinx
if app.config.html_awesome_docsearch:
context["script_files"] = []

context["docsearch"] = app.config.html_awesome_docsearch
# update local context for rendering the `layout.html` templates for every page
context["docsearch_config"] = docsearch_config
# update the global context for writing the `docsearch_config.js` file
app.builder.globalcontext["docsearch_config"] = docsearch_config # type: ignore [union-attr] # noqa: B950,E501


def setup(app: Sphinx) -> Dict[str, Any]:
"""Register the extension."""
app.add_css_file("docsearch.css", priority=150)
app.connect("html-page-context", setup_docsearch)

return {
"version": __version__,
"parallel_read_safe": True,
"parallel_write_safe": True,
}
27 changes: 2 additions & 25 deletions src/sphinxawesome_theme/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
data-action="sidebar#open"
data-sidebar-target="hamburger"
class="xl:hidden h-14 w-14 sm:h-18 sm:w-18 leading-14 sm:leading-18 text-gray-100 hover:bg-gray-700 hover:text-brand focus:outline-none focus:bg-gray-700 focus:text-brand">
<span class="sr-only">Open navigation menu</span>
<svg aria-hidden="true" class="fill-current h-8 w-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>
</button>
{%- endset -%}
Expand Down Expand Up @@ -41,31 +42,7 @@

{#- show a search input field on the right side -#}
<div class="flex justify-end items-center flex-1">
<form
id="searchbox"
action="{{ pathto('search') }}"
data-action="click->search#focusSearchInput"
method="get"
class="flex print:hidden justify-between items-center leading-14 sm:leading-18 md:ml-4 bg-gray-dark text-gray-300 focus-within:bg-gray-50 focus-within:text-gray-800 focus-within:absolute focus-within:inset-x-0 focus-within:top-0 md:focus-within:w-full md:focus-within:static">

<button
class="text-inherit h-14 w-14 sm:h-18 sm:w-18"
aria-label="{{ _('Get search results') }}"
tabindex="-1"
>
<svg aria-hidden="true" class="fill-current stroke-current h-8 w-8" stroke-width="0.5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
</button>

<input
name="q"
id="search-input"
data-search-target="searchInput"
type="search"
aria-label="Search the docs"
placeholder="{{ _('Search the docs') }}"
class="pr-2 bg-transparent text-inherit focus:outline-none w-0 md:w-auto focus:w-full transition-all duration-100"
/>
</form>
{%- include "searchbox.html" %}

{%- if theme_extra_header_links|tobool -%}
{%- block extra_header_links -%}
Expand Down
1 change: 0 additions & 1 deletion src/sphinxawesome_theme/html_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,6 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
"""
app.set_translator("html", AwesomeHTMLTranslator)
app.set_translator("dirhtml", AwesomeHTMLTranslator)
app.add_config_value("html_collapsible_definitions", False, "html")

return {
"version": __version__,
Expand Down
41 changes: 12 additions & 29 deletions src/sphinxawesome_theme/layout.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{%- macro script() %}
<script id="documentation_options"
data-url_root="{{ pathto('', 1) }}"
src="{{ pathto('_static/documentation_options.js', 1) }}">
</script>
{%- for js in script_files %}
{{ js_tag(js) }}
{{ js_tag(js) }}
{%- endfor %}
<script src="{{ pathto(asset('_static/theme.js'), 1) }}" defer></script>
<script src="{{ pathto(asset('_static/theme.js'), 1) }}"></script>
{%- if docsearch %}
<script src="{{ pathto(asset('_static/docsearch.js'), 1) }}"></script>
<script src="{{ pathto('_static/docsearch_config.js', 1) }}"></script>
{%- endif %}
{%- endmacro -%}

{%- set lang_attr = "en" if language == None else (language|replace('_','-')) -%}
Expand All @@ -24,14 +24,11 @@
<!DOCTYPE html>
<html lang="{{ lang_attr }}">
<head>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="utf-8" />
{{ metatags }}
{%- block htmltitle %}
<title>
{{ title|striptags|e if title else docstitle }}{{ titlesuffix }}
</title>
<title>{{ title|striptags|e if title else docstitle }}{{ titlesuffix }}</title>
{%- endblock %}
{#- Extra CSS files for overriding stuff #}
{%- for css in css_files %}
Expand All @@ -44,35 +41,21 @@
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}" />
{%- endif %}
{%- block linktags %}
{%- if hasdoc('search') %}
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}"/>
{%- if hasdoc('search') and not docsearch %}
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
{%- endif %}
{%- if hasdoc('genindex') %}
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}"/>
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
{%- endif %}
{%- if next %}
<link
rel="next"
title="{{ next.title|striptags|e }}"
href="{{ next.link|e }}"
/>
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
{%- endif %}
{%- if prev %}
<link
rel="prev"
title="{{ prev.title|striptags|e }}"
href="{{ prev.link|e }}"
/>
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
{%- endif %}
{%- endblock %}
</head>

{#-
Theme variables are strings and even if they are defined as booleans in the
``html_theme_options`` dictionary, they are treated as strings. Therefore,
we must use string comparison to check, whether a value is ``true`` or not.
-#}

<body class="antialiased text-sm sm:text-base text-gray">
<div
id="page"
Expand Down
1 change: 0 additions & 1 deletion src/sphinxawesome_theme/postprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ def post_process_html(app: Sphinx, exc: Optional[Exception]) -> None:
def setup(app: "Sphinx") -> Dict[str, Any]:
"""Set this up as internal extension."""
app.connect("build-finished", post_process_html)
app.add_config_value("html_awesome_headerlinks", True, "html")

return {
"version": __version__,
Expand Down
33 changes: 33 additions & 0 deletions src/sphinxawesome_theme/searchbox.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{#-
Template for the search box in the header.
-#}

{%- if docsearch %}
<div id="{{ docsearch_config.container[1:] }}"></div>
{%- else -%}
<form
id="searchbox"
action="{{ pathto('search') }}"
data-action="click->search#focusSearchInput"
method="get"
class="flex print:hidden justify-between items-center leading-14 sm:leading-18 md:ml-4 bg-gray-dark text-gray-300 focus-within:bg-gray-50 focus-within:text-gray-800 focus-within:absolute focus-within:inset-x-0 focus-within:top-0 md:focus-within:w-full md:focus-within:static">

<button
class="text-inherit h-14 w-14 sm:h-18 sm:w-18"
aria-label="{{ _('Get search results') }}"
tabindex="-1"
>
<svg aria-hidden="true" class="fill-current stroke-current h-8 w-8" stroke-width="0.5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
</button>

<input
name="q"
id="search-input"
data-search-target="searchInput"
type="search"
aria-label="Search the docs"
placeholder="{{ _('Search the docs') }}"
class="pr-2 bg-transparent text-inherit focus:outline-none w-0 md:w-auto focus:w-full transition-all duration-100"
/>
</form>
{%- endif -%}
Loading

0 comments on commit 4a815f1

Please sign in to comment.