[PHP-WEBMASTER] [web-php] master: Update search to use new pre-combined indexes + local docs support (#1586)

[email protected] (AllenJB via GitHub)
Newsgroups php.webmaster
Message-ID <[email protected]>
Author: AllenJB (AllenJB)
Committer: GitHub (web-flow)
Pusher: derickr
Date: 2025-11-04T17:17:06Z

Commit: https://github.com/php/web-php/commit/658fafec3eefa3f4d10846fc3ca5c887879fa7a1
Raw diff: https://github.com/php/web-php/commit/658fafec3eefa3f4d10846fc3ca5c887879fa7a1.diff

Update search to use new pre-combined indexes + local docs support (#1586)

* Update search.js to use new pre-combined indexes; Support for running search on local docs

* Update search.js to use new pre-combined indexes; Support for running search on local docs

Changed paths:
  M  js/search-index.php
  M  js/search.js


Diff:

diff --git a/js/search-index.php b/js/search-index.php
index 9e76196dff..cfee19d506 100644
--- a/js/search-index.php
+++ b/js/search-index.php
@@ -11,28 +11,9 @@
     header("Location: http://php.net");
 }
 
-/*
-$types = array(
-    "phpdoc:varentry",
-    "refentry",
-    "phpdoc:exceptionref",
-    "phpdoc:classref",
-    "section",
-    "chapter",
-    "book",
-    "reference",
-    "set",
-    "appendix",
-    "article",
-);
- */
+$combinedIndex = $_SERVER["DOCUMENT_ROOT"] . "/manual/$lang/search-combined.json";
+$tsstring = gmdate("D, d M Y H:i:s ", filemtime($combinedIndex)) . "GMT";
 
-$indexfile = $_SERVER["DOCUMENT_ROOT"] . "/manual/$lang/search-index.json";
-$descfile = $_SERVER["DOCUMENT_ROOT"] . "/manual/$lang/search-description.json";
-
-/* {{{ Cache this */
-$time = max(filemtime($indexfile), filemtime($descfile));
-$tsstring = gmdate("D, d M Y H:i:s ", $time) . "GMT";
 if (isset($_SERVER["HTTP_IF_MODIFIED_SINCE"]) &&
     ($_SERVER["HTTP_IF_MODIFIED_SINCE"] == $tsstring)) {
     header("HTTP/1.1 304 Not Modified");
@@ -41,26 +22,4 @@
 
 header("Last-Modified: " . $tsstring);
 header("Content-Type: application/javascript");
-/* }}} */
-
-$s = file_get_contents($indexfile);
-$js = json_decode($s, true);
-
-$index = [];
-foreach ($js as $item) {
-    if ($item[0]) {
-        /* key: ID/filename, 0=>*/
-        $index[$item[1]] = [$item[0], "", $item[2]];
-    }
-}
-
-$s = file_get_contents($descfile);
-$js = json_decode($s, true);
-
-foreach ($js as $k => $item) {
-    if ($item && isset($index[$k])) {
-        $index[$k][1] = $item;
-    }
-}
-
-echo json_encode($index);
+readfile($combinedIndex);
diff --git a/js/search.js b/js/search.js
index 69248837b6..807360ca99 100644
--- a/js/search.js
+++ b/js/search.js
@@ -11,78 +11,21 @@ const initPHPSearch = async (language) => {
     const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
     const CACHE_DAYS = 14;
 
-    /**
-     * Converts the structure from search-index.php into an array of objects,
-     * mapping the index entries to their respective types.
-     *
-     * @param {object} index
-     * @returns {Array}
-     */
-    const processIndex = (index) => {
-        return Object.entries(index)
-            .map(([id, [name, description, tag]]) => {
-                if (!name) return null;
-
-                let type = "General";
-                switch (tag) {
-                    case "phpdoc:varentry":
-                        type = "Variable";
-                        break;
-
-                    case "refentry":
-                        type = "Function";
-                        break;
-
-                    case "phpdoc:exceptionref":
-                        type = "Exception";
-                        break;
-
-                    case "phpdoc:classref":
-                        type = "Class";
-                        break;
-
-                    case "set":
-                    case "book":
-                    case "reference":
-                        type = "Extension";
-                        break;
-                }
-
-                return {
-                    id,
-                    name,
-                    description,
-                    tag,
-                    type,
-                    methodName: name.split("::").pop(),
-                };
-            })
-            .filter(Boolean);
-    };
-
     /**
      * Looks up the search index cached in localStorage.
      *
      * @returns {Array|null}
      */
     const lookupIndexCache = () => {
-        const key = `search-${language}`;
+        const key = `search2-${language}`;
         const cache = window.localStorage.getItem(key);
 
-        if (!cache) {
+        if ((!cache) || (language === 'local')) {
             return null;
         }
 
         const { data, time: cachedDate } = JSON.parse(cache);
 
-        // Invalidate old search cache format (previously an object)
-        // TODO: Remove this check once the new search index (a single array)
-        // has been in use for a while.
-        if (!Array.isArray(data)) {
-            console.log("Invalidating old search cache format");
-            return null;
-        }
-
         const expireDate = cachedDate + CACHE_DAYS * MILLISECONDS_PER_DAY;
 
         if (Date.now() > expireDate) {
@@ -98,23 +41,27 @@ const initPHPSearch = async (language) => {
      * @returns {Promise<Array>} The search index.
      */
     const fetchIndex = async () => {
-        const key = `search-${language}`;
-        const response = await fetch(`/js/search-index.php?lang=${language}`);
-        const data = await response.json();
-        const items = processIndex(data);
-
-        try {
-            localStorage.setItem(
-                key,
-                JSON.stringify({
-                    data: items,
-                    time: Date.now(),
-                }),
-            );
-        } catch (e) {
-            // Local storage might be full, or other error.
-            // Just continue without caching.
-            console.error("Failed to cache search index", e);
+        const key = `search2-${language}`;
+        let items;
+        if (language === 'local') {
+            items = localSearchIndexes;
+        } else {
+            const response = await fetch(`/js/search-index.php?lang=${language}`);
+            items = await response.json();
+
+            try {
+                localStorage.setItem(
+                    key,
+                    JSON.stringify({
+                        data: items,
+                        time: Date.now(),
+                    }),
+                );
+            } catch (e) {
+                // Local storage might be full, or other error.
+                // Just continue without caching.
+                console.error("Failed to cache search index", e);
+            }
         }
 
         return items;
@@ -139,7 +86,7 @@ const initPHPSearch = async (language) => {
         try {
             return await loadIndex();
         } catch (error) {
-            if (language !== "en") {
+            if ((language !== "en") && (language !== "local")) {
                 language = "en";
                 return loadIndexWithFallback();
             }
@@ -200,7 +147,7 @@ const initSearchModal = () => {
     const inputElement = document.getElementById("search-modal__input");
 
     const focusTrapHandler = (event) => {
-        if (event.key != "Tab") {
+        if (event.key !== "Tab") {
             return;
         }
 
@@ -276,19 +223,25 @@ const initSearchModal = () => {
         "navbar__search-button-mobile",
     );
     const searchButton = document.getElementById("navbar__search-button");
+    let buttons = [searchButton];
 
     // Enhance mobile search
-    searchLink.setAttribute("hidden", "true");
-    searchButtonMobile.removeAttribute("hidden");
+    if (searchLink !== null) {
+        searchLink.setAttribute("hidden", "true");
+        searchButtonMobile.removeAttribute("hidden");
+        buttons.push(searchButtonMobile);
+    }
 
     // Enhance desktop search
-    document
-        .querySelector(".navbar__search-form")
-        .setAttribute("hidden", "true");
+    const searchForm = document
+        .querySelector(".navbar__search-form");
+    if (searchForm !== null) {
+        searchForm.setAttribute("hidden", "true");
+    }
     searchButton.removeAttribute("hidden");
 
     // Open when the search button is clicked
-    [searchButton, searchButtonMobile].forEach((button) =>
+    buttons.forEach((button) =>
         button.addEventListener("click", show),
     );
 
@@ -390,7 +343,10 @@ const initSearchUI = ({ searchCallback, language, limit = 30 }) => {
             const icon = ["General", "Extension"].includes(item.type)
                 ? DOCUMENT_ICON
                 : BRACES_ICON;
-            const link = `/manual/${encodeURIComponent(language)}/${encodeURIComponent(item.id)}.php`;
+            let link = `/manual/${encodeURIComponent(language)}/${encodeURIComponent(item.id)}.php`;
+            if (language === 'local') {
+                link = encodeURIComponent(item.id) + '.html';
+            }
 
             const description =
                 item.type !== "General"
@@ -459,7 +415,7 @@ const initSearchUI = ({ searchCallback, language, limit = 30 }) => {
                 if (selectedIndex !== -1) {
                     event.preventDefault();
                     resultsElements[selectedIndex].click();
-                } else {
+                } else if (language !== 'local') {
                     window.location.href = `/search.php?lang=${language}&q=${encodeURIComponent(inputElement.value)}`;
                 }
                 break;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.