gh-115317: Rewrite changelog filter to use vanilla JavaScript
diff --git a/.editorconfig b/.editorconfig
index 0169eed951cd3f..a6187d64f3ce46 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,6 +1,6 @@
 root = true
 
-[*.{py,c,cpp,h,rst,md,yml}]
+[*.{py,c,cpp,h,js,rst,md,yml}]
 trim_trailing_whitespace = true
 insert_final_newline = true
 indent_style = space
@@ -11,5 +11,5 @@ indent_size = 4
 [*.rst]
 indent_size = 3
 
-[*.yml]
+[*.{js,yml}]
 indent_size = 2
diff --git a/Doc/tools/static/changelog_search.js b/Doc/tools/static/changelog_search.js
index c881a9bd4c84a7..0a77c0d71ae937 100644
--- a/Doc/tools/static/changelog_search.js
+++ b/Doc/tools/static/changelog_search.js
@@ -1,53 +1,59 @@
-$(document).ready(function() {
-    // add the search form and bind the events
-    $('h1').after([
-      '<p>Filter entries by content:',
-      '<input type="text" value="" id="searchbox" style="width: 50%">',
-      '<input type="submit" id="searchbox-submit" value="Filter"></p>'
-    ].join('\n'));
+document.addEventListener("DOMContentLoaded", function () {
+  // add the search form and bind the events
+  document
+    .querySelector("h1")
+    .insertAdjacentHTML(
+      "afterend",
+      [
+        "<p>Filter entries by content:",
+        '<input type="text" value="" id="searchbox" style="width: 50%">',
+        '<input type="submit" id="searchbox-submit" value="Filter"></p>',
+      ].join("\n"),
+    );
 
-    function dofilter() {
-        try {
-            var query = new RegExp($('#searchbox').val(), 'i');
+  function doFilter() {
+    let query;
+    try {
+      query = new RegExp(document.querySelector("#searchbox").value, "i");
+    } catch (e) {
+      return; // not a valid regex (yet)
+    }
+    // find headers for the versions (What's new in Python X.Y.Z?)
+    const h2s = document.querySelectorAll("#changelog h2");
+    for (const h2 of h2s) {
+      let sections_found = 0;
+      // find headers for the sections (Core, Library, etc.)
+      const h3s = h2.parentNode.querySelectorAll("h3");
+      for (const h3 of h3s) {
+        let entries_found = 0;
+        // find all the entries
+        const lis = h3.parentNode.querySelectorAll("li");
+        for (let li of lis) {
+          // check if the query matches the entry
+          if (query.test(li.textContent)) {
+            li.style.display = "block";
+            entries_found++;
+          } else {
+            li.style.display = "none";
+          }
         }
-        catch (e) {
-            return; // not a valid regex (yet)
+        // if there are entries, show the section, otherwise hide it
+        if (entries_found > 0) {
+          h3.parentNode.style.display = "block";
+          sections_found++;
+        } else {
+          h3.parentNode.style.display = "none";
         }
-        // find headers for the versions (What's new in Python X.Y.Z?)
-        $('#changelog h2').each(function(index1, h2) {
-            var h2_parent = $(h2).parent();
-            var sections_found = 0;
-            // find headers for the sections (Core, Library, etc.)
-            h2_parent.find('h3').each(function(index2, h3) {
-                var h3_parent = $(h3).parent();
-                var entries_found = 0;
-                // find all the entries
-                h3_parent.find('li').each(function(index3, li) {
-                    var li = $(li);
-                    // check if the query matches the entry
-                    if (query.test(li.text())) {
-                        li.show();
-                        entries_found++;
-                    }
-                    else {
-                        li.hide();
-                    }
-                });
-                // if there are entries, show the section, otherwise hide it
-                if (entries_found > 0) {
-                    h3_parent.show();
-                    sections_found++;
-                }
-                else {
-                    h3_parent.hide();
-                }
-            });
-            if (sections_found > 0)
-                h2_parent.show();
-            else
-                h2_parent.hide();
-        });
+      }
+      if (sections_found > 0) {
+        h2.parentNode.style.display = "block";
+      } else {
+        h2.parentNode.style.display = "none";
+      }
     }
-    $('#searchbox').keyup(dofilter);
-    $('#searchbox-submit').click(dofilter);
+  }
+  document.querySelector("#searchbox").addEventListener("keyup", doFilter);
+  document
+    .querySelector("#searchbox-submit")
+    .addEventListener("click", doFilter);
 });

<!--
Thanks for your contribution!
Please read this comment in its entirety. It's quite important.

# Pull Request title

It should be in the following format:

```
gh-NNNNN: Summary of the changes made
```

Where: gh-NNNNN refers to the GitHub issue number.

Most PRs will require an issue number. Trivial changes, like fixing a typo, do not need an issue.

# Backport Pull Request title

If this is a backport PR (PR made against branches other than `main`),
please ensure that the PR title is in the following format:

```
[X.Y] <title from the original PR> (GH-NNNN)
```

Where: [X.Y] is the branch name, e.g. [3.6].

GH-NNNN refers to the PR number from `main`.

-->

Sphinx removed the jQuery library to improve performance, but the changelog filter was still using it. This PR rewrites it to use basic JavaScript. 


<!-- gh-issue-number: gh-115317 -->
* Issue: gh-115317
<!-- /gh-issue-number -->

# Demo

https://cpython-previews--115324.org.readthedocs.build/en/115324/whatsnew/changelog.html

