fix: cater for transformItems to use DS with custom indices (#2705)
This commit is contained in:
parent
95af410f82
commit
3478262b3e
47 changed files with 669 additions and 288 deletions
|
|
@ -6,11 +6,11 @@
|
|||
},
|
||||
{
|
||||
"path": "packages/docsearch-react/dist/umd/index.js",
|
||||
"maxSize": "70 kB"
|
||||
"maxSize": "75 kB"
|
||||
},
|
||||
{
|
||||
"path": "packages/docsearch-js/dist/umd/index.js",
|
||||
"maxSize": "85 kB"
|
||||
"maxSize": "90 kB"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"name": "@docsearch/askai-example",
|
||||
"description": "DocSearch v4 AskAI example",
|
||||
"version": "4.0.0-beta.5",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docsearch/css": "workspace:*",
|
||||
"@docsearch/react": "workspace:*",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"vite": "^6.0.7"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
|
||||
Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
import type { JSX } from 'react';
|
||||
import './App.css';
|
||||
import '@docsearch/css/dist/style.css';
|
||||
|
||||
function App(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<h1>DocSearch v4 - AskAI</h1>
|
||||
<DocSearch
|
||||
indexName="docsearch"
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
askAi={{
|
||||
assistantId: 'askAIDemo',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
},
|
||||
}}
|
||||
insights={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
|
@ -1,14 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DocSearch v4 - React</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"name": "@docsearch/react-18-example",
|
||||
"description": "DocSearch v3 React 18 example",
|
||||
"version": "4.0.0-beta.5",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docsearch/css": "workspace:*",
|
||||
"@docsearch/react": "workspace:*",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"vite": "^6.0.7"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
|
||||
Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import { DocSearch } from '@docsearch/react';
|
||||
import React from 'react';
|
||||
import './App.css';
|
||||
import '@docsearch/css/dist/style.css';
|
||||
|
||||
function App(): React.JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<h1>DocSearch v4 - React - 18</h1>
|
||||
<DocSearch indexName="vuejs" appId="ML0LEBN7FQ" apiKey="21cf9df0734770a2448a9da64a700c22" insights={true} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import App from './App.tsx';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"vite.config.ts"
|
||||
],
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
});
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docsearch/css": "4.0.0-beta.5",
|
||||
"@docsearch/react": "4.0.0-beta.5",
|
||||
"@docsearch/css": "workspace:*",
|
||||
"@docsearch/react": "workspace:*",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
160
examples/demo-react/src/App.css
Normal file
160
examples/demo-react/src/App.css
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&family=TikTok+Sans:opsz,wght@12..36,300..900&display=swap');
|
||||
|
||||
/* reset and base styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: 'TikTok Sans', 'sans-serif';
|
||||
min-height: 100vh;
|
||||
background: radial-gradient(125% 125% at 50% 90%, #fff 40%, #7c3aed 100%);
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* container styles */
|
||||
.app-container {
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* header styles */
|
||||
.app-header {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: black;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.app-subtitle {
|
||||
font-size: 1rem;
|
||||
color: black;
|
||||
color: #666;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* demo section styles */
|
||||
.demo-section {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.section-description {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-top: -1rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.ai-powered {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #f2e8ff;
|
||||
text-underline-offset: 4px;
|
||||
text-decoration-thickness: 2px;
|
||||
}
|
||||
|
||||
/* search component wrapper */
|
||||
.search-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
/* divider styles */
|
||||
.divider {
|
||||
border: none;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, #e2e8f0, transparent);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.app-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.section-description {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* docsearch button customization */
|
||||
.DocSearch-Button {
|
||||
margin: 0 !important;
|
||||
background: #f7fafc !important;
|
||||
border: 2px solid #e2e8f0 !important;
|
||||
border-radius: 12px !important;
|
||||
padding: 12px 16px !important;
|
||||
font-size: 16px !important;
|
||||
transition: all 0.2s ease !important;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover {
|
||||
border-color: #667eea !important;
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
}
|
||||
|
||||
.DocSearch-Button-Placeholder {
|
||||
color: #718096 !important;
|
||||
}
|
||||
|
||||
/* add subtle animations */
|
||||
.demo-section {
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
}
|
||||
|
||||
.demo-section:nth-child(2) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
.demo-section:nth-child(3) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
56
examples/demo-react/src/App.tsx
Normal file
56
examples/demo-react/src/App.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { version } from '@docsearch/react';
|
||||
import type { JSX } from 'react';
|
||||
|
||||
import './App.css';
|
||||
import '@docsearch/css/dist/style.css';
|
||||
|
||||
import Basic from './examples/basic';
|
||||
import BasicAskAI from './examples/basic-askai';
|
||||
import WHitComponent from './examples/w-hit-component';
|
||||
import WTransformItems from './examples/w-hit-transformItems';
|
||||
|
||||
function App(): JSX.Element {
|
||||
return (
|
||||
<div className="body-container">
|
||||
<div className="app-container">
|
||||
<header className="app-header">
|
||||
<h1 className="app-title">DocSearch v{version}</h1>
|
||||
<p className="app-subtitle">Experience the power of intelligent documentation search</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section className="demo-section">
|
||||
<p className="section-description">basic search functionality</p>
|
||||
<div className="search-wrapper">
|
||||
<Basic />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="demo-section">
|
||||
<p className="section-description">search with askai integration</p>
|
||||
<div className="search-wrapper">
|
||||
<BasicAskAI />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="demo-section">
|
||||
<p className="section-description">custom hit rendering</p>
|
||||
<div className="search-wrapper">
|
||||
<WHitComponent />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="demo-section">
|
||||
<p className="section-description">transform items before rendering</p>
|
||||
<div className="search-wrapper">
|
||||
<WTransformItems />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
21
examples/demo-react/src/examples/basic-askai.tsx
Normal file
21
examples/demo-react/src/examples/basic-askai.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
import type { JSX } from 'react';
|
||||
|
||||
export default function BasicAskAI(): JSX.Element {
|
||||
return (
|
||||
<DocSearch
|
||||
indexName="docsearch"
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
askAi={{
|
||||
assistantId: 'askAIDemo',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
},
|
||||
}}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'Search with AskAI' } }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
15
examples/demo-react/src/examples/basic.tsx
Normal file
15
examples/demo-react/src/examples/basic.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
import type { JSX } from 'react';
|
||||
|
||||
export default function Basic(): JSX.Element {
|
||||
return (
|
||||
<DocSearch
|
||||
indexName="docsearch"
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
translations={{ button: { buttonText: 'Keyword search' } }}
|
||||
insights={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
125
examples/demo-react/src/examples/w-hit-component.tsx
Normal file
125
examples/demo-react/src/examples/w-hit-component.tsx
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
import type { JSX } from 'react';
|
||||
|
||||
function CustomHit({ hit }: { hit: any }): JSX.Element {
|
||||
return (
|
||||
<a
|
||||
href={hit.url}
|
||||
style={{
|
||||
display: 'block',
|
||||
padding: '12px 16px',
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
borderBottom: '1px solid #f0f0f0',
|
||||
transition: 'background-color 0.2s ease',
|
||||
backgroundColor: 'transparent',
|
||||
cursor: 'pointer',
|
||||
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f8f9fa';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '12px' }}>
|
||||
<div
|
||||
style={{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
backgroundColor: '#e3f2fd',
|
||||
borderRadius: '6px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
color: '#1976d2',
|
||||
}}
|
||||
>
|
||||
{/* eslint-disable-next-line no-nested-ternary */}
|
||||
{hit.type === 'lvl1' ? 'H1' : hit.type === 'lvl2' ? 'H2' : hit.type === 'lvl3' ? 'H3' : 'DOC'}
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
color: '#1a1a1a',
|
||||
marginBottom: '2px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{hit.hierarchy.lvl1 || 'Untitled'}
|
||||
</div>
|
||||
|
||||
{hit.hierarchy.lvl2 && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
color: '#666',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{hit.hierarchy.lvl2}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hit.content && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color: '#888',
|
||||
marginTop: '4px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
lineHeight: 1.3,
|
||||
}}
|
||||
>
|
||||
{hit.content}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
fontSize: '11px',
|
||||
color: '#999',
|
||||
marginTop: '4px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
}}
|
||||
>
|
||||
<span>{hit.url}</span>
|
||||
{hit.type && <span style={{ textTransform: 'capitalize' }}>{hit.type}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WHitComponent(): JSX.Element {
|
||||
return (
|
||||
<DocSearch
|
||||
indexName="docsearch"
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'Search with custom hits' } }}
|
||||
hitComponent={CustomHit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
61
examples/demo-react/src/examples/w-hit-transformItems.tsx
Normal file
61
examples/demo-react/src/examples/w-hit-transformItems.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
import type { JSX } from 'react';
|
||||
|
||||
// this type matches the structure of the provided example hit
|
||||
/* type _DocSearchCustomHit = {
|
||||
path: string;
|
||||
metaDescription: string;
|
||||
title: string;
|
||||
h1: string;
|
||||
h2: string;
|
||||
content: string;
|
||||
breadcrumb: string[];
|
||||
variation: Record<string, unknown>;
|
||||
pageDepth: number;
|
||||
domain: string;
|
||||
objectID: string;
|
||||
_snippetResult: any;
|
||||
_highlightResult: any;
|
||||
}; */
|
||||
|
||||
export default function WTransformItems(): JSX.Element {
|
||||
return (
|
||||
<DocSearch
|
||||
indexName="crawler_doc"
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
askAi={{
|
||||
assistantId: 'askAIDemo',
|
||||
}}
|
||||
insights={true}
|
||||
searchParameters={{
|
||||
attributesToRetrieve: ['*'],
|
||||
attributesToSnippet: ['*'],
|
||||
hitsPerPage: 20,
|
||||
}}
|
||||
transformItems={(items) => {
|
||||
return items.map((item: any) => ({
|
||||
objectID: item.objectID,
|
||||
content: item.content ?? '',
|
||||
url: new URL(item.domain + item.path).toString(),
|
||||
hierarchy: {
|
||||
lvl0: item.breadcrumb.join(' > ') ?? '',
|
||||
lvl1: item.h1 ?? '',
|
||||
lvl2: item.h2 ?? '',
|
||||
lvl3: null,
|
||||
lvl4: null,
|
||||
lvl5: null,
|
||||
lvl6: null,
|
||||
},
|
||||
url_without_anchor: new URL(item.domain + item.path).toString(),
|
||||
type: 'content' as const,
|
||||
anchor: null,
|
||||
_highlightResult: item._highlightResult,
|
||||
_snippetResult: item._snippetResult,
|
||||
}));
|
||||
}}
|
||||
translations={{ button: { buttonText: 'Search with transformItems' } }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DocSearch v4 - React</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.4 KiB |
|
|
@ -1,4 +0,0 @@
|
|||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
|
||||
Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import { DocSearch } from '@docsearch/react';
|
||||
import type { JSX } from 'react';
|
||||
import './App.css';
|
||||
import '@docsearch/css/dist/style.css';
|
||||
|
||||
function App(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<h1>DocSearch v4 - React</h1>
|
||||
<DocSearch indexName="vuejs" appId="ML0LEBN7FQ" apiKey="21cf9df0734770a2448a9da64a700c22" insights={true} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import App from './App.tsx';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
1
examples/demo/src/vite-env.d.ts
vendored
1
examples/demo/src/vite-env.d.ts
vendored
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"vite.config.ts"
|
||||
],
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
});
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.4 KiB |
1
examples/js-demo/src/vite-env.d.ts
vendored
1
examples/js-demo/src/vite-env.d.ts
vendored
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
|
@ -20,7 +20,6 @@
|
|||
"lint": "eslint .",
|
||||
"playground:build": "yarn workspace @docsearch/react-example build",
|
||||
"playground:start": "yarn workspace @docsearch/react-example dev --host",
|
||||
"playground-askai:start": "yarn workspace @docsearch/askai-example dev --host",
|
||||
"playground-js:start": "yarn workspace @docsearch/js-example dev --host",
|
||||
"release": "shipjs prepare",
|
||||
"start": "yarn run watch",
|
||||
|
|
|
|||
|
|
@ -1211,7 +1211,7 @@ assistive tech users */
|
|||
color: var(--docsearch-muted-color);
|
||||
}
|
||||
|
||||
.DocSearch-AskAiScreen-MessageContent-Tool.Tool--Result:first-child {
|
||||
.DocSearch-AskAiScreen-MessageContent-Tool.Tool--Result {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ const buildQuerySources = async ({
|
|||
{
|
||||
query,
|
||||
indexName,
|
||||
attributesToRetrieve: [
|
||||
attributesToRetrieve: searchParameters?.attributesToRetrieve ?? [
|
||||
'hierarchy.lvl0',
|
||||
'hierarchy.lvl1',
|
||||
'hierarchy.lvl2',
|
||||
|
|
@ -162,7 +162,7 @@ const buildQuerySources = async ({
|
|||
'type',
|
||||
'url',
|
||||
],
|
||||
attributesToSnippet: [
|
||||
attributesToSnippet: searchParameters?.attributesToSnippet ?? [
|
||||
`hierarchy.lvl1:${snippetLength.current}`,
|
||||
`hierarchy.lvl2:${snippetLength.current}`,
|
||||
`hierarchy.lvl3:${snippetLength.current}`,
|
||||
|
|
@ -171,11 +171,11 @@ const buildQuerySources = async ({
|
|||
`hierarchy.lvl6:${snippetLength.current}`,
|
||||
`content:${snippetLength.current}`,
|
||||
],
|
||||
snippetEllipsisText: '…',
|
||||
highlightPreTag: '<mark>',
|
||||
highlightPostTag: '</mark>',
|
||||
hitsPerPage: 20,
|
||||
clickAnalytics: insightsActive,
|
||||
snippetEllipsisText: searchParameters?.snippetEllipsisText ?? '…',
|
||||
highlightPreTag: searchParameters?.highlightPreTag ?? '<mark>',
|
||||
highlightPostTag: searchParameters?.highlightPostTag ?? '</mark>',
|
||||
hitsPerPage: searchParameters?.hitsPerPage ?? 20,
|
||||
clickAnalytics: searchParameters?.clickAnalytics ?? insightsActive,
|
||||
...searchParameters,
|
||||
},
|
||||
],
|
||||
|
|
@ -183,7 +183,8 @@ const buildQuerySources = async ({
|
|||
|
||||
const firstResult = results[0] as SearchResponse<DocSearchHit>;
|
||||
const { hits, nbHits } = firstResult;
|
||||
const sources = groupBy<DocSearchHit>(hits, (hit) => removeHighlightTags(hit), maxResultsPerGroup);
|
||||
const transformedHits = transformItems(hits);
|
||||
const sources = groupBy<DocSearchHit>(transformedHits, (hit) => removeHighlightTags(hit), maxResultsPerGroup);
|
||||
|
||||
// We store the `lvl0`s to display them as search suggestions
|
||||
// in the "no results" screen.
|
||||
|
|
@ -222,7 +223,6 @@ const buildQuerySources = async ({
|
|||
},
|
||||
getItems(): InternalDocSearchHit[] {
|
||||
return Object.values(groupBy(items, (item) => item.hierarchy.lvl1, maxResultsPerGroup))
|
||||
.map(transformItems)
|
||||
.map((groupedHits) =>
|
||||
groupedHits.map((item) => {
|
||||
let parent: InternalDocSearchHit | null = null;
|
||||
|
|
|
|||
212
packages/website/docs/examples.mdx
Normal file
212
packages/website/docs/examples.mdx
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
---
|
||||
id: examples
|
||||
title: Examples and extensions
|
||||
description: live demos showing how to use and extend docsearch beyond documentation-only use cases.
|
||||
---
|
||||
|
||||
import { DocSearch } from '@docsearch/react';
|
||||
import '@docsearch/css/dist/style.css';
|
||||
|
||||
> These examples are interactive. click a button to open the modal and try a query.
|
||||
|
||||
## Basic keyword search
|
||||
|
||||
Use the default experience with your index credentials. this works great for typical docs, blogs, and any site with a docsearch-compliant index.
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indexName="docsearch"
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'keyword search (demo)' } }}
|
||||
/>
|
||||
```
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indexName="docsearch"
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'keyword search (demo)' } }}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Ask AI: ai-assisted answers
|
||||
|
||||
Add algolia askai to get synthesized answers grounded in your indexed content. you can scope the llm context using `searchParameters`.
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indexName="docsearch"
|
||||
askAi={{
|
||||
assistantId: 'askAIDemo',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
},
|
||||
}}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'search with askai (demo)' } }}
|
||||
/>
|
||||
```
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indexName="docsearch"
|
||||
askAi={{
|
||||
assistantId: 'askAIDemo',
|
||||
searchParameters: {
|
||||
facetFilters: ['language:en'],
|
||||
},
|
||||
}}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'search with askai (demo)' } }}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Custom hit rendering (`hitComponent`)
|
||||
|
||||
Replace the default hit markup to match your brand and layout. below is a minimal example of a custom component.
|
||||
|
||||
```jsx
|
||||
function CustomHit({ hit }) {
|
||||
// render a compact, branded hit card
|
||||
return (
|
||||
<a href={hit.url} style={{ display: 'block', padding: '12px 16px', textDecoration: 'none' }}>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<div style={{ width: 40, height: 40, backgroundColor: '#e3f2fd', borderRadius: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 600, color: '#1976d2' }}>
|
||||
{hit.type?.toUpperCase?.() || 'DOC'}
|
||||
</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{hit.hierarchy?.lvl1 || 'untitled'}</div>
|
||||
{hit.hierarchy?.lvl2 && <div style={{ color: '#666', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{hit.hierarchy.lvl2}</div>}
|
||||
{hit.content && <div style={{ color: '#888', marginTop: 4 }}>{hit.content}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indexName="docsearch"
|
||||
hitComponent={CustomHit}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'custom hits (demo)' } }}
|
||||
/>
|
||||
```
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indexName="docsearch"
|
||||
hitComponent={({ hit }) => {
|
||||
// render a compact, branded hit card
|
||||
return (
|
||||
<a href={hit.url} style={{ display: 'block', padding: '12px 16px', textDecoration: 'none' }}>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<div style={{ width: 40, height: 40, backgroundColor: '#e3f2fd', borderRadius: 6, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 600, color: '#1976d2' }}>
|
||||
{hit.type?.toUpperCase?.() || 'DOC'}
|
||||
</div>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{hit.hierarchy?.lvl1 || 'untitled'}</div>
|
||||
{hit.hierarchy?.lvl2 && <div style={{ color: '#666', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{hit.hierarchy.lvl2}</div>}
|
||||
{hit.content && <div style={{ color: '#888', marginTop: 4 }}>{hit.content}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'custom hits (demo)' } }}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Bring-your-own-data shape with `transformItems`
|
||||
|
||||
Docsearch is not limited to docsearch-like records. use `transformItems` to adapt any record shape into the internal structure docsearch expects. this lets you build search for apps, help centers, changelogs, or any custom content.
|
||||
|
||||
the snippet below maps a non-standard record to the internal format. try it live:
|
||||
|
||||
```jsx
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indexName="crawler_doc"
|
||||
askAi={{ assistantId: 'askAIDemo' }}
|
||||
searchParameters={{
|
||||
attributesToRetrieve: ['*'],
|
||||
attributesToSnippet: ['*'],
|
||||
hitsPerPage: 20,
|
||||
}}
|
||||
transformItems={(items) =>
|
||||
items.map((item) => ({
|
||||
objectID: item.objectID,
|
||||
content: item.content ?? '',
|
||||
url: item.domain + item.path,
|
||||
hierarchy: {
|
||||
lvl0: (item.breadcrumb || []).join(' > ') ?? '',
|
||||
lvl1: item.h1 ?? '',
|
||||
lvl2: item.h2 ?? '',
|
||||
lvl3: null,
|
||||
lvl4: null,
|
||||
lvl5: null,
|
||||
lvl6: null,
|
||||
},
|
||||
url_without_anchor: item.domain + item.path,
|
||||
type: 'content',
|
||||
anchor: null,
|
||||
_highlightResult: item._highlightResult,
|
||||
_snippetResult: item._snippetResult,
|
||||
}))
|
||||
}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'transform items (demo)' } }}
|
||||
/>
|
||||
```
|
||||
|
||||
<DocSearch
|
||||
appId="PMZUYBQDAK"
|
||||
apiKey="24b09689d5b4223813d9b8e48563c8f6"
|
||||
indexName="crawler_doc"
|
||||
askAi={{ assistantId: 'askAIDemo' }}
|
||||
searchParameters={{ attributesToRetrieve: ['*'], attributesToSnippet: ['*'], hitsPerPage: 20 }}
|
||||
transformItems={(items) =>
|
||||
items.map((item) => ({
|
||||
objectID: item.objectID,
|
||||
content: item.content ?? '',
|
||||
url: item.domain + item.path,
|
||||
hierarchy: {
|
||||
lvl0: (item.breadcrumb || []).join(' > ') ?? '',
|
||||
lvl1: item.h1 ?? '',
|
||||
lvl2: item.h2 ?? '',
|
||||
lvl3: null,
|
||||
lvl4: null,
|
||||
lvl5: null,
|
||||
lvl6: null,
|
||||
},
|
||||
url_without_anchor: item.domain + item.path,
|
||||
type: 'content',
|
||||
anchor: null,
|
||||
_highlightResult: item._highlightResult,
|
||||
_snippetResult: item._snippetResult,
|
||||
}))
|
||||
}
|
||||
insights={true}
|
||||
translations={{ button: { buttonText: 'transform items (demo)' } }}
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
## Tips
|
||||
|
||||
- **Instrumentation**: enable `insights` to send usage analytics and iterate on relevance.
|
||||
- **AskAI scoping**: use `facetFilters` to keep ai answers strictly within your desired corpus (language, product, version, etc.).
|
||||
- **Customization**: use `hitComponent`, `transformItems`, and `translations` to make docsearch feel native to any product surface.
|
||||
|
|
@ -19,7 +19,7 @@ export default {
|
|||
{
|
||||
type: 'category',
|
||||
label: 'DocSearch v4',
|
||||
items: ['docsearch', 'api', 'migrating-from-v3'],
|
||||
items: ['docsearch', 'api', 'examples', 'migrating-from-v3'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
|
|
|
|||
34
yarn.lock
34
yarn.lock
|
|
@ -2310,19 +2310,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@docsearch/askai-example@workspace:examples/demo-askai":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@docsearch/askai-example@workspace:examples/demo-askai"
|
||||
dependencies:
|
||||
"@docsearch/css": "workspace:*"
|
||||
"@docsearch/react": "workspace:*"
|
||||
"@vitejs/plugin-react": "npm:^4.3.4"
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
vite: "npm:^6.0.7"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@docsearch/css@npm:4.0.0-beta.4":
|
||||
version: 4.0.0-beta.4
|
||||
resolution: "@docsearch/css@npm:4.0.0-beta.4"
|
||||
|
|
@ -2338,9 +2325,9 @@ __metadata:
|
|||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@docsearch/js-example@workspace:examples/js-demo":
|
||||
"@docsearch/js-example@workspace:examples/demo-js":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@docsearch/js-example@workspace:examples/js-demo"
|
||||
resolution: "@docsearch/js-example@workspace:examples/demo-js"
|
||||
dependencies:
|
||||
"@docsearch/css": "npm:4.0.0-beta.5"
|
||||
"@docsearch/js": "npm:4.0.0-beta.5"
|
||||
|
|
@ -2418,26 +2405,13 @@ __metadata:
|
|||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@docsearch/react-18-example@workspace:examples/demo-react-18":
|
||||
"@docsearch/react-example@workspace:examples/demo-react":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@docsearch/react-18-example@workspace:examples/demo-react-18"
|
||||
resolution: "@docsearch/react-example@workspace:examples/demo-react"
|
||||
dependencies:
|
||||
"@docsearch/css": "workspace:*"
|
||||
"@docsearch/react": "workspace:*"
|
||||
"@vitejs/plugin-react": "npm:^4.3.4"
|
||||
react: "npm:^18.0.0"
|
||||
react-dom: "npm:^18.0.0"
|
||||
vite: "npm:^6.0.7"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@docsearch/react-example@workspace:examples/demo":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@docsearch/react-example@workspace:examples/demo"
|
||||
dependencies:
|
||||
"@docsearch/css": "npm:4.0.0-beta.5"
|
||||
"@docsearch/react": "npm:4.0.0-beta.5"
|
||||
"@vitejs/plugin-react": "npm:^4.3.4"
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
vite: "npm:^6.0.7"
|
||||
|
|
|
|||
Loading…
Reference in a new issue