Skip to content

Commit 2d4eccf

Browse files
[3.12] GH-121970: Improve the glossary preview in HTML search (GH-121991) (#122016)
GH-121970: Improve the glossary preview in HTML search (GH-121991) (cherry picked from commit adf0b94) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
1 parent 39dea21 commit 2d4eccf

File tree

3 files changed

+91
-93
lines changed

3 files changed

+91
-93
lines changed

Diff for: Doc/tools/extensions/glossary_search.py

+32-36
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,59 @@
1-
# -*- coding: utf-8 -*-
2-
"""
3-
glossary_search.py
4-
~~~~~~~~~~~~~~~~
1+
"""Feature search results for glossary items prominently."""
52

6-
Feature search results for glossary items prominently.
3+
from __future__ import annotations
74

8-
:license: Python license.
9-
"""
105
import json
11-
import os.path
12-
from docutils.nodes import definition_list_item
6+
from pathlib import Path
7+
from typing import TYPE_CHECKING
8+
9+
from docutils import nodes
1310
from sphinx.addnodes import glossary
1411
from sphinx.util import logging
1512

13+
if TYPE_CHECKING:
14+
from sphinx.application import Sphinx
15+
from sphinx.util.typing import ExtensionMetadata
1616

1717
logger = logging.getLogger(__name__)
18-
STATIC_DIR = '_static'
19-
JSON = 'glossary.json'
2018

2119

22-
def process_glossary_nodes(app, doctree, fromdocname):
20+
def process_glossary_nodes(app: Sphinx, doctree: nodes.document, _docname: str) -> None:
2321
if app.builder.format != 'html' or app.builder.embedded:
2422
return
2523

26-
terms = {}
24+
if hasattr(app.env, 'glossary_terms'):
25+
terms = app.env.glossary_terms
26+
else:
27+
terms = app.env.glossary_terms = {}
2728

2829
for node in doctree.findall(glossary):
29-
for glossary_item in node.findall(definition_list_item):
30-
term = glossary_item[0].astext().lower()
31-
definition = glossary_item[1]
30+
for glossary_item in node.findall(nodes.definition_list_item):
31+
term = glossary_item[0].astext()
32+
definition = glossary_item[-1]
3233

3334
rendered = app.builder.render_partial(definition)
34-
terms[term] = {
35-
'title': glossary_item[0].astext(),
35+
terms[term.lower()] = {
36+
'title': term,
3637
'body': rendered['html_body']
3738
}
3839

39-
if hasattr(app.env, 'glossary_terms'):
40-
app.env.glossary_terms.update(terms)
41-
else:
42-
app.env.glossary_terms = terms
4340

44-
def on_build_finish(app, exc):
45-
if not hasattr(app.env, 'glossary_terms'):
46-
return
47-
if not app.env.glossary_terms:
41+
def write_glossary_json(app: Sphinx, _exc: Exception) -> None:
42+
if not getattr(app.env, 'glossary_terms', None):
4843
return
4944

50-
logger.info(f'Writing {JSON}', color='green')
51-
52-
dest_dir = os.path.join(app.outdir, STATIC_DIR)
53-
os.makedirs(dest_dir, exist_ok=True)
54-
55-
with open(os.path.join(dest_dir, JSON), 'w') as f:
56-
json.dump(app.env.glossary_terms, f)
45+
logger.info(f'Writing glossary.json', color='green')
46+
dest = Path(app.outdir, '_static', 'glossary.json')
47+
dest.parent.mkdir(exist_ok=True)
48+
dest.write_text(json.dumps(app.env.glossary_terms), encoding='utf-8')
5749

5850

59-
def setup(app):
51+
def setup(app: Sphinx) -> ExtensionMetadata:
6052
app.connect('doctree-resolved', process_glossary_nodes)
61-
app.connect('build-finished', on_build_finish)
53+
app.connect('build-finished', write_glossary_json)
6254

63-
return {'version': '0.1', 'parallel_read_safe': True}
55+
return {
56+
'version': '1.0',
57+
'parallel_read_safe': True,
58+
'parallel_write_safe': True,
59+
}

Diff for: Doc/tools/static/glossary_search.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"use strict";
2+
3+
const GLOSSARY_PAGE = "glossary.html";
4+
5+
const glossary_search = async () => {
6+
const response = await fetch("_static/glossary.json");
7+
if (!response.ok) {
8+
throw new Error("Failed to fetch glossary.json");
9+
}
10+
const glossary = await response.json();
11+
12+
const params = new URLSearchParams(document.location.search).get("q");
13+
if (!params) {
14+
return;
15+
}
16+
17+
const searchParam = params.toLowerCase();
18+
const glossaryItem = glossary[searchParam];
19+
if (!glossaryItem) {
20+
return;
21+
}
22+
23+
// set up the title text with a link to the glossary page
24+
const glossaryTitle = document.getElementById("glossary-title");
25+
glossaryTitle.textContent = "Glossary: " + glossaryItem.title;
26+
const linkTarget = searchParam.replace(/ /g, "-");
27+
glossaryTitle.href = GLOSSARY_PAGE + "#term-" + linkTarget;
28+
29+
// rewrite any anchor links (to other glossary terms)
30+
// to have a full reference to the glossary page
31+
const glossaryBody = document.getElementById("glossary-body");
32+
glossaryBody.innerHTML = glossaryItem.body;
33+
const anchorLinks = glossaryBody.querySelectorAll('a[href^="#"]');
34+
anchorLinks.forEach(function (link) {
35+
const currentUrl = link.getAttribute("href");
36+
link.href = GLOSSARY_PAGE + currentUrl;
37+
});
38+
39+
const glossaryResult = document.getElementById("glossary-result");
40+
glossaryResult.style.display = "";
41+
};
42+
43+
if (document.readyState !== "loading") {
44+
glossary_search().catch(console.error);
45+
} else {
46+
document.addEventListener("DOMContentLoaded", glossary_search);
47+
}

Diff for: Doc/tools/templates/search.html

+12-57
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,16 @@
22
{% block extrahead %}
33
{{ super() }}
44
<meta name="robots" content="noindex">
5-
<script type="text/javascript">
6-
const GLOSSARY_PAGE = 'glossary.html';
7-
8-
document.addEventListener('DOMContentLoaded', function() {
9-
fetch('_static/glossary.json')
10-
.then(function(response) {
11-
if (response.ok) {
12-
return response.json();
13-
} else {
14-
throw new Error('Failed to fetch glossary.json');
15-
}
16-
})
17-
.then(function(glossary) {
18-
const RESULT_TEMPLATE = '<div style="display: none" class="admonition seealso" id="glossary-result">' +
19-
' <p class="topic-title">' +
20-
' <a class="glossary-title" href="#"></a>' +
21-
' </p>' +
22-
' <div class="glossary-body"></div>' +
23-
'</div>';
24-
let searchResults = document.getElementById('search-results');
25-
searchResults.insertAdjacentHTML('afterbegin', RESULT_TEMPLATE);
26-
27-
const params = new URLSearchParams(document.location.search).get("q");
28-
if (params) {
29-
const searchParam = params.toLowerCase();
30-
const glossaryItem = glossary[searchParam];
31-
if (glossaryItem) {
32-
let resultDiv = document.getElementById('glossary-result');
33-
34-
// set up the title text with a link to the glossary page
35-
let glossaryTitle = resultDiv.querySelector('.glossary-title');
36-
glossaryTitle.textContent = 'Glossary: ' + glossaryItem.title;
37-
const linkTarget = searchParam.replace(/ /g, '-');
38-
glossaryTitle.href = GLOSSARY_PAGE + '#term-' + linkTarget;
39-
40-
// rewrite any anchor links (to other glossary terms)
41-
// to have a full reference to the glossary page
42-
let body = document.createElement('div');
43-
body.innerHTML = glossaryItem.body;
44-
const anchorLinks = body.querySelectorAll('a[href^="#"]');
45-
anchorLinks.forEach(function(link) {
46-
const currentUrl = link.getAttribute('href');
47-
link.href = GLOSSARY_PAGE + currentUrl;
48-
});
49-
resultDiv.querySelector('.glossary-body').appendChild(body);
50-
51-
resultDiv.style.display = '';
52-
} else {
53-
document.getElementById('glossary-result').style.display = 'none';
54-
}
55-
}
56-
})
57-
.catch(function(error) {
58-
console.error(error);
59-
});
60-
});
61-
</script>
5+
<script type="text/javascript" src="{{ pathto('_static/glossary_search.js', resource=True) }}"></script>
6+
{% endblock %}
7+
{% block searchresults %}
8+
<div id="search-results">
9+
{# For glossary_search.js #}
10+
<div style="display: none;" class="admonition seealso" id="glossary-result">
11+
<p class="topic-title">
12+
<a id="glossary-title" href="#"></a>
13+
</p>
14+
<div id="glossary-body"></div>
15+
</div>
16+
</div>
6217
{% endblock %}

0 commit comments

Comments
 (0)