From b23988e8d307e86b9474cd74171fd58837daa855 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:40:03 +0200 Subject: [PATCH] ENH: Add type-dependent class to search result entries This allows styling different types of results individually. It's helpful to visually distinguish different content types. This PR adds `context` to the javascript result entries (one of "title", "index", "object", "text") and adds them as a classes context-title, context-index, context-object, context-text to the
  • item in the result list. This allows styling via CSS. The basic theme only contains the mechanism to add the HTML classes. It does intentionally not do any styling via CSS. We reserve that freedom to derived themes. For the internal sphinx13 theme, I've styled with unicode symbols, which should give a decent look without the need to ship our own symbols. Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com> --- CHANGES.rst | 3 +++ doc/_themes/sphinx13/static/sphinx13.css | 22 +++++++++++++++++++ sphinx/themes/basic/static/basic.css.jinja | 8 ++----- sphinx/themes/basic/static/searchtools.js | 25 ++++++++++++++++++---- tests/js/searchtools.js | 15 ++++++++----- 5 files changed, 58 insertions(+), 15 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4f881ac175c..732bc7a487c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -92,6 +92,9 @@ Deprecated Features added -------------- +* #12474: Support type-dependent search result highlighting via CSS. + Patch by Tim Hoffmann. + Bugs fixed ---------- diff --git a/doc/_themes/sphinx13/static/sphinx13.css b/doc/_themes/sphinx13/static/sphinx13.css index 8bf98144a81..54e707e386c 100644 --- a/doc/_themes/sphinx13/static/sphinx13.css +++ b/doc/_themes/sphinx13/static/sphinx13.css @@ -694,3 +694,25 @@ div.sphinx-feature > p.admonition-title::before { justify-content: center; gap: 10px; } + +/* -- 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 */ +} diff --git a/sphinx/themes/basic/static/basic.css.jinja b/sphinx/themes/basic/static/basic.css.jinja index 297b9bfaeff..53fbadb0b6a 100644 --- a/sphinx/themes/basic/static/basic.css.jinja +++ b/sphinx/themes/basic/static/basic.css.jinja @@ -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 { diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index b08d58c9b9b..f1ee12781c0 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -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") { @@ -138,7 +150,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) => { @@ -248,6 +260,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"); @@ -318,7 +331,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 = []; @@ -337,6 +350,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultContext.title, ]); } } @@ -354,6 +368,7 @@ const Search = { null, score, filenames[file], + SearchResultContext.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +490,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultContext.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +601,7 @@ const Search = { null, score, filenames[file], + SearchResultContext.text, ]); } return results; diff --git a/tests/js/searchtools.js b/tests/js/searchtools.js index c82c6f1f968..0566d73463d 100644 --- a/tests/js/searchtools.js +++ b/tests/js/searchtools.js @@ -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); }); @@ -88,8 +92,9 @@ describe('Basic html theme search', function() { 'Main Page', '', null, - 16, - 'index.rst' + 100, + 'index.rst', + 'title' ] ]; expect(Search._performSearch(...searchParameters)).toEqual(hits);