Support type-dependent search result highlighting via CSS (#12474)

Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
Co-authored-by: James Addison <55152140+jayaddison@users.noreply.github.com>
This commit is contained in:
Tim Hoffmann 2024-08-11 21:22:21 +02:00 committed by GitHub
parent 9171f533cc
commit 646a5d7482
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 116 additions and 16 deletions

View File

@ -28,6 +28,8 @@ Features added
* #11328: Mention evaluation of templated content during production of static
output files.
* #12474: Support type-dependent search result highlighting via CSS.
Patch by Tim Hoffmann.
Bugs fixed
----------

View File

@ -691,7 +691,28 @@ div.sphinx-feature > p.admonition-title::before {
justify-content: center;
gap: 10px;
}
.sphinx-users-logos .headerlink {
display: none;
}
/* -- search results -------------------------------------------------------- */
ul.search {
padding-left: 30px;
}
ul.search li {
padding: 5px 0 5px 10px;
list-style-type: "\25A1"; /* Unicode: White Square */
}
ul.search li.context-index {
list-style-type: "\1F4D1"; /* Unicode: Bookmark Tabs */
}
ul.search li.context-object {
list-style-type: "\1F4E6"; /* Unicode: Package */
}
ul.search li.context-title {
list-style-type: "\1F4C4"; /* Unicode: Page Facing Up */
}
ul.search li.context-text {
list-style-type: "\1F4C4"; /* Unicode: Page Facing Up */
}

View File

@ -221,6 +221,64 @@ If your theme package contains two or more themes, please call
``sphinx.html_themes`` entry_points feature.
Styling with CSS
----------------
The :confval:`!stylesheets` setting can be used to add custom CSS files to a theme.
.. caution::
The structure of the HTML elements and their classes are currently not a
well-defined public API. Please infer them from inspecting the built HTML
pages. While we cannot guarantee full stability, they tend to be fairly
stable.
Styling search result entries by category
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 8.0
The search result items have classes indicating the context in which the
search term was found. You can use the CSS selectors:
- ``ul.search li.context-index``:
For results in an index, such as the glossary
- ``ul.search li.context-object``:
For results in source code, like Python function definitions
- ``ul.search li.context-title``:
For results found in section headings
- ``ul.search li.context-text``:
For results found anywhere else in the documentation text
As a base for inheritance by other themes, the ``basic`` theme is
intentionally minimal and does not define CSS rules using these.
Derived themes are encouraged to use these selectors as they see fit.
For example, the following stylesheet adds contextual icons to the
search result list:
.. code-block:: css
ul.search {
padding-left: 30px;
}
ul.search li {
padding: 5px 0 5px 10px;
list-style-type: "\25A1"; /* Unicode: White Square */
}
ul.search li.context-index {
list-style-type: "\1F4D1"; /* Unicode: Bookmark Tabs */
}
ul.search li.context-object {
list-style-type: "\1F4E6"; /* Unicode: Package */
}
ul.search li.context-title {
list-style-type: "\1F4C4"; /* Unicode: Page Facing Up */
}
ul.search li.context-text {
list-style-type: "\1F4C4"; /* Unicode: Page Facing Up */
}
Templating
----------

View File

@ -115,15 +115,11 @@ img {
/* -- search page ----------------------------------------------------------- */
ul.search {
margin: 10px 0 0 20px;
padding: 0;
margin-top: 10px;
}
ul.search li {
padding: 5px 0 5px 20px;
background-image: url(file.png);
background-repeat: no-repeat;
background-position: 0 7px;
padding: 5px 0;
}
ul.search li a {

View File

@ -20,7 +20,7 @@ if (typeof Scorer === "undefined") {
// and returns the new score.
/*
score: result => {
const [docname, title, anchor, descr, score, filename] = result
const [docname, title, anchor, descr, score, filename, context] = result
return score
},
*/
@ -47,6 +47,14 @@ if (typeof Scorer === "undefined") {
};
}
// Global search result kind enum, used by themes to style search results.
class SearchResultContext {
static get index() { return "index"; }
static get object() { return "object"; }
static get text() { return "text"; }
static get title() { return "title"; }
}
const _removeChildren = (element) => {
while (element && element.lastChild) element.removeChild(element.lastChild);
};
@ -64,9 +72,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => {
const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY;
const contentRoot = document.documentElement.dataset.content_root;
const [docName, title, anchor, descr, score, _filename] = item;
const [docName, title, anchor, descr, score, _filename, context] = item;
let listItem = document.createElement("li");
// Add a class representing the item's type:
// can be used by a theme's CSS selector for styling
// See SearchResultContext for the class names.
listItem.classList.add(`context-${context}`);
let requestUrl;
let linkUrl;
if (docBuilder === "dirhtml") {
@ -140,7 +152,7 @@ const _displayNextItem = (
else _finishSearch(resultCount);
};
// Helper function used by query() to order search results.
// Each input is an array of [docname, title, anchor, descr, score, filename].
// Each input is an array of [docname, title, anchor, descr, score, filename, context].
// Order the results by score (in opposite order of appearance, since the
// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically.
const _orderResultsByScoreThenName = (a, b) => {
@ -250,6 +262,7 @@ const Search = {
searchSummary.classList.add("search-summary");
searchSummary.innerText = "";
const searchList = document.createElement("ul");
searchList.setAttribute("role", "list");
searchList.classList.add("search");
const out = document.getElementById("search-results");
@ -320,7 +333,7 @@ const Search = {
const indexEntries = Search._index.indexentries;
// Collect multiple result groups to be sorted separately and then ordered.
// Each is an array of [docname, title, anchor, descr, score, filename].
// Each is an array of [docname, title, anchor, descr, score, filename, context].
const normalResults = [];
const nonMainIndexResults = [];
@ -339,6 +352,7 @@ const Search = {
null,
score + boost,
filenames[file],
SearchResultContext.title,
]);
}
}
@ -356,6 +370,7 @@ const Search = {
null,
score,
filenames[file],
SearchResultContext.index,
];
if (isMain) {
normalResults.push(result);
@ -477,6 +492,7 @@ const Search = {
descr,
score,
filenames[match[0]],
SearchResultContext.object,
]);
};
Object.keys(objects).forEach((prefix) =>
@ -587,6 +603,7 @@ const Search = {
null,
score,
filenames[file],
SearchResultContext.text,
]);
}
return results;

View File

@ -38,7 +38,8 @@ describe('Basic html theme search', function() {
"",
null,
5,
"index.rst"
"index.rst",
"text"
]];
expect(Search.performTermsSearch(searchterms, excluded)).toEqual(hits);
});
@ -53,7 +54,9 @@ describe('Basic html theme search', function() {
'',
null,
15,
'index.rst']];
'index.rst',
'text'
]];
expect(Search.performTermsSearch(searchterms, excluded)).toEqual(hits);
});
@ -68,7 +71,8 @@ describe('Basic html theme search', function() {
"",
null,
7,
"index.rst"
"index.rst",
"text"
]];
expect(Search.performTermsSearch(searchterms, excluded)).toEqual(hits);
});
@ -86,7 +90,8 @@ describe('Basic html theme search', function() {
"",
null,
2,
"index.rst"
"index.rst",
"text"
]];
expect(Search.performTermsSearch(searchterms, excluded, terms, titleterms)).toEqual(hits);
});
@ -107,7 +112,8 @@ describe('Basic html theme search', function() {
'',
null,
16,
'index.rst'
'index.rst',
'title'
]
];
expect(Search._performSearch(...searchParameters)).toEqual(hits);