Skip to content

Commit

Permalink
Merge pull request #108 from yel-hadd/master
Browse files Browse the repository at this point in the history
New Search API Integration
  • Loading branch information
yel-hadd authored Dec 26, 2024
2 parents 27bae66 + 51a0095 commit 71ebfb2
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 210 deletions.
Binary file removed .DS_Store
Binary file not shown.
Binary file removed docs/.DS_Store
Binary file not shown.
6 changes: 3 additions & 3 deletions docs/.vuepress/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ export default defineClientConfig({
appId: "R7FCMJM4P7"
},

MAX_ALGOLIA_VISIBLE_RESULT: 20,
MAX_ALGOLIA_VISIBLE_ROWS: 15,
MAX_ALGOLIA_HITS_PER_PAGE: 20,
MAX_VISIBLE_RESULT: 12,
MAX_VISIBLE_ROWS: 12,
MAX_HITS_PER_PAGE: 12,
})
}
})
59 changes: 18 additions & 41 deletions docs/.vuepress/theme/drawer/Drawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,15 @@
<p @click="onCloseDrawer" class="drawer-cross__text">close</p>
</div>
</div>
<DrawerTabs v-model="selectedTabIndex" :data="tabs"/>
<main>
<div class="drawer-main">
<div class="drawer-main__wrapper">
<div class="drawer-main__breadcrumb">
<p v-if="drawerArticleResult.length" class="drawer-main__breadcrumb__text">Home
<img :src="withBase('/arrows/arrow-right-breadcrumb.svg')" alt="breadcrumb icon"/>
Documentation
</p>
</div>
<DrawerSearchResult :modelValue="modelValue" :data="drawerArticleResult"/>
</div>
</div>
<div class="drawer-main">
<div class="drawer-main__wrapper">
<div class="drawer-main__breadcrumb">
<!-- Optional breadcrumb can stay here -->
</div>
<DrawerSearchResult :modelValue="modelValue" :data="drawerArticleResult"/>
</div>
</div>
<Footer v-if="isOpenDrawer && isMobileWidth" class="drawer-footer__mobile"/>
</main>
</div>
Expand All @@ -32,10 +28,9 @@
</template>

<script setup>
import {withBase} from "@vuepress/client";
import { withBase } from "@vuepress/client";
import Footer from "../footer/Footer.vue";
import {computed, ref, watch} from "vue";
import DrawerTabs from "./DrawerTabs.vue";
import { computed, ref, watch } from "vue";
import DrawerSearchResult from "./DrawerSearchResult.vue";
const props = defineProps({
Expand All @@ -44,7 +39,7 @@ const props = defineProps({
required: true,
default: false
},
isMobileWidth:{
isMobileWidth: {
type: Boolean,
required: true,
default: false
Expand All @@ -59,39 +54,21 @@ const props = defineProps({
required: true,
default: () => []
}
})
const emit = defineEmits(['closeDrawer', 'update:modelValue'])
const selectedTabIndex = ref(0);
const tabs = computed(() => {
const uniqueTitles = props.homeLayoutSearchResult.reduce((unique, result) => {
const title = result.hierarchy?.lvl0;
unique[title] = unique[title] || { title, numberResults: 0 };
unique[title].numberResults++;
return unique;
}, {});
return Object.values(uniqueTitles);
});
const emit = defineEmits(['closeDrawer', 'update:modelValue']);
const drawerArticleResult = computed(() => {
if (selectedTabIndex.value === -1) {
return props.homeLayoutSearchResult || [];
}
const selectedTab = tabs.value[selectedTabIndex.value];
return props.homeLayoutSearchResult.filter(result => result.hierarchy.lvl0 === selectedTab?.title);
})
return props.homeLayoutSearchResult; // Now directly returning all results since there are no tabs
});
const onCloseDrawer = () => {
emit('closeDrawer')
selectedTabIndex.value = 0
emit('closeDrawer');
}
watch(() => props.isOpenDrawer, () => {
document.body.classList.toggle('disable-scroll', props.isOpenDrawer)
})
document.body.classList.toggle('disable-scroll', props.isOpenDrawer);
});
</script>

<style lang="stylus">
Expand Down
164 changes: 124 additions & 40 deletions docs/.vuepress/theme/drawer/DrawerSearch.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
<template>
<form id="search-form" class="drawer-header__input">
<input :value="modelValue"
<form id="search-form" class="drawer-header__input" @submit.prevent="performSearch">
<input type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
id="algolia-search-input"
:placeholder="placeholder"
:class="activeSearchClass"
@keypress.enter.prevent="$emit('openDrawer')"
maxlength="100"
/>
<div :class="activeSearchIconClass">
<img @click="$emit('openDrawer')" alt="search icon" :src="withBase(activeSearchIcon)"/>
<img v-if="!loading" @click="performSearch" alt="search icon" :src="withBase(activeSearchIcon)"/>
<div v-if="loading" class="spinner"></div>
</div>
</form>
</template>

<script setup>
import {usePageFrontmatter, withBase} from "@vuepress/client";
import {computed, inject, watch} from "vue";
const { MAX_ALGOLIA_HITS_PER_PAGE } = inject('themeConfig')
import {computed, inject, ref, watch} from "vue";
const { MAX_HITS_PER_PAGE } = inject('themeConfig')
const {headerDefaultSearchIcon, headerSearchIcon, headerSearchPlaceholder} = inject('themeConfig')
const props = defineProps({
options: {
type: [Object, Array],
Expand All @@ -37,9 +39,10 @@ const props = defineProps({
type: Boolean,
}
});
const emit = defineEmits(["openDrawer", 'update:modelValue', 'result'])
const emit = defineEmits(["openDrawer", 'update:modelValue', 'result'])
const frontmatter = usePageFrontmatter()
const isGlobalLayout = computed(() => {
return frontmatter.value.layout === 'HomeLayout'
})
Expand All @@ -61,43 +64,111 @@ const placeholderDesktop = computed(() => {
})
const placeholder = computed(() => {
return props.isMobileWidth ? 'Search accross all Imunify Security support' : placeholderDesktop.value
return props.isMobileWidth ? 'Search accross all Tuxcare Docs' : placeholderDesktop.value
})
const initialize = async (userOptions) => {
if( typeof window === 'undefined' ) return
const [docsearchModule] = await Promise.all([
import(/* webpackChunkName: "docsearch" */ "docsearch.js/dist/cdn/docsearch.min.js"),
import(/* webpackChunkName: "docsearch" */ "docsearch.js/dist/cdn/docsearch.min.css"),
]);
const docsearch = docsearchModule.default;
docsearch(
Object.assign({}, userOptions, {
inputSelector: "#algolia-search-input",
algoliaOptions: {
hitsPerPage: MAX_ALGOLIA_HITS_PER_PAGE,
},
handleSelected: () => {
emit('openDrawer')
function parseDocs(api_response) {
return api_response.tuxcare_docs.map((doc) => {
const titleParts = doc.title.split("->").map((part) => part.trim());
const hierarchy = {
lvl0: titleParts[0] || null,
lvl1: titleParts[1] || null,
lvl2: titleParts[2] || null,
lvl3: titleParts[3] || null,
lvl4: titleParts[4] || null,
lvl5: null,
lvl6: null,
};
const anchor = doc.url.split("#")[1] || "";
const objectID = doc.id;
return {
anchor,
content: null,
hierarchy,
url: doc.url,
title: doc.title,
preview: doc.preview,
category: doc.category,
section: doc.section,
objectID,
_highlightResult: {
hierarchy: {
lvl0: {
value: hierarchy.lvl0 || "",
matchLevel: "none",
matchedWords: [],
},
lvl1: {
value: hierarchy.lvl1 || "",
matchLevel: "full",
fullyHighlighted: false,
matchedWords: [hierarchy.lvl1?.toLowerCase()],
},
},
transformData: (hits) => {
emit('result', hits)
},
})
);
};
watch(
() => props.options,
async (newValue) => {
await initialize(newValue);
}, {
immediate: true
hierarchy_camel: [
{
lvl0: {
value: hierarchy.lvl0 || "",
matchLevel: "none",
matchedWords: [],
},
lvl1: {
value: `<span class="algolia-docsearch-suggestion--highlight">${hierarchy.lvl1 || ""}</span>`,
matchLevel: "full",
fullyHighlighted: false,
matchedWords: [hierarchy.lvl1?.toLowerCase()],
},
},
],
},
};
});
}
async function queryGlobalSearch(query, n_results=10) {
const baseUrl = 'https://global-search.cl-edu.com/search';
let urlEncodedQuery = encodeURIComponent(query);
let url = `${baseUrl}?query=${urlEncodedQuery}&collections=tuxcare_docs&n_results=${n_results}&source=tuxcare_docs`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error querying global search:', error);
return null;
}
}
const loading = ref(false); // Reactive variable for loading state
const performSearch = async () => {
loading.value = true; // Set loading to true when search starts
const data = await queryGlobalSearch(props.modelValue, MAX_HITS_PER_PAGE);
loading.value = false; // Set loading to false when search finishes
if (data) {
const hits = parseDocs(data);
emit('result', hits);
emit('openDrawer');
}
}
watch(
() => props.options,
async (newValue) => {
// Initialize if needed or any other dependent setup
}, {
immediate: true
}
);
</script>

<style lang="stylus">
@import '../../styles/config.styl'
.algolia-autocomplete
Expand Down Expand Up @@ -139,7 +210,6 @@ watch(
justify-content center
align-content center
@media (max-width: $mobileBreakpoint)
.drawer-header__search
width 100%
Expand All @@ -158,4 +228,18 @@ watch(
width 75%
.header-layout__search-title
text-align center
</style>
.spinner
border 4px solid rgba(0, 0, 0, 0.1)
border-top 4px solid #3498db
border-radius 50%
width 20px
height 20px
animation spin 1s linear infinite
@keyframes spin
0%
transform rotate(0deg)
100%
transform rotate(360deg)
</style>
Loading

0 comments on commit 71ebfb2

Please sign in to comment.