Skip to content

Commit 8c5651f

Browse files
committed
feat: enhanced search command box with manual options
- Added social media links, theme switcher, and modules - Implemented copy page functionality from CopyButtonGroup - Refactored to use computed lists for maintainability - Respects page configuration for copy options - Bigger module icons (size-24) with darker descriptions - Priority ordering: modules, utilities, copy actions, social
1 parent 1c71768 commit 8c5651f

File tree

1 file changed

+212
-13
lines changed

1 file changed

+212
-13
lines changed

packages/nimiq-vitepress-theme/src/layout/SearchCommandBox.vue

Lines changed: 212 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,29 @@
44
import type { SearchResult } from 'minisearch'
55
import type { GenericComponentInstance } from 'reka-ui'
66
import type { Ref } from 'vue'
7+
import type { NimiqVitepressThemeConfig } from '../types'
78
import { computedAsync, debouncedWatch } from '@vueuse/core'
89
import Mark from 'mark.js/src/vanilla.js'
910
import MiniSearch from 'minisearch'
1011
import { DialogClose, ListboxContent, ListboxFilter, ListboxItem, ListboxRoot } from 'reka-ui'
11-
import { useData } from 'vitepress'
12-
import { markRaw, nextTick, onMounted, ref, shallowRef, watch } from 'vue'
12+
import { useData, withBase } from 'vitepress'
13+
import { computed, markRaw, nextTick, onMounted, ref, shallowRef, watch } from 'vue'
14+
import { useSourceCode } from '../composables/useSourceCode'
1315
import { LRUCache } from '../lib/lru'
1416
1517
const emit = defineEmits<{ close: [] }>()
1618
17-
const { localeIndex } = useData()
19+
const { localeIndex, theme, isDark } = useData<NimiqVitepressThemeConfig>()
20+
21+
const {
22+
copyMarkdownContent,
23+
copyMarkdownLink,
24+
chatGPTUrl,
25+
claudeUrl,
26+
sourceCodeUrl,
27+
copyOptionsConfig,
28+
showCopyMarkdown,
29+
} = useSourceCode()
1830
1931
const filterText = ref('')
2032
const enableNoResults = ref(false)
@@ -61,6 +73,80 @@ const searchIndex = computedAsync(async () =>
6173
6274
const cache = new LRUCache(16) // 16 files
6375
76+
// Computed lists for better maintainability
77+
const moduleItems = computed(() =>
78+
theme.value.modules?.filter(m => !m.hidden) || [],
79+
)
80+
81+
const socialItems = computed(() =>
82+
theme.value.links || [],
83+
)
84+
85+
const copyItems = computed(() => {
86+
if (!showCopyMarkdown.value)
87+
return []
88+
89+
const items = []
90+
91+
// Copy page content (always available if showCopyMarkdown is true)
92+
items.push({
93+
value: 'copy-page',
94+
icon: 'i-nimiq:copy',
95+
label: 'Copy page content',
96+
action: handleCopyPage,
97+
})
98+
99+
// Copy markdown link
100+
if (copyOptionsConfig.value.markdownLink) {
101+
items.push({
102+
value: 'copy-markdown-link',
103+
icon: 'i-nimiq:link',
104+
label: 'Copy markdown link',
105+
action: handleCopyMarkdownLink,
106+
})
107+
}
108+
109+
// View as markdown
110+
if (copyOptionsConfig.value.viewMarkdown) {
111+
items.push({
112+
value: 'view-markdown',
113+
icon: 'i-nimiq:logos-github-mono',
114+
label: 'View as markdown',
115+
action: handleViewAsMarkdown,
116+
})
117+
}
118+
119+
// AI tools
120+
if (copyOptionsConfig.value.claude) {
121+
items.push({
122+
value: 'open-claude',
123+
icon: 'i-simple-icons:claude',
124+
label: 'Open in Claude',
125+
action: handleOpenInClaude,
126+
})
127+
}
128+
129+
if (copyOptionsConfig.value.chatgpt) {
130+
items.push({
131+
value: 'open-chatgpt',
132+
icon: 'i-simple-icons:openai',
133+
label: 'Open in ChatGPT',
134+
action: handleOpenInChatGPT,
135+
})
136+
}
137+
138+
return items
139+
})
140+
141+
const utilityItems = computed(() => [
142+
{
143+
value: 'theme-switcher',
144+
icon: 'i-nimiq:moon',
145+
label: `Switch to ${isDark.value ? 'light' : 'dark'} theme`,
146+
action: toggleTheme,
147+
},
148+
])
149+
64150
debouncedWatch(
65151
() => [searchIndex.value, filterText.value] as const,
66152
async ([index, filterTextValue], old, onCleanup) => {
@@ -128,12 +214,52 @@ function formMarkRegex(terms: Set<string>) {
128214
'gi',
129215
)
130216
}
217+
218+
function toggleTheme() {
219+
isDark.value = !isDark.value
220+
emit('close')
221+
}
222+
223+
function handleSocialLink(link: string) {
224+
window.open(withBase(link), '_blank', 'noopener,noreferrer')
225+
emit('close')
226+
}
227+
228+
function handleModuleLink(link: string) {
229+
window.location.href = withBase(link)
230+
emit('close')
231+
}
232+
233+
async function handleCopyPage() {
234+
await copyMarkdownContent()
235+
emit('close')
236+
}
237+
238+
async function handleCopyMarkdownLink() {
239+
await copyMarkdownLink()
240+
emit('close')
241+
}
242+
243+
function handleViewAsMarkdown() {
244+
window.open(sourceCodeUrl.value, '_blank', 'noopener,noreferrer')
245+
emit('close')
246+
}
247+
248+
function handleOpenInChatGPT() {
249+
window.open(chatGPTUrl.value, '_blank', 'noopener,noreferrer')
250+
emit('close')
251+
}
252+
253+
function handleOpenInClaude() {
254+
window.open(claudeUrl.value, '_blank', 'noopener,noreferrer')
255+
emit('close')
256+
}
131257
</script>
132258

133259
<template>
134260
<ListboxRoot ref="listboxRef">
135261
<div w-full flex="~ items-center" relative f-p-2xs>
136-
<ListboxFilter v-model="filterText" rounded-3 nq-input-box of-hidden w-full bg-transparent flex-1 placeholder="Search documentation" auto-focus border="none" outline="8 ~ neutral-500 hocus:blue" transition-outline-color />
262+
<ListboxFilter v-model="filterText" rounded-3 nq-input-box of-hidden w-full bg-transparent flex-1 placeholder="Search documentation" auto-focus border="none" outline="1.5 ~ neutral-500 hocus:blue" transition-outline-color />
137263
<DialogClose absolute right-4 stack size-48="!" cursor-pointer>
138264
<div i-nimiq:cross text="10 group-focus-within:blue neutral-700" right-16 mx-auto />
139265
</DialogClose>
@@ -143,19 +269,92 @@ function formMarkRegex(terms: Set<string>) {
143269
:ref="(node) => { if (node && '$el' in node){ resultsEl = node.$el } }"
144270
as="ul" md:max-h-55vh of-auto empty="hidden md:block"
145271
>
272+
<!-- Manual Options (only show when no filter text) -->
273+
<template v-if="!filterText">
274+
<!-- 1. Modules (Most Important - Navigation) -->
275+
<ListboxItem
276+
v-for="module in moduleItems" :key="module.subpath"
277+
:value="`module-${module.subpath}`"
278+
class="data-[highlighted]:bg-blue-400 data-[highlighted]:text-blue data-[highlighted]:font-semibold"
279+
as-child @select="handleModuleLink(module.defaultPageLink)"
280+
>
281+
<div inline-flex f-p-sm f-p-xs w-full group cursor-pointer>
282+
<div flex="~ items-center gap-12">
283+
<div :class="module.icon || 'i-nimiq:document'" size-24 text="neutral-700 group-hocus:blue" />
284+
<div flex="~ col">
285+
<span>{{ module.text }}</span>
286+
<span v-if="module.description" text="f-xs neutral-800">{{ module.description }}</span>
287+
</div>
288+
</div>
289+
</div>
290+
</ListboxItem>
291+
292+
<!-- 2. Utility Actions (Theme, etc.) -->
293+
<ListboxItem
294+
v-for="item in utilityItems" :key="item.value"
295+
:value="item.value"
296+
class="data-[highlighted]:bg-blue-400 data-[highlighted]:text-blue data-[highlighted]:font-semibold"
297+
as-child @select="item.action"
298+
>
299+
<div inline-flex f-p-sm f-p-xs w-full group cursor-pointer>
300+
<div flex="~ items-center gap-12">
301+
<div :class="item.icon" size-16 text="neutral-700 group-hocus:blue" />
302+
<span>{{ item.label }}</span>
303+
</div>
304+
</div>
305+
</ListboxItem>
306+
307+
<!-- 3. Copy & Page Actions -->
308+
<ListboxItem
309+
v-for="item in copyItems" :key="item.value"
310+
:value="item.value"
311+
class="data-[highlighted]:bg-blue-400 data-[highlighted]:text-blue data-[highlighted]:font-semibold"
312+
as-child @select="item.action"
313+
>
314+
<div inline-flex f-p-sm f-p-xs w-full group cursor-pointer>
315+
<div flex="~ items-center gap-12">
316+
<div :class="item.icon" size-16 text="neutral-700 group-hocus:blue" />
317+
<span>{{ item.label }}</span>
318+
</div>
319+
</div>
320+
</ListboxItem>
321+
322+
<!-- 4. Social Media Links -->
323+
<ListboxItem
324+
v-for="social in socialItems" :key="social.link"
325+
:value="`social-${social.link}`"
326+
class="data-[highlighted]:bg-blue-400 data-[highlighted]:text-blue data-[highlighted]:font-semibold"
327+
as-child @select="handleSocialLink(social.link)"
328+
>
329+
<div inline-flex f-p-sm f-p-xs w-full group cursor-pointer>
330+
<div flex="~ items-center gap-12">
331+
<div :class="social.icon" size-16 text="neutral-700 group-hocus:blue" />
332+
<span>{{ social.label }}</span>
333+
</div>
334+
</div>
335+
</ListboxItem>
336+
337+
<!-- Separator -->
338+
<li v-if="results.length" h-1 bg-neutral-200 my-8 />
339+
</template>
340+
341+
<!-- Search Results -->
146342
<ListboxItem
147343
v-for="p in results" :key="p.id" :value="p.id"
148344
class="data-[highlighted]:bg-blue-400 data-[highlighted]:text-blue data-[highlighted]:font-semibold" as-child @select="emit('close')"
149345
>
150346
<a :href="p.id" inline-flex f-p-sm f-p-xs w-full group>
151-
<div flex="~ items-center wrap">
152-
<span v-for="(t, index) in p.titles" :key="index" flex="~ items-center" font-normal>
153-
<span v-html="t" />
154-
<div i-nimiq:chevron-right mx-8 text="8 neutral-700 group-hocus:blue" />
155-
</span>
156-
<span font-normal>
157-
<span v-html="p.title" />
158-
</span>
347+
<div flex="~ items-center gap-12">
348+
<div i-nimiq:document size-16 text="neutral-700 group-hocus:blue" />
349+
<div flex="~ items-center wrap">
350+
<span v-for="(t, index) in p.titles" :key="index" flex="~ items-center" font-normal>
351+
<span v-html="t" />
352+
<div i-nimiq:chevron-right mx-8 text="8 neutral-700 group-hocus:blue" />
353+
</span>
354+
<span font-normal>
355+
<span v-html="p.title" />
356+
</span>
357+
</div>
159358
</div>
160359
</a>
161360
</ListboxItem>
@@ -164,7 +363,7 @@ function formMarkRegex(terms: Set<string>) {
164363
No results for "<strong>{{ filterText }}</strong>"
165364
</li>
166365

167-
<li v-else-if="!filterText && !results.length" italic text="f-xs neutral-700 center" f-py-md>
366+
<li v-else-if="!filterText && !results.length && !moduleItems.length && !utilityItems.length && !copyItems.length && !socialItems.length" italic text="f-xs neutral-700 center" f-py-md>
168367
Start typing to search
169368
</li>
170369
</ListboxContent>

0 commit comments

Comments
 (0)