@voidzero-dev/vitepress-theme 4.4.2 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -4
- package/src/components/vitepress-default/VPAlgoliaSearchBox.vue +211 -106
- package/src/components/vitepress-default/VPLocalSearchBox.vue +11 -33
- package/src/components/vitepress-default/VPMenuLink.vue +2 -1
- package/src/components/vitepress-default/VPNavBar.vue +14 -20
- package/src/components/vitepress-default/VPNavBarAskAiButton.vue +31 -0
- package/src/components/vitepress-default/VPNavBarExtra.vue +7 -1
- package/src/components/vitepress-default/VPNavBarSearch.vue +119 -70
- package/src/components/vitepress-default/VPNavBarSearchButton.vue +43 -152
- package/src/components/vitepress-default/VPNavBarTranslations.vue +7 -1
- package/src/components/vitepress-default/VPNavScreen.vue +1 -3
- package/src/shims.d.ts +10 -0
- package/src/styles/base.css +2 -0
- package/src/styles/vitepress-default/base-scoped.css +2 -0
- package/src/styles/vitepress-default/base.css +2 -0
- package/src/styles/vitepress-default/docsearch.css +120 -0
- package/src/styles/vitepress-default/icons.css +4 -1
- package/src/styles/vitepress-default/vars.css +5 -1
- package/src/support/vitepress-default/docsearch.ts +253 -0
- package/src/support/vitepress-default/reactivity.ts +14 -0
- package/src/types/docsearch.d.ts +58 -26
- package/src/types/local-search.d.ts +26 -31
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
@import '@docsearch/css/dist/style.css';
|
|
2
|
+
@import '@docsearch/css/dist/sidepanel.css';
|
|
3
|
+
|
|
4
|
+
#vp-docsearch,
|
|
5
|
+
#vp-docsearch-sidepanel,
|
|
6
|
+
.DocSearch-SidepanelButton {
|
|
7
|
+
display: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
:root:root {
|
|
11
|
+
--docsearch-actions-height: auto;
|
|
12
|
+
--docsearch-actions-width: auto;
|
|
13
|
+
--docsearch-background-color: var(--vp-c-bg-soft);
|
|
14
|
+
--docsearch-container-background: var(--vp-backdrop-bg-color);
|
|
15
|
+
--docsearch-dropdown-menu-background: var(--vp-c-bg-elv);
|
|
16
|
+
--docsearch-dropdown-menu-item-hover-background: var(--vp-c-default-soft);
|
|
17
|
+
--docsearch-focus-color: var(--vp-c-brand-1);
|
|
18
|
+
--docsearch-footer-background: var(--vp-c-bg-alt);
|
|
19
|
+
--docsearch-highlight-color: var(--vp-c-brand-1);
|
|
20
|
+
--docsearch-hit-background: var(--vp-c-bg);
|
|
21
|
+
--docsearch-hit-color: var(--vp-c-text-1);
|
|
22
|
+
--docsearch-hit-highlight-color: var(--vp-c-brand-soft);
|
|
23
|
+
--docsearch-icon-color: var(--vp-c-text-2);
|
|
24
|
+
--docsearch-key-background: var(--vp-code-bg);
|
|
25
|
+
--docsearch-modal-background: var(--vp-c-bg-soft);
|
|
26
|
+
--docsearch-muted-color: var(--vp-c-text-2);
|
|
27
|
+
--docsearch-primary-color: var(--vp-c-brand-1);
|
|
28
|
+
--docsearch-searchbox-background: var(--vp-c-bg-alt);
|
|
29
|
+
--docsearch-searchbox-focus-background: transparent;
|
|
30
|
+
--docsearch-secondary-text-color: var(--vp-c-text-2);
|
|
31
|
+
--docsearch-sidepanel-accent-muted: var(--vp-c-text-3);
|
|
32
|
+
--docsearch-sidepanel-text-base: var(--vp-c-text-1);
|
|
33
|
+
--docsearch-soft-muted-color: var(--vp-c-default-soft);
|
|
34
|
+
--docsearch-soft-primary-color: var(--vp-c-brand-soft);
|
|
35
|
+
--docsearch-subtle-color: var(--vp-c-divider);
|
|
36
|
+
--docsearch-success-color: var(--vp-c-brand-soft);
|
|
37
|
+
--docsearch-text-color: var(--vp-c-text-1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:root.dark {
|
|
41
|
+
--docsearch-modal-shadow: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.DocSearch-AskAiScreen-RelatedSources-Item-Link {
|
|
45
|
+
padding: 8px 12px 8px 10px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.DocSearch-AskAiScreen-RelatedSources-Item-Link svg {
|
|
49
|
+
width: 16px;
|
|
50
|
+
height: 16px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.DocSearch-AskAiScreen-RelatedSources-Title {
|
|
54
|
+
padding-bottom: 0;
|
|
55
|
+
font-size: 12px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.DocSearch-Clear {
|
|
59
|
+
padding-right: 6px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.DocSearch-Commands-Key {
|
|
63
|
+
padding: 4px;
|
|
64
|
+
border: 1px solid var(--docsearch-subtle-color);
|
|
65
|
+
border-radius: 4px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.DocSearch-Hit a:focus-visible {
|
|
69
|
+
outline: 2px solid var(--docsearch-focus-color);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.DocSearch-Logo [class^='cls-'] {
|
|
73
|
+
fill: currentColor;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.DocSearch-Markdown-Content code {
|
|
77
|
+
padding: 0.2em 0.4em;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.DocSearch-Menu-content {
|
|
81
|
+
margin-top: -4px;
|
|
82
|
+
padding: 6px;
|
|
83
|
+
border: 1px solid var(--vp-c-divider);
|
|
84
|
+
border-radius: 6px;
|
|
85
|
+
box-shadow: var(--vp-shadow-2);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.DocSearch-Menu-item {
|
|
89
|
+
border-radius: 4px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.DocSearch-SearchBar + .DocSearch-Footer {
|
|
93
|
+
border-top-color: transparent;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.DocSearch-Sidepanel-Prompt--form {
|
|
97
|
+
border-color: var(--docsearch-subtle-color);
|
|
98
|
+
transition: border-color 0.2s;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.DocSearch-Sidepanel-Prompt--submit {
|
|
102
|
+
background-color: var(--docsearch-soft-primary-color);
|
|
103
|
+
color: var(--docsearch-primary-color);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.DocSearch-Sidepanel-Prompt--submit:hover {
|
|
107
|
+
background-color: var(--vp-button-brand-hover-bg);
|
|
108
|
+
color: var(--vp-button-brand-text);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.DocSearch-Sidepanel-Prompt--submit:disabled,
|
|
112
|
+
.DocSearch-Sidepanel-Prompt--submit[aria-disabled='true'] {
|
|
113
|
+
background-color: var(--docsearch-soft-muted-color);
|
|
114
|
+
color: var(--docsearch-muted-color);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.DocSearch-Title {
|
|
118
|
+
font-size: revert;
|
|
119
|
+
line-height: revert;
|
|
120
|
+
}
|
|
@@ -74,7 +74,10 @@
|
|
|
74
74
|
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2c-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z'/%3E%3C/svg%3E");
|
|
75
75
|
}
|
|
76
76
|
.vpi-search {
|
|
77
|
-
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='
|
|
77
|
+
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='m21 21l-4.34-4.34'/%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3C/g%3E%3C/svg%3E");
|
|
78
|
+
}
|
|
79
|
+
.vpi-sparkles {
|
|
80
|
+
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.8'%3E%3Cpath d='M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594zM20 2v4m2-2h-4'/%3E%3Ccircle cx='4' cy='20' r='2'/%3E%3C/g%3E%3C/svg%3E");
|
|
78
81
|
}
|
|
79
82
|
.vpi-layout-list {
|
|
80
83
|
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Crect width='7' height='7' x='3' y='3' rx='1'/%3E%3Crect width='7' height='7' x='3' y='14' rx='1'/%3E%3Cpath d='M14 4h7m-7 5h7m-7 6h7m-7 5h7'/%3E%3C/g%3E%3C/svg%3E");
|
|
@@ -396,6 +396,9 @@
|
|
|
396
396
|
:lang(zh) {
|
|
397
397
|
--vp-code-copy-copied-text-content: '已复制';
|
|
398
398
|
}
|
|
399
|
+
:lang(ja) {
|
|
400
|
+
--vp-code-copy-copied-text-content: 'コピー完了';
|
|
401
|
+
}
|
|
399
402
|
|
|
400
403
|
/**
|
|
401
404
|
* Component: Button
|
|
@@ -537,7 +540,8 @@
|
|
|
537
540
|
--vp-backdrop-bg-color: rgba(255, 255, 255, 0.7);
|
|
538
541
|
}
|
|
539
542
|
|
|
540
|
-
.dark
|
|
543
|
+
.dark,
|
|
544
|
+
[data-theme="dark"] {
|
|
541
545
|
--vp-backdrop-bg-color: rgba(22, 23, 29, 0.8);
|
|
542
546
|
}
|
|
543
547
|
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import type { DefaultTheme } from 'vitepress/theme'
|
|
2
|
+
import type { DocSearchAskAi } from '../../types/docsearch'
|
|
3
|
+
import { isObject } from '@vueuse/shared'
|
|
4
|
+
|
|
5
|
+
export type FacetFilter = string | string[] | FacetFilter[]
|
|
6
|
+
|
|
7
|
+
export interface ValidatedCredentials {
|
|
8
|
+
valid: boolean
|
|
9
|
+
appId?: string
|
|
10
|
+
apiKey?: string
|
|
11
|
+
indexName?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type DocSearchMode = 'auto' | 'sidePanel' | 'hybrid' | 'modal'
|
|
15
|
+
|
|
16
|
+
export interface ResolvedMode {
|
|
17
|
+
mode: DocSearchMode
|
|
18
|
+
showKeywordSearch: boolean
|
|
19
|
+
useSidePanel: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolves the effective mode based on config and available features.
|
|
24
|
+
*
|
|
25
|
+
* - 'auto': infer hybrid vs sidePanel-only from provided config
|
|
26
|
+
* - 'sidePanel': force sidePanel-only even if keyword search is configured
|
|
27
|
+
* - 'hybrid': force hybrid (error if keyword search is not configured)
|
|
28
|
+
* - 'modal': force modal even if sidePanel is configured
|
|
29
|
+
*/
|
|
30
|
+
export function resolveMode(
|
|
31
|
+
options: Pick<
|
|
32
|
+
DefaultTheme.AlgoliaSearchOptions,
|
|
33
|
+
'appId' | 'apiKey' | 'indexName' | 'askAi' | 'mode'
|
|
34
|
+
>
|
|
35
|
+
): ResolvedMode {
|
|
36
|
+
const mode = options.mode ?? 'auto'
|
|
37
|
+
const hasKeyword = hasKeywordSearch(options)
|
|
38
|
+
const askAi = options.askAi
|
|
39
|
+
const hasSidePanelConfig = Boolean(
|
|
40
|
+
askAi && typeof askAi === 'object' && askAi.sidePanel
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
switch (mode) {
|
|
44
|
+
case 'sidePanel':
|
|
45
|
+
// Force sidePanel-only - hide keyword search
|
|
46
|
+
return {
|
|
47
|
+
mode,
|
|
48
|
+
showKeywordSearch: false,
|
|
49
|
+
useSidePanel: true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
case 'hybrid':
|
|
53
|
+
// Force hybrid - keyword search must be configured
|
|
54
|
+
if (!hasKeyword) {
|
|
55
|
+
console.error(
|
|
56
|
+
'[vitepress] mode: "hybrid" requires keyword search credentials (appId, apiKey, indexName).'
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
mode,
|
|
61
|
+
showKeywordSearch: hasKeyword,
|
|
62
|
+
useSidePanel: true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
case 'modal':
|
|
66
|
+
// Force modal - don't use sidepanel for askai, even if configured
|
|
67
|
+
return {
|
|
68
|
+
mode,
|
|
69
|
+
showKeywordSearch: hasKeyword,
|
|
70
|
+
useSidePanel: false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
case 'auto':
|
|
74
|
+
default:
|
|
75
|
+
// Auto-detect based on config
|
|
76
|
+
return {
|
|
77
|
+
mode: 'auto',
|
|
78
|
+
showKeywordSearch: hasKeyword,
|
|
79
|
+
useSidePanel: hasSidePanelConfig
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function hasKeywordSearch(
|
|
85
|
+
options: Pick<
|
|
86
|
+
DefaultTheme.AlgoliaSearchOptions,
|
|
87
|
+
'appId' | 'apiKey' | 'indexName'
|
|
88
|
+
>
|
|
89
|
+
): boolean {
|
|
90
|
+
return Boolean(options.appId && options.apiKey && options.indexName)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function hasAskAi(
|
|
94
|
+
askAi: DefaultTheme.AlgoliaSearchOptions['askAi']
|
|
95
|
+
): boolean {
|
|
96
|
+
if (!askAi) return false
|
|
97
|
+
if (typeof askAi === 'string') return askAi.length > 0
|
|
98
|
+
return Boolean(askAi.assistantId)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Removes existing `lang:` filters and appends `lang:${lang}`.
|
|
103
|
+
* Handles both flat arrays and nested arrays (for OR conditions).
|
|
104
|
+
*/
|
|
105
|
+
export function mergeLangFacetFilters(
|
|
106
|
+
rawFacetFilters: FacetFilter | FacetFilter[] | undefined,
|
|
107
|
+
lang: string
|
|
108
|
+
): FacetFilter[] {
|
|
109
|
+
const input = Array.isArray(rawFacetFilters)
|
|
110
|
+
? rawFacetFilters
|
|
111
|
+
: rawFacetFilters
|
|
112
|
+
? [rawFacetFilters]
|
|
113
|
+
: []
|
|
114
|
+
|
|
115
|
+
const filtered = input
|
|
116
|
+
.map((filter) => {
|
|
117
|
+
if (Array.isArray(filter)) {
|
|
118
|
+
// Handle nested arrays (OR conditions)
|
|
119
|
+
return filter.filter(
|
|
120
|
+
(f) => typeof f === 'string' && !f.startsWith('lang:')
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
return filter
|
|
124
|
+
})
|
|
125
|
+
.filter((filter) => {
|
|
126
|
+
if (typeof filter === 'string') {
|
|
127
|
+
return !filter.startsWith('lang:')
|
|
128
|
+
}
|
|
129
|
+
// Keep nested arrays with remaining filters
|
|
130
|
+
return Array.isArray(filter) && filter.length > 0
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
return [...filtered, `lang:${lang}`]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Validates that required Algolia credentials are present.
|
|
138
|
+
*/
|
|
139
|
+
export function validateCredentials(
|
|
140
|
+
options: Pick<
|
|
141
|
+
DefaultTheme.AlgoliaSearchOptions,
|
|
142
|
+
'appId' | 'apiKey' | 'indexName'
|
|
143
|
+
>
|
|
144
|
+
): ValidatedCredentials {
|
|
145
|
+
const appId = options.appId
|
|
146
|
+
const apiKey = options.apiKey
|
|
147
|
+
const indexName = options.indexName
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
valid: Boolean(appId && apiKey && indexName),
|
|
151
|
+
appId,
|
|
152
|
+
apiKey,
|
|
153
|
+
indexName
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Builds Ask AI configuration from various input formats.
|
|
159
|
+
*/
|
|
160
|
+
export function buildAskAiConfig(
|
|
161
|
+
askAiProp: NonNullable<DefaultTheme.AlgoliaSearchOptions['askAi']>,
|
|
162
|
+
options: DefaultTheme.AlgoliaSearchOptions,
|
|
163
|
+
lang: string
|
|
164
|
+
): DocSearchAskAi {
|
|
165
|
+
const isAskAiString = typeof askAiProp === 'string'
|
|
166
|
+
|
|
167
|
+
const askAiSearchParameters =
|
|
168
|
+
!isAskAiString && askAiProp.searchParameters
|
|
169
|
+
? { ...askAiProp.searchParameters }
|
|
170
|
+
: undefined
|
|
171
|
+
|
|
172
|
+
// If Ask AI defines its own facetFilters, merge lang filtering into those.
|
|
173
|
+
// Otherwise, reuse the keyword search facetFilters so Ask AI follows the
|
|
174
|
+
// same language filtering behavior by default.
|
|
175
|
+
const askAiFacetFiltersSource =
|
|
176
|
+
askAiSearchParameters?.facetFilters ??
|
|
177
|
+
options.searchParameters?.facetFilters
|
|
178
|
+
const askAiFacetFilters = mergeLangFacetFilters(
|
|
179
|
+
askAiFacetFiltersSource as FacetFilter | FacetFilter[] | undefined,
|
|
180
|
+
lang
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
const mergedAskAiSearchParameters = {
|
|
184
|
+
...askAiSearchParameters,
|
|
185
|
+
facetFilters: askAiFacetFilters.length ? askAiFacetFilters : undefined
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const result: Record<string, any> = {
|
|
189
|
+
...(isAskAiString ? {} : askAiProp),
|
|
190
|
+
indexName: isAskAiString ? options.indexName : askAiProp.indexName,
|
|
191
|
+
apiKey: isAskAiString ? options.apiKey : askAiProp.apiKey,
|
|
192
|
+
appId: isAskAiString ? options.appId : askAiProp.appId,
|
|
193
|
+
assistantId: isAskAiString ? askAiProp : askAiProp.assistantId
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Keep `searchParameters` undefined unless it has at least one key.
|
|
197
|
+
if (Object.values(mergedAskAiSearchParameters).some((v) => v != null)) {
|
|
198
|
+
result.searchParameters = mergedAskAiSearchParameters
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return result
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Resolves Algolia search options for the given language,
|
|
206
|
+
* merging in locale-specific overrides and language facet filters.
|
|
207
|
+
*/
|
|
208
|
+
export function resolveOptionsForLanguage(
|
|
209
|
+
options: DefaultTheme.AlgoliaSearchOptions,
|
|
210
|
+
localeIndex: string,
|
|
211
|
+
lang: string
|
|
212
|
+
): DefaultTheme.AlgoliaSearchOptions {
|
|
213
|
+
options = deepMerge(options, options.locales?.[localeIndex] || {})
|
|
214
|
+
|
|
215
|
+
const facetFilters = mergeLangFacetFilters(
|
|
216
|
+
options.searchParameters?.facetFilters,
|
|
217
|
+
lang
|
|
218
|
+
)
|
|
219
|
+
const askAi = options.askAi
|
|
220
|
+
? buildAskAiConfig(options.askAi, options, lang)
|
|
221
|
+
: undefined
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
...options,
|
|
225
|
+
searchParameters: { ...options.searchParameters, facetFilters },
|
|
226
|
+
askAi
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function deepMerge<T>(target: T, source: Partial<T>): T {
|
|
231
|
+
const result = { ...target } as any
|
|
232
|
+
|
|
233
|
+
for (const key in source) {
|
|
234
|
+
const value = source[key]
|
|
235
|
+
if (value === undefined) continue
|
|
236
|
+
|
|
237
|
+
// special case: replace entirely
|
|
238
|
+
if (key === 'searchParameters') {
|
|
239
|
+
result[key] = value
|
|
240
|
+
continue
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// deep-merge only plain objects; arrays are replaced entirely
|
|
244
|
+
if (isObject(value) && isObject(result[key])) {
|
|
245
|
+
result[key] = deepMerge(result[key], value)
|
|
246
|
+
} else {
|
|
247
|
+
result[key] = value
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
delete result.locales
|
|
252
|
+
return result
|
|
253
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ComputedRef, computed } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function smartComputed<T>(
|
|
4
|
+
getter: () => T,
|
|
5
|
+
comparator = (oldValue: T, newValue: T) =>
|
|
6
|
+
JSON.stringify(oldValue) === JSON.stringify(newValue)
|
|
7
|
+
): ComputedRef<T> {
|
|
8
|
+
return computed((oldValue) => {
|
|
9
|
+
const newValue = getter()
|
|
10
|
+
return oldValue === undefined || !comparator(oldValue, newValue)
|
|
11
|
+
? newValue
|
|
12
|
+
: oldValue
|
|
13
|
+
})
|
|
14
|
+
}
|
package/src/types/docsearch.d.ts
CHANGED
|
@@ -1,31 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* These are optional peer dependencies that may or may not be installed.
|
|
5
|
-
* The declarations ensure TypeScript doesn't error on dynamic imports.
|
|
6
|
-
*/
|
|
1
|
+
import { type DocSearchProps as DocSearchPropsJS } from '@docsearch/js'
|
|
2
|
+
import { type SidepanelProps as SidepanelPropsBase } from '@docsearch/sidepanel-js'
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
export type DocSearchProps = Partial<
|
|
5
|
+
Pick<
|
|
6
|
+
DocSearchPropsJS,
|
|
7
|
+
| 'appId'
|
|
8
|
+
| 'apiKey'
|
|
9
|
+
| 'placeholder'
|
|
10
|
+
| 'maxResultsPerGroup'
|
|
11
|
+
| 'disableUserPersonalization'
|
|
12
|
+
| 'initialQuery'
|
|
13
|
+
| 'translations'
|
|
14
|
+
| 'recentSearchesLimit'
|
|
15
|
+
| 'recentSearchesWithFavoritesLimit'
|
|
16
|
+
>
|
|
17
|
+
> & {
|
|
18
|
+
/**
|
|
19
|
+
* Name of the algolia index to query.
|
|
20
|
+
*/
|
|
21
|
+
indexName?: string
|
|
22
|
+
/**
|
|
23
|
+
* Additional algolia search parameters to merge into each query.
|
|
24
|
+
*/
|
|
25
|
+
searchParameters?: DocSearchPropsJS['searchParameters']
|
|
26
|
+
/**
|
|
27
|
+
* Insights client integration options to send analytics events.
|
|
28
|
+
*/
|
|
29
|
+
insights?: boolean
|
|
30
|
+
/**
|
|
31
|
+
* Configuration or assistant id to enable ask ai mode. Pass a string assistant id or a full config object.
|
|
32
|
+
*/
|
|
33
|
+
askAi?: DocSearchAskAi | string
|
|
34
|
+
/**
|
|
35
|
+
* Ask AI side panel integration mode.
|
|
36
|
+
*
|
|
37
|
+
* - 'auto': infer hybrid vs sidePanel-only from provided config
|
|
38
|
+
* - 'sidePanel': force sidePanel-only even if keyword search is configured
|
|
39
|
+
* - 'hybrid': force hybrid (error if keyword search is not configured)
|
|
40
|
+
* - 'modal': force modal even if sidePanel is configured (ask ai in modal stays in modal)
|
|
41
|
+
*
|
|
42
|
+
* @default 'auto'
|
|
43
|
+
*/
|
|
44
|
+
mode?: 'auto' | 'sidePanel' | 'hybrid' | 'modal'
|
|
12
45
|
}
|
|
13
46
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
transformItems?: (items: any[]) => any[]
|
|
23
|
-
navigator?: {
|
|
24
|
-
navigate?: (params: { itemUrl: string }) => void
|
|
25
|
-
}
|
|
26
|
-
[key: string]: any
|
|
27
|
-
}
|
|
47
|
+
export type DocSearchAskAi = Partial<
|
|
48
|
+
Exclude<DocSearchPropsJS['askAi'], string | undefined>
|
|
49
|
+
> & {
|
|
50
|
+
/**
|
|
51
|
+
* Ask AI side panel configuration.
|
|
52
|
+
*/
|
|
53
|
+
sidePanel?: boolean | SidepanelProps
|
|
54
|
+
}
|
|
28
55
|
|
|
29
|
-
|
|
30
|
-
|
|
56
|
+
export type SidepanelProps = Partial<
|
|
57
|
+
Pick<SidepanelPropsBase, 'button' | 'keyboardShortcuts'>
|
|
58
|
+
> & {
|
|
59
|
+
/**
|
|
60
|
+
* Props specific to the Sidepanel panel.
|
|
61
|
+
*/
|
|
62
|
+
panel?: Omit<NonNullable<SidepanelPropsBase['panel']>, 'portalContainer'>
|
|
31
63
|
}
|
|
@@ -1,38 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export interface LocalSearchTranslations {
|
|
2
|
+
button?: ButtonTranslations
|
|
3
|
+
modal?: ModalTranslations
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface ButtonTranslations {
|
|
7
|
+
buttonText?: string
|
|
8
|
+
buttonAriaLabel?: string
|
|
9
|
+
}
|
|
4
10
|
|
|
5
11
|
export interface ModalTranslations {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
footer?: {
|
|
12
|
-
selectText?: string
|
|
13
|
-
selectKeyAriaLabel?: string
|
|
14
|
-
navigateText?: string
|
|
15
|
-
navigateUpKeyAriaLabel?: string
|
|
16
|
-
navigateDownKeyAriaLabel?: string
|
|
17
|
-
closeText?: string
|
|
18
|
-
closeKeyAriaLabel?: string
|
|
19
|
-
}
|
|
20
|
-
}
|
|
12
|
+
displayDetails?: string
|
|
13
|
+
resetButtonTitle?: string
|
|
14
|
+
backButtonTitle?: string
|
|
15
|
+
noResultsText?: string
|
|
16
|
+
footer?: FooterTranslations
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
export interface
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
export interface FooterTranslations {
|
|
20
|
+
selectText?: string
|
|
21
|
+
selectKeyAriaLabel?: string
|
|
22
|
+
navigateText?: string
|
|
23
|
+
navigateUpKeyAriaLabel?: string
|
|
24
|
+
navigateDownKeyAriaLabel?: string
|
|
25
|
+
closeText?: string
|
|
26
|
+
closeKeyAriaLabel?: string
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*/
|
|
35
|
-
declare module '@localSearchIndex' {
|
|
36
|
-
const data: Record<string, () => Promise<{ default: string }>>
|
|
37
|
-
export default data
|
|
29
|
+
export interface PageSplitSection {
|
|
30
|
+
anchor?: string
|
|
31
|
+
titles: string[]
|
|
32
|
+
text: string
|
|
38
33
|
}
|