Skip to content

Commit 5477783

Browse files
committed
New Search API Integration
1 parent 27bae66 commit 5477783

File tree

6 files changed

+314
-210
lines changed

6 files changed

+314
-210
lines changed

docs/.vuepress/client.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ export default defineClientConfig({
7878
appId: "R7FCMJM4P7"
7979
},
8080

81-
MAX_ALGOLIA_VISIBLE_RESULT: 20,
82-
MAX_ALGOLIA_VISIBLE_ROWS: 15,
83-
MAX_ALGOLIA_HITS_PER_PAGE: 20,
81+
MAX_VISIBLE_RESULT: 12,
82+
MAX_VISIBLE_ROWS: 12,
83+
MAX_HITS_PER_PAGE: 12,
8484
})
8585
}
8686
})

docs/.vuepress/theme/drawer/Drawer.vue

+18-41
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,15 @@
1111
<p @click="onCloseDrawer" class="drawer-cross__text">close</p>
1212
</div>
1313
</div>
14-
<DrawerTabs v-model="selectedTabIndex" :data="tabs"/>
1514
<main>
16-
<div class="drawer-main">
17-
<div class="drawer-main__wrapper">
18-
<div class="drawer-main__breadcrumb">
19-
<p v-if="drawerArticleResult.length" class="drawer-main__breadcrumb__text">Home
20-
<img :src="withBase('/arrows/arrow-right-breadcrumb.svg')" alt="breadcrumb icon"/>
21-
Documentation
22-
</p>
23-
</div>
24-
<DrawerSearchResult :modelValue="modelValue" :data="drawerArticleResult"/>
25-
</div>
26-
</div>
15+
<div class="drawer-main">
16+
<div class="drawer-main__wrapper">
17+
<div class="drawer-main__breadcrumb">
18+
<!-- Optional breadcrumb can stay here -->
19+
</div>
20+
<DrawerSearchResult :modelValue="modelValue" :data="drawerArticleResult"/>
21+
</div>
22+
</div>
2723
<Footer v-if="isOpenDrawer && isMobileWidth" class="drawer-footer__mobile"/>
2824
</main>
2925
</div>
@@ -32,10 +28,9 @@
3228
</template>
3329

3430
<script setup>
35-
import {withBase} from "@vuepress/client";
31+
import { withBase } from "@vuepress/client";
3632
import Footer from "../footer/Footer.vue";
37-
import {computed, ref, watch} from "vue";
38-
import DrawerTabs from "./DrawerTabs.vue";
33+
import { computed, ref, watch } from "vue";
3934
import DrawerSearchResult from "./DrawerSearchResult.vue";
4035
4136
const props = defineProps({
@@ -44,7 +39,7 @@ const props = defineProps({
4439
required: true,
4540
default: false
4641
},
47-
isMobileWidth:{
42+
isMobileWidth: {
4843
type: Boolean,
4944
required: true,
5045
default: false
@@ -59,39 +54,21 @@ const props = defineProps({
5954
required: true,
6055
default: () => []
6156
}
62-
})
63-
64-
65-
const emit = defineEmits(['closeDrawer', 'update:modelValue'])
66-
67-
const selectedTabIndex = ref(0);
68-
69-
const tabs = computed(() => {
70-
const uniqueTitles = props.homeLayoutSearchResult.reduce((unique, result) => {
71-
const title = result.hierarchy?.lvl0;
72-
unique[title] = unique[title] || { title, numberResults: 0 };
73-
unique[title].numberResults++;
74-
return unique;
75-
}, {});
76-
return Object.values(uniqueTitles);
7757
});
7858
59+
const emit = defineEmits(['closeDrawer', 'update:modelValue']);
60+
7961
const drawerArticleResult = computed(() => {
80-
if (selectedTabIndex.value === -1) {
81-
return props.homeLayoutSearchResult || [];
82-
}
83-
const selectedTab = tabs.value[selectedTabIndex.value];
84-
return props.homeLayoutSearchResult.filter(result => result.hierarchy.lvl0 === selectedTab?.title);
85-
})
62+
return props.homeLayoutSearchResult; // Now directly returning all results since there are no tabs
63+
});
8664
8765
const onCloseDrawer = () => {
88-
emit('closeDrawer')
89-
selectedTabIndex.value = 0
66+
emit('closeDrawer');
9067
}
9168
9269
watch(() => props.isOpenDrawer, () => {
93-
document.body.classList.toggle('disable-scroll', props.isOpenDrawer)
94-
})
70+
document.body.classList.toggle('disable-scroll', props.isOpenDrawer);
71+
});
9572
</script>
9673

9774
<style lang="stylus">

docs/.vuepress/theme/drawer/DrawerSearch.vue

+124-40
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
11
<template>
2-
<form id="search-form" class="drawer-header__input">
3-
<input :value="modelValue"
2+
<form id="search-form" class="drawer-header__input" @submit.prevent="performSearch">
3+
<input type="text"
4+
:value="modelValue"
45
@input="$emit('update:modelValue', $event.target.value)"
56
id="algolia-search-input"
67
:placeholder="placeholder"
78
:class="activeSearchClass"
8-
@keypress.enter.prevent="$emit('openDrawer')"
9+
maxlength="100"
910
/>
1011
<div :class="activeSearchIconClass">
11-
<img @click="$emit('openDrawer')" alt="search icon" :src="withBase(activeSearchIcon)"/>
12+
<img v-if="!loading" @click="performSearch" alt="search icon" :src="withBase(activeSearchIcon)"/>
13+
<div v-if="loading" class="spinner"></div>
1214
</div>
1315
</form>
1416
</template>
1517

1618
<script setup>
1719
import {usePageFrontmatter, withBase} from "@vuepress/client";
18-
import {computed, inject, watch} from "vue";
19-
const { MAX_ALGOLIA_HITS_PER_PAGE } = inject('themeConfig')
20-
20+
import {computed, inject, ref, watch} from "vue";
2121
22+
const { MAX_HITS_PER_PAGE } = inject('themeConfig')
2223
const {headerDefaultSearchIcon, headerSearchIcon, headerSearchPlaceholder} = inject('themeConfig')
24+
2325
const props = defineProps({
2426
options: {
2527
type: [Object, Array],
@@ -37,9 +39,10 @@ const props = defineProps({
3739
type: Boolean,
3840
}
3941
});
40-
const emit = defineEmits(["openDrawer", 'update:modelValue', 'result'])
4142
43+
const emit = defineEmits(["openDrawer", 'update:modelValue', 'result'])
4244
const frontmatter = usePageFrontmatter()
45+
4346
const isGlobalLayout = computed(() => {
4447
return frontmatter.value.layout === 'HomeLayout'
4548
})
@@ -61,43 +64,111 @@ const placeholderDesktop = computed(() => {
6164
})
6265
6366
const placeholder = computed(() => {
64-
return props.isMobileWidth ? 'Search accross all Imunify Security support' : placeholderDesktop.value
67+
return props.isMobileWidth ? 'Search accross all Tuxcare Docs' : placeholderDesktop.value
6568
})
6669
67-
68-
const initialize = async (userOptions) => {
69-
if( typeof window === 'undefined' ) return
70-
const [docsearchModule] = await Promise.all([
71-
import(/* webpackChunkName: "docsearch" */ "docsearch.js/dist/cdn/docsearch.min.js"),
72-
import(/* webpackChunkName: "docsearch" */ "docsearch.js/dist/cdn/docsearch.min.css"),
73-
]);
74-
const docsearch = docsearchModule.default;
75-
docsearch(
76-
Object.assign({}, userOptions, {
77-
inputSelector: "#algolia-search-input",
78-
algoliaOptions: {
79-
hitsPerPage: MAX_ALGOLIA_HITS_PER_PAGE,
80-
},
81-
handleSelected: () => {
82-
emit('openDrawer')
70+
function parseDocs(api_response) {
71+
return api_response.tuxcare_docs.map((doc) => {
72+
const titleParts = doc.title.split("->").map((part) => part.trim());
73+
74+
const hierarchy = {
75+
lvl0: titleParts[0] || null,
76+
lvl1: titleParts[1] || null,
77+
lvl2: titleParts[2] || null,
78+
lvl3: titleParts[3] || null,
79+
lvl4: titleParts[4] || null,
80+
lvl5: null,
81+
lvl6: null,
82+
};
83+
84+
const anchor = doc.url.split("#")[1] || "";
85+
86+
const objectID = doc.id;
87+
88+
return {
89+
anchor,
90+
content: null,
91+
hierarchy,
92+
url: doc.url,
93+
title: doc.title,
94+
preview: doc.preview,
95+
category: doc.category,
96+
section: doc.section,
97+
objectID,
98+
_highlightResult: {
99+
hierarchy: {
100+
lvl0: {
101+
value: hierarchy.lvl0 || "",
102+
matchLevel: "none",
103+
matchedWords: [],
104+
},
105+
lvl1: {
106+
value: hierarchy.lvl1 || "",
107+
matchLevel: "full",
108+
fullyHighlighted: false,
109+
matchedWords: [hierarchy.lvl1?.toLowerCase()],
110+
},
83111
},
84-
transformData: (hits) => {
85-
emit('result', hits)
86-
},
87-
})
88-
);
89-
};
90-
watch(
91-
() => props.options,
92-
async (newValue) => {
93-
await initialize(newValue);
94-
}, {
95-
immediate: true
112+
hierarchy_camel: [
113+
{
114+
lvl0: {
115+
value: hierarchy.lvl0 || "",
116+
matchLevel: "none",
117+
matchedWords: [],
118+
},
119+
lvl1: {
120+
value: `<span class="algolia-docsearch-suggestion--highlight">${hierarchy.lvl1 || ""}</span>`,
121+
matchLevel: "full",
122+
fullyHighlighted: false,
123+
matchedWords: [hierarchy.lvl1?.toLowerCase()],
124+
},
125+
},
126+
],
127+
},
128+
};
129+
});
130+
}
131+
132+
async function queryGlobalSearch(query, n_results=10) {
133+
const baseUrl = 'https://global-search.cl-edu.com/search';
134+
let urlEncodedQuery = encodeURIComponent(query);
135+
let url = `${baseUrl}?query=${urlEncodedQuery}&collections=tuxcare_docs&n_results=${n_results}&source=tuxcare_docs`;
136+
try {
137+
const response = await fetch(url);
138+
if (!response.ok) {
139+
throw new Error(`HTTP error! status: ${response.status}`);
96140
}
141+
const data = await response.json();
142+
return data;
143+
} catch (error) {
144+
console.error('Error querying global search:', error);
145+
return null;
146+
}
147+
}
148+
149+
const loading = ref(false); // Reactive variable for loading state
150+
151+
const performSearch = async () => {
152+
loading.value = true; // Set loading to true when search starts
153+
const data = await queryGlobalSearch(props.modelValue, MAX_HITS_PER_PAGE);
154+
loading.value = false; // Set loading to false when search finishes
155+
if (data) {
156+
const hits = parseDocs(data);
157+
emit('result', hits);
158+
emit('openDrawer');
159+
}
160+
}
161+
162+
watch(
163+
() => props.options,
164+
async (newValue) => {
165+
// Initialize if needed or any other dependent setup
166+
}, {
167+
immediate: true
168+
}
97169
);
98170
</script>
99171
100-
101172
<style lang="stylus">
102173
@import '../../styles/config.styl'
103174
.algolia-autocomplete
@@ -139,7 +210,6 @@ watch(
139210
justify-content center
140211
align-content center
141212
142-
143213
@media (max-width: $mobileBreakpoint)
144214
.drawer-header__search
145215
width 100%
@@ -158,4 +228,18 @@ watch(
158228
width 75%
159229
.header-layout__search-title
160230
text-align center
161-
</style>
231+
232+
.spinner
233+
border 4px solid rgba(0, 0, 0, 0.1)
234+
border-top 4px solid #3498db
235+
border-radius 50%
236+
width 20px
237+
height 20px
238+
animation spin 1s linear infinite
239+
240+
@keyframes spin
241+
0%
242+
transform rotate(0deg)
243+
100%
244+
transform rotate(360deg)
245+
</style>

0 commit comments

Comments
 (0)