@ampernic/vitepress-theme-alt-docs 0.1.4 → 0.1.5
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.
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onKeyStroke, useScrollLock } from '@vueuse/core'
|
|
3
|
+
import { useData, useRouter } from 'vitepress'
|
|
4
|
+
import { computed, nextTick, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'
|
|
5
|
+
|
|
6
|
+
const emit = defineEmits<{ (e: 'close'): void }>()
|
|
7
|
+
const { site } = useData()
|
|
8
|
+
const router = useRouter()
|
|
9
|
+
|
|
10
|
+
const ALL_DISTROS = [
|
|
11
|
+
'alt-domain', 'alt-education', 'alt-kworkstation', 'alt-mobile',
|
|
12
|
+
'alt-platform', 'alt-server', 'alt-server-v', 'alt-virtualisation-one',
|
|
13
|
+
'alt-virtualization-pve', 'alt-workstation', 'group-policy', 'simply-linux',
|
|
14
|
+
'alt-education-e2k', 'alt-server-e2k', 'alt-workstation-e2k', 'simply-linux-e2k',
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
const DISTRO_NAMES: Record<string, string> = {
|
|
18
|
+
'alt-domain': 'Альт Домен',
|
|
19
|
+
'alt-education': 'Альт Образование',
|
|
20
|
+
'alt-education-e2k': 'Альт Образование для Эльбрус',
|
|
21
|
+
'alt-server': 'Альт Сервер',
|
|
22
|
+
'alt-server-e2k': 'Альт Сервер для Эльбрус',
|
|
23
|
+
'alt-server-v': 'Альт Сервер Виртуализации',
|
|
24
|
+
'alt-workstation': 'Альт Рабочая станция',
|
|
25
|
+
'alt-workstation-e2k': 'Альт Рабочая станция для Эльбрус',
|
|
26
|
+
'alt-kworkstation': 'Альт Рабочая станция K',
|
|
27
|
+
'alt-platform': 'Альт Платформа',
|
|
28
|
+
'alt-mobile': 'Альт Мобильный',
|
|
29
|
+
'alt-virtualisation-one': 'Альт Виртуализация ONE',
|
|
30
|
+
'alt-virtualization-pve': 'Альт Виртуализация PVE',
|
|
31
|
+
'simply-linux': 'Симпли Линукс',
|
|
32
|
+
'simply-linux-e2k': 'Симпли Линукс для Эльбрус',
|
|
33
|
+
'group-policy': 'Групповые политики',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Pagefind ──────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
const pagefind = shallowRef<any>(null)
|
|
40
|
+
const indexLoading = ref(true)
|
|
41
|
+
const loadError = ref(false)
|
|
42
|
+
const availableDistros = ref<string[]>([])
|
|
43
|
+
const availableVersions = ref<string[]>([])
|
|
44
|
+
|
|
45
|
+
async function loadIndexes() {
|
|
46
|
+
// base = '' for index site (/), '/alt-domain' for distro sites
|
|
47
|
+
const base = site.value.base === '/' ? '' : site.value.base.replace(/\/+$/, '')
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
let pf: any = null
|
|
51
|
+
let firstIdx = -1
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < ALL_DISTROS.length; i++) {
|
|
54
|
+
try {
|
|
55
|
+
// @vite-ignore
|
|
56
|
+
pf = await import(/* @vite-ignore */ `${base}/${ALL_DISTROS[i]}/pagefind/pagefind.js`)
|
|
57
|
+
firstIdx = i
|
|
58
|
+
break
|
|
59
|
+
} catch { /* distro not deployed yet */ }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!pf) throw new Error('no pagefind index found')
|
|
63
|
+
|
|
64
|
+
for (let i = firstIdx + 1; i < ALL_DISTROS.length; i++) {
|
|
65
|
+
try {
|
|
66
|
+
await pf.mergeIndex(`${base}/${ALL_DISTROS[i]}/pagefind`)
|
|
67
|
+
} catch { /* skip */ }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await pf.init()
|
|
71
|
+
|
|
72
|
+
const filters = await pf.filters() as Record<string, Record<string, number>>
|
|
73
|
+
availableDistros.value = Object.keys(filters.distro ?? {}).sort()
|
|
74
|
+
availableVersions.value = Object.keys(filters.version ?? {}).sort((a, b) => {
|
|
75
|
+
const [aMaj, aMin] = a.split('.').map(Number)
|
|
76
|
+
const [bMaj, bMin] = b.split('.').map(Number)
|
|
77
|
+
return bMaj !== aMaj ? bMaj - aMaj : bMin - aMin
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
return pf
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Filters ───────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
const selectedDistro = ref('')
|
|
86
|
+
const selectedVersion = ref('')
|
|
87
|
+
|
|
88
|
+
watch(selectedDistro, () => { selectedVersion.value = '' })
|
|
89
|
+
|
|
90
|
+
// ── Search ────────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
interface ResultData {
|
|
93
|
+
url: string
|
|
94
|
+
meta: { title: string }
|
|
95
|
+
excerpt: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const query = ref('')
|
|
99
|
+
const selectedIndex = ref(0)
|
|
100
|
+
const results = ref<ResultData[]>([])
|
|
101
|
+
const searching = ref(false)
|
|
102
|
+
|
|
103
|
+
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
|
104
|
+
|
|
105
|
+
watch([query, selectedDistro, selectedVersion], () => {
|
|
106
|
+
selectedIndex.value = 0
|
|
107
|
+
if (searchTimer) clearTimeout(searchTimer)
|
|
108
|
+
if (!query.value.trim() || !pagefind.value) {
|
|
109
|
+
results.value = []
|
|
110
|
+
searching.value = false
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
searching.value = true
|
|
114
|
+
searchTimer = setTimeout(async () => {
|
|
115
|
+
const f: Record<string, string> = {}
|
|
116
|
+
if (selectedDistro.value) f.distro = selectedDistro.value
|
|
117
|
+
if (selectedVersion.value) f.version = selectedVersion.value
|
|
118
|
+
const search = await pagefind.value.search(query.value, Object.keys(f).length ? { filters: f } : {})
|
|
119
|
+
results.value = await Promise.all(
|
|
120
|
+
search.results.slice(0, 12).map((r: { data: () => Promise<ResultData> }) => r.data()),
|
|
121
|
+
)
|
|
122
|
+
searching.value = false
|
|
123
|
+
}, 200)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
function parseUrl(url: string) {
|
|
127
|
+
const m = url.match(/\/([a-z][a-z0-9-]+)\/(\d+\.\d+(?:-\w+)?)\//)
|
|
128
|
+
return m ? { distro: m[1], version: m[2] } : null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function go(url: string) {
|
|
132
|
+
router.go(url)
|
|
133
|
+
close()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Keyboard ──────────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
onKeyStroke('ArrowDown', (e) => {
|
|
139
|
+
e.preventDefault()
|
|
140
|
+
selectedIndex.value = (selectedIndex.value + 1) % Math.max(results.value.length, 1)
|
|
141
|
+
})
|
|
142
|
+
onKeyStroke('ArrowUp', (e) => {
|
|
143
|
+
e.preventDefault()
|
|
144
|
+
selectedIndex.value = (selectedIndex.value - 1 + Math.max(results.value.length, 1)) % Math.max(results.value.length, 1)
|
|
145
|
+
})
|
|
146
|
+
onKeyStroke('Enter', () => {
|
|
147
|
+
const r = results.value[selectedIndex.value]
|
|
148
|
+
if (r) go(r.url)
|
|
149
|
+
})
|
|
150
|
+
onKeyStroke('Escape', () => close())
|
|
151
|
+
|
|
152
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
const inputEl = shallowRef<HTMLInputElement>()
|
|
155
|
+
const scrollLock = useScrollLock(typeof document !== 'undefined' ? document.body : null)
|
|
156
|
+
|
|
157
|
+
onMounted(async () => {
|
|
158
|
+
scrollLock.value = true
|
|
159
|
+
await nextTick()
|
|
160
|
+
inputEl.value?.focus()
|
|
161
|
+
try {
|
|
162
|
+
pagefind.value = await loadIndexes()
|
|
163
|
+
} catch {
|
|
164
|
+
loadError.value = true
|
|
165
|
+
} finally {
|
|
166
|
+
indexLoading.value = false
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
onUnmounted(() => { scrollLock.value = false })
|
|
171
|
+
|
|
172
|
+
function close() { emit('close') }
|
|
173
|
+
function onOverlayClick(e: MouseEvent) { if (e.target === e.currentTarget) close() }
|
|
174
|
+
</script>
|
|
175
|
+
|
|
176
|
+
<template>
|
|
177
|
+
<Teleport to="body">
|
|
178
|
+
<div class="gs-overlay" @click="onOverlayClick">
|
|
179
|
+
<div class="gs-modal" role="dialog" aria-modal="true">
|
|
180
|
+
|
|
181
|
+
<!-- Input row -->
|
|
182
|
+
<div class="gs-input-row">
|
|
183
|
+
<span class="gs-icon">
|
|
184
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
|
185
|
+
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
186
|
+
<circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
187
|
+
</svg>
|
|
188
|
+
</span>
|
|
189
|
+
<input
|
|
190
|
+
ref="inputEl"
|
|
191
|
+
v-model="query"
|
|
192
|
+
class="gs-input"
|
|
193
|
+
placeholder="Поиск по всей документации…"
|
|
194
|
+
autocomplete="off"
|
|
195
|
+
spellcheck="false"
|
|
196
|
+
/>
|
|
197
|
+
<button class="gs-close" aria-label="Закрыть" @click="close"><kbd>Esc</kbd></button>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<!-- Filters row (shown once indexes loaded and have data) -->
|
|
201
|
+
<div v-if="!indexLoading && (availableDistros.length || availableVersions.length)" class="gs-filters">
|
|
202
|
+
<select v-model="selectedDistro" class="gs-select">
|
|
203
|
+
<option value="">Все дистрибутивы</option>
|
|
204
|
+
<option v-for="d in availableDistros" :key="d" :value="d">
|
|
205
|
+
{{ DISTRO_NAMES[d] ?? d }}
|
|
206
|
+
</option>
|
|
207
|
+
</select>
|
|
208
|
+
<select v-model="selectedVersion" class="gs-select">
|
|
209
|
+
<option value="">Все версии</option>
|
|
210
|
+
<option v-for="v in availableVersions" :key="v" :value="v">{{ v }}</option>
|
|
211
|
+
</select>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<!-- Results -->
|
|
215
|
+
<ul v-if="results.length" class="gs-results">
|
|
216
|
+
<li
|
|
217
|
+
v-for="(result, i) in results"
|
|
218
|
+
:key="result.url"
|
|
219
|
+
class="gs-result"
|
|
220
|
+
:class="{ 'is-selected': i === selectedIndex }"
|
|
221
|
+
@mouseenter="selectedIndex = i"
|
|
222
|
+
@click="go(result.url)"
|
|
223
|
+
>
|
|
224
|
+
<div class="gs-result-header">
|
|
225
|
+
<span class="gs-result-title">{{ result.meta.title }}</span>
|
|
226
|
+
<span v-if="parseUrl(result.url)" class="gs-result-badges">
|
|
227
|
+
<span class="gs-badge gs-badge--distro">
|
|
228
|
+
{{ DISTRO_NAMES[parseUrl(result.url)!.distro] ?? parseUrl(result.url)!.distro }}
|
|
229
|
+
</span>
|
|
230
|
+
<span class="gs-badge gs-badge--version">{{ parseUrl(result.url)!.version }}</span>
|
|
231
|
+
</span>
|
|
232
|
+
</div>
|
|
233
|
+
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
234
|
+
<div class="gs-result-excerpt" v-html="result.excerpt" />
|
|
235
|
+
</li>
|
|
236
|
+
</ul>
|
|
237
|
+
|
|
238
|
+
<div v-else-if="indexLoading" class="gs-status">
|
|
239
|
+
<span class="gs-spinner" />
|
|
240
|
+
Загружаем индексы…
|
|
241
|
+
</div>
|
|
242
|
+
<div v-else-if="loadError" class="gs-status gs-status--error">
|
|
243
|
+
Индекс не найден. Запустите сборку документации.
|
|
244
|
+
</div>
|
|
245
|
+
<div v-else-if="searching" class="gs-status">Поиск…</div>
|
|
246
|
+
<div v-else-if="query && !searching" class="gs-status">
|
|
247
|
+
Ничего не найдено для «<strong>{{ query }}</strong>»
|
|
248
|
+
</div>
|
|
249
|
+
<div v-else-if="!query && !indexLoading" class="gs-status gs-status--hint">
|
|
250
|
+
Начните вводить запрос
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<!-- Footer -->
|
|
254
|
+
<div class="gs-footer">
|
|
255
|
+
<span><kbd>↑</kbd><kbd>↓</kbd> навигация</span>
|
|
256
|
+
<span><kbd>Enter</kbd> открыть</span>
|
|
257
|
+
<span><kbd>Esc</kbd> закрыть</span>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
</Teleport>
|
|
263
|
+
</template>
|
|
264
|
+
|
|
265
|
+
<style scoped>
|
|
266
|
+
.gs-overlay {
|
|
267
|
+
position: fixed;
|
|
268
|
+
inset: 0;
|
|
269
|
+
z-index: 100;
|
|
270
|
+
background: rgba(0, 0, 0, 0.5);
|
|
271
|
+
backdrop-filter: blur(4px);
|
|
272
|
+
display: flex;
|
|
273
|
+
align-items: flex-start;
|
|
274
|
+
justify-content: center;
|
|
275
|
+
padding-top: 10vh;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.gs-modal {
|
|
279
|
+
background: var(--vp-c-bg);
|
|
280
|
+
border: 1px solid var(--vp-c-divider);
|
|
281
|
+
border-radius: 12px;
|
|
282
|
+
box-shadow: var(--vp-shadow-4);
|
|
283
|
+
width: min(680px, calc(100vw - 32px));
|
|
284
|
+
max-height: 72vh;
|
|
285
|
+
display: flex;
|
|
286
|
+
flex-direction: column;
|
|
287
|
+
overflow: hidden;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.gs-input-row {
|
|
291
|
+
display: flex;
|
|
292
|
+
align-items: center;
|
|
293
|
+
gap: 8px;
|
|
294
|
+
padding: 12px 16px;
|
|
295
|
+
border-bottom: 1px solid var(--vp-c-divider);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.gs-icon {
|
|
299
|
+
color: var(--vp-c-text-3);
|
|
300
|
+
flex-shrink: 0;
|
|
301
|
+
display: flex;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.gs-input {
|
|
305
|
+
flex: 1;
|
|
306
|
+
background: transparent;
|
|
307
|
+
border: none;
|
|
308
|
+
outline: none;
|
|
309
|
+
font-size: 16px;
|
|
310
|
+
color: var(--vp-c-text-1);
|
|
311
|
+
min-width: 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.gs-input::placeholder {
|
|
315
|
+
color: var(--vp-c-text-3);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.gs-close {
|
|
319
|
+
background: none;
|
|
320
|
+
border: none;
|
|
321
|
+
cursor: pointer;
|
|
322
|
+
padding: 2px;
|
|
323
|
+
color: var(--vp-c-text-3);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.gs-filters {
|
|
327
|
+
display: flex;
|
|
328
|
+
gap: 8px;
|
|
329
|
+
padding: 8px 16px;
|
|
330
|
+
border-bottom: 1px solid var(--vp-c-divider);
|
|
331
|
+
background: var(--vp-c-default-soft);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.gs-select {
|
|
335
|
+
flex: 1;
|
|
336
|
+
min-width: 0;
|
|
337
|
+
padding: 5px 8px;
|
|
338
|
+
font-size: 13px;
|
|
339
|
+
background: var(--vp-c-bg);
|
|
340
|
+
border: 1px solid var(--vp-c-divider);
|
|
341
|
+
border-radius: 6px;
|
|
342
|
+
color: var(--vp-c-text-1);
|
|
343
|
+
cursor: pointer;
|
|
344
|
+
outline: none;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.gs-select:focus {
|
|
348
|
+
border-color: var(--vp-c-brand-1);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.gs-results {
|
|
352
|
+
overflow-y: auto;
|
|
353
|
+
list-style: none;
|
|
354
|
+
margin: 0;
|
|
355
|
+
padding: 8px;
|
|
356
|
+
flex: 1;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.gs-result {
|
|
360
|
+
padding: 8px 12px;
|
|
361
|
+
border-radius: 8px;
|
|
362
|
+
cursor: pointer;
|
|
363
|
+
transition: background 0.1s;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.gs-result.is-selected,
|
|
367
|
+
.gs-result:hover {
|
|
368
|
+
background: var(--vp-c-default-soft);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.gs-result-header {
|
|
372
|
+
display: flex;
|
|
373
|
+
align-items: baseline;
|
|
374
|
+
gap: 8px;
|
|
375
|
+
margin-bottom: 4px;
|
|
376
|
+
flex-wrap: wrap;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.gs-result-title {
|
|
380
|
+
font-size: 14px;
|
|
381
|
+
color: var(--vp-c-text-1);
|
|
382
|
+
font-weight: 500;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.gs-result-badges {
|
|
386
|
+
display: flex;
|
|
387
|
+
gap: 4px;
|
|
388
|
+
flex-shrink: 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.gs-badge {
|
|
392
|
+
font-size: 11px;
|
|
393
|
+
padding: 1px 6px;
|
|
394
|
+
border-radius: 4px;
|
|
395
|
+
font-weight: 500;
|
|
396
|
+
white-space: nowrap;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.gs-badge--distro {
|
|
400
|
+
background: var(--vp-c-brand-soft);
|
|
401
|
+
color: var(--vp-c-brand-1);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.gs-badge--version {
|
|
405
|
+
background: var(--vp-c-default-soft);
|
|
406
|
+
color: var(--vp-c-text-2);
|
|
407
|
+
border: 1px solid var(--vp-c-divider);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.gs-result-excerpt {
|
|
411
|
+
font-size: 12px;
|
|
412
|
+
color: var(--vp-c-text-2);
|
|
413
|
+
line-height: 1.5;
|
|
414
|
+
overflow: hidden;
|
|
415
|
+
display: -webkit-box;
|
|
416
|
+
-webkit-line-clamp: 2;
|
|
417
|
+
-webkit-box-orient: vertical;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.gs-status {
|
|
421
|
+
padding: 24px;
|
|
422
|
+
text-align: center;
|
|
423
|
+
color: var(--vp-c-text-2);
|
|
424
|
+
font-size: 14px;
|
|
425
|
+
display: flex;
|
|
426
|
+
align-items: center;
|
|
427
|
+
justify-content: center;
|
|
428
|
+
gap: 8px;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.gs-status--error {
|
|
432
|
+
color: var(--vp-c-danger-1);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.gs-status--hint {
|
|
436
|
+
color: var(--vp-c-text-3);
|
|
437
|
+
font-style: italic;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.gs-spinner {
|
|
441
|
+
width: 16px;
|
|
442
|
+
height: 16px;
|
|
443
|
+
border: 2px solid var(--vp-c-divider);
|
|
444
|
+
border-top-color: var(--vp-c-brand-1);
|
|
445
|
+
border-radius: 50%;
|
|
446
|
+
animation: spin 0.7s linear infinite;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
@keyframes spin {
|
|
450
|
+
to { transform: rotate(360deg); }
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.gs-footer {
|
|
454
|
+
display: flex;
|
|
455
|
+
gap: 16px;
|
|
456
|
+
padding: 8px 16px;
|
|
457
|
+
border-top: 1px solid var(--vp-c-divider);
|
|
458
|
+
font-size: 12px;
|
|
459
|
+
color: var(--vp-c-text-3);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
kbd {
|
|
463
|
+
background: var(--vp-c-default-soft);
|
|
464
|
+
border: 1px solid var(--vp-c-divider);
|
|
465
|
+
border-radius: 4px;
|
|
466
|
+
padding: 1px 5px;
|
|
467
|
+
font-size: 11px;
|
|
468
|
+
font-family: inherit;
|
|
469
|
+
}
|
|
470
|
+
</style>
|
|
471
|
+
|
|
472
|
+
<style>
|
|
473
|
+
.gs-result-excerpt mark {
|
|
474
|
+
background: transparent;
|
|
475
|
+
color: var(--vp-c-brand-1);
|
|
476
|
+
font-weight: 600;
|
|
477
|
+
}
|
|
478
|
+
</style>
|
|
@@ -4,6 +4,7 @@ import { useData, useRoute } from 'vitepress'
|
|
|
4
4
|
import { computed, defineAsyncComponent, ref } from 'vue'
|
|
5
5
|
|
|
6
6
|
const ADSearch = defineAsyncComponent(() => import('./ADSearch.vue'))
|
|
7
|
+
const ADGlobalSearch = defineAsyncComponent(() => import('./ADGlobalSearch.vue'))
|
|
7
8
|
|
|
8
9
|
const { site } = useData()
|
|
9
10
|
const route = useRoute()
|
|
@@ -17,18 +18,17 @@ const contentPath = computed(() => {
|
|
|
17
18
|
return base.length > 1 && path.startsWith(base) ? path.slice(base.length - 1) : path
|
|
18
19
|
})
|
|
19
20
|
|
|
20
|
-
//
|
|
21
|
-
// Hidden only on site root and distro-selection landing pages
|
|
21
|
+
// Versioned content page — per-distro search
|
|
22
22
|
const isContentPage = computed(() =>
|
|
23
23
|
/^\/\d+\.\d+(?:-\w+)?\//.test(contentPath.value)
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
function openSearch() {
|
|
27
|
-
|
|
27
|
+
open.value = true
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
onKeyStroke('k', (e) => {
|
|
31
|
-
if (
|
|
31
|
+
if (e.metaKey || e.ctrlKey) {
|
|
32
32
|
e.preventDefault()
|
|
33
33
|
e.stopPropagation()
|
|
34
34
|
open.value = true
|
|
@@ -38,7 +38,7 @@ onKeyStroke('k', (e) => {
|
|
|
38
38
|
onKeyStroke('/', (e) => {
|
|
39
39
|
const tag = (e.target as HTMLElement).tagName
|
|
40
40
|
const editing = (e.target as HTMLElement).isContentEditable || tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA'
|
|
41
|
-
if (!editing
|
|
41
|
+
if (!editing) {
|
|
42
42
|
e.preventDefault()
|
|
43
43
|
open.value = true
|
|
44
44
|
}
|
|
@@ -49,7 +49,6 @@ onKeyStroke('/', (e) => {
|
|
|
49
49
|
<!-- Wrapper mimics .VPNavBarSearch layout (flex: 1 on ≥768px) -->
|
|
50
50
|
<div class="ad-search-wrap">
|
|
51
51
|
<button
|
|
52
|
-
v-if="isContentPage"
|
|
53
52
|
type="button"
|
|
54
53
|
class="DocSearch DocSearch-Button"
|
|
55
54
|
aria-label="Поиск"
|
|
@@ -65,7 +64,8 @@ onKeyStroke('/', (e) => {
|
|
|
65
64
|
</span>
|
|
66
65
|
</button>
|
|
67
66
|
|
|
68
|
-
<ADSearch v-if="open" @close="open = false" />
|
|
67
|
+
<ADSearch v-if="open && isContentPage" @close="open = false" />
|
|
68
|
+
<ADGlobalSearch v-if="open && !isContentPage" @close="open = false" />
|
|
69
69
|
</div>
|
|
70
70
|
</template>
|
|
71
71
|
|
|
@@ -94,4 +94,3 @@ onKeyStroke('/', (e) => {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
</style>
|
|
97
|
-
|
package/dist/config/shared.js
CHANGED
|
@@ -15,6 +15,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
15
15
|
function createSharedConfig(options) {
|
|
16
16
|
const base = process.env.VITE_BASE ?? "/";
|
|
17
17
|
const pagefind = (0, _vitepressPluginPagefind.PagefindPlugin)({
|
|
18
|
+
distroName: options.distroName,
|
|
18
19
|
extractFilters: id => {
|
|
19
20
|
const m = id.match(/[/\\](\d+\.\d+(?:-\w+)?)[/\\]/);
|
|
20
21
|
return m ? {
|
package/dist/config/shared.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { PagefindPlugin } from "@ampernic/vitepress-plugin-pagefind";
|
|
|
8
8
|
export function createSharedConfig(options) {
|
|
9
9
|
const base = process.env.VITE_BASE ?? "/";
|
|
10
10
|
const pagefind = PagefindPlugin({
|
|
11
|
+
distroName: options.distroName,
|
|
11
12
|
extractFilters: (id) => {
|
|
12
13
|
const m = id.match(/[/\\](\d+\.\d+(?:-\w+)?)[/\\]/);
|
|
13
14
|
return m ? { version: m[1] } : null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampernic/vitepress-theme-alt-docs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Shared VitePress theme for ALT Linux documentation",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"author": "Ampernic",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"vitepress-plugin-tabs": "^0.6.0",
|
|
36
36
|
"@ampernic/vitepress-plugin-alt-docs-versioning": "0.1.2",
|
|
37
37
|
"@ampernic/vitepress-plugin-html-image": "0.1.2",
|
|
38
|
-
"@ampernic/vitepress-plugin-
|
|
39
|
-
"@ampernic/vitepress-plugin-
|
|
38
|
+
"@ampernic/vitepress-plugin-pagefind": "0.1.5",
|
|
39
|
+
"@ampernic/vitepress-plugin-cross-site-router": "0.1.2"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"builtin-modules": "^3.3.0",
|