diff --git a/assets/js/components/doc-search.js b/assets/js/components/doc-search.js
new file mode 100644
index 000000000..4722a7393
--- /dev/null
+++ b/assets/js/components/doc-search.js
@@ -0,0 +1,173 @@
+/**
+ * DocSearch component for InfluxData documentation
+ * Handles asynchronous loading and initialization of Algolia DocSearch
+ */
+
+export default function DocSearch({ component }) {
+ // Store configuration from component data attributes
+ const config = {
+ apiKey: component.getAttribute('data-api-key'),
+ appId: component.getAttribute('data-app-id'),
+ indexName: component.getAttribute('data-index-name'),
+ inputSelector: component.getAttribute('data-input-selector'),
+ searchTag: component.getAttribute('data-search-tag'),
+ includeFlux: component.getAttribute('data-include-flux') === 'true',
+ includeResources:
+ component.getAttribute('data-include-resources') === 'true',
+ debug: component.getAttribute('data-debug') === 'true',
+ };
+
+ // Initialize global object to track DocSearch state
+ window.InfluxDocs = window.InfluxDocs || {};
+ window.InfluxDocs.search = {
+ initialized: false,
+ options: config,
+ };
+
+ // Load DocSearch asynchronously
+ function loadDocSearch() {
+ console.log('Loading DocSearch script...');
+ const script = document.createElement('script');
+ script.src =
+ 'https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js';
+ script.async = true;
+ script.onload = initializeDocSearch;
+ document.body.appendChild(script);
+ }
+
+ // Initialize DocSearch after script loads
+ function initializeDocSearch() {
+ console.log('Initializing DocSearch...');
+ const multiVersion = ['influxdb'];
+
+ // Use object-based lookups instead of conditionals for version and product names
+ // These can be replaced with data from productData in the future
+
+ // Version display name mappings
+ const versionDisplayNames = {
+ cloud: 'Cloud (TSM)',
+ core: 'Core',
+ enterprise: 'Enterprise',
+ 'cloud-serverless': 'Cloud Serverless',
+ 'cloud-dedicated': 'Cloud Dedicated',
+ clustered: 'Clustered',
+ explorer: 'Explorer',
+ };
+
+ // Product display name mappings
+ const productDisplayNames = {
+ influxdb: 'InfluxDB',
+ influxdb3: 'InfluxDB 3',
+ explorer: 'InfluxDB 3 Explorer',
+ enterprise_influxdb: 'InfluxDB Enterprise',
+ flux: 'Flux',
+ telegraf: 'Telegraf',
+ chronograf: 'Chronograf',
+ kapacitor: 'Kapacitor',
+ platform: 'InfluxData Platform',
+ resources: 'Additional Resources',
+ };
+
+ // Initialize DocSearch with configuration
+ window.docsearch({
+ apiKey: config.apiKey,
+ appId: config.appId,
+ indexName: config.indexName,
+ inputSelector: config.inputSelector,
+ debug: config.debug,
+ transformData: function (hits) {
+ // Format version using object lookup instead of if-else chain
+ function fmtVersion(version, productKey) {
+ if (version == null) {
+ return '';
+ } else if (versionDisplayNames[version]) {
+ return versionDisplayNames[version];
+ } else if (multiVersion.includes(productKey)) {
+ return version;
+ } else {
+ return '';
+ }
+ }
+
+ hits.map((hit) => {
+ const pathData = new URL(hit.url).pathname
+ .split('/')
+ .filter((n) => n);
+ const product = productDisplayNames[pathData[0]] || pathData[0];
+ const version = fmtVersion(pathData[1], pathData[0]);
+
+ hit.product = product;
+ hit.version = version;
+ hit.hierarchy.lvl0 =
+ hit.hierarchy.lvl0 +
+ ` ${product} ${version}`;
+ hit._highlightResult.hierarchy.lvl0.value =
+ hit._highlightResult.hierarchy.lvl0.value +
+ ` ${product} ${version}`;
+ });
+ return hits;
+ },
+ algoliaOptions: {
+ hitsPerPage: 10,
+ facetFilters: buildFacetFilters(config),
+ },
+ autocompleteOptions: {
+ templates: {
+ header:
+ '
Search all InfluxData content ',
+ empty:
+ '
',
+ },
+ },
+ });
+
+ // Mark DocSearch as initialized
+ window.InfluxDocs.search.initialized = true;
+
+ // Dispatch event for other components to know DocSearch is ready
+ window.dispatchEvent(new CustomEvent('docsearch-initialized'));
+ }
+
+ /**
+ * Helper function to build facet filters based on config
+ * - Uses nested arrays for AND conditions
+ * - Includes space after colon in filter expressions
+ */
+ function buildFacetFilters(config) {
+ if (!config.searchTag) {
+ return ['latest:true'];
+ } else if (config.includeFlux) {
+ // Return a nested array to match original template structure
+ // Note the space after each colon
+ return [
+ [
+ 'searchTag: ' + config.searchTag,
+ 'flux:true',
+ 'resources: ' + config.includeResources,
+ ],
+ ];
+ } else {
+ // Return a nested array to match original template structure
+ // Note the space after each colon
+ return [
+ [
+ 'searchTag: ' + config.searchTag,
+ 'resources: ' + config.includeResources,
+ ],
+ ];
+ }
+ }
+
+ // Load DocSearch when page is idle or after a slight delay
+ if ('requestIdleCallback' in window) {
+ requestIdleCallback(loadDocSearch);
+ } else {
+ setTimeout(loadDocSearch, 500);
+ }
+
+ // Return cleanup function
+ return function cleanup() {
+ // Clean up any event listeners if needed
+ console.log('DocSearch component cleanup');
+ };
+}
diff --git a/assets/js/main.js b/assets/js/main.js
index 3578952dc..126189fff 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -34,6 +34,7 @@ import AskAITrigger from './ask-ai-trigger.js';
import CodePlaceholder from './code-placeholders.js';
import { CustomTimeTrigger } from './custom-timestamps.js';
import Diagram from './components/diagram.js';
+import DocSearch from './components/doc-search.js';
import FeatureCallout from './feature-callouts.js';
import FluxGroupKeysDemo from './flux-group-keys.js';
import FluxInfluxDBVersionsTrigger from './flux-influxdb-versions.js';
@@ -62,6 +63,7 @@ const componentRegistry = {
'code-placeholder': CodePlaceholder,
'custom-time-trigger': CustomTimeTrigger,
'diagram': Diagram,
+ 'doc-search': DocSearch,
'feature-callout': FeatureCallout,
'flux-group-keys-demo': FluxGroupKeysDemo,
'flux-influxdb-versions-trigger': FluxInfluxDBVersionsTrigger,
diff --git a/assets/js/utils/search-interactions.js b/assets/js/utils/search-interactions.js
index 83d217382..d83433667 100644
--- a/assets/js/utils/search-interactions.js
+++ b/assets/js/utils/search-interactions.js
@@ -1,22 +1,97 @@
+/**
+ * Manages search interactions for DocSearch integration
+ * Uses MutationObserver to watch for dropdown creation
+ */
export default function SearchInteractions({ searchInput }) {
const contentWrapper = document.querySelector('.content-wrapper');
- const dropdownMenu = document.querySelector('.ds-dropdown-menu');
-
+ let observer = null;
+ let dropdownObserver = null;
+ let dropdownMenu = null;
+
// Fade content wrapper when focusing on search input
- searchInput.addEventListener('focus', () => {
- // Using CSS transitions instead of jQuery's fadeTo for better performance
+ function handleFocus() {
contentWrapper.style.opacity = '0.35';
contentWrapper.style.transition = 'opacity 300ms';
- });
+ }
// Hide search dropdown when leaving search input
- searchInput.addEventListener('blur', () => {
+ function handleBlur(event) {
+ // Only process blur if not clicking within dropdown
+ const relatedTarget = event.relatedTarget;
+ if (relatedTarget && (
+ relatedTarget.closest('.algolia-autocomplete') ||
+ relatedTarget.closest('.ds-dropdown-menu'))) {
+ return;
+ }
+
contentWrapper.style.opacity = '1';
contentWrapper.style.transition = 'opacity 200ms';
- // Hide dropdown menu
+ // Hide dropdown if it exists
if (dropdownMenu) {
dropdownMenu.style.display = 'none';
}
+ }
+
+ // Add event listeners
+ searchInput.addEventListener('focus', handleFocus);
+ searchInput.addEventListener('blur', handleBlur);
+
+ // Use MutationObserver to detect when dropdown is added to the DOM
+ observer = new MutationObserver((mutations) => {
+ for (const mutation of mutations) {
+ if (mutation.type === 'childList') {
+ const newDropdown = document.querySelector('.ds-dropdown-menu:not([data-monitored])');
+ if (newDropdown) {
+ console.log('DocSearch dropdown detected');
+
+ // Save reference to dropdown
+ dropdownMenu = newDropdown;
+ newDropdown.setAttribute('data-monitored', 'true');
+
+ // Monitor dropdown removal/display changes
+ dropdownObserver = new MutationObserver((dropdownMutations) => {
+ for (const dropdownMutation of dropdownMutations) {
+ if (dropdownMutation.type === 'attributes' &&
+ dropdownMutation.attributeName === 'style') {
+ console.log('Dropdown style changed:', dropdownMenu.style.display);
+ }
+ }
+ });
+
+ // Observe changes to dropdown attributes (like style)
+ dropdownObserver.observe(dropdownMenu, {
+ attributes: true,
+ attributeFilter: ['style']
+ });
+
+ // Add event listeners to keep dropdown open when interacted with
+ dropdownMenu.addEventListener('mousedown', (e) => {
+ // Prevent blur on searchInput when clicking in dropdown
+ e.preventDefault();
+ });
+ }
+ }
+ }
});
+
+ // Start observing the document body for dropdown creation
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true
+ });
+
+ // Return cleanup function
+ return function cleanup() {
+ searchInput.removeEventListener('focus', handleFocus);
+ searchInput.removeEventListener('blur', handleBlur);
+
+ if (observer) {
+ observer.disconnect();
+ }
+
+ if (dropdownObserver) {
+ dropdownObserver.disconnect();
+ }
+ };
}
\ No newline at end of file
diff --git a/layouts/partials/footer/search.html b/layouts/partials/footer/search.html
index c0405957f..f61459af5 100644
--- a/layouts/partials/footer/search.html
+++ b/layouts/partials/footer/search.html
@@ -7,84 +7,15 @@
{{ $includeFlux := and (in $fluxSupported $product) (in $influxdbFluxSupport $version) }}
{{ $includeResources := not (in (slice "cloud-serverless" "cloud-dedicated" "clustered" "core" "enterprise" "explorer") $version) }}
-
-
\ No newline at end of file
+
+