feat: korrektly search

This commit is contained in:
Aditya Tripathi
2025-10-26 22:15:39 +00:00
parent d72fd75022
commit 6d8762b08c
12 changed files with 705 additions and 136 deletions
+1 -1
View File
@@ -5,4 +5,4 @@ VITE_SITE_URL=https://coolify.io/docs/
# Analytics domain for Plausible
# Default: coolify.io/docs
VITE_ANALYTICS_DOMAIN=coolify.io/docs
VITE_ANALYTICS_DOMAIN=coolify.io/docs
+30
View File
@@ -0,0 +1,30 @@
name: Update Korrektly
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
on:
push:
branches:
- v4.x
jobs:
run:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Update Korrektly Chunks
env:
KORREKTLY_BASE_URL: ${{ secrets.KORREKTLY_BASE_URL }}
KORREKTLY_API_TOKEN: ${{ secrets.KORREKTLY_API_TOKEN }}
KORREKTLY_DATASET_ID: ${{ secrets.KORREKTLY_DATASET_ID }}
NODE_OPTIONS: --max-old-space-size=8192
run: bunx @korrektly/vitepress --path . -r https://coolify.io -a docs/api-reference/api/operations
-34
View File
@@ -1,34 +0,0 @@
name: Update Trieve
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
on:
push:
branches:
- v4.x
jobs:
run:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install Trieve Vitepress Adapter
run: bun install -g trieve-vitepress-adapter
- name: Update Trieve Chunks
env:
TRIEVE_API_HOST: ${{ secrets.TRIEVE_API_HOST }}
TRIEVE_API_KEY: ${{ secrets.TRIEVE_API_KEY }}
TRIEVE_ORGANIZATION_ID: ${{ secrets.TRIEVE_ORGANIZATION_ID }}
TRIEVE_DATASET_TRACKING_ID: ${{ secrets.TRIEVE_DATASET_TRACKING_ID }}
NODE_OPTIONS: --max-old-space-size=8192
run: trieve-vitepress-adapter --path . -s https://coolify.io/docs/openapi.json -r https://coolify.io -a docs/api-reference/api/operations
BIN
View File
Binary file not shown.
+5 -1
View File
@@ -58,7 +58,6 @@ export default defineConfig({
['link', { rel: 'icon', href: '/docs/coolify-logo-transparent.png', alt: "Coolify's Logo" }],
['link', { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
['script', { defer: 'true', src: 'https://analytics.coollabs.io/js/script.tagged-events.js', 'data-domain': env.VITE_ANALYTICS_DOMAIN ?? 'coolify.io/docs' }],
['script', { async: 'true', src: '/docs/trieve-user-script.js' }],
],
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
@@ -566,6 +565,11 @@ export default defineConfig({
}),
],
assetsInclude: ['**/*.yml'],
define: {
'import.meta.env.VITE_KORREKTLY_BASE_URL': JSON.stringify(env.KORREKTLY_BASE_URL || env.VITE_KORREKTLY_BASE_URL || ''),
'import.meta.env.VITE_KORREKTLY_API_TOKEN': JSON.stringify(env.KORREKTLY_API_TOKEN || env.VITE_KORREKTLY_API_TOKEN || ''),
'import.meta.env.VITE_KORREKTLY_DATASET_ID': JSON.stringify(env.KORREKTLY_DATASET_ID || env.VITE_KORREKTLY_DATASET_ID || ''),
},
build: {
chunkSizeWarningLimit: 5000
},
@@ -0,0 +1,399 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { useData } from 'vitepress'
import { onKeyStroke } from '@vueuse/core'
const { isDark } = useData()
interface SearchResult {
id: string
title: string
content: string
url: string
highlight?: string
hierarchy?: string
breadcrumb?: string
}
const isOpen = ref(false)
const searchQuery = ref('')
const searchResults = ref<SearchResult[]>([])
const isLoading = ref(false)
const selectedIndex = ref(0)
const searchInputRef = ref<HTMLInputElement | null>(null)
const searchError = ref<string | null>(null)
// Korrektly SDK configuration
const korrektlyConfig = {
baseUrl: 'https://korrektly.com',
apiToken: 'kly_pub_iUEPNafSehwGUVKpG4hW2OTj9qwFFn5CYTjaQLFo',
datasetId: '019a220d-e4f3-733f-b405-f8457b7bc8ac',
}
// Initialize Korrektly SDK
let korrektlySDK: any = null
const openSearch = () => {
isOpen.value = true
setTimeout(() => {
searchInputRef.value?.focus()
}, 100)
}
onMounted(async () => {
try {
const { Korrektly } = await import('@korrektly/sdk')
korrektlySDK = new Korrektly({
baseUrl: korrektlyConfig.baseUrl,
apiToken: korrektlyConfig.apiToken,
})
} catch (error) {
console.error('Failed to initialize Korrektly SDK:', error)
}
})
// Expose openSearch method so it can be called from parent components
defineExpose({
openSearch
})
const closeSearch = () => {
isOpen.value = false
searchQuery.value = ''
searchResults.value = []
selectedIndex.value = 0
searchError.value = null
}
// Keyboard shortcuts (only for modal interactions)
onKeyStroke('Escape', () => {
if (isOpen.value) {
closeSearch()
}
})
onKeyStroke('ArrowDown', (e) => {
if (isOpen.value && searchResults.value.length > 0) {
e.preventDefault()
selectedIndex.value = Math.min(selectedIndex.value + 1, searchResults.value.length - 1)
}
})
onKeyStroke('ArrowUp', (e) => {
if (isOpen.value && searchResults.value.length > 0) {
e.preventDefault()
selectedIndex.value = Math.max(selectedIndex.value - 1, 0)
}
})
onKeyStroke('Enter', () => {
if (isOpen.value && searchResults.value[selectedIndex.value]) {
navigateToResult(searchResults.value[selectedIndex.value])
}
})
// Debounced search
let searchTimeout: NodeJS.Timeout | null = null
watch(searchQuery, async (newQuery) => {
if (searchTimeout) {
clearTimeout(searchTimeout)
}
if (!newQuery.trim()) {
searchResults.value = []
searchError.value = null
return
}
searchTimeout = setTimeout(async () => {
await performSearch(newQuery)
}, 300)
})
const performSearch = async (query: string) => {
if (!korrektlySDK) {
console.error('Korrektly SDK not initialized')
searchError.value = 'Search service not initialized. Please try refreshing the page.'
return
}
isLoading.value = true
searchError.value = null
try {
const response = await korrektlySDK.search(korrektlyConfig.datasetId, {
query,
limit: 10,
search_type: 'hybrid',
})
// Check if response contains an error
if (response?.error || response?.message) {
const errorMessage = response.message || response.error || 'An unknown error occurred'
searchError.value = errorMessage
searchResults.value = []
console.error('Search API error:', response)
return
}
// The API returns { success, data: { results: [...] } }
const results = response?.data?.results || response?.results || response?.chunks || []
searchResults.value = results.map((chunk: any) => {
// Extract metadata from array format
const getMetadata = (key: string) => {
const meta = chunk.metadata?.find((m: any) => m.key === key)
return meta?.value || ''
}
const title = getMetadata('title') || getMetadata('heading') || extractTitle(chunk.content_html) || 'Untitled'
const description = getMetadata('description') || ''
const hierarchy = getMetadata('hierarchy') || ''
// Build URL from source_url or group tracking_id
let url = chunk.source_url || ''
if (url.includes('/home/aditya/workspace/coollabs/coolify-docs/docs')) {
// Convert file path to URL path
url = url.replace('/home/aditya/workspace/coollabs/coolify-docs/docs', '/docs')
url = url.replace('.md', '')
} else if (chunk.group?.tracking_id) {
url = chunk.group.tracking_id.replace('/home/aditya/workspace/coollabs/coolify-docs/docs', '/docs')
url = url.replace('.md', '')
}
// Create breadcrumb from hierarchy or URL (excluding 'docs' prefix)
let breadcrumb = ''
if (hierarchy) {
// Convert "home > aditya > workspace > coollabs > coolify-docs > docs > services > n8n"
// to "services / n8n"
const parts = hierarchy.split(' > ')
const docsIndex = parts.indexOf('docs')
if (docsIndex !== -1 && docsIndex < parts.length - 1) {
breadcrumb = parts.slice(docsIndex + 1).join(' / ')
} else {
breadcrumb = hierarchy.replace(/ > /g, ' / ')
}
} else if (url) {
// Extract from URL: /docs/services/n8n -> services / n8n
breadcrumb = url.replace(/^\/docs\//, '').replace(/\//g, ' / ')
}
return {
id: chunk.id,
title,
content: description || chunk.content_html || chunk.content || '',
url,
highlight: chunk.content_html,
hierarchy,
breadcrumb,
}
})
selectedIndex.value = 0
} catch (error: any) {
console.error('Search error:', error)
// Try to extract error message from the error object
let errorMessage = 'An unexpected error occurred while searching.'
if (error?.response?.data?.message) {
errorMessage = error.response.data.message
} else if (error?.message) {
errorMessage = error.message
} else if (typeof error === 'string') {
errorMessage = error
}
searchError.value = errorMessage
searchResults.value = []
} finally {
isLoading.value = false
}
}
const extractTitle = (html: string): string => {
if (!html) return ''
const temp = document.createElement('div')
temp.innerHTML = html
const heading = temp.querySelector('h1, h2, h3, h4, h5, h6')
return heading?.textContent?.trim() || ''
}
const navigateToResult = (result: SearchResult) => {
window.location.href = result.url
closeSearch()
}
const handleBackdropClick = (e: MouseEvent) => {
if (e.target === e.currentTarget) {
closeSearch()
}
}
const stripHtml = (html: string) => {
const temp = document.createElement('div')
temp.innerHTML = html
return temp.textContent || temp.innerText || ''
}
const truncate = (text: string, length: number) => {
if (text.length <= length) return text
return text.substring(0, length) + '...'
}
</script>
<template>
<Teleport to="body">
<Transition
enter-active-class="transition-opacity duration-200"
leave-active-class="transition-opacity duration-200"
enter-from-class="opacity-0"
leave-to-class="opacity-0"
>
<div
v-if="isOpen"
class="fixed inset-0 z-[9999] flex items-start justify-center overflow-y-auto bg-black/50 backdrop-blur-sm p-4 pt-[10vh]"
@click="handleBackdropClick"
>
<Transition
enter-active-class="transition-all duration-200"
leave-active-class="transition-all duration-200"
enter-from-class="opacity-0 -translate-y-5"
leave-to-class="opacity-0 -translate-y-5"
>
<div
v-if="isOpen"
class="w-full max-w-2xl bg-[var(--vp-c-bg)] rounded-xl shadow-2xl border border-[var(--vp-c-divider)] flex flex-col max-h-[80vh]"
@click.stop
>
<!-- Search Header -->
<div class="p-4 border-b border-[var(--vp-c-divider)]">
<div class="relative flex items-center">
<svg class="absolute left-3 w-5 h-5 text-[var(--vp-c-text-2)] pointer-events-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<input
ref="searchInputRef"
v-model="searchQuery"
type="text"
placeholder="Search documentation... (⌘K or Ctrl+K)"
class="w-full pl-11 pr-10 py-3 text-base bg-[var(--vp-c-bg-soft)] rounded-lg border-none outline-none text-[var(--vp-c-text-1)] placeholder:text-[var(--vp-c-text-3)] focus:bg-[var(--vp-c-bg-elv)] transition-colors"
/>
<button
v-if="searchQuery"
class="absolute right-3 w-6 h-6 flex items-center justify-center rounded bg-[var(--vp-c-bg-soft)] text-[var(--vp-c-text-2)] hover:bg-[var(--vp-c-bg-elv)] hover:text-[var(--vp-c-text-1)] transition-all text-xl leading-none"
@click="searchQuery = ''"
>
×
</button>
</div>
</div>
<!-- Search Body -->
<div class="flex-1 overflow-y-auto min-h-[200px] max-h-[500px]">
<!-- Loading State -->
<div v-if="isLoading" class="flex flex-col items-center justify-center py-12 px-6 text-[var(--vp-c-text-2)]">
<div class="w-8 h-8 border-3 border-[var(--vp-c-divider)] border-t-[var(--vp-c-brand)] rounded-full animate-spin mb-4"></div>
<p>Searching...</p>
</div>
<!-- Error State -->
<div v-else-if="searchError" class="flex flex-col items-center justify-center py-12 px-6 text-center">
<div class="max-w-md">
<div class="mb-4 text-4xl"></div>
<h3 class="text-lg font-semibold text-[var(--vp-c-text-1)] mb-3">Search Error</h3>
<div class="bg-[var(--vp-c-bg-soft)] border border-[var(--vp-c-divider)] rounded-lg p-4 mb-4">
<p class="text-sm text-[var(--vp-c-text-2)] font-mono break-words">{{ searchError }}</p>
</div>
<p class="text-sm text-[var(--vp-c-text-2)] mb-4">
If this issue persists, please contact support for assistance.
</p>
<a
href="mailto:support@korrektly.com"
class="inline-flex items-center gap-2 px-4 py-2 bg-[var(--vp-c-brand)] text-white rounded-lg hover:bg-[var(--vp-c-brand-dark)] transition-colors no-underline text-sm font-medium"
>
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="20" height="16" x="2" y="4" rx="2"></rect>
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path>
</svg>
Contact Korrektly Support
</a>
</div>
</div>
<!-- Empty State -->
<div v-else-if="searchQuery && searchResults.length === 0" class="flex items-center justify-center py-12 px-6 text-center text-[var(--vp-c-text-2)]">
<p>No results found for "{{ searchQuery }}"</p>
</div>
<!-- Results List -->
<div v-else-if="searchResults.length > 0" class="p-2">
<a
v-for="(result, index) in searchResults"
:key="result.id"
:href="result.url"
class="block p-3 mb-1 rounded-lg cursor-pointer no-underline transition-all border border-transparent hover:bg-[var(--vp-c-bg-soft)] hover:border-[var(--vp-c-brand)]"
:class="{ 'bg-[var(--vp-c-bg-soft)] border-[var(--vp-c-brand)]': index === selectedIndex }"
@click.prevent="navigateToResult(result)"
@mouseenter="selectedIndex = index"
>
<div class="mb-1.5">
<div class="font-semibold text-sm text-[var(--vp-c-text-1)] mb-0.5">{{ result.title }}</div>
<div v-if="result.breadcrumb" class="flex items-center gap-1 text-[11px] text-[var(--vp-c-text-3)] font-mono opacity-80 mt-0.5">
<span class="text-[10px] opacity-60">📁</span>
<span>{{ result.breadcrumb }}</span>
</div>
</div>
<div class="text-[13px] text-[var(--vp-c-text-2)] leading-relaxed line-clamp-2">
{{ truncate(stripHtml(result.content), 150) }}
</div>
</a>
</div>
<!-- Initial State -->
<div v-else class="py-12 px-6 text-center text-[var(--vp-c-text-2)]">
<p class="text-sm mb-6">Start typing to search...</p>
<div class="mt-6">
<p class="text-xs font-semibold text-[var(--vp-c-text-2)] mb-3 uppercase tracking-wide">Popular searches:</p>
<div class="flex flex-wrap gap-2 justify-center">
<button
v-for="tag in ['Backups', 'PostgreSQL', 'Docker Compose', 'GitHub Actions']"
:key="tag"
class="px-3 py-1.5 bg-[var(--vp-c-bg-soft)] border border-[var(--vp-c-divider)] rounded-md text-[13px] text-[var(--vp-c-text-1)] hover:bg-[var(--vp-c-brand)] hover:text-white hover:border-[var(--vp-c-brand)] transition-all cursor-pointer"
@click="searchQuery = tag"
>
{{ tag }}
</button>
</div>
</div>
</div>
</div>
<!-- Search Footer -->
<div class="px-4 py-3 border-t border-[var(--vp-c-divider)] flex justify-between items-center text-xs text-[var(--vp-c-text-2)]">
<div class="hidden md:flex gap-3">
<span class="flex items-center gap-1">
<kbd class="px-1.5 py-0.5 bg-[var(--vp-c-bg-soft)] border border-[var(--vp-c-divider)] rounded text-[11px] font-mono"></kbd>
<kbd class="px-1.5 py-0.5 bg-[var(--vp-c-bg-soft)] border border-[var(--vp-c-divider)] rounded text-[11px] font-mono"></kbd>
<span class="ml-1">Navigate</span>
</span>
<span class="flex items-center gap-1">
<kbd class="px-1.5 py-0.5 bg-[var(--vp-c-bg-soft)] border border-[var(--vp-c-divider)] rounded text-[11px] font-mono"></kbd>
<span class="ml-1">Select</span>
</span>
<span class="flex items-center gap-1">
<kbd class="px-1.5 py-0.5 bg-[var(--vp-c-bg-soft)] border border-[var(--vp-c-divider)] rounded text-[11px] font-mono">ESC</kbd>
<span class="ml-1">Close</span>
</span>
</div>
<div>
Powered by <a href="https://korrektly.com" target="_blank" rel="noopener" class="text-[var(--vp-c-brand)] no-underline hover:underline">Korrektly</a>
</div>
</div>
</div>
</Transition>
</div>
</Transition>
</Teleport>
</template>
@@ -0,0 +1,228 @@
<script setup lang="ts">
import { inject, onMounted } from 'vue'
import { onKeyStroke } from '@vueuse/core'
// Inject the openSearch function from KorrektlySearch (with global fallback)
const openSearchInjected = inject<() => void>('openKorrektlySearch', () => {})
const openSearch = () => {
// Try injected function first
if (openSearchInjected && typeof openSearchInjected === 'function') {
openSearchInjected()
return
}
// Fallback to global window reference
if (typeof window !== 'undefined' && (window as any).__korrektlySearch) {
(window as any).__korrektlySearch.openSearch()
} else {
console.warn('KorrektlySearch not available')
}
}
// Handle keyboard shortcut (Cmd/Ctrl+K)
onKeyStroke(['k', 'K'], (e) => {
if (e.metaKey || e.ctrlKey) {
e.preventDefault()
openSearch()
}
})
const handleClick = () => {
openSearch()
}
// Detect Mac for keyboard shortcut display
onMounted(() => {
if (typeof window !== 'undefined' && /(mac|iphone|ipod|ipad)/i.test(navigator.platform)) {
document.documentElement.classList.add('mac')
}
})
</script>
<template>
<div class="VPNavBarSearch search" @click="handleClick">
<button
type="button"
class="DocSearch DocSearch-Button"
aria-label="Search"
>
<span class="DocSearch-Button-Container">
<svg
class="DocSearch-Search-Icon"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<span class="DocSearch-Button-Placeholder">Search</span>
</span>
<span class="DocSearch-Button-Keys">
<kbd class="DocSearch-Button-Key"></kbd>
<kbd class="DocSearch-Button-Key">K</kbd>
</span>
</button>
</div>
</template>
<style scoped>
.VPNavBarSearch {
display: flex;
align-items: center;
padding-left: 16px;
}
@media (min-width: 768px) {
.VPNavBarSearch {
padding-left: 0;
}
}
.DocSearch {
--docsearch-primary-color: var(--vp-c-brand-1);
--docsearch-text-color: var(--vp-c-text-1);
--docsearch-spacing: 12px;
--docsearch-icon-stroke-width: 1.4;
--docsearch-highlight-color: var(--docsearch-primary-color);
--docsearch-muted-color: var(--vp-c-text-2);
--docsearch-container-background: rgba(101, 108, 133, 0.8);
--docsearch-modal-background: var(--vp-c-bg-elv);
--docsearch-searchbox-background: var(--vp-c-bg-alt);
--docsearch-searchbox-focus-background: var(--vp-c-bg);
--docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color);
--docsearch-hit-color: var(--vp-c-text-2);
--docsearch-hit-active-color: var(--vp-c-text-1);
--docsearch-hit-background: var(--vp-c-default-soft);
--docsearch-hit-shadow: none;
--docsearch-footer-background: var(--vp-c-bg);
}
.DocSearch-Button {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0 12px 0 14px;
width: 100%;
height: 42px;
background: transparent;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
font-size: 13px;
font-weight: 500;
transition: border-color 0.25s, background-color 0.25s;
cursor: pointer;
}
.DocSearch-Button:hover {
border-color: var(--vp-c-brand-1);
background: var(--vp-c-bg-alt);
}
.DocSearch-Button-Container {
display: flex;
align-items: center;
}
.DocSearch-Search-Icon {
position: relative;
width: 18px;
height: 18px;
color: var(--vp-c-text-2);
fill: none;
transition: color 0.25s;
}
.DocSearch-Button:hover .DocSearch-Search-Icon {
color: var(--vp-c-text-1);
}
.DocSearch-Button-Placeholder {
display: none;
padding: 0 10px 0 8px;
font-size: 13px;
font-weight: 500;
color: var(--vp-c-text-2);
transition: color 0.25s;
}
.DocSearch-Button:hover .DocSearch-Button-Placeholder {
color: var(--vp-c-text-1);
}
@media (min-width: 768px) {
.DocSearch-Button-Placeholder {
display: inline-block;
}
}
.DocSearch-Button-Keys {
direction: ltr;
display: none;
min-width: auto;
}
@media (min-width: 768px) {
.DocSearch-Button-Keys {
display: flex;
align-items: center;
}
}
.DocSearch-Button-Key {
display: block;
margin: 2px 0;
border: 1px solid var(--vp-c-divider);
border-right: none;
border-radius: 3px;
padding-left: 6px;
padding-right: 6px;
min-width: 0;
width: auto;
height: 22px;
line-height: 22px;
font-family: var(--vp-font-family-base);
font-size: 12px;
font-weight: 500;
transition: color 0.25s, border-color 0.25s;
}
.DocSearch-Button-Key:first-child {
font-size: 0 !important;
}
.DocSearch-Button-Key:first-child::after {
content: "⌘";
font-size: 12px;
}
html:not(.mac) .DocSearch-Button-Key:first-child::after {
content: "Ctrl";
}
.DocSearch-Button-Key:last-child {
border-right: 1px solid var(--vp-c-divider);
}
@media (min-width: 768px) {
.VPNavBarSearch {
flex-grow: 1;
padding-left: 24px;
}
}
@media (min-width: 960px) {
.VPNavBarSearch {
padding-left: 32px;
}
}
</style>
+20 -2
View File
@@ -4,7 +4,7 @@ import { inBrowser } from 'vitepress'
import { ref, watch } from 'vue'
import { useSidebar } from 'vitepress/theme'
import VPSidebarGroup from 'vitepress/dist/client/theme-default/components/VPSidebarGroup.vue'
import VPNavBarSearch from 'vitepress/dist/client/theme-default/components/VPNavBarSearch.vue'
import VPNavBarSearch from './VPNavBarSearch.vue'
import VPNavBarAppearance from 'vitepress/dist/client/theme-default/components/VPNavBarAppearance.vue'
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
@@ -48,7 +48,7 @@ watch(
<nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1">
<VPNavBarSearch class="sm:block search w-full my-auto px-0 " />
<VPNavBarSearch class="sidebar-search" />
<span class="visually-hidden" id="sidebar-aria-label">
Sidebar Navigation
@@ -134,4 +134,22 @@ watch(
.nav {
outline: 0;
}
.sidebar-search {
padding-left: 0 !important;
margin-bottom: 16px;
}
.sidebar-search :deep(.DocSearch-Button) {
justify-content: space-between !important;
}
.sidebar-search :deep(.DocSearch-Button-Placeholder) {
display: inline-block !important;
}
.sidebar-search :deep(.DocSearch-Button-Keys) {
display: flex !important;
margin-left: auto;
}
</style>
+2
View File
@@ -33,6 +33,7 @@ import TabBlock from "./components/TabBlock.vue";
import ZoomableImage from "./components/ZoomableImage.vue";
import Globe from "./components/Landing/Globe.vue";
import Browser from "./components/Landing/Browser.vue";
import KorrektlySearch from "./components/KorrektlySearch.vue";
// Import Vdoc overrides
import VPDoc from "./components/VPDoc.vue";
@@ -80,6 +81,7 @@ export default {
app.component("ZoomableImage", ZoomableImage);
app.component("Globe", Globe);
app.component("Browser", Browser);
app.component("KorrektlySearch", KorrektlySearch);
// Register Vdoc overrides
app.component("VPDoc", VPDoc);
+18
View File
@@ -1,4 +1,5 @@
<template>
<KorrektlySearch ref="korrektlySearchRef" />
<Layout>
<template #home-features-before>
</template>
@@ -6,9 +7,26 @@
</template>
<script setup lang="ts">
import { ref, provide, onMounted } from 'vue'
import DefaultTheme from 'vitepress/theme'
import { useData } from 'vitepress'
import KorrektlySearch from '../components/KorrektlySearch.vue'
const { Layout } = DefaultTheme
const { frontmatter } = useData()
// Create ref to KorrektlySearch component
const korrektlySearchRef = ref<InstanceType<typeof KorrektlySearch> | null>(null)
// Provide the openSearch function to all child components
provide('openKorrektlySearch', () => {
korrektlySearchRef.value?.openSearch()
})
// Also expose globally for easier access
onMounted(() => {
if (typeof window !== 'undefined') {
(window as any).__korrektlySearch = korrektlySearchRef.value
}
})
</script>
-97
View File
@@ -1,97 +0,0 @@
// ==UserScript==
// @name Coolify
// @namespace http://tampermonkey.net/
// @version 2025-01-16
// @description try to take over the world!
// @author You
// @match https://coolify.io/docs/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=coolify.io
// @grant none
// ==/UserScript==
const removeAllClickListeners = (element) => {
const newElement = element.cloneNode(true);
element.parentNode.replaceChild(newElement, element);
return newElement;
};
const makeDefaultSearchTrieve = async () => {
let defaultSearchBar = null;
let retries = 0;
while (!defaultSearchBar && retries < 10) {
for (const el of document.querySelectorAll("*")) {
if (el.querySelector('#local-search > button')) {
defaultSearchBar = el.querySelector('#local-search > button');
break;
}
}
retries++;
await new Promise((resolve) => setTimeout(resolve, 10));
}
if (!defaultSearchBar) {
return;
}
defaultSearchBar = removeAllClickListeners(defaultSearchBar);
defaultSearchBar.onclick = () => {
const event = new CustomEvent("trieve-open-with-text", {
detail: { text: "" },
});
window.dispatchEvent(event);
};
};
window.addEventListener('load', function() {
makeDefaultSearchTrieve();
});
const originalPushState = history.pushState;
history.pushState = function() {
originalPushState.apply(this, arguments);
makeDefaultSearchTrieve();
};
(async function () {
"use strict";
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "https://cdn.trieve.ai/beta/search-component/index.css";
document.head.appendChild(link);
import("https://cdn.trieve.ai/beta/search-component/vanilla/index.js").then(
async (module) => {
const { renderToDiv } = module;
const root = document.createElement("div");
root.classList.add("trigger");
document.body.appendChild(root);
const colorScheme = document.documentElement?.classList?.contains("dark")
? "dark"
: null;
renderToDiv(root, {
apiKey: "tr-4ge266qRg6AzfMAyWyqqUjmG3VC1CYYM",
datasetId: "cae68afa-93e1-4fb2-9945-693e65906409",
baseUrl: "https://api.trieve.ai",
type: "docs",
analytics: true,
theme: colorScheme === "dark" ? "dark" : null,
brandLogoImgSrcUrl: "https://coolify.io/docs/coolify-logo-transparent.png",
brandName: "Coolify",
brandColor: "#9664f3",
placeholder: "How can I help?",
defaultSearchQueries: ["Backups", "Postgresql", "Private NPM registry"],
defaultAiQuestions: ["How to fix expired GitHub personal access token (PAT)?", "My Raspberry Pi is crashing", "How to use Docker Compose?"],
defaultSearchMode: "search",
showFloatingButton: "true",
cssRelease: "none",
hideOpenButton: true,
});
},
(error) => {
console.error("Failed to load module:", error);
}
);
})();
+2 -1
View File
@@ -34,9 +34,10 @@
"transform-openapi": "tsx scripts/convert-openapi.ts"
},
"dependencies": {
"@korrektly/sdk": "^0.1.1",
"@vueuse/core": "12.5.0",
"globe.gl": "2.39.7",
"vitepress-openapi": "0.0.3-alpha.78"
},
"packageManager": "pnpm@10.6.3+sha512.bb45e34d50a9a76e858a95837301bfb6bd6d35aea2c5d52094fa497a467c43f5c440103ce2511e9e0a2f89c3d6071baac3358fc68ac6fb75e2ceb3d2736065e6"
}
}