@leed-ai/cli 0.0.0-stage → 6.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +57 -0
- package/README.md +125 -2
- package/dist/LICENSE.md +57 -0
- package/dist/acorn-DAgDT1om.js +5087 -0
- package/dist/angular-l0_4PK8N.js +3598 -0
- package/dist/authShared-Dray38Ls.js +1161 -0
- package/dist/babel-CJeRQd5u.js +10038 -0
- package/dist/buildResults-tiq1nugP.js +21180 -0
- package/dist/chunk-1VJ9lAw7.js +42 -0
- package/dist/cliAuth-38JjD6hw.js +15 -0
- package/dist/env-B0bHiXJJ.js +29 -0
- package/dist/errors-CqKamGCu.js +481 -0
- package/dist/estree-E8WG8UHj.js +6930 -0
- package/dist/flow-6xaRIbPQ.js +52255 -0
- package/dist/glimmer-aJPjsfbT.js +7272 -0
- package/dist/graphql-mcgtPEiZ.js +1901 -0
- package/dist/html--eeea3Kl.js +6553 -0
- package/dist/leed.eleventy.mjs +6525 -0
- package/dist/leed.mjs +88952 -0
- package/dist/leed.sources.json +798 -0
- package/dist/leedLogger-Zkada5aO.js +78 -0
- package/dist/markdown-D73jNKwx.js +6836 -0
- package/dist/meriyah-CMp1nDDW.js +8932 -0
- package/dist/package.json +101 -0
- package/dist/postcss-h4fBr2Fn.js +6760 -0
- package/dist/prettier-CwFSKxfN.js +16821 -0
- package/dist/static/customer-site-worker/package.json +9 -0
- package/dist/static/customer-site-worker/src/assetsHydration.ts +55 -0
- package/dist/static/customer-site-worker/src/constants.ts +20 -0
- package/dist/static/customer-site-worker/src/cors.ts +120 -0
- package/dist/static/customer-site-worker/src/index.ts +242 -0
- package/dist/static/customer-site-worker/src/mcp-proxy.ts +98 -0
- package/dist/static/customer-site-worker/src/proxy.ts +92 -0
- package/dist/static/customer-site-worker/src/proxyHeaders.ts +47 -0
- package/dist/static/customer-site-worker/src/tier.ts +40 -0
- package/dist/static/customer-site-worker/src/types.ts +82 -0
- package/dist/static/customer-site-worker/test/functions-mock.worker.js +50 -0
- package/dist/static/customer-site-worker/tsconfig.json +19 -0
- package/dist/static/customer-site-worker/vitest.workers.config.ts +61 -0
- package/dist/static/install/husky/colors.mjs +5 -0
- package/dist/static/install/husky/hooks/commit-msg +1 -0
- package/dist/static/install/husky/hooks/pre-commit +1 -0
- package/dist/static/install/husky/hooks/pre-push +1 -0
- package/dist/static/install/husky/valid-command.mjs +29 -0
- package/dist/static/install/husky/valid-commit-msg.mjs +20 -0
- package/dist/static/install/package.json +10 -0
- package/dist/static/install/packages.txt +4 -0
- package/dist/static/install/tasks.json +111 -0
- package/dist/static/javascript/detectIncognito.js +195 -0
- package/dist/static/javascript/documentation.js +2781 -0
- package/dist/static/javascript/fp/fp.esm.js +2896 -0
- package/dist/static/javascript/fp/fp.js +3067 -0
- package/dist/static/javascript/live-search.js +986 -0
- package/dist/static/javascript/lunr-search.js +843 -0
- package/dist/static/javascript/timeme.js +451 -0
- package/dist/static/javascript/tracker/webmcp.js +67 -0
- package/dist/static/javascript/tracker/whisper.js +1 -0
- package/dist/static/javascript/utilities.js +819 -0
- package/dist/static/javascript/versions.json +74 -0
- package/dist/static/javascript/zoomable.js +488 -0
- package/dist/static/site/_data/eleventyComputed.js +228 -0
- package/dist/static/site/_data/leed-form-data.json +253 -0
- package/dist/static/site/_includes/leed/api/bits/array-type.hbs +24 -0
- package/dist/static/site/_includes/leed/api/bits/badges.hbs +9 -0
- package/dist/static/site/_includes/leed/api/bits/description.hbs +9 -0
- package/dist/static/site/_includes/leed/api/bits/examples.hbs +42 -0
- package/dist/static/site/_includes/leed/api/bits/scopes.hbs +28 -0
- package/dist/static/site/_includes/leed/api/object.hbs +110 -0
- package/dist/static/site/_includes/leed/api/parameters.hbs +11 -0
- package/dist/static/site/_includes/leed/api/property.hbs +153 -0
- package/dist/static/site/_includes/leed/api/request-body.hbs +51 -0
- package/dist/static/site/_includes/leed/api/responses.hbs +89 -0
- package/dist/static/site/_includes/leed/api/security-constraints.hbs +59 -0
- package/dist/static/site/_includes/leed/api/security.hbs +75 -0
- package/dist/static/site/_includes/leed/components/pagination.hbs +45 -0
- package/dist/static/site/_includes/leed/components/scroll-status-indicator-horizontal.hbs +22 -0
- package/dist/static/site/_includes/leed/docs/alpha/api.hbs +6 -0
- package/dist/static/site/_includes/leed/docs/alpha/base.hbs +26 -0
- package/dist/static/site/_includes/leed/docs/alpha/documentation.hbs +7 -0
- package/dist/static/site/_includes/leed/docs/api-article.hbs +75 -0
- package/dist/static/site/_includes/leed/docs/bravo/api.hbs +5 -0
- package/dist/static/site/_includes/leed/docs/bravo/base.hbs +31 -0
- package/dist/static/site/_includes/leed/docs/bravo/documentation.hbs +7 -0
- package/dist/static/site/_includes/leed/docs/charlie/api.hbs +6 -0
- package/dist/static/site/_includes/leed/docs/charlie/base.hbs +27 -0
- package/dist/static/site/_includes/leed/docs/charlie/documentation.hbs +10 -0
- package/dist/static/site/_includes/leed/docs/doc-article.hbs +40 -0
- package/dist/static/site/_includes/leed/docs/docs-footer.hbs +35 -0
- package/dist/static/site/_includes/leed/docs/docs-header.hbs +59 -0
- package/dist/static/site/_includes/leed/docs/document-search-modal.hbs +36 -0
- package/dist/static/site/_includes/leed/docs/document-search.hbs +23 -0
- package/dist/static/site/_includes/leed/docs/footer.hbs +12 -0
- package/dist/static/site/_includes/leed/docs/header/brand.hbs +19 -0
- package/dist/static/site/_includes/leed/docs/header/chrome.hbs +33 -0
- package/dist/static/site/_includes/leed/docs/header/compact.hbs +19 -0
- package/dist/static/site/_includes/leed/docs/header/cta.hbs +15 -0
- package/dist/static/site/_includes/leed/docs/header/nav.hbs +18 -0
- package/dist/static/site/_includes/leed/docs/header/top-search.hbs +15 -0
- package/dist/static/site/_includes/leed/docs/mini-bar.hbs +53 -0
- package/dist/static/site/_includes/leed/docs/pagination.hbs +19 -0
- package/dist/static/site/_includes/leed/docs/shell.hbs +56 -0
- package/dist/static/site/_includes/leed/docs/sidebar.hbs +52 -0
- package/dist/static/site/_includes/leed/docs/social-icons.hbs +81 -0
- package/dist/static/site/_includes/leed/docs/style.hbs +17 -0
- package/dist/static/site/_includes/leed/docs/toc.hbs +7 -0
- package/dist/static/site/_includes/leed/form.hbs +266 -0
- package/dist/static/site/_includes/leed/head.hbs +67 -0
- package/dist/static/site/_includes/leed/menu/builder.hbs +35 -0
- package/dist/static/site/_includes/leed/menu/item.hbs +67 -0
- package/dist/static/site/_includes/leed/menu/link.hbs +59 -0
- package/dist/static/site/_includes/leed/metadata/ogCard.hbs +40 -0
- package/dist/static/site/_includes/leed/metadata/siteMetadata.hbs +24 -0
- package/dist/static/site/_includes/leed/metadata/twitterCard.hbs +34 -0
- package/dist/static/site/_includes/leed/popup-video.hbs +21 -0
- package/dist/static/site/_includes/leed/stub-template.hbs +17 -0
- package/dist/static/site/_includes/leed/unsubscribe-form.hbs +30 -0
- package/dist/static/site/_layouts/leed-documentation.hbs +12 -0
- package/dist/static/site/leed-feed-atom.hbs +42 -0
- package/dist/static/site/leed-feed-json.hbs +56 -0
- package/dist/static/site/leed-feed-rss.hbs +39 -0
- package/dist/static/site/leed-headers.hbs +38 -0
- package/dist/static/site/leed-redirects.hbs +10 -0
- package/dist/static/site/leed-sitemap-pageType.hbs +28 -0
- package/dist/static/site/leed-sitemap.hbs +19 -0
- package/dist/static/site/leed-unsubscribe.hbs +44 -0
- package/dist/static/site/leed-unsubscribed.hbs +40 -0
- package/dist/static/tailwind/components/alerts.css +132 -0
- package/dist/static/tailwind/components/api-methods.css +39 -0
- package/dist/static/tailwind/components/breadcrumbs.css +31 -0
- package/dist/static/tailwind/components/code.css +88 -0
- package/dist/static/tailwind/components/collapsible.css +38 -0
- package/dist/static/tailwind/components/document-search.css +222 -0
- package/dist/static/tailwind/components/form.css +185 -0
- package/dist/static/tailwind/components/kbd.css +7 -0
- package/dist/static/tailwind/components/pagination.css +81 -0
- package/dist/static/tailwind/components/scroll-status-indicator.css +5 -0
- package/dist/static/tailwind/components/tab-groups.css +117 -0
- package/dist/static/tailwind/components/tables.css +0 -0
- package/dist/static/tailwind/components/toc.css +60 -0
- package/dist/static/tailwind/components.css +248 -0
- package/dist/static/tailwind/defaults.css +260 -0
- package/dist/static/tailwind/docs/api.css +701 -0
- package/dist/static/tailwind/docs/components/blockquote.css +5 -0
- package/dist/static/tailwind/docs/components/button.css +11 -0
- package/dist/static/tailwind/docs/components/docs-pagination.css +71 -0
- package/dist/static/tailwind/docs/components/mobile-bar.css +83 -0
- package/dist/static/tailwind/docs/components/navigation.css +279 -0
- package/dist/static/tailwind/docs/components/table.css +54 -0
- package/dist/static/tailwind/docs/components.css +6 -0
- package/dist/static/tailwind/docs/docs.css +427 -0
- package/dist/static/tailwind/docs/layouts/alpha.css +108 -0
- package/dist/static/tailwind/docs/layouts/bravo.css +218 -0
- package/dist/static/tailwind/docs/layouts/charlie.css +105 -0
- package/dist/static/tailwind/docs/layouts.css +3 -0
- package/dist/static/tailwind/docs/themes/amber.css +113 -0
- package/dist/static/tailwind/docs/themes/blue.css +94 -0
- package/dist/static/tailwind/docs/themes/cyan.css +111 -0
- package/dist/static/tailwind/docs/themes/emerald.css +111 -0
- package/dist/static/tailwind/docs/themes/fuchsia.css +111 -0
- package/dist/static/tailwind/docs/themes/green.css +95 -0
- package/dist/static/tailwind/docs/themes/indigo.css +111 -0
- package/dist/static/tailwind/docs/themes/lime.css +111 -0
- package/dist/static/tailwind/docs/themes/orange.css +111 -0
- package/dist/static/tailwind/docs/themes/pink.css +111 -0
- package/dist/static/tailwind/docs/themes/purple.css +111 -0
- package/dist/static/tailwind/docs/themes/red.css +111 -0
- package/dist/static/tailwind/docs/themes/rose.css +111 -0
- package/dist/static/tailwind/docs/themes/sky.css +111 -0
- package/dist/static/tailwind/docs/themes/teal.css +111 -0
- package/dist/static/tailwind/docs/themes/violet.css +111 -0
- package/dist/static/tailwind/docs/themes/yellow.css +111 -0
- package/dist/static/tailwind/docs/themes.css +84 -0
- package/dist/static/tailwind/font-awesome/free-7.3.1/brands.css +2455 -0
- package/dist/static/tailwind/font-awesome/free-7.3.1/fontawesome.css +8997 -0
- package/dist/static/tailwind/font-awesome/free-7.3.1/fonts.css +45 -0
- package/dist/static/tailwind/font-awesome/free-7.3.1/icons.json +2608 -0
- package/dist/static/tailwind/font-awesome/free-7.3.1.css +3 -0
- package/dist/static/tailwind/font-awesome/pro-7.0.0/brands.css +2215 -0
- package/dist/static/tailwind/font-awesome/pro-7.0.0/fontawesome.css +19281 -0
- package/dist/static/tailwind/font-awesome/pro-7.0.0/fonts.css +445 -0
- package/dist/static/tailwind/font-awesome/pro-7.0.0.css +3 -0
- package/dist/static/tailwind/font-awesome/pro-7.3.1/brands.css +2455 -0
- package/dist/static/tailwind/font-awesome/pro-7.3.1/fontawesome.css +20365 -0
- package/dist/static/tailwind/font-awesome/pro-7.3.1/fonts.css +589 -0
- package/dist/static/tailwind/font-awesome/pro-7.3.1.css +3 -0
- package/dist/static/tailwind/fonts/all.css +6 -0
- package/dist/static/tailwind/fonts/config.jsonc +31 -0
- package/dist/static/tailwind/fonts/geist-mono.css +142 -0
- package/dist/static/tailwind/fonts/geist.css +142 -0
- package/dist/static/tailwind/fonts/inter.css +215 -0
- package/dist/static/tailwind/fonts/jetbrains-mono.css +126 -0
- package/dist/static/tailwind/fonts/noto-sans.css +134 -0
- package/dist/static/tailwind/fonts/open-sans.css +89 -0
- package/dist/static/tailwind/highlighters.css +14 -0
- package/dist/static/tailwind/hljs/README.md +53 -0
- package/dist/static/tailwind/hljs/a11y.css +96 -0
- package/dist/static/tailwind/hljs/atom-fruit.css +90 -0
- package/dist/static/tailwind/hljs/atom-one.css +164 -0
- package/dist/static/tailwind/hljs/default.css +164 -0
- package/dist/static/tailwind/hljs/github.css +101 -0
- package/dist/static/tailwind/hljs/google-code.css +112 -0
- package/dist/static/tailwind/hljs/grayscale.css +115 -0
- package/dist/static/tailwind/hljs/gruvbox-hard.css +115 -0
- package/dist/static/tailwind/hljs/gruvbox-medium.css +115 -0
- package/dist/static/tailwind/hljs/gruvbox-soft.css +115 -0
- package/dist/static/tailwind/hljs/papercolor.css +120 -0
- package/dist/static/tailwind/hljs/qt-creator.css +91 -0
- package/dist/static/tailwind/hljs/summerfruit.css +115 -0
- package/dist/static/tailwind/hljs/tomorrow.css +115 -0
- package/dist/static/tailwind/leed.css +170 -0
- package/dist/static/tailwind/site-tokens.reference.css +105 -0
- package/dist/static/tailwind/utils/icons.css +102 -0
- package/dist/static/tailwind/utils/utilities.css +105 -0
- package/dist/static/templates/pages/PAGE.hbs +27 -0
- package/dist/static/templates/pages/PAGE_LAYOUT.hbs +19 -0
- package/dist/static/templates/pages/PAGE_TYPE_INDEX.hbs +89 -0
- package/dist/static/templates/pages/PAGINATED_PAGE_TYPE_ITEM.hbs +52 -0
- package/dist/static/templates/pages/PAGINATED_PAGE_TYPE_LIST.hbs +39 -0
- package/dist/static/templates/pages/PAGINATED_PAGE_TYPE_PAGINATION.hbs +11 -0
- package/dist/static/templates/raw-content/.ci/build.sh +58 -0
- package/dist/static/templates/raw-content/README.md +179 -0
- package/dist/static/templates/raw-content/_gitignore +50 -0
- package/dist/static/templates/raw-content/src/404.hbs +21 -0
- package/dist/static/templates/raw-content/src/_data/autolink.json +1 -0
- package/dist/static/templates/raw-content/src/_data/ctaList.json +1 -0
- package/dist/static/templates/raw-content/src/_data/leedForms.json +1 -0
- package/dist/static/templates/raw-content/src/_data/menu.json +1 -0
- package/dist/static/templates/raw-content/src/_includes/footer.hbs +18 -0
- package/dist/static/templates/raw-content/src/_includes/header-includes.hbs +25 -0
- package/dist/static/templates/raw-content/src/_includes/header.hbs +5 -0
- package/dist/static/templates/raw-content/src/_includes/site-template.hbs +23 -0
- package/dist/static/templates/raw-content/src/_includes/unsubscribe-DISABLED.hbs +17 -0
- package/dist/static/templates/raw-content/src/_includes/unsubscribed-DISABLED.hbs +13 -0
- package/dist/static/templates/raw-content/src/_layouts/empty.hbs +2 -0
- package/dist/static/templates/raw-content/src/index.hbs +21 -0
- package/dist/static/templates/raw-content/src/robots.hbs +9 -0
- package/dist/static/templates/raw-content/tailwind/site/base.css +44 -0
- package/dist/static/templates/raw-content/tailwind/site.config.css +3 -0
- package/dist/static/templates/sandbox/_gitignore +50 -0
- package/dist/static/templates/sandbox/identity.json +1 -0
- package/dist/static/templates/sandbox/src/404.hbs +21 -0
- package/dist/static/templates/sandbox/src/_data/autolink.json +1 -0
- package/dist/static/templates/sandbox/src/_data/ctaList.json +1 -0
- package/dist/static/templates/sandbox/src/_data/labelList.json +9 -0
- package/dist/static/templates/sandbox/src/_data/leedForms.json +20 -0
- package/dist/static/templates/sandbox/src/_data/menu.json +19 -0
- package/dist/static/templates/sandbox/src/_data/pageTypeList.json +14 -0
- package/dist/static/templates/sandbox/src/_data/searchIndexList.json +1 -0
- package/dist/static/templates/sandbox/src/_data/userList.json +11 -0
- package/dist/static/templates/sandbox/src/_includes/footer.hbs +18 -0
- package/dist/static/templates/sandbox/src/_includes/header-includes.hbs +25 -0
- package/dist/static/templates/sandbox/src/_includes/header.hbs +5 -0
- package/dist/static/templates/sandbox/src/_includes/site-template.hbs +23 -0
- package/dist/static/templates/sandbox/src/_layouts/empty.hbs +2 -0
- package/dist/static/templates/sandbox/src/index.hbs +20 -0
- package/dist/static/templates/sandbox/src/robots.hbs +9 -0
- package/dist/static/templates/sandbox/src/src.11tydata.json +39 -0
- package/dist/static/templates/sandbox/tailwind/site/base.css +44 -0
- package/dist/static/templates/sandbox/tailwind/site.config.css +3 -0
- package/dist/thrown-sukVkp_1.js +113 -0
- package/dist/typescript-D4T0V_Hn.js +23004 -0
- package/dist/yaml-9wR8GxF_.js +5236 -0
- package/package.json +157 -4
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side search implementation using the generated search index
|
|
3
|
+
*
|
|
4
|
+
* This is an example of how to use the search index generated by the search plugin.
|
|
5
|
+
* Include this script in your site and call initializeSearch() when the page loads.
|
|
6
|
+
*
|
|
7
|
+
* Dependencies:
|
|
8
|
+
* - lunr.js (https://lunrjs.com/)
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* 1. Include lunr.js in your page
|
|
12
|
+
* 2. Include this script
|
|
13
|
+
* 3. Call initializeSearch() when DOM is ready
|
|
14
|
+
* 4. Use performSearch(query) to search
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
let searchIndex = null;
|
|
18
|
+
let searchDocuments = [];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Initialize the search functionality by loading the search index and documents
|
|
22
|
+
*/
|
|
23
|
+
async function initializeSearch() {
|
|
24
|
+
// Safely check if variables are defined (they may not be if search is not configured)
|
|
25
|
+
if (
|
|
26
|
+
typeof searchIndexId === "undefined" ||
|
|
27
|
+
typeof searchIndexHash === "undefined" ||
|
|
28
|
+
typeof searchDocumentsHash === "undefined"
|
|
29
|
+
) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const indexPath = `/static/search/${searchIndexId}-index.json?v=${searchIndexHash}`;
|
|
34
|
+
const documentsPath = `/static/search/${searchIndexId}-documents.json?v=${searchDocumentsHash}`;
|
|
35
|
+
|
|
36
|
+
// Guard on the MODAL, not the trigger button. The docs shell always renders the
|
|
37
|
+
// modal, while the button lives in the header — which a site can replace via
|
|
38
|
+
// `_includes/docs-header.hbs`. Guarding on the button meant a custom header
|
|
39
|
+
// without it skipped index loading entirely, so Ctrl+K opened a dead modal.
|
|
40
|
+
const searchElement = document.querySelector("#document-search-modal");
|
|
41
|
+
if (!searchElement) {
|
|
42
|
+
// Silent: a page without a search component is not an error, and this script
|
|
43
|
+
// is copied verbatim to the customer site, so a log here prints in every
|
|
44
|
+
// reader's console on every page. console.warn/error below are kept.
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Load the search index and documents in parallel
|
|
50
|
+
const [indexResponse, documentsResponse] = await Promise.all([fetch(indexPath), fetch(documentsPath)]);
|
|
51
|
+
|
|
52
|
+
if (!indexResponse.ok) {
|
|
53
|
+
throw new Error(`Failed to load search index ${indexPath}: ${indexResponse.status}`);
|
|
54
|
+
}
|
|
55
|
+
if (!documentsResponse.ok) {
|
|
56
|
+
throw new Error(`Failed to load search documents ${documentsPath}: ${documentsResponse.status}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const [indexData, documentsData] = await Promise.all([indexResponse.json(), documentsResponse.json()]);
|
|
60
|
+
|
|
61
|
+
// Load the pre-built lunr index
|
|
62
|
+
searchIndex = lunr.Index.load(indexData.index);
|
|
63
|
+
searchDocuments = documentsData.documents;
|
|
64
|
+
|
|
65
|
+
return true;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error("Failed to initialize search:", error);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Perform a search query
|
|
74
|
+
* @param {string} query - The search query
|
|
75
|
+
* @param {Object} options - Search options
|
|
76
|
+
* @param {number} options.limit - Maximum number of results (default: 10)
|
|
77
|
+
* @param {string[]} options.pageTypes - Filter by page types
|
|
78
|
+
* @returns {Array} Array of search results
|
|
79
|
+
*/
|
|
80
|
+
function performSearch(query, options = {}) {
|
|
81
|
+
if (!searchIndex || !query.trim()) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { limit = 10, pageTypes = [] } = options;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
// Perform the search using lunr
|
|
89
|
+
const results = searchIndex.search(query);
|
|
90
|
+
|
|
91
|
+
// Map results to full document data
|
|
92
|
+
let searchResults = results
|
|
93
|
+
.map((result) => {
|
|
94
|
+
const document = searchDocuments.find((doc) => doc.id === result.ref);
|
|
95
|
+
if (!document) return null;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
...document,
|
|
99
|
+
score: result.score,
|
|
100
|
+
matches: result.matchData,
|
|
101
|
+
};
|
|
102
|
+
})
|
|
103
|
+
.filter(Boolean);
|
|
104
|
+
|
|
105
|
+
// Filter by page types if specified
|
|
106
|
+
if (pageTypes.length > 0) {
|
|
107
|
+
searchResults = searchResults.filter((result) => pageTypes.includes(result.pageType));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Limit results
|
|
111
|
+
return searchResults.slice(0, limit);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error("Search error:", error);
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get search suggestions based on partial query
|
|
120
|
+
* @param {string} partialQuery - Partial search query
|
|
121
|
+
* @param {number} limit - Maximum number of suggestions
|
|
122
|
+
* @returns {Array} Array of search suggestions
|
|
123
|
+
*/
|
|
124
|
+
function getSearchSuggestions(partialQuery, limit = 5) {
|
|
125
|
+
if (!searchDocuments || !partialQuery.trim()) {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const query = partialQuery.toLowerCase();
|
|
130
|
+
const suggestions = [];
|
|
131
|
+
|
|
132
|
+
// Simple suggestion algorithm - match titles and excerpts
|
|
133
|
+
for (const doc of searchDocuments) {
|
|
134
|
+
if (suggestions.length >= limit) break;
|
|
135
|
+
|
|
136
|
+
const title = doc.title.toLowerCase();
|
|
137
|
+
const summary = doc.summary.toLowerCase();
|
|
138
|
+
|
|
139
|
+
if (title.includes(query) || summary.includes(query)) {
|
|
140
|
+
suggestions.push({
|
|
141
|
+
title: doc.title,
|
|
142
|
+
url: doc.url,
|
|
143
|
+
summary: doc.summary,
|
|
144
|
+
pageTypeName: doc.pageTypeName,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return suggestions;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Search UI integration for existing modal
|
|
154
|
+
*/
|
|
155
|
+
class SearchUI {
|
|
156
|
+
constructor(options = {}) {
|
|
157
|
+
this.searchInput = null;
|
|
158
|
+
this.searchResults = null;
|
|
159
|
+
this.searchTimeout = null;
|
|
160
|
+
this.saveTimeout = null;
|
|
161
|
+
this.isInitialized = false;
|
|
162
|
+
|
|
163
|
+
// Selection tracking
|
|
164
|
+
this.selectedIndex = -1;
|
|
165
|
+
this.selectableItems = [];
|
|
166
|
+
|
|
167
|
+
// Search history configuration
|
|
168
|
+
this.maxHistoryItems = options.maxHistoryItems || 5;
|
|
169
|
+
this.historyStorageKey = options.historyStorageKey || "searchHistory";
|
|
170
|
+
this.searchHistory = this.loadSearchHistory();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Initialize search UI with existing modal elements
|
|
175
|
+
*/
|
|
176
|
+
initialize() {
|
|
177
|
+
// Find the search input in the existing modal
|
|
178
|
+
this.searchInput = document.querySelector('#document-search-modal input[type="text"]');
|
|
179
|
+
this.searchResults = document.querySelector("#document-search-modal .search-results-container");
|
|
180
|
+
|
|
181
|
+
if (!this.searchInput || !this.searchResults) {
|
|
182
|
+
console.warn("Search modal elements not found");
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.setupEventListeners();
|
|
187
|
+
|
|
188
|
+
// Show recent searches by default
|
|
189
|
+
this.showRecentSearches();
|
|
190
|
+
|
|
191
|
+
// Set up modal open listener to clear input
|
|
192
|
+
this.setupModalOpenListener();
|
|
193
|
+
|
|
194
|
+
this.isInitialized = true;
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Setup listener for when modal opens to clear input
|
|
200
|
+
*/
|
|
201
|
+
setupModalOpenListener() {
|
|
202
|
+
// Listen for clicks on search trigger buttons
|
|
203
|
+
const searchTriggers = document.querySelectorAll("#document-search, #document-search-container");
|
|
204
|
+
searchTriggers.forEach((trigger) => {
|
|
205
|
+
trigger.addEventListener("click", () => {
|
|
206
|
+
// Clear input when modal opens via click and show recent searches
|
|
207
|
+
setTimeout(() => {
|
|
208
|
+
if (this.searchInput) {
|
|
209
|
+
this.searchInput.value = "";
|
|
210
|
+
this.searchInput.focus();
|
|
211
|
+
// Show recent searches when input is cleared
|
|
212
|
+
this.showRecentSearches();
|
|
213
|
+
}
|
|
214
|
+
}, 100);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Setup event listeners for search functionality
|
|
221
|
+
*/
|
|
222
|
+
setupEventListeners() {
|
|
223
|
+
// Handle search input
|
|
224
|
+
this.searchInput.addEventListener("input", (e) => {
|
|
225
|
+
const query = e.target.value.trim();
|
|
226
|
+
this.handleSearchInput(query);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Handle keyboard events
|
|
230
|
+
this.searchInput.addEventListener("keydown", (e) => {
|
|
231
|
+
if (e.key === "Enter") {
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
this.handleEnterKey();
|
|
234
|
+
} else if (e.key === "ArrowDown") {
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
this.navigateSelection(1);
|
|
237
|
+
} else if (e.key === "ArrowUp") {
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
this.navigateSelection(-1);
|
|
240
|
+
} else if (e.key === "Escape") {
|
|
241
|
+
// Let Alpine.js handle the escape key
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Handle result clicks
|
|
247
|
+
this.searchResults.addEventListener("click", (e) => {
|
|
248
|
+
const resultItem = e.target.closest("[data-url]");
|
|
249
|
+
if (resultItem) {
|
|
250
|
+
const url = resultItem.getAttribute("data-url");
|
|
251
|
+
// Save to history before navigating
|
|
252
|
+
this.addToSearchHistory(this.searchInput.value.trim());
|
|
253
|
+
this.navigateToUrl(url);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Handle search history item clicks
|
|
258
|
+
const historyItem = e.target.closest("[data-query]");
|
|
259
|
+
if (historyItem) {
|
|
260
|
+
const query = historyItem.getAttribute("data-query");
|
|
261
|
+
this.searchInput.value = query;
|
|
262
|
+
this.performSearch(query, true); // Save to history when clicking history item
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Handle search input with debouncing
|
|
269
|
+
*/
|
|
270
|
+
handleSearchInput(query) {
|
|
271
|
+
clearTimeout(this.searchTimeout);
|
|
272
|
+
clearTimeout(this.saveTimeout);
|
|
273
|
+
|
|
274
|
+
// Quick search for live results
|
|
275
|
+
this.searchTimeout = setTimeout(() => {
|
|
276
|
+
if (query.length >= 2) {
|
|
277
|
+
this.performSearch(query, false); // Don't save to history on live search
|
|
278
|
+
} else {
|
|
279
|
+
this.showRecentSearches();
|
|
280
|
+
}
|
|
281
|
+
}, 300);
|
|
282
|
+
|
|
283
|
+
// Longer timeout to save to history if user stops typing
|
|
284
|
+
if (query.length >= 2) {
|
|
285
|
+
this.saveTimeout = setTimeout(() => {
|
|
286
|
+
this.addToSearchHistory(query);
|
|
287
|
+
}, 2000); // Save to history after 2 seconds of no typing
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Handle Enter key press
|
|
293
|
+
*/
|
|
294
|
+
handleEnterKey() {
|
|
295
|
+
const query = this.searchInput.value.trim();
|
|
296
|
+
|
|
297
|
+
// If we have a selected item, navigate to it (regardless of input length)
|
|
298
|
+
if (this.selectedIndex >= 0 && this.selectedIndex < this.selectableItems.length) {
|
|
299
|
+
const selectedItem = this.selectableItems[this.selectedIndex];
|
|
300
|
+
const url = selectedItem.getAttribute("data-url");
|
|
301
|
+
const historyQuery = selectedItem.getAttribute("data-query");
|
|
302
|
+
|
|
303
|
+
if (url) {
|
|
304
|
+
this.addToSearchHistory(query);
|
|
305
|
+
this.navigateToUrl(url);
|
|
306
|
+
return;
|
|
307
|
+
} else if (historyQuery) {
|
|
308
|
+
// Handle history item selection
|
|
309
|
+
this.searchInput.value = historyQuery;
|
|
310
|
+
this.performSearch(historyQuery, true);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Only check for minimum query length if no item is selected
|
|
316
|
+
if (query.length < 2) {
|
|
317
|
+
return; // Don't do anything for short queries
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Fallback: check if there are search results
|
|
321
|
+
const firstResult = this.searchResults.querySelector("[data-url]");
|
|
322
|
+
if (firstResult) {
|
|
323
|
+
// Navigate to first result and save to history
|
|
324
|
+
const url = firstResult.getAttribute("data-url");
|
|
325
|
+
this.addToSearchHistory(query);
|
|
326
|
+
this.navigateToUrl(url);
|
|
327
|
+
} else {
|
|
328
|
+
// No results, just save the search to history
|
|
329
|
+
this.addToSearchHistory(query);
|
|
330
|
+
// Optionally show a "no results" message
|
|
331
|
+
this.displayResults([], query);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Navigate selection with arrow keys
|
|
337
|
+
*/
|
|
338
|
+
navigateSelection(direction) {
|
|
339
|
+
if (this.selectableItems.length === 0) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Update selected index
|
|
344
|
+
this.selectedIndex += direction;
|
|
345
|
+
|
|
346
|
+
// Wrap around
|
|
347
|
+
if (this.selectedIndex < 0) {
|
|
348
|
+
this.selectedIndex = this.selectableItems.length - 1;
|
|
349
|
+
} else if (this.selectedIndex >= this.selectableItems.length) {
|
|
350
|
+
this.selectedIndex = 0;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Update visual selection
|
|
354
|
+
this.updateSelection();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Update visual selection highlighting
|
|
359
|
+
*/
|
|
360
|
+
updateSelection() {
|
|
361
|
+
// Remove all selection classes
|
|
362
|
+
this.selectableItems.forEach((item) => {
|
|
363
|
+
item.classList.remove("search-result-selected");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Add selection class to current item
|
|
367
|
+
if (this.selectedIndex >= 0 && this.selectedIndex < this.selectableItems.length) {
|
|
368
|
+
const selectedItem = this.selectableItems[this.selectedIndex];
|
|
369
|
+
selectedItem.classList.add("search-result-selected");
|
|
370
|
+
|
|
371
|
+
// Scroll the selected item into view
|
|
372
|
+
selectedItem.scrollIntoView({
|
|
373
|
+
behavior: "smooth",
|
|
374
|
+
block: "nearest",
|
|
375
|
+
inline: "nearest",
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Reset selection
|
|
382
|
+
*/
|
|
383
|
+
resetSelection() {
|
|
384
|
+
this.selectedIndex = -1;
|
|
385
|
+
this.selectableItems = [];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Navigate to a URL, handling current page case by closing modal
|
|
390
|
+
*/
|
|
391
|
+
navigateToUrl(url) {
|
|
392
|
+
const currentUrl = window.location.pathname + window.location.search + window.location.hash;
|
|
393
|
+
|
|
394
|
+
// If the URL is exactly the same as current page (including hash), close the modal
|
|
395
|
+
if (url === currentUrl || url === window.location.href) {
|
|
396
|
+
this.closeModal();
|
|
397
|
+
} else {
|
|
398
|
+
// Check if it's the same page but with a different hash fragment
|
|
399
|
+
const currentPathAndQuery = window.location.pathname + window.location.search;
|
|
400
|
+
const targetPathAndQuery = url.split("#")[0]; // Remove hash for comparison
|
|
401
|
+
|
|
402
|
+
if (currentPathAndQuery === targetPathAndQuery) {
|
|
403
|
+
// Same page but different hash - navigate to the new hash and close modal
|
|
404
|
+
window.location.href = url;
|
|
405
|
+
// Close modal after navigation
|
|
406
|
+
setTimeout(() => this.closeModal(), 100);
|
|
407
|
+
} else {
|
|
408
|
+
// Different page - navigate normally
|
|
409
|
+
window.location.href = url;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Close the search modal
|
|
416
|
+
*/
|
|
417
|
+
closeModal() {
|
|
418
|
+
const searchModal = document.getElementById("document-search-modal");
|
|
419
|
+
if (searchModal && window.Alpine) {
|
|
420
|
+
// Find the parent Alpine.js component that contains headerSearchShow
|
|
421
|
+
// The headerSearchShow variable is in the header component, not the modal
|
|
422
|
+
let currentElement = searchModal;
|
|
423
|
+
let alpineData = null;
|
|
424
|
+
|
|
425
|
+
// Walk up the DOM tree to find the Alpine.js component with headerSearchShow
|
|
426
|
+
while (currentElement && currentElement !== document.body) {
|
|
427
|
+
alpineData = Alpine.$data(currentElement);
|
|
428
|
+
if (alpineData && typeof alpineData.headerSearchShow !== "undefined") {
|
|
429
|
+
alpineData.headerSearchShow = false;
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
currentElement = currentElement.parentElement;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Fallback: try to find any Alpine.js component with headerSearchShow
|
|
436
|
+
if (!alpineData || typeof alpineData.headerSearchShow === "undefined") {
|
|
437
|
+
const allElements = document.querySelectorAll("[x-data]");
|
|
438
|
+
for (const element of allElements) {
|
|
439
|
+
const data = Alpine.$data(element);
|
|
440
|
+
if (data && typeof data.headerSearchShow !== "undefined") {
|
|
441
|
+
data.headerSearchShow = false;
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Perform search and display results
|
|
451
|
+
*/
|
|
452
|
+
performSearch(query, saveToHistory = false) {
|
|
453
|
+
const results = performSearch(query, { limit: 10 });
|
|
454
|
+
this.displayResults(results, query);
|
|
455
|
+
|
|
456
|
+
// Only add to search history if explicitly requested
|
|
457
|
+
if (saveToHistory) {
|
|
458
|
+
this.addToSearchHistory(query);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Load search history from localStorage
|
|
464
|
+
*/
|
|
465
|
+
loadSearchHistory() {
|
|
466
|
+
try {
|
|
467
|
+
const history = localStorage.getItem(this.historyStorageKey);
|
|
468
|
+
return history ? JSON.parse(history) : [];
|
|
469
|
+
} catch (error) {
|
|
470
|
+
console.warn("Failed to load search history:", error);
|
|
471
|
+
return [];
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Save search history to localStorage
|
|
477
|
+
*/
|
|
478
|
+
saveSearchHistory() {
|
|
479
|
+
try {
|
|
480
|
+
localStorage.setItem(this.historyStorageKey, JSON.stringify(this.searchHistory));
|
|
481
|
+
} catch (error) {
|
|
482
|
+
console.warn("Failed to save search history:", error);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Add a search query to history
|
|
488
|
+
*/
|
|
489
|
+
addToSearchHistory(query) {
|
|
490
|
+
if (!query || query.trim().length < 2) return;
|
|
491
|
+
|
|
492
|
+
const trimmedQuery = query.trim();
|
|
493
|
+
|
|
494
|
+
// Remove if already exists
|
|
495
|
+
this.searchHistory = this.searchHistory.filter((item) => item.query !== trimmedQuery);
|
|
496
|
+
|
|
497
|
+
// Add to beginning
|
|
498
|
+
this.searchHistory.unshift({
|
|
499
|
+
query: trimmedQuery,
|
|
500
|
+
timestamp: Date.now(),
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// Keep only the most recent items
|
|
504
|
+
this.searchHistory = this.searchHistory.slice(0, this.maxHistoryItems);
|
|
505
|
+
|
|
506
|
+
// Save to localStorage
|
|
507
|
+
this.saveSearchHistory();
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Clear search history
|
|
512
|
+
*/
|
|
513
|
+
clearSearchHistory() {
|
|
514
|
+
this.searchHistory = [];
|
|
515
|
+
this.saveSearchHistory();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Display search results
|
|
520
|
+
*/
|
|
521
|
+
displayResults(results, query) {
|
|
522
|
+
// Reset selection when displaying new results
|
|
523
|
+
this.resetSelection();
|
|
524
|
+
|
|
525
|
+
if (results.length === 0) {
|
|
526
|
+
this.searchResults.innerHTML = `
|
|
527
|
+
<div class="search-no-results">
|
|
528
|
+
<div class="search-no-results-title">No results found for "${query}"</div>
|
|
529
|
+
<div class="search-no-results-subtitle">Try different keywords or check your spelling</div>
|
|
530
|
+
</div>
|
|
531
|
+
`;
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const resultsHTML = `
|
|
536
|
+
<div class="search-results-header">Search results (${results.length})</div>
|
|
537
|
+
${results.map((result) => this.createResultHTML(result, query)).join("")}
|
|
538
|
+
`;
|
|
539
|
+
|
|
540
|
+
this.searchResults.innerHTML = resultsHTML;
|
|
541
|
+
|
|
542
|
+
// Set up selectable items for keyboard navigation
|
|
543
|
+
this.selectableItems = Array.from(this.searchResults.querySelectorAll("[data-url]"));
|
|
544
|
+
|
|
545
|
+
// Set first item as selected by default
|
|
546
|
+
if (this.selectableItems.length > 0) {
|
|
547
|
+
this.selectedIndex = 0;
|
|
548
|
+
this.updateSelection();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Create HTML for a single search result
|
|
554
|
+
*/
|
|
555
|
+
createResultHTML(result, query = "") {
|
|
556
|
+
const breadcrumb = this.createBreadcrumb(result);
|
|
557
|
+
const labels = this.createLabels(result);
|
|
558
|
+
|
|
559
|
+
// Highlight matching keywords in title and summary
|
|
560
|
+
const highlightedTitle = this.highlightKeywords(result.title, query);
|
|
561
|
+
const highlightedSummary = this.highlightKeywords(result.content, query);
|
|
562
|
+
|
|
563
|
+
return `
|
|
564
|
+
<div class="searched-item" data-url="${result.url}">
|
|
565
|
+
<i class="search-result-icon"></i>
|
|
566
|
+
<div class="search-result-content">
|
|
567
|
+
<div class="search-result-breadcrumb">
|
|
568
|
+
${breadcrumb}
|
|
569
|
+
${labels}
|
|
570
|
+
</div>
|
|
571
|
+
<div class="search-result-title">${highlightedTitle}</div>
|
|
572
|
+
<div class="search-result-summary">${highlightedSummary}</div>
|
|
573
|
+
</div>
|
|
574
|
+
<i class="search-result-goto-icon"></i>
|
|
575
|
+
</div>
|
|
576
|
+
`;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
createLabels(result) {
|
|
580
|
+
if (!result.labels || result.labels.length === 0) {
|
|
581
|
+
return "";
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return `
|
|
585
|
+
<span class="search-result-labels">${result.labels.map((label) => `<span class="search-label">${label}</span>`).join("")}</span>
|
|
586
|
+
`;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Create breadcrumb navigation for search result
|
|
591
|
+
*/
|
|
592
|
+
createBreadcrumb(result) {
|
|
593
|
+
if (!result.pageTypeName) {
|
|
594
|
+
return "";
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return `
|
|
598
|
+
<div class="search-result-breadcrumb">
|
|
599
|
+
<span>${result.pageTypeName}</span>
|
|
600
|
+
</div>
|
|
601
|
+
`;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Highlight matching keywords in text
|
|
606
|
+
*/
|
|
607
|
+
highlightKeywords(text, query) {
|
|
608
|
+
if (!query || !text) return text;
|
|
609
|
+
|
|
610
|
+
// Split query into individual words and filter out common words
|
|
611
|
+
const queryWords = query
|
|
612
|
+
.toLowerCase()
|
|
613
|
+
.split(/\s+/)
|
|
614
|
+
.filter((word) => word.length > 2) // Only highlight words longer than 2 characters
|
|
615
|
+
.filter(
|
|
616
|
+
(word) =>
|
|
617
|
+
![
|
|
618
|
+
"the",
|
|
619
|
+
"and",
|
|
620
|
+
"for",
|
|
621
|
+
"are",
|
|
622
|
+
"but",
|
|
623
|
+
"not",
|
|
624
|
+
"you",
|
|
625
|
+
"all",
|
|
626
|
+
"can",
|
|
627
|
+
"had",
|
|
628
|
+
"her",
|
|
629
|
+
"was",
|
|
630
|
+
"one",
|
|
631
|
+
"our",
|
|
632
|
+
"out",
|
|
633
|
+
"day",
|
|
634
|
+
"get",
|
|
635
|
+
"has",
|
|
636
|
+
"him",
|
|
637
|
+
"his",
|
|
638
|
+
"how",
|
|
639
|
+
"its",
|
|
640
|
+
"may",
|
|
641
|
+
"new",
|
|
642
|
+
"now",
|
|
643
|
+
"old",
|
|
644
|
+
"see",
|
|
645
|
+
"two",
|
|
646
|
+
"way",
|
|
647
|
+
"who",
|
|
648
|
+
"boy",
|
|
649
|
+
"did",
|
|
650
|
+
"man",
|
|
651
|
+
"oil",
|
|
652
|
+
"sit",
|
|
653
|
+
"try",
|
|
654
|
+
"use",
|
|
655
|
+
].includes(word),
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
if (queryWords.length === 0) {
|
|
659
|
+
return text;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Create regex pattern to match any of the query words
|
|
663
|
+
const pattern = new RegExp(`(${queryWords.map((word) => this.escapeRegExp(word)).join("|")})`, "gi");
|
|
664
|
+
|
|
665
|
+
// Replace matches with highlighted spans
|
|
666
|
+
return text.replace(pattern, '<span class="search-highlight">$1</span>');
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Escape special regex characters
|
|
671
|
+
*/
|
|
672
|
+
escapeRegExp(string) {
|
|
673
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Show recent searches from history
|
|
678
|
+
*/
|
|
679
|
+
showRecentSearches() {
|
|
680
|
+
// Reset selection when showing recent searches
|
|
681
|
+
this.resetSelection();
|
|
682
|
+
|
|
683
|
+
if (this.searchHistory.length === 0) {
|
|
684
|
+
this.searchResults.innerHTML = `
|
|
685
|
+
<div class="search-recent-header">Recent searches</div>
|
|
686
|
+
<div class="search-no-results">
|
|
687
|
+
<div class="search-no-results-subtitle">No recent searches</div>
|
|
688
|
+
</div>
|
|
689
|
+
`;
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const historyHTML = this.searchHistory
|
|
694
|
+
.map((item) => {
|
|
695
|
+
const timeAgo = this.getTimeAgo(item.timestamp);
|
|
696
|
+
return `
|
|
697
|
+
<div class="searched-item search-history-item" data-query="${item.query}">
|
|
698
|
+
<i class="search-history-icon"></i>
|
|
699
|
+
<div class="search-result-content">
|
|
700
|
+
<div class="search-result-title">${item.query}</div>
|
|
701
|
+
<div class="search-result-summary">Searched ${timeAgo}</div>
|
|
702
|
+
</div>
|
|
703
|
+
<i class="search-result-goto-icon"></i>
|
|
704
|
+
</div>
|
|
705
|
+
`;
|
|
706
|
+
})
|
|
707
|
+
.join("");
|
|
708
|
+
|
|
709
|
+
this.searchResults.innerHTML = `
|
|
710
|
+
<div class="search-recent-header">Recent searches</div>
|
|
711
|
+
${historyHTML}
|
|
712
|
+
`;
|
|
713
|
+
|
|
714
|
+
// Set up selectable items for keyboard navigation (history items)
|
|
715
|
+
this.selectableItems = Array.from(this.searchResults.querySelectorAll("[data-query]"));
|
|
716
|
+
|
|
717
|
+
// Set first item as selected by default
|
|
718
|
+
if (this.selectableItems.length > 0) {
|
|
719
|
+
this.selectedIndex = 0;
|
|
720
|
+
this.updateSelection();
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Get human-readable time ago string
|
|
726
|
+
*/
|
|
727
|
+
getTimeAgo(timestamp) {
|
|
728
|
+
const now = Date.now();
|
|
729
|
+
const diff = now - timestamp;
|
|
730
|
+
const minutes = Math.floor(diff / 60000);
|
|
731
|
+
const hours = Math.floor(diff / 3600000);
|
|
732
|
+
const days = Math.floor(diff / 86400000);
|
|
733
|
+
|
|
734
|
+
if (minutes < 1) {
|
|
735
|
+
return "just now";
|
|
736
|
+
}
|
|
737
|
+
if (minutes < 60) {
|
|
738
|
+
return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
|
|
739
|
+
}
|
|
740
|
+
if (hours < 24) {
|
|
741
|
+
return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Global search UI instance with configurable options
|
|
749
|
+
const searchUI = new SearchUI({
|
|
750
|
+
maxHistoryItems: 7, // Number of recent searches to keep
|
|
751
|
+
historyStorageKey: "leedSearchHistory", // localStorage key
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// Export functions for use in other scripts
|
|
755
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
756
|
+
module.exports = {
|
|
757
|
+
initializeSearch,
|
|
758
|
+
performSearch,
|
|
759
|
+
getSearchSuggestions,
|
|
760
|
+
searchUI,
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Auto-initialize search when DOM is ready
|
|
765
|
+
if (typeof document !== "undefined") {
|
|
766
|
+
function initializeSearchUI() {
|
|
767
|
+
// Initialize the search index first
|
|
768
|
+
initializeSearch().then((success) => {
|
|
769
|
+
if (success) {
|
|
770
|
+
// Then initialize the UI
|
|
771
|
+
searchUI.initialize();
|
|
772
|
+
|
|
773
|
+
// Only add global Ctrl+K handler if search initialization was successful
|
|
774
|
+
document.addEventListener(
|
|
775
|
+
"keydown",
|
|
776
|
+
(e) => {
|
|
777
|
+
if (e.ctrlKey && e.key === "k") {
|
|
778
|
+
e.preventDefault();
|
|
779
|
+
e.stopPropagation();
|
|
780
|
+
|
|
781
|
+
// Trigger the search modal if it exists
|
|
782
|
+
const searchModal = document.getElementById("document-search-modal");
|
|
783
|
+
if (searchModal && window.Alpine) {
|
|
784
|
+
// Find the parent Alpine.js component that contains headerSearchShow
|
|
785
|
+
let currentElement = searchModal;
|
|
786
|
+
let alpineData = null;
|
|
787
|
+
|
|
788
|
+
// Walk up the DOM tree to find the Alpine.js component with headerSearchShow
|
|
789
|
+
while (currentElement && currentElement !== document.body) {
|
|
790
|
+
alpineData = Alpine.$data(currentElement);
|
|
791
|
+
if (alpineData && typeof alpineData.headerSearchShow !== "undefined") {
|
|
792
|
+
alpineData.headerSearchShow = true;
|
|
793
|
+
// Clear the search input when opening the modal and show recent searches
|
|
794
|
+
setTimeout(() => {
|
|
795
|
+
const searchInput = document.querySelector('#document-search-modal input[type="text"]');
|
|
796
|
+
if (searchInput) {
|
|
797
|
+
searchInput.value = "";
|
|
798
|
+
searchInput.focus();
|
|
799
|
+
// Show recent searches when input is cleared
|
|
800
|
+
searchUI.showRecentSearches();
|
|
801
|
+
}
|
|
802
|
+
}, 50);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
currentElement = currentElement.parentElement;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Fallback: try to find any Alpine.js component with headerSearchShow
|
|
809
|
+
if (!alpineData || typeof alpineData.headerSearchShow === "undefined") {
|
|
810
|
+
const allElements = document.querySelectorAll("[x-data]");
|
|
811
|
+
for (const element of allElements) {
|
|
812
|
+
const data = Alpine.$data(element);
|
|
813
|
+
if (data && typeof data.headerSearchShow !== "undefined") {
|
|
814
|
+
data.headerSearchShow = true;
|
|
815
|
+
// Clear the search input when opening the modal and show recent searches
|
|
816
|
+
setTimeout(() => {
|
|
817
|
+
const searchInput = document.querySelector('#document-search-modal input[type="text"]');
|
|
818
|
+
if (searchInput) {
|
|
819
|
+
searchInput.value = "";
|
|
820
|
+
searchInput.focus();
|
|
821
|
+
// Show recent searches when input is cleared
|
|
822
|
+
searchUI.showRecentSearches();
|
|
823
|
+
}
|
|
824
|
+
}, 50);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
},
|
|
832
|
+
true,
|
|
833
|
+
); // Use capture phase to intercept before other handlers
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (document.readyState === "loading") {
|
|
839
|
+
document.addEventListener("DOMContentLoaded", initializeSearchUI);
|
|
840
|
+
} else {
|
|
841
|
+
initializeSearchUI();
|
|
842
|
+
}
|
|
843
|
+
}
|