mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-10-13 08:52:13 -05:00
Adopt Prettier for JavaScript formatting (#13581)
This commit is contained in:
1 parent
1a69059295
commit
2c2159fb9e
12 files changed
+310
-217
No files matched your search
@@ -123,3 +123,21 @@ jobs:
|
||||
run: |
|
||||
python -m build .
|
||||
twine check dist/*
|
||||
|
||||
prettier:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
- run: >
|
||||
npx prettier@3.5
|
||||
--check
|
||||
"sphinx/themes/**/*.js"
|
||||
"!sphinx/themes/bizstyle/static/css3-mediaqueries*.js"
|
||||
"tests/js/**/*.{js,mjs}"
|
||||
"!tests/js/fixtures/**"
|
||||
@@ -0,0 +1,2 @@
|
||||
# https://prettier.io/docs/options
|
||||
experimentalOperatorPosition = "start"
|
||||
@@ -81,11 +81,11 @@ class SearchLanguage:
|
||||
/**
|
||||
* Dummy stemmer for languages without stemming rules.
|
||||
*/
|
||||
var Stemmer = function() {
|
||||
this.stemWord = function(w) {
|
||||
var Stemmer = function () {
|
||||
this.stemWord = function (w) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
"""
|
||||
|
||||
_word_re = re.compile(r'\w+')
|
||||
|
||||
@@ -59,7 +59,7 @@ const Documentation = {
|
||||
Object.assign(Documentation.TRANSLATIONS, catalog.messages);
|
||||
Documentation.PLURAL_EXPR = new Function(
|
||||
"n",
|
||||
`return (${catalog.plural_expr})`
|
||||
`return (${catalog.plural_expr})`,
|
||||
);
|
||||
Documentation.LOCALE = catalog.locale;
|
||||
},
|
||||
@@ -89,7 +89,7 @@ const Documentation = {
|
||||
|
||||
const togglerElements = document.querySelectorAll("img.toggler");
|
||||
togglerElements.forEach((el) =>
|
||||
el.addEventListener("click", (event) => toggler(event.currentTarget))
|
||||
el.addEventListener("click", (event) => toggler(event.currentTarget)),
|
||||
);
|
||||
togglerElements.forEach((el) => (el.style.display = ""));
|
||||
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
|
||||
@@ -98,14 +98,15 @@ const Documentation = {
|
||||
initOnKeyListeners: () => {
|
||||
// only install a listener if it is really needed
|
||||
if (
|
||||
!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
|
||||
!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
|
||||
!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS
|
||||
&& !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
|
||||
)
|
||||
return;
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
// bail for input elements
|
||||
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
|
||||
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName))
|
||||
return;
|
||||
// bail with special keys
|
||||
if (event.altKey || event.ctrlKey || event.metaKey) return;
|
||||
|
||||
|
||||
@@ -41,11 +41,12 @@ if (typeof Scorer === "undefined") {
|
||||
}
|
||||
|
||||
// Global search result kind enum, used by themes to style search results.
|
||||
// prettier-ignore
|
||||
class SearchResultKind {
|
||||
static get index() { return "index"; }
|
||||
static get object() { return "object"; }
|
||||
static get text() { return "text"; }
|
||||
static get title() { return "title"; }
|
||||
static get index() { return "index"; }
|
||||
static get object() { return "object"; }
|
||||
static get text() { return "text"; }
|
||||
static get title() { return "title"; }
|
||||
}
|
||||
|
||||
const _removeChildren = (element) => {
|
||||
@@ -95,20 +96,25 @@ const _displayItem = (item, searchTerms, highlightTerms) => {
|
||||
listItem.appendChild(document.createElement("span")).innerHTML =
|
||||
" (" + descr + ")";
|
||||
// highlight search terms in the description
|
||||
if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
|
||||
highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
|
||||
}
|
||||
else if (showSearchSummary)
|
||||
if (SPHINX_HIGHLIGHT_ENABLED)
|
||||
// SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js
|
||||
highlightTerms.forEach((term) =>
|
||||
_highlightText(listItem, term, "highlighted"),
|
||||
);
|
||||
} else if (showSearchSummary)
|
||||
fetch(requestUrl)
|
||||
.then((responseData) => responseData.text())
|
||||
.then((data) => {
|
||||
if (data)
|
||||
listItem.appendChild(
|
||||
Search.makeSearchSummary(data, searchTerms, anchor)
|
||||
Search.makeSearchSummary(data, searchTerms, anchor),
|
||||
);
|
||||
// highlight search terms in the summary
|
||||
if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
|
||||
highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
|
||||
if (SPHINX_HIGHLIGHT_ENABLED)
|
||||
// SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js
|
||||
highlightTerms.forEach((term) =>
|
||||
_highlightText(listItem, term, "highlighted"),
|
||||
);
|
||||
});
|
||||
Search.output.appendChild(listItem);
|
||||
};
|
||||
@@ -117,14 +123,14 @@ const _finishSearch = (resultCount) => {
|
||||
Search.title.innerText = _("Search Results");
|
||||
if (!resultCount)
|
||||
Search.status.innerText = Documentation.gettext(
|
||||
"Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."
|
||||
"Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.",
|
||||
);
|
||||
else
|
||||
Search.status.innerText = Documentation.ngettext(
|
||||
"Search finished, found one page matching the search query.",
|
||||
"Search finished, found ${resultCount} pages matching the search query.",
|
||||
resultCount,
|
||||
).replace('${resultCount}', resultCount);
|
||||
).replace("${resultCount}", resultCount);
|
||||
};
|
||||
const _displayNextItem = (
|
||||
results,
|
||||
@@ -138,7 +144,7 @@ const _displayNextItem = (
|
||||
_displayItem(results.pop(), searchTerms, highlightTerms);
|
||||
setTimeout(
|
||||
() => _displayNextItem(results, resultCount, searchTerms, highlightTerms),
|
||||
5
|
||||
5,
|
||||
);
|
||||
}
|
||||
// search finished, update title and status message
|
||||
@@ -170,9 +176,10 @@ const _orderResultsByScoreThenName = (a, b) => {
|
||||
* This is the same as ``\W+`` in Python, preserving the surrogate pair area.
|
||||
*/
|
||||
if (typeof splitQuery === "undefined") {
|
||||
var splitQuery = (query) => query
|
||||
var splitQuery = (query) =>
|
||||
query
|
||||
.split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu)
|
||||
.filter(term => term) // remove remaining empty strings
|
||||
.filter((term) => term); // remove remaining empty strings
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,16 +191,23 @@ const Search = {
|
||||
_pulse_status: -1,
|
||||
|
||||
htmlToText: (htmlString, anchor) => {
|
||||
const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html');
|
||||
const htmlElement = new DOMParser().parseFromString(
|
||||
htmlString,
|
||||
"text/html",
|
||||
);
|
||||
for (const removalQuery of [".headerlink", "script", "style"]) {
|
||||
htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() });
|
||||
htmlElement.querySelectorAll(removalQuery).forEach((el) => {
|
||||
el.remove();
|
||||
});
|
||||
}
|
||||
if (anchor) {
|
||||
const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`);
|
||||
const anchorContent = htmlElement.querySelector(
|
||||
`[role="main"] ${anchor}`,
|
||||
);
|
||||
if (anchorContent) return anchorContent.textContent;
|
||||
|
||||
console.warn(
|
||||
`Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`
|
||||
`Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -202,7 +216,7 @@ const Search = {
|
||||
if (docContent) return docContent.textContent;
|
||||
|
||||
console.warn(
|
||||
"Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template."
|
||||
"Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template.",
|
||||
);
|
||||
return "";
|
||||
},
|
||||
@@ -288,8 +302,7 @@ const Search = {
|
||||
|
||||
// maybe skip this "word"
|
||||
// stopwords set is from language_data.js
|
||||
if (stopwords.has(queryTermLower) || queryTerm.match(/^\d+$/))
|
||||
return;
|
||||
if (stopwords.has(queryTermLower) || queryTerm.match(/^\d+$/)) return;
|
||||
|
||||
// stem the word
|
||||
let word = stemmer.stemWord(queryTermLower);
|
||||
@@ -301,8 +314,12 @@ const Search = {
|
||||
}
|
||||
});
|
||||
|
||||
if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js
|
||||
localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" "))
|
||||
if (SPHINX_HIGHLIGHT_ENABLED) {
|
||||
// SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js
|
||||
localStorage.setItem(
|
||||
"sphinx_highlight_terms",
|
||||
[...highlightTerms].join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
// console.debug("SEARCH: searching for:");
|
||||
@@ -315,7 +332,13 @@ const Search = {
|
||||
/**
|
||||
* execute search (requires search index to be loaded)
|
||||
*/
|
||||
_performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => {
|
||||
_performSearch: (
|
||||
query,
|
||||
searchTerms,
|
||||
excludedTerms,
|
||||
highlightTerms,
|
||||
objectTerms,
|
||||
) => {
|
||||
const filenames = Search._index.filenames;
|
||||
const docNames = Search._index.docnames;
|
||||
const titles = Search._index.titles;
|
||||
@@ -331,10 +354,15 @@ const Search = {
|
||||
|
||||
const queryLower = query.toLowerCase().trim();
|
||||
for (const [title, foundTitles] of Object.entries(allTitles)) {
|
||||
if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) {
|
||||
if (
|
||||
title.toLowerCase().trim().includes(queryLower)
|
||||
&& queryLower.length >= title.length / 2
|
||||
) {
|
||||
for (const [file, id] of foundTitles) {
|
||||
const score = Math.round(Scorer.title * queryLower.length / title.length);
|
||||
const boost = titles[file] === title ? 1 : 0; // add a boost for document titles
|
||||
const score = Math.round(
|
||||
(Scorer.title * queryLower.length) / title.length,
|
||||
);
|
||||
const boost = titles[file] === title ? 1 : 0; // add a boost for document titles
|
||||
normalResults.push([
|
||||
docNames[file],
|
||||
titles[file] !== title ? `${titles[file]} > ${title}` : title,
|
||||
@@ -350,9 +378,9 @@ const Search = {
|
||||
|
||||
// search for explicit entries in index directives
|
||||
for (const [entry, foundEntries] of Object.entries(indexEntries)) {
|
||||
if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) {
|
||||
if (entry.includes(queryLower) && queryLower.length >= entry.length / 2) {
|
||||
for (const [file, id, isMain] of foundEntries) {
|
||||
const score = Math.round(100 * queryLower.length / entry.length);
|
||||
const score = Math.round((100 * queryLower.length) / entry.length);
|
||||
const result = [
|
||||
docNames[file],
|
||||
titles[file],
|
||||
@@ -373,11 +401,13 @@ const Search = {
|
||||
|
||||
// lookup as object
|
||||
objectTerms.forEach((term) =>
|
||||
normalResults.push(...Search.performObjectSearch(term, objectTerms))
|
||||
normalResults.push(...Search.performObjectSearch(term, objectTerms)),
|
||||
);
|
||||
|
||||
// lookup as search terms in fulltext
|
||||
normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms));
|
||||
normalResults.push(
|
||||
...Search.performTermsSearch(searchTerms, excludedTerms),
|
||||
);
|
||||
|
||||
// let the scorer override scores with a custom scoring function
|
||||
if (Scorer.score) {
|
||||
@@ -398,7 +428,11 @@ const Search = {
|
||||
// note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept
|
||||
let seen = new Set();
|
||||
results = results.reverse().reduce((acc, result) => {
|
||||
let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(',');
|
||||
let resultStr = result
|
||||
.slice(0, 4)
|
||||
.concat([result[5]])
|
||||
.map((v) => String(v))
|
||||
.join(",");
|
||||
if (!seen.has(resultStr)) {
|
||||
acc.push(result);
|
||||
seen.add(resultStr);
|
||||
@@ -410,8 +444,20 @@ const Search = {
|
||||
},
|
||||
|
||||
query: (query) => {
|
||||
const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query);
|
||||
const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms);
|
||||
const [
|
||||
searchQuery,
|
||||
searchTerms,
|
||||
excludedTerms,
|
||||
highlightTerms,
|
||||
objectTerms,
|
||||
] = Search._parseQuery(query);
|
||||
const results = Search._performSearch(
|
||||
searchQuery,
|
||||
searchTerms,
|
||||
excludedTerms,
|
||||
highlightTerms,
|
||||
objectTerms,
|
||||
);
|
||||
|
||||
// for debugging
|
||||
//Search.lastresults = results.slice(); // a copy
|
||||
@@ -434,7 +480,7 @@ const Search = {
|
||||
const results = [];
|
||||
|
||||
const objectSearchCallback = (prefix, match) => {
|
||||
const name = match[4]
|
||||
const name = match[4];
|
||||
const fullname = (prefix ? prefix + "." : "") + name;
|
||||
const fullnameLower = fullname.toLowerCase();
|
||||
if (fullnameLower.indexOf(object) < 0) return;
|
||||
@@ -486,9 +532,7 @@ const Search = {
|
||||
]);
|
||||
};
|
||||
Object.keys(objects).forEach((prefix) =>
|
||||
objects[prefix].forEach((array) =>
|
||||
objectSearchCallback(prefix, array)
|
||||
)
|
||||
objects[prefix].forEach((array) => objectSearchCallback(prefix, array)),
|
||||
);
|
||||
return results;
|
||||
},
|
||||
@@ -513,8 +557,14 @@ const Search = {
|
||||
// find documents, if any, containing the query word in their text/title term indices
|
||||
// use Object.hasOwnProperty to avoid mismatching against prototype properties
|
||||
const arr = [
|
||||
{ files: terms.hasOwnProperty(word) ? terms[word] : undefined, score: Scorer.term },
|
||||
{ files: titleTerms.hasOwnProperty(word) ? titleTerms[word] : undefined, score: Scorer.title },
|
||||
{
|
||||
files: terms.hasOwnProperty(word) ? terms[word] : undefined,
|
||||
score: Scorer.term,
|
||||
},
|
||||
{
|
||||
files: titleTerms.hasOwnProperty(word) ? titleTerms[word] : undefined,
|
||||
score: Scorer.title,
|
||||
},
|
||||
];
|
||||
// add support for partial matches
|
||||
if (word.length > 2) {
|
||||
@@ -555,7 +605,8 @@ const Search = {
|
||||
// create the mapping
|
||||
files.forEach((file) => {
|
||||
if (!fileMap.has(file)) fileMap.set(file, [word]);
|
||||
else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word);
|
||||
else if (fileMap.get(file).indexOf(word) === -1)
|
||||
fileMap.get(file).push(word);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -566,11 +617,11 @@ const Search = {
|
||||
|
||||
// as search terms with length < 3 are discarded
|
||||
const filteredTermCount = [...searchTerms].filter(
|
||||
(term) => term.length > 2
|
||||
(term) => term.length > 2,
|
||||
).length;
|
||||
if (
|
||||
wordList.length !== searchTerms.size &&
|
||||
wordList.length !== filteredTermCount
|
||||
wordList.length !== searchTerms.size
|
||||
&& wordList.length !== filteredTermCount
|
||||
)
|
||||
continue;
|
||||
|
||||
@@ -578,10 +629,10 @@ const Search = {
|
||||
if (
|
||||
[...excludedTerms].some(
|
||||
(term) =>
|
||||
terms[term] === file ||
|
||||
titleTerms[term] === file ||
|
||||
(terms[term] || []).includes(file) ||
|
||||
(titleTerms[term] || []).includes(file)
|
||||
terms[term] === file
|
||||
|| titleTerms[term] === file
|
||||
|| (terms[term] || []).includes(file)
|
||||
|| (titleTerms[term] || []).includes(file),
|
||||
)
|
||||
)
|
||||
break;
|
||||
@@ -623,7 +674,8 @@ const Search = {
|
||||
|
||||
let summary = document.createElement("p");
|
||||
summary.classList.add("context");
|
||||
summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;
|
||||
summary.textContent =
|
||||
top + text.substr(startWithContext, 240).trim() + tail;
|
||||
|
||||
return summary;
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* Highlighting utilities for Sphinx HTML documentation. */
|
||||
"use strict";
|
||||
|
||||
const SPHINX_HIGHLIGHT_ENABLED = true
|
||||
const SPHINX_HIGHLIGHT_ENABLED = true;
|
||||
|
||||
/**
|
||||
* highlight a given string on a node by wrapping it in
|
||||
@@ -13,9 +13,9 @@ const _highlight = (node, addItems, text, className) => {
|
||||
const parent = node.parentNode;
|
||||
const pos = val.toLowerCase().indexOf(text);
|
||||
if (
|
||||
pos >= 0 &&
|
||||
!parent.classList.contains(className) &&
|
||||
!parent.classList.contains("nohighlight")
|
||||
pos >= 0
|
||||
&& !parent.classList.contains(className)
|
||||
&& !parent.classList.contains("nohighlight")
|
||||
) {
|
||||
let span;
|
||||
|
||||
@@ -30,13 +30,7 @@ const _highlight = (node, addItems, text, className) => {
|
||||
|
||||
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
|
||||
const rest = document.createTextNode(val.substr(pos + text.length));
|
||||
parent.insertBefore(
|
||||
span,
|
||||
parent.insertBefore(
|
||||
rest,
|
||||
node.nextSibling
|
||||
)
|
||||
);
|
||||
parent.insertBefore(span, parent.insertBefore(rest, node.nextSibling));
|
||||
node.nodeValue = val.substr(0, pos);
|
||||
/* There may be more occurrences of search term in this node. So call this
|
||||
* function recursively on the remaining fragment.
|
||||
@@ -46,7 +40,7 @@ const _highlight = (node, addItems, text, className) => {
|
||||
if (isInSVG) {
|
||||
const rect = document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"rect"
|
||||
"rect",
|
||||
);
|
||||
const bbox = parent.getBBox();
|
||||
rect.x.baseVal.value = bbox.x;
|
||||
@@ -65,7 +59,7 @@ const _highlightText = (thisNode, text, className) => {
|
||||
let addItems = [];
|
||||
_highlight(thisNode, addItems, text, className);
|
||||
addItems.forEach((obj) =>
|
||||
obj.parent.insertAdjacentElement("beforebegin", obj.target)
|
||||
obj.parent.insertAdjacentElement("beforebegin", obj.target),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -73,25 +67,27 @@ const _highlightText = (thisNode, text, className) => {
|
||||
* Small JavaScript module for the documentation.
|
||||
*/
|
||||
const SphinxHighlight = {
|
||||
|
||||
/**
|
||||
* highlight the search words provided in localstorage in the text
|
||||
*/
|
||||
highlightSearchWords: () => {
|
||||
if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
|
||||
if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
|
||||
|
||||
// get and clear terms from localstorage
|
||||
const url = new URL(window.location);
|
||||
const highlight =
|
||||
localStorage.getItem("sphinx_highlight_terms")
|
||||
|| url.searchParams.get("highlight")
|
||||
|| "";
|
||||
localStorage.removeItem("sphinx_highlight_terms")
|
||||
localStorage.getItem("sphinx_highlight_terms")
|
||||
|| url.searchParams.get("highlight")
|
||||
|| "";
|
||||
localStorage.removeItem("sphinx_highlight_terms");
|
||||
url.searchParams.delete("highlight");
|
||||
window.history.replaceState({}, "", url);
|
||||
|
||||
// get individual terms from highlight string
|
||||
const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
|
||||
const terms = highlight
|
||||
.toLowerCase()
|
||||
.split(/\s+/)
|
||||
.filter((x) => x);
|
||||
if (terms.length === 0) return; // nothing to do
|
||||
|
||||
// There should never be more than one element matching "div.body"
|
||||
@@ -107,11 +103,11 @@ const SphinxHighlight = {
|
||||
document
|
||||
.createRange()
|
||||
.createContextualFragment(
|
||||
'<p class="highlight-link">' +
|
||||
'<a href="javascript:SphinxHighlight.hideSearchWords()">' +
|
||||
_("Hide Search Matches") +
|
||||
"</a></p>"
|
||||
)
|
||||
'<p class="highlight-link">'
|
||||
+ '<a href="javascript:SphinxHighlight.hideSearchWords()">'
|
||||
+ _("Hide Search Matches")
|
||||
+ "</a></p>",
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -125,7 +121,7 @@ const SphinxHighlight = {
|
||||
document
|
||||
.querySelectorAll("span.highlighted")
|
||||
.forEach((el) => el.classList.remove("highlighted"));
|
||||
localStorage.removeItem("sphinx_highlight_terms")
|
||||
localStorage.removeItem("sphinx_highlight_terms");
|
||||
},
|
||||
|
||||
initEscapeListener: () => {
|
||||
@@ -134,10 +130,15 @@ const SphinxHighlight = {
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
// bail for input elements
|
||||
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
|
||||
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName))
|
||||
return;
|
||||
// bail with special keys
|
||||
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
|
||||
if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
|
||||
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)
|
||||
return;
|
||||
if (
|
||||
DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
|
||||
&& event.key === "Escape"
|
||||
) {
|
||||
SphinxHighlight.hideSearchWords();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const initialiseThemeExtras = () => {
|
||||
const toc = document.getElementById("toc")
|
||||
toc.style.display = ""
|
||||
const items = toc.getElementsByTagName("ul")[0]
|
||||
items.style.display = "none"
|
||||
toc.getElementsByTagName("h3").addEventListener("click", () => {
|
||||
if (items.style.display !== "none") toc.classList.remove("expandedtoc")
|
||||
else toc.classList.add("expandedtoc");
|
||||
})
|
||||
}
|
||||
if (document.readyState !== "loading") initialiseThemeExtras()
|
||||
else document.addEventListener("DOMContentLoaded", initialiseThemeExtras)
|
||||
const toc = document.getElementById("toc");
|
||||
toc.style.display = "";
|
||||
const items = toc.getElementsByTagName("ul")[0];
|
||||
items.style.display = "none";
|
||||
toc.getElementsByTagName("h3").addEventListener("click", () => {
|
||||
if (items.style.display !== "none") toc.classList.remove("expandedtoc");
|
||||
else toc.classList.add("expandedtoc");
|
||||
});
|
||||
};
|
||||
if (document.readyState !== "loading") initialiseThemeExtras();
|
||||
else document.addEventListener("DOMContentLoaded", initialiseThemeExtras);
|
||||
@@ -1,28 +1,26 @@
|
||||
export default {
|
||||
srcDir: ".",
|
||||
srcFiles: [
|
||||
'sphinx/themes/basic/static/doctools.js',
|
||||
'sphinx/themes/basic/static/searchtools.js',
|
||||
'sphinx/themes/basic/static/sphinx_highlight.js',
|
||||
'tests/js/fixtures/**/*.js',
|
||||
'tests/js/documentation_options.js',
|
||||
'tests/js/language_data.js',
|
||||
"sphinx/themes/basic/static/doctools.js",
|
||||
"sphinx/themes/basic/static/searchtools.js",
|
||||
"sphinx/themes/basic/static/sphinx_highlight.js",
|
||||
"tests/js/fixtures/**/*.js",
|
||||
"tests/js/documentation_options.js",
|
||||
"tests/js/language_data.js",
|
||||
],
|
||||
specDir: "tests/js",
|
||||
specFiles: [
|
||||
'**/*.spec.js',
|
||||
],
|
||||
specFiles: ["**/*.spec.js"],
|
||||
helpers: [],
|
||||
env: {
|
||||
stopSpecOnExpectationFailure: false,
|
||||
stopOnSpecFailure: false,
|
||||
random: true
|
||||
random: true,
|
||||
},
|
||||
|
||||
listenAddress: "127.0.0.1",
|
||||
hostname: "127.0.0.1",
|
||||
|
||||
browser: {
|
||||
name: "headlessFirefox"
|
||||
}
|
||||
name: "headlessFirefox",
|
||||
},
|
||||
};
|
||||
@@ -4,17 +4,15 @@
|
||||
*/
|
||||
|
||||
const stopwords = new Set([]);
|
||||
window.stopwords = stopwords; // Export to global scope
|
||||
|
||||
window.stopwords = stopwords; // Export to global scope
|
||||
|
||||
/* Non-minified versions are copied as separate JavaScript files, if available */
|
||||
|
||||
/**
|
||||
* Dummy stemmer for languages without stemming rules.
|
||||
*/
|
||||
var Stemmer = function() {
|
||||
this.stemWord = function(w) {
|
||||
var Stemmer = function () {
|
||||
this.stemWord = function (w) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
@@ -1,10 +1,9 @@
|
||||
describe('Basic html theme search', function() {
|
||||
|
||||
describe("Basic html theme search", function () {
|
||||
function loadFixture(name) {
|
||||
req = new XMLHttpRequest();
|
||||
req.open("GET", `__src__/tests/js/fixtures/${name}`, false);
|
||||
req.send(null);
|
||||
return req.responseText;
|
||||
req = new XMLHttpRequest();
|
||||
req.open("GET", `__src__/tests/js/fixtures/${name}`, false);
|
||||
req.send(null);
|
||||
return req.responseText;
|
||||
}
|
||||
|
||||
function checkRanking(expectedRanking, results) {
|
||||
@@ -16,7 +15,11 @@ describe('Basic html theme search', function() {
|
||||
let [expectedPage, expectedTitle, expectedTarget] = nextExpected;
|
||||
let [page, title, target] = result;
|
||||
|
||||
if (page == expectedPage && title == expectedTitle && target == expectedTarget) {
|
||||
if (
|
||||
page == expectedPage
|
||||
&& title == expectedTitle
|
||||
&& target == expectedTarget
|
||||
) {
|
||||
[nextExpected, ...remainingItems] = remainingItems;
|
||||
}
|
||||
}
|
||||
@@ -25,13 +28,14 @@ describe('Basic html theme search', function() {
|
||||
expect(nextExpected).toEqual(undefined);
|
||||
}
|
||||
|
||||
describe('terms search', function() {
|
||||
|
||||
it('should find "C++" when in index', function() {
|
||||
describe("terms search", function () {
|
||||
it('should find "C++" when in index', function () {
|
||||
eval(loadFixture("cpp/searchindex.js"));
|
||||
|
||||
[_searchQuery, searchterms, excluded, ..._remainingItems] = Search._parseQuery('C++');
|
||||
[_searchQuery, searchterms, excluded, ..._remainingItems] =
|
||||
Search._parseQuery("C++");
|
||||
|
||||
// prettier-ignore
|
||||
hits = [[
|
||||
"index",
|
||||
"<no title>",
|
||||
@@ -44,10 +48,12 @@ describe('Basic html theme search', function() {
|
||||
expect(Search.performTermsSearch(searchterms, excluded)).toEqual(hits);
|
||||
});
|
||||
|
||||
it('should be able to search for multiple terms', function() {
|
||||
it("should be able to search for multiple terms", function () {
|
||||
eval(loadFixture("multiterm/searchindex.js"));
|
||||
|
||||
[_searchQuery, searchterms, excluded, ..._remainingItems] = Search._parseQuery('main page');
|
||||
[_searchQuery, searchterms, excluded, ..._remainingItems] =
|
||||
Search._parseQuery("main page");
|
||||
// prettier-ignore
|
||||
hits = [[
|
||||
'index',
|
||||
'Main Page',
|
||||
@@ -60,11 +66,13 @@ describe('Basic html theme search', function() {
|
||||
expect(Search.performTermsSearch(searchterms, excluded)).toEqual(hits);
|
||||
});
|
||||
|
||||
it('should partially-match "sphinx" when in title index', function() {
|
||||
it('should partially-match "sphinx" when in title index', function () {
|
||||
eval(loadFixture("partial/searchindex.js"));
|
||||
|
||||
[_searchQuery, searchterms, excluded, ..._remainingItems] = Search._parseQuery('sphinx');
|
||||
[_searchQuery, searchterms, excluded, ..._remainingItems] =
|
||||
Search._parseQuery("sphinx");
|
||||
|
||||
// prettier-ignore
|
||||
hits = [[
|
||||
"index",
|
||||
"sphinx_utils module",
|
||||
@@ -77,13 +85,15 @@ describe('Basic html theme search', function() {
|
||||
expect(Search.performTermsSearch(searchterms, excluded)).toEqual(hits);
|
||||
});
|
||||
|
||||
it('should partially-match within "possible" when in term index', function() {
|
||||
it('should partially-match within "possible" when in term index', function () {
|
||||
eval(loadFixture("partial/searchindex.js"));
|
||||
|
||||
[_searchQuery, searchterms, excluded, ..._remainingItems] = Search._parseQuery('ossibl');
|
||||
[_searchQuery, searchterms, excluded, ..._remainingItems] =
|
||||
Search._parseQuery("ossibl");
|
||||
terms = Search._index.terms;
|
||||
titleterms = Search._index.titleterms;
|
||||
|
||||
// prettier-ignore
|
||||
hits = [[
|
||||
"index",
|
||||
"sphinx_utils module",
|
||||
@@ -93,18 +103,19 @@ describe('Basic html theme search', function() {
|
||||
"index.rst",
|
||||
"text"
|
||||
]];
|
||||
expect(Search.performTermsSearch(searchterms, excluded, terms, titleterms)).toEqual(hits);
|
||||
expect(
|
||||
Search.performTermsSearch(searchterms, excluded, terms, titleterms),
|
||||
).toEqual(hits);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('aggregation of search results', function() {
|
||||
|
||||
it('should combine document title and document term matches', function() {
|
||||
describe("aggregation of search results", function () {
|
||||
it("should combine document title and document term matches", function () {
|
||||
eval(loadFixture("multiterm/searchindex.js"));
|
||||
|
||||
searchParameters = Search._parseQuery('main page');
|
||||
searchParameters = Search._parseQuery("main page");
|
||||
|
||||
// prettier-ignore
|
||||
hits = [
|
||||
[
|
||||
'index',
|
||||
@@ -118,11 +129,9 @@ describe('Basic html theme search', function() {
|
||||
];
|
||||
expect(Search._performSearch(...searchParameters)).toEqual(hits);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('search result ranking', function() {
|
||||
|
||||
describe("search result ranking", function () {
|
||||
/*
|
||||
* These tests should not proscribe precise expected ordering of search
|
||||
* results; instead each test case should describe a single relevance rule
|
||||
@@ -137,95 +146,96 @@ describe('Basic html theme search', function() {
|
||||
* [1] - https://github.com/sphinx-doc/sphinx.git/
|
||||
*/
|
||||
|
||||
it('should score a code module match above a page-title match', function() {
|
||||
it("should score a code module match above a page-title match", function () {
|
||||
eval(loadFixture("titles/searchindex.js"));
|
||||
|
||||
// prettier-ignore
|
||||
expectedRanking = [
|
||||
['index', 'relevance', '#module-relevance'], /* py:module documentation */
|
||||
['relevance', 'Relevance', ''], /* main title */
|
||||
];
|
||||
|
||||
searchParameters = Search._parseQuery('relevance');
|
||||
searchParameters = Search._parseQuery("relevance");
|
||||
results = Search._performSearch(...searchParameters);
|
||||
|
||||
checkRanking(expectedRanking, results);
|
||||
});
|
||||
|
||||
it('should score a main-title match above an object member match', function() {
|
||||
it("should score a main-title match above an object member match", function () {
|
||||
eval(loadFixture("titles/searchindex.js"));
|
||||
|
||||
// prettier-ignore
|
||||
expectedRanking = [
|
||||
['relevance', 'Relevance', ''], /* main title */
|
||||
['index', 'relevance.Example.relevance', '#relevance.Example.relevance'], /* py:class attribute */
|
||||
];
|
||||
|
||||
searchParameters = Search._parseQuery('relevance');
|
||||
searchParameters = Search._parseQuery("relevance");
|
||||
results = Search._performSearch(...searchParameters);
|
||||
|
||||
checkRanking(expectedRanking, results);
|
||||
});
|
||||
|
||||
it('should score a title match above a standard index entry match', function() {
|
||||
it("should score a title match above a standard index entry match", function () {
|
||||
eval(loadFixture("titles/searchindex.js"));
|
||||
|
||||
// prettier-ignore
|
||||
expectedRanking = [
|
||||
['relevance', 'Relevance', ''], /* title */
|
||||
['index', 'Main Page', '#index-1'], /* index entry */
|
||||
];
|
||||
|
||||
searchParameters = Search._parseQuery('relevance');
|
||||
searchParameters = Search._parseQuery("relevance");
|
||||
results = Search._performSearch(...searchParameters);
|
||||
|
||||
checkRanking(expectedRanking, results);
|
||||
});
|
||||
|
||||
it('should score a priority index entry match above a title match', function() {
|
||||
it("should score a priority index entry match above a title match", function () {
|
||||
eval(loadFixture("titles/searchindex.js"));
|
||||
|
||||
// prettier-ignore
|
||||
expectedRanking = [
|
||||
['index', 'Main Page', '#index-0'], /* index entry */
|
||||
['index', 'Main Page > Result Scoring', '#result-scoring'], /* title */
|
||||
];
|
||||
|
||||
searchParameters = Search._parseQuery('scoring');
|
||||
searchParameters = Search._parseQuery("scoring");
|
||||
results = Search._performSearch(...searchParameters);
|
||||
|
||||
checkRanking(expectedRanking, results);
|
||||
});
|
||||
|
||||
it('should score a main-title match above a subheading-title match', function() {
|
||||
it("should score a main-title match above a subheading-title match", function () {
|
||||
eval(loadFixture("titles/searchindex.js"));
|
||||
|
||||
// prettier-ignore
|
||||
expectedRanking = [
|
||||
['relevance', 'Relevance', ''], /* main title */
|
||||
['index', 'Main Page > Relevance', '#relevance'], /* subsection heading title */
|
||||
];
|
||||
|
||||
searchParameters = Search._parseQuery('relevance');
|
||||
searchParameters = Search._parseQuery("relevance");
|
||||
results = Search._performSearch(...searchParameters);
|
||||
|
||||
checkRanking(expectedRanking, results);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('can handle edge-case search queries', function() {
|
||||
|
||||
it('does not find the javascript prototype property in unrelated documents', function() {
|
||||
describe("can handle edge-case search queries", function () {
|
||||
it("does not find the javascript prototype property in unrelated documents", function () {
|
||||
eval(loadFixture("partial/searchindex.js"));
|
||||
|
||||
searchParameters = Search._parseQuery('__proto__');
|
||||
searchParameters = Search._parseQuery("__proto__");
|
||||
|
||||
// prettier-ignore
|
||||
hits = [];
|
||||
expect(Search._performSearch(...searchParameters)).toEqual(hits);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("htmlToText", function() {
|
||||
|
||||
describe("htmlToText", function () {
|
||||
const testHTML = `<html>
|
||||
<body>
|
||||
<script src="directory/filename.js"></script>
|
||||
@@ -257,44 +267,47 @@ describe("htmlToText", function() {
|
||||
</html>`;
|
||||
|
||||
it("basic case", () => {
|
||||
expect(Search.htmlToText(testHTML).trim().split(/\s+/)).toEqual([
|
||||
'Getting', 'Started', 'Some', 'text',
|
||||
'Other', 'Section', 'Other', 'text',
|
||||
'Yet', 'Another', 'Section', 'More', 'text'
|
||||
]);
|
||||
expect(Search.htmlToText(testHTML).trim().split(/\s+/)).toEqual(
|
||||
/* prettier-ignore */ [
|
||||
"Getting", "Started", "Some", "text",
|
||||
"Other", "Section", "Other", "text",
|
||||
"Yet", "Another", "Section", "More", "text"
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
it("will start reading from the anchor", () => {
|
||||
expect(Search.htmlToText(testHTML, '#other-section').trim().split(/\s+/)).toEqual(['Other', 'Section', 'Other', 'text']);
|
||||
expect(
|
||||
Search.htmlToText(testHTML, "#other-section").trim().split(/\s+/),
|
||||
).toEqual(["Other", "Section", "Other", "text"]);
|
||||
});
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/sphinx-doc/sphinx/issues/3150
|
||||
describe('splitQuery regression tests', () => {
|
||||
describe("splitQuery regression tests", () => {
|
||||
it("can split English words", () => {
|
||||
const parts = splitQuery(" Hello World ");
|
||||
expect(parts).toEqual(["Hello", "World"]);
|
||||
});
|
||||
|
||||
it('can split English words', () => {
|
||||
const parts = splitQuery(' Hello World ')
|
||||
expect(parts).toEqual(['Hello', 'World'])
|
||||
})
|
||||
it("can split special characters", () => {
|
||||
const parts = splitQuery("Pin-Code");
|
||||
expect(parts).toEqual(["Pin", "Code"]);
|
||||
});
|
||||
|
||||
it('can split special characters', () => {
|
||||
const parts = splitQuery('Pin-Code')
|
||||
expect(parts).toEqual(['Pin', 'Code'])
|
||||
})
|
||||
it("can split Chinese characters", () => {
|
||||
const parts = splitQuery("Hello from 中国 上海");
|
||||
expect(parts).toEqual(["Hello", "from", "中国", "上海"]);
|
||||
});
|
||||
|
||||
it('can split Chinese characters', () => {
|
||||
const parts = splitQuery('Hello from 中国 上海')
|
||||
expect(parts).toEqual(['Hello', 'from', '中国', '上海'])
|
||||
})
|
||||
it("can split Emoji (surrogate pair) characters. It should keep emojis.", () => {
|
||||
const parts = splitQuery("😁😁");
|
||||
expect(parts).toEqual(["😁😁"]);
|
||||
});
|
||||
|
||||
it('can split Emoji (surrogate pair) characters. It should keep emojis.', () => {
|
||||
const parts = splitQuery('😁😁')
|
||||
expect(parts).toEqual(['😁😁'])
|
||||
})
|
||||
|
||||
it('can split umlauts. It should keep umlauts.', () => {
|
||||
const parts = splitQuery('Löschen Prüfung Abändern ærlig spørsmål')
|
||||
expect(parts).toEqual(['Löschen', 'Prüfung', 'Abändern', 'ærlig', 'spørsmål'])
|
||||
})
|
||||
|
||||
})
|
||||
it("can split umlauts. It should keep umlauts.", () => {
|
||||
const parts = splitQuery("Löschen Prüfung Abändern ærlig spørsmål");
|
||||
// prettier-ignore
|
||||
expect(parts).toEqual(["Löschen", "Prüfung", "Abändern", "ærlig", "spørsmål"])
|
||||
});
|
||||
});
|
||||
@@ -1,30 +1,33 @@
|
||||
describe('highlightText', function() {
|
||||
describe("highlightText", function () {
|
||||
const cyrillicTerm = "шеллы";
|
||||
const umlautTerm = "gänsefüßchen";
|
||||
|
||||
const cyrillicTerm = 'шеллы';
|
||||
const umlautTerm = 'gänsefüßchen';
|
||||
|
||||
it('should highlight text incl. special characters correctly in HTML', function() {
|
||||
it("should highlight text incl. special characters correctly in HTML", function () {
|
||||
const highlightTestSpan = new DOMParser().parseFromString(
|
||||
'<span>This is the шеллы and Gänsefüßchen test!</span>', 'text/html').body.firstChild
|
||||
_highlightText(highlightTestSpan, cyrillicTerm, 'highlighted');
|
||||
_highlightText(highlightTestSpan, umlautTerm, 'highlighted');
|
||||
"<span>This is the шеллы and Gänsefüßchen test!</span>",
|
||||
"text/html",
|
||||
).body.firstChild;
|
||||
_highlightText(highlightTestSpan, cyrillicTerm, "highlighted");
|
||||
_highlightText(highlightTestSpan, umlautTerm, "highlighted");
|
||||
const expectedHtmlString =
|
||||
'This is the <span class=\"highlighted\">шеллы</span> and ' +
|
||||
'<span class=\"highlighted\">Gänsefüßchen</span> test!';
|
||||
'This is the <span class=\"highlighted\">шеллы</span> and '
|
||||
+ '<span class=\"highlighted\">Gänsefüßchen</span> test!';
|
||||
expect(highlightTestSpan.innerHTML).toEqual(expectedHtmlString);
|
||||
});
|
||||
|
||||
it('should highlight text incl. special characters correctly in SVG', function() {
|
||||
it("should highlight text incl. special characters correctly in SVG", function () {
|
||||
const highlightTestSvg = new DOMParser().parseFromString(
|
||||
'<span id="svg-highlight-test">' +
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" height="50" width="500">' +
|
||||
'<text x="0" y="15">' +
|
||||
'This is the шеллы and Gänsefüßchen test!' +
|
||||
'</text>' +
|
||||
'</svg>' +
|
||||
'</span>', 'text/html').body.firstChild
|
||||
_highlightText(highlightTestSvg, cyrillicTerm, 'highlighted');
|
||||
_highlightText(highlightTestSvg, umlautTerm, 'highlighted');
|
||||
'<span id="svg-highlight-test">'
|
||||
+ '<svg xmlns="http://www.w3.org/2000/svg" height="50" width="500">'
|
||||
+ '<text x="0" y="15">'
|
||||
+ "This is the шеллы and Gänsefüßchen test!"
|
||||
+ "</text>"
|
||||
+ "</svg>"
|
||||
+ "</span>",
|
||||
"text/html",
|
||||
).body.firstChild;
|
||||
_highlightText(highlightTestSvg, cyrillicTerm, "highlighted");
|
||||
_highlightText(highlightTestSvg, umlautTerm, "highlighted");
|
||||
/* Note wild cards and ``toMatch``; allowing for some variability
|
||||
seems to be necessary, even between different FF versions */
|
||||
const expectedSvgString =
|
||||
@@ -32,8 +35,9 @@ describe('highlightText', function() {
|
||||
+ '<rect x=".*" y=".*" width=".*" height=".*" class="highlighted"/>'
|
||||
+ '<rect x=".*" y=".*" width=".*" height=".*" class="highlighted"/>'
|
||||
+ '<text x=".*" y=".*">This is the <tspan>шеллы</tspan> and '
|
||||
+ '<tspan>Gänsefüßchen</tspan> test!</text></svg>';
|
||||
expect(new XMLSerializer().serializeToString(highlightTestSvg.firstChild)).toMatch(new RegExp(expectedSvgString));
|
||||
+ "<tspan>Gänsefüßchen</tspan> test!</text></svg>";
|
||||
expect(
|
||||
new XMLSerializer().serializeToString(highlightTestSvg.firstChild),
|
||||
).toMatch(new RegExp(expectedSvgString));
|
||||
});
|
||||
|
||||
});
|
||||
@@ -84,3 +84,9 @@ dependency_groups =
|
||||
types
|
||||
commands =
|
||||
mypy {posargs}
|
||||
|
||||
[testenv:prettier]
|
||||
description =
|
||||
Run the Prettier JavaScript formatter.
|
||||
commands =
|
||||
npx prettier@3.5 --write "sphinx/themes/**/*.js" "!sphinx/themes/bizstyle/static/css3-mediaqueries*.js" "tests/js/**/*.{js,mjs}" "!tests/js/fixtures/**"
|
||||
Reference in new issue
Block a user