1
1
<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"
4
5
@input =" $emit('update:modelValue', $event.target.value)"
5
6
id =" algolia-search-input"
6
7
:placeholder =" placeholder"
7
8
:class =" activeSearchClass"
8
- @keypress.enter.prevent = " $emit('openDrawer') "
9
+ maxlength = " 100 "
9
10
/>
10
11
<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 >
12
14
</div >
13
15
</form >
14
16
</template >
15
17
16
18
<script setup>
17
19
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" ;
21
21
22
+ const { MAX_HITS_PER_PAGE } = inject (' themeConfig' )
22
23
const {headerDefaultSearchIcon , headerSearchIcon , headerSearchPlaceholder } = inject (' themeConfig' )
24
+
23
25
const props = defineProps ({
24
26
options: {
25
27
type: [Object , Array ],
@@ -37,9 +39,10 @@ const props = defineProps({
37
39
type: Boolean ,
38
40
}
39
41
});
40
- const emit = defineEmits ([" openDrawer" , ' update:modelValue' , ' result' ])
41
42
43
+ const emit = defineEmits ([" openDrawer" , ' update:modelValue' , ' result' ])
42
44
const frontmatter = usePageFrontmatter ()
45
+
43
46
const isGlobalLayout = computed (() => {
44
47
return frontmatter .value .layout === ' HomeLayout'
45
48
})
@@ -61,43 +64,111 @@ const placeholderDesktop = computed(() => {
61
64
})
62
65
63
66
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
65
68
})
66
69
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
+ },
83
111
},
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 } ` );
96
140
}
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
+ }
97
169
);
98
170
< / script>
99
171
100
-
101
172
< style lang= " stylus" >
102
173
@import ' ../../styles/config.styl'
103
174
.algolia - autocomplete
@@ -139,7 +210,6 @@ watch(
139
210
justify- content center
140
211
align- content center
141
212
142
-
143
213
@media (max- width: $mobileBreakpoint)
144
214
.drawer - header__search
145
215
width 100 %
@@ -158,4 +228,18 @@ watch(
158
228
width 75 %
159
229
.header - layout__search- title
160
230
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