valaxy-theme-press 0.28.0 → 0.28.2

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.
@@ -1,9 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import type { DocSearchInstance, DocSearchProps } from '@docsearch/js'
3
3
  import type { SidepanelInstance, SidepanelProps } from '@docsearch/sidepanel-js'
4
- import type { AlgoliaSearchOptions } from 'valaxy-addon-algolia'
5
- import { useAddonAlgoliaConfig } from 'valaxy-addon-algolia'
6
- import { nextTick, onUnmounted, watch } from 'vue'
4
+ import type { AlgoliaSearchOptions } from '../types/algolia'
5
+ import { nextTick, onUnmounted, ref, watch } from 'vue'
7
6
  import { useRouter } from 'vue-router'
8
7
 
9
8
  import '../styles/docsearch.css'
@@ -16,7 +15,15 @@ const props = defineProps<{
16
15
  }>()
17
16
 
18
17
  const router = useRouter()
19
- const algoliaConfig = useAddonAlgoliaConfig()
18
+
19
+ // Dynamically import addon to avoid hard dependency
20
+ const algoliaConfig = ref<{ options?: AlgoliaSearchOptions }>()
21
+ import('valaxy-addon-algolia').then(({ useAddonAlgoliaConfig }) => {
22
+ // Direct assignment — the synchronous watcher below will react to this change
23
+ algoliaConfig.value = useAddonAlgoliaConfig().value
24
+ }).catch(() => {
25
+ console.warn('[valaxy-theme-press] valaxy-addon-algolia is not installed. Algolia search will not work.')
26
+ })
20
27
 
21
28
  let cleanup = () => {}
22
29
  let docsearchInstance: DocSearchInstance | undefined
@@ -15,7 +15,7 @@ defineProps<{
15
15
  position: fixed;
16
16
  inset: 0;
17
17
  z-index: var(--pr-z-backdrop);
18
- background: rgba(0, 0, 0, .6);
18
+ background: rgb(0 0 0 / .6);
19
19
  transition: opacity var(--va-transition-duration-moderate);
20
20
 
21
21
  .fade-enter-from,
@@ -56,7 +56,7 @@ const control = usePrevNext()
56
56
  gap: 1rem;
57
57
  }
58
58
 
59
- @media (min-width: 640px) {
59
+ @media (width >= 640px) {
60
60
  .prev-next {
61
61
  grid-template-columns: repeat(2, 1fr);
62
62
  }
@@ -1,14 +1,11 @@
1
1
  <script lang="ts" setup>
2
2
  import { useFrontmatter } from 'valaxy'
3
3
  import { computed } from 'vue'
4
- import { useI18n } from 'vue-i18n'
5
4
  import { useLocaleConfig } from '../composables'
6
5
  import PressButton from './PressButton.vue'
7
6
 
8
7
  const fm = useFrontmatter()
9
8
 
10
- const { t } = useI18n()
11
-
12
9
  const { currentLocale, currentLocaleKey, hasLocales } = useLocaleConfig()
13
10
 
14
11
  /**
@@ -31,36 +28,39 @@ const actions = computed(() => {
31
28
  </script>
32
29
 
33
30
  <template>
34
- <div text="center" m="md:t-24 t-10 md:t-20" flex="~ col" justify="center" items="center">
35
- <ValaxyLogo mb="2" />
36
- <h1 my="10" text="4xl md:8xl" font="black" class="gradient-text from-purple-800 to-blue-500 bg-gradient-to-r">
37
- {{ fm.hero.name }}
38
- </h1>
39
- </div>
31
+ <template v-if="fm.hero">
32
+ <div text="center" m="md:t-24 t-10 md:t-20" flex="~ col" justify="center" items="center">
33
+ <ValaxyLogo mb="2" />
34
+ <h1 my="10" text="4xl md:8xl" font="black" class="gradient-text from-purple-800 to-blue-500 bg-gradient-to-r">
35
+ {{ fm.hero.name }}
36
+ </h1>
37
+ </div>
38
+
39
+ <h2 v-if="fm.hero?.text" flex="~ wrap justify-center" px="2" m="b-10" text="center 6xl" font="black" leading="tight">
40
+ {{ fm.hero.text }}
41
+ </h2>
40
42
 
41
- <h2 flex="~ wrap justify-center" px="2" m="b-10" text="center 6xl" font="black" leading="tight">
42
- <span mx-1>{{ t('banner.next-generation') }}</span>
43
- <span mx-1>{{ t('banner.static') }} </span>
44
- <span mx-1 class="gradient-text from-blue-500 to-purple-700 bg-gradient-to-r">{{ t('banner.blog') }}</span>
45
- <span mx-1 class="break-keep">{{ t('banner.framework') }}</span>
46
- </h2>
43
+ <p v-if="fm.hero?.tagline" m="b-10" text="center xl" op="80">
44
+ {{ fm.hero.tagline }}
45
+ </p>
47
46
 
48
- <div p="2" text="center" class="flex justify-center items-center">
49
- <template v-for="action in actions" :key="action.link">
50
- <PressGetStarted
51
- v-if="action.type === 'fly'"
52
- :theme="action.theme"
53
- :link="action.link"
54
- :text="action.text"
55
- />
56
- <PressButton
57
- v-else
58
- :theme="action.theme"
59
- :link="action.link"
60
- :text="action.text"
61
- />
62
- </template>
63
- </div>
47
+ <div v-if="actions.length" p="2" text="center" class="flex justify-center items-center">
48
+ <template v-for="action in actions" :key="action.link">
49
+ <PressGetStarted
50
+ v-if="action.type === 'fly'"
51
+ :theme="action.theme"
52
+ :link="action.link"
53
+ :text="action.text"
54
+ />
55
+ <PressButton
56
+ v-else
57
+ :theme="action.theme"
58
+ :link="action.link"
59
+ :text="action.text"
60
+ />
61
+ </template>
62
+ </div>
64
63
 
65
- <br>
64
+ <br>
65
+ </template>
66
66
  </template>
@@ -0,0 +1,19 @@
1
+ <script lang="ts" setup>
2
+ import { ref } from 'vue'
3
+
4
+ const open = ref(false)
5
+
6
+ function toggleSearch() {
7
+ open.value = !open.value
8
+ }
9
+
10
+ defineExpose({
11
+ open,
12
+ toggleSearch,
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <PressFuseSearchButton :open="open" @toggle="toggleSearch" />
18
+ <PressLocalSearchModal :open="open" @close="open = false" />
19
+ </template>
@@ -0,0 +1,502 @@
1
+ <script lang="ts" setup>
2
+ import { isClient, useScrollLock } from '@vueuse/core'
3
+ import { nextTick, ref, watch } from 'vue'
4
+ import { useI18n } from 'vue-i18n'
5
+
6
+ const props = defineProps<{
7
+ open: boolean
8
+ }>()
9
+
10
+ const emit = defineEmits(['close'])
11
+
12
+ const isLocked = useScrollLock(isClient ? document.documentElement : null)
13
+ const { t } = useI18n()
14
+
15
+ const searchInputRef = ref<HTMLInputElement>()
16
+
17
+ function handleModalClick(event: Event) {
18
+ if (event.target === event.currentTarget) {
19
+ emit('close')
20
+ }
21
+ }
22
+
23
+ watch(() => props.open, (val) => {
24
+ if (val) {
25
+ nextTick(() => {
26
+ searchInputRef.value?.focus()
27
+ })
28
+ }
29
+ })
30
+ </script>
31
+
32
+ <template>
33
+ <Teleport to="body">
34
+ <Transition
35
+ name="modal"
36
+ @enter="isLocked = true"
37
+ @after-leave="isLocked = false"
38
+ >
39
+ <div
40
+ v-if="open"
41
+ class="press-search-modal"
42
+ @click="handleModalClick"
43
+ >
44
+ <div class="press-search-content">
45
+ <ValaxyLocalSearch :open="open" @close="emit('close')">
46
+ <template #default="{ query, results, loading, selectedIndex, updateQuery, navigate, onKeydown, getPageTitle, getSectionTitle }">
47
+ <div class="press-search-header">
48
+ <div class="press-search-input-wrapper">
49
+ <div class="press-search-icon">
50
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
51
+ <path d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" fill="currentColor" />
52
+ </svg>
53
+ </div>
54
+ <input
55
+ ref="searchInputRef"
56
+ :value="query"
57
+ class="press-search-input"
58
+ :placeholder="t('search.placeholder')"
59
+ @input="updateQuery(($event.target as HTMLInputElement).value)"
60
+ @keydown="onKeydown"
61
+ >
62
+ <button
63
+ class="press-search-close"
64
+ @click="emit('close')"
65
+ >
66
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
67
+ <path d="M6.28 5.72a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.72z" fill="currentColor" />
68
+ </svg>
69
+ </button>
70
+ </div>
71
+ </div>
72
+
73
+ <div v-if="loading" class="press-search-hits">
74
+ <span class="press-search-hits-text">{{ t('search.loading') }}</span>
75
+ </div>
76
+
77
+ <div v-if="query" class="press-search-hits">
78
+ <span class="press-search-hits-text">
79
+ {{ t('search.hits', results.length || 0) }}
80
+ </span>
81
+ </div>
82
+
83
+ <div v-if="results.length > 0" class="press-search-results">
84
+ <div class="press-search-container">
85
+ <div class="press-result-list">
86
+ <a
87
+ v-for="(result, index) in results"
88
+ :key="result.id"
89
+ class="press-result-item"
90
+ :class="{ 'press-result-item-active': index === selectedIndex }"
91
+ :style="{ animationDelay: `${index * 50}ms` }"
92
+ href="javascript:void(0)"
93
+ @click="navigate(result)"
94
+ >
95
+ <div class="press-result-content">
96
+ <div class="press-result-breadcrumb">
97
+ <span class="press-result-page">{{ getPageTitle(result.id) }}</span>
98
+ <span v-for="(title, i) in result.titles" :key="i" class="press-result-crumb">
99
+ &rsaquo; {{ title }}
100
+ </span>
101
+ </div>
102
+ <h3 class="press-result-title">
103
+ {{ result.title }}
104
+ </h3>
105
+ <div class="press-result-meta">
106
+ <span class="press-result-link">
107
+ {{ getSectionTitle(result) }}
108
+ </span>
109
+ <span class="press-result-score">
110
+ <span class="press-result-score-label">Score:</span>
111
+ <span class="press-result-score-value">{{ result.score.toFixed(1) }}</span>
112
+ </span>
113
+ </div>
114
+ </div>
115
+ <div class="press-result-arrow">
116
+ <svg width="16" height="16" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
117
+ <path d="M7.5 4.5L12.5 9.5L7.5 14.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
118
+ </svg>
119
+ </div>
120
+ </a>
121
+ </div>
122
+ </div>
123
+
124
+ <div class="press-search-footer">
125
+ <div class="press-search-footer-content">
126
+ <span class="press-search-powered-by">Search by MiniSearch</span>
127
+ </div>
128
+ </div>
129
+ </div>
130
+
131
+ <div v-else-if="query && results.length === 0 && !loading" class="press-search-empty">
132
+ <div class="press-search-empty-icon">
133
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
134
+ <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
135
+ </svg>
136
+ </div>
137
+ <p class="press-search-empty-text">
138
+ {{ t('search.no_results') }}
139
+ </p>
140
+ <p class="press-search-empty-subtext">
141
+ {{ t('search.no_results_hint') }}
142
+ </p>
143
+ </div>
144
+ </template>
145
+ </ValaxyLocalSearch>
146
+ </div>
147
+ </div>
148
+ </Transition>
149
+ </Teleport>
150
+ </template>
151
+
152
+ <style lang="scss" scoped>
153
+ .press-search-modal {
154
+ position: fixed;
155
+ top: 0;
156
+ left: 0;
157
+ width: 100%;
158
+ height: 100%;
159
+ display: flex;
160
+ flex-direction: column;
161
+ align-items: center;
162
+ padding-top: 2rem;
163
+ backdrop-filter: blur(20px);
164
+ z-index: var(--pr-z-search);
165
+ background-color: rgb(0 0 0 / 0.4);
166
+ pointer-events: auto;
167
+ }
168
+
169
+ .press-search-content {
170
+ width: 100%;
171
+ max-width: 680px;
172
+ padding: 0 1.5rem;
173
+ display: flex;
174
+ flex-direction: column;
175
+ align-items: center;
176
+ }
177
+
178
+ .press-search-header {
179
+ width: 100%;
180
+ margin-bottom: 1.5rem;
181
+ }
182
+
183
+ .press-search-input-wrapper {
184
+ position: relative;
185
+ width: 100%;
186
+ max-width: 600px;
187
+ margin: 0 auto;
188
+ display: flex;
189
+ align-items: center;
190
+ background: var(--vp-c-bg);
191
+ border-radius: 12px;
192
+ border: 2px solid var(--vp-c-divider);
193
+ box-shadow: 0 4px 20px rgb(0 0 0 / 0.1);
194
+ transition: all var(--va-transition-duration-fast) ease;
195
+
196
+ &:focus-within {
197
+ border-color: var(--vp-c-brand);
198
+ box-shadow: 0 4px 20px rgb(var(--vp-c-brand-rgb), 0.15);
199
+ }
200
+ }
201
+
202
+ .press-search-icon {
203
+ display: flex;
204
+ align-items: center;
205
+ justify-content: center;
206
+ width: 48px;
207
+ height: 48px;
208
+ color: var(--vp-c-text-3);
209
+ flex-shrink: 0;
210
+ }
211
+
212
+ .press-search-input {
213
+ flex: 1;
214
+ padding: 0.875rem 0;
215
+ font-size: 1.125rem;
216
+ line-height: 1.5;
217
+ color: var(--vp-c-text-1);
218
+ background: transparent;
219
+ border: none;
220
+ outline: none;
221
+ font-weight: 500;
222
+
223
+ &::placeholder {
224
+ color: var(--vp-c-text-3);
225
+ font-weight: 400;
226
+ }
227
+ }
228
+
229
+ .press-search-close {
230
+ display: flex;
231
+ align-items: center;
232
+ justify-content: center;
233
+ width: 48px;
234
+ height: 48px;
235
+ color: var(--vp-c-text-3);
236
+ background: transparent;
237
+ border: none;
238
+ cursor: pointer;
239
+ border-radius: 8px;
240
+ transition: all var(--va-transition-duration-fast) ease;
241
+ flex-shrink: 0;
242
+
243
+ &:hover {
244
+ color: var(--vp-c-text-1);
245
+ }
246
+ }
247
+
248
+ .press-search-hits {
249
+ width: 100%;
250
+ max-width: 600px;
251
+ padding: 0.75rem 0;
252
+ margin-bottom: 1rem;
253
+ }
254
+
255
+ .press-search-hits-text {
256
+ color: var(--vp-c-text-2);
257
+ font-size: 0.875rem;
258
+ font-weight: 500;
259
+ }
260
+
261
+ .press-search-results {
262
+ width: 100%;
263
+ max-width: 600px;
264
+ max-height: calc(100vh - 280px);
265
+ display: flex;
266
+ flex-direction: column;
267
+ border-radius: 12px;
268
+ background: var(--vp-c-bg);
269
+ box-shadow: 0 4px 20px rgb(0 0 0 / 0.1);
270
+ overflow: hidden;
271
+ }
272
+
273
+ .press-search-container {
274
+ flex: 1;
275
+ overflow-y: auto;
276
+ min-height: 0;
277
+ }
278
+
279
+ .press-result-list {
280
+ width: 100%;
281
+ flex: 1;
282
+ }
283
+
284
+ .press-result-item {
285
+ display: flex;
286
+ align-items: center;
287
+ padding: 1rem 1.25rem;
288
+ color: var(--vp-c-text-1);
289
+ text-decoration: none;
290
+ transition: all var(--va-transition-duration-fast) ease;
291
+ animation: slide-in-up 0.3s ease forwards;
292
+ opacity: 0;
293
+ transform: translateY(10px);
294
+
295
+ &:hover,
296
+ &.press-result-item-active {
297
+ background-color: var(--vp-c-bg-soft);
298
+ transform: translateY(-1px);
299
+ }
300
+
301
+ &:not(:last-child) {
302
+ border-bottom: 1px solid var(--vp-c-divider);
303
+ }
304
+ }
305
+
306
+ .press-result-content {
307
+ flex: 1;
308
+ min-width: 0;
309
+ }
310
+
311
+ .press-result-breadcrumb {
312
+ margin-bottom: 0.25rem;
313
+ font-size: 0.75rem;
314
+ color: var(--vp-c-text-3);
315
+ }
316
+
317
+ .press-result-page {
318
+ font-weight: 500;
319
+ }
320
+
321
+ .press-result-crumb {
322
+ margin-left: 0.125rem;
323
+ }
324
+
325
+ .press-result-title {
326
+ margin: 0 0 0.5rem;
327
+ font-size: 1rem;
328
+ font-weight: 600;
329
+ line-height: 1.4;
330
+ color: var(--vp-c-text-1);
331
+ display: -webkit-box;
332
+ line-clamp: 2;
333
+ -webkit-box-orient: vertical;
334
+ overflow: hidden;
335
+ }
336
+
337
+ .press-result-meta {
338
+ display: flex;
339
+ justify-content: space-between;
340
+ align-items: center;
341
+ font-size: 0.75rem;
342
+ line-height: 1.4;
343
+ color: var(--vp-c-text-3);
344
+ }
345
+
346
+ .press-result-link {
347
+ flex: 1;
348
+ min-width: 0;
349
+ overflow: hidden;
350
+ text-overflow: ellipsis;
351
+ white-space: nowrap;
352
+ }
353
+
354
+ .press-result-score {
355
+ display: flex;
356
+ align-items: center;
357
+ gap: 0.25rem;
358
+ flex-shrink: 0;
359
+ margin-left: 1rem;
360
+ }
361
+
362
+ .press-result-score-label {
363
+ color: var(--vp-c-text-3);
364
+ }
365
+
366
+ .press-result-score-value {
367
+ color: var(--vp-c-brand);
368
+ font-weight: 600;
369
+ }
370
+
371
+ .press-result-arrow {
372
+ display: flex;
373
+ align-items: center;
374
+ justify-content: center;
375
+ width: 24px;
376
+ height: 24px;
377
+ color: var(--vp-c-text-3);
378
+ margin-left: 0.75rem;
379
+ flex-shrink: 0;
380
+ transition: all var(--va-transition-duration-fast) ease;
381
+ }
382
+
383
+ .press-result-item:hover .press-result-arrow {
384
+ color: var(--vp-c-brand);
385
+ transform: translateX(2px);
386
+ }
387
+
388
+ .press-search-footer {
389
+ width: 100%;
390
+ padding: 0.75rem 1.25rem;
391
+ border-top: 1px solid var(--vp-c-divider);
392
+ background: var(--vp-c-bg-soft);
393
+ flex-shrink: 0;
394
+ border-bottom-left-radius: 12px;
395
+ border-bottom-right-radius: 12px;
396
+ }
397
+
398
+ .press-search-footer-content {
399
+ display: flex;
400
+ justify-content: flex-end;
401
+ align-items: center;
402
+ }
403
+
404
+ .press-search-powered-by {
405
+ font-size: 0.75rem;
406
+ color: var(--vp-c-text-3);
407
+ font-weight: 500;
408
+ }
409
+
410
+ .press-search-empty {
411
+ width: 100%;
412
+ max-width: 600px;
413
+ padding: 3rem 1rem;
414
+ text-align: center;
415
+ }
416
+
417
+ .press-search-empty-icon {
418
+ display: flex;
419
+ justify-content: center;
420
+ margin-bottom: 1rem;
421
+ color: var(--vp-c-text-3);
422
+ }
423
+
424
+ .press-search-empty-text {
425
+ margin: 0 0 0.5rem;
426
+ font-size: 1.125rem;
427
+ font-weight: 600;
428
+ color: var(--vp-c-text-1);
429
+ }
430
+
431
+ .press-search-empty-subtext {
432
+ margin: 0;
433
+ font-size: 0.875rem;
434
+ color: var(--vp-c-text-2);
435
+ }
436
+
437
+ @keyframes slide-in-up {
438
+ to {
439
+ opacity: 1;
440
+ transform: translateY(0);
441
+ }
442
+ }
443
+
444
+ .modal-enter-active,
445
+ .modal-leave-active {
446
+ transition: all var(--va-transition-duration) cubic-bezier(0.4, 0, 0.2, 1);
447
+ }
448
+
449
+ .modal-enter-from {
450
+ opacity: 0;
451
+ transform: scale(0.95);
452
+ }
453
+
454
+ .modal-leave-to {
455
+ opacity: 0;
456
+ transform: scale(0.95);
457
+ }
458
+
459
+ // Scrollbar styling
460
+ .press-search-container::-webkit-scrollbar {
461
+ width: 6px;
462
+ }
463
+
464
+ .press-search-container::-webkit-scrollbar-track {
465
+ background: transparent;
466
+ }
467
+
468
+ .press-search-container::-webkit-scrollbar-thumb {
469
+ background: var(--vp-c-divider);
470
+ border-radius: 3px;
471
+ }
472
+
473
+ .press-search-container::-webkit-scrollbar-thumb:hover {
474
+ background: var(--vp-c-text-3);
475
+ }
476
+
477
+ @media (width <= 768px) {
478
+ .press-search-modal {
479
+ padding-top: 1rem;
480
+ }
481
+
482
+ .press-search-content {
483
+ padding: 0 1rem;
484
+ }
485
+
486
+ .press-search-input-wrapper {
487
+ max-width: 100%;
488
+ }
489
+
490
+ .press-search-results {
491
+ max-height: calc(100vh - 240px);
492
+ }
493
+
494
+ .press-result-item {
495
+ padding: 0.875rem 1rem;
496
+ }
497
+
498
+ .press-result-title {
499
+ font-size: 0.9375rem;
500
+ }
501
+ }
502
+ </style>
@@ -43,11 +43,11 @@ const homeLink = computed(() => hasLocales.value ? currentLocale.value.link : '/
43
43
  @use 'valaxy/client/styles/mixins/index.scss' as *;
44
44
 
45
45
  :root {
46
- --pr-navbar-c-bg: rgba(255, 255, 255, 0.8);
46
+ --pr-navbar-c-bg: rgb(255 255 255 / 0.8);
47
47
  }
48
48
 
49
49
  .dark {
50
- --pr-navbar-c-bg: rgba(24, 24, 24, 0.3);
50
+ --pr-navbar-c-bg: rgb(24 24 24 / 0.3);
51
51
  }
52
52
 
53
53
  .logo {
@@ -81,11 +81,11 @@ const homeLink = computed(() => hasLocales.value ? currentLocale.value.link : '/
81
81
 
82
82
  @supports not (backdrop-filter: saturate(50%) blur(8px)) {
83
83
  .pr-navbar.has-sidebar .content {
84
- background: rgba(255, 255, 255, 0.95);
84
+ background: rgb(255 255 255 / 0.95);
85
85
  }
86
86
 
87
87
  .dark .pr-navbar.has-sidebar .content {
88
- background: rgba(36, 36, 36, 0.95);
88
+ background: rgb(36 36 36 / 0.95);
89
89
  }
90
90
  }
91
91
  }
@@ -1,4 +1,5 @@
1
1
  <script lang="ts" setup>
2
+ import type { AlgoliaSearchOptions } from '../types/algolia'
2
3
  import { onKeyStroke } from '@vueuse/core'
3
4
  import { useSiteConfig } from 'valaxy'
4
5
  import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
@@ -9,6 +10,7 @@ const siteConfig = useSiteConfig()
9
10
 
10
11
  const isAlgolia = computed(() => siteConfig.value.search.provider === 'algolia')
11
12
  const isFuse = computed(() => siteConfig.value.search.provider === 'fuse')
13
+ const isLocal = computed(() => siteConfig.value.search.provider === 'local')
12
14
 
13
15
  // Whether to show the Ask AI button (requires askAi config in addon-algolia)
14
16
  const showAskAi = ref(false)
@@ -16,7 +18,7 @@ const showAskAi = ref(false)
16
18
  if (isAlgolia.value) {
17
19
  import('valaxy-addon-algolia').then(({ useAddonAlgoliaConfig }) => {
18
20
  const algoliaConfig = useAddonAlgoliaConfig()
19
- const askAi = algoliaConfig.value?.options?.askAi
21
+ const askAi = (algoliaConfig.value?.options as AlgoliaSearchOptions | undefined)?.askAi
20
22
  showAskAi.value = !!askAi
21
23
  }).catch(() => {})
22
24
  }
@@ -29,6 +31,10 @@ const PressFuseSearch = isFuse.value
29
31
  ? defineAsyncComponent(() => import('./PressFuseSearch.vue'))
30
32
  : () => null
31
33
 
34
+ const PressLocalSearch = isLocal.value
35
+ ? defineAsyncComponent(() => import('./PressLocalSearch.vue'))
36
+ : () => null
37
+
32
38
  // #region Algolia lazy loading
33
39
 
34
40
  type OpenTarget = 'search' | 'askAi' | 'toggleAskAi'
@@ -105,6 +111,17 @@ function loadAndOpen(target: OpenTarget) {
105
111
 
106
112
  // #endregion
107
113
 
114
+ // Local search keyboard shortcut
115
+ const localSearchRef = ref()
116
+ if (isLocal.value) {
117
+ onKeyStroke('k', (event) => {
118
+ if (event.ctrlKey || event.metaKey) {
119
+ event.preventDefault()
120
+ localSearchRef.value?.toggleSearch()
121
+ }
122
+ })
123
+ }
124
+
108
125
  function isEditingContent(event: KeyboardEvent): boolean {
109
126
  const element = event.target as HTMLElement
110
127
  const tagName = element.tagName
@@ -141,6 +158,9 @@ function isEditingContent(event: KeyboardEvent): boolean {
141
158
  <template v-else-if="isFuse">
142
159
  <PressFuseSearch />
143
160
  </template>
161
+ <template v-else-if="isLocal">
162
+ <PressLocalSearch ref="localSearchRef" />
163
+ </template>
144
164
  </div>
145
165
  </template>
146
166
 
@@ -34,7 +34,7 @@ const isLocked = useScrollLock(isClient ? document.body : null)
34
34
  /* stylelint-disable selector-class-pattern */
35
35
  .pr-NavScreen {
36
36
  position: fixed;
37
- inset: calc(var(--pr-nav-height) + var(--pr-layout-top-height, 0px) + 1px) 0 0 0;
37
+ inset: calc(var(--pr-nav-height) + var(--pr-layout-top-height, 0px) + 1px) 0 0;
38
38
 
39
39
  /* rtl:ignore */
40
40
 
@@ -230,6 +230,7 @@ function onSelect(key: string) {
230
230
  opacity: 0;
231
231
  transform: translateY(-4px);
232
232
  }
233
+
233
234
  to {
234
235
  opacity: 1;
235
236
  transform: translateY(0);
@@ -158,9 +158,8 @@ const { hasSidebar } = useSidebar()
158
158
  max-width: 320px;
159
159
  background-color: var(--va-c-bg);
160
160
  opacity: 0;
161
- overflow-x: hidden;
161
+ overflow: hidden auto;
162
162
  overflow-y: auto;
163
- overflow-y: overlay;
164
163
  transform: translateX(-100%);
165
164
  transition: opacity var(--va-transition-duration-moderate), transform var(--va-transition-duration) ease;
166
165
 
@@ -40,7 +40,7 @@ const appStore = useAppStore()
40
40
  height: 18px;
41
41
  border-radius: 50%;
42
42
  background-color: #fff;
43
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);
43
+ box-shadow: 0 1px 2px rgb(0 0 0 / 0.04), 0 1px 2px rgb(0 0 0 / 0.06);
44
44
  transition: background-color var(--va-transition-duration), transform var(--va-transition-duration);
45
45
  }
46
46
 
@@ -60,11 +60,11 @@ const appStore = useAppStore()
60
60
  .icon {
61
61
  width: 12px;
62
62
  height: 12px;
63
- background-color: rgba(60, 60, 60, 0.7);
63
+ background-color: rgb(60 60 60 / 0.7);
64
64
  }
65
65
 
66
66
  .dark .icon {
67
- background-color: rgba(255, 255, 255, 0.87);
67
+ background-color: rgb(255 255 255 / 0.87);
68
68
  }
69
69
 
70
70
  .dark .switch-appearance :deep(.check) {
package/layouts/404.vue CHANGED
@@ -29,6 +29,6 @@ const { t } = useI18n()
29
29
  <style lang="scss" scoped>
30
30
  .not-found {
31
31
  font-size: 10rem;
32
- text-shadow: 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15);
32
+ text-shadow: 0 5px 10px rgb(0 0 0 / .25), 0 20px 20px rgb(0 0 0 / .15);
33
33
  }
34
34
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valaxy-theme-press",
3
- "version": "0.28.0",
3
+ "version": "0.28.2",
4
4
  "description": "Docs Theme for Valaxy",
5
5
  "author": {
6
6
  "email": "me@yunyoujun.cn",
@@ -22,12 +22,13 @@
22
22
  "main": "node/index.ts",
23
23
  "types": "types/index.d.ts",
24
24
  "dependencies": {
25
- "@docsearch/css": "^4.6.1",
26
- "@docsearch/js": "^4.6.1",
27
- "@docsearch/sidepanel-js": "^4.6.1",
28
- "reka-ui": "^2.9.2"
25
+ "@docsearch/css": "^4.6.2",
26
+ "@docsearch/js": "^4.6.2",
27
+ "@docsearch/sidepanel-js": "^4.6.2",
28
+ "reka-ui": "^2.9.2",
29
+ "vitepress": "^2.0.0-alpha.17"
29
30
  },
30
31
  "devDependencies": {
31
- "valaxy": "0.28.0"
32
+ "valaxy": "0.28.2"
32
33
  }
33
34
  }
package/styles/main.scss CHANGED
@@ -1,5 +1,5 @@
1
- @use "./helper.scss";
2
- @use "./markdown.scss";
1
+ @use "./helper";
2
+ @use "./markdown";
3
3
 
4
4
  .layout {
5
5
  display: flex;
@@ -61,14 +61,24 @@
61
61
  .va-git-log-contributor {
62
62
  padding-left: 0;
63
63
  padding-top: 0;
64
+ margin-top: 0;
65
+ margin-bottom: 0;
64
66
 
65
67
  li,
66
68
  li+li {
67
69
  margin-top: 0;
70
+ margin-bottom: 0;
68
71
  }
69
72
 
70
73
  li {
71
74
  margin-right: 0.5rem;
72
75
  }
76
+
77
+ .va-contributor-avatar img {
78
+ width: 32px;
79
+ height: 32px;
80
+ margin: 0;
81
+ object-fit: cover;
82
+ }
73
83
  }
74
84
  }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Algolia search types for valaxy-theme-press.
3
+ *
4
+ * These are defined locally to avoid depending on unpublished types
5
+ * from valaxy-addon-algolia. When the addon publishes updated types,
6
+ * these can be replaced with re-exports.
7
+ */
8
+
9
+ export interface AlgoliaSearchOptions extends DocSearchProps {
10
+ locales?: Record<string, Partial<DocSearchProps>>
11
+ /**
12
+ * Configuration or assistant id to enable Ask AI mode.
13
+ * Pass a string (assistant id) or a full config object.
14
+ * Omit to disable the Ask AI button entirely.
15
+ */
16
+ askAi?: AlgoliaAskAiOptions | string
17
+ /**
18
+ * Ask AI side panel integration mode.
19
+ *
20
+ * @default 'auto'
21
+ */
22
+ mode?: 'auto' | 'sidePanel' | 'hybrid' | 'modal'
23
+ }
24
+
25
+ export interface AlgoliaAskAiOptions {
26
+ assistantId: string
27
+ appId?: string
28
+ apiKey?: string
29
+ indexName?: string
30
+ suggestedQuestions?: boolean
31
+ sidePanel?: boolean | AlgoliaSidepanelOptions
32
+ }
33
+
34
+ export interface AlgoliaSidepanelOptions {
35
+ button?: Record<string, any>
36
+ keyboardShortcuts?: Record<string, boolean>
37
+ panel?: {
38
+ variant?: 'floating' | 'inline'
39
+ side?: 'left' | 'right'
40
+ width?: string
41
+ expandedWidth?: string
42
+ suggestedQuestions?: boolean
43
+ }
44
+ }
45
+
46
+ export interface DocSearchProps {
47
+ appId: string
48
+ apiKey: string
49
+ indexName: string
50
+ placeholder?: string
51
+ searchParameters?: Record<string, any>
52
+ disableUserPersonalization?: boolean
53
+ initialQuery?: string
54
+ insights?: boolean
55
+ translations?: Record<string, any>
56
+ }