Skip to content

Commit 9670d2f

Browse files
committed
feat: add logo context menu with version & custom items
1 parent 95480ec commit 9670d2f

File tree

5 files changed

+217
-2
lines changed

5 files changed

+217
-2
lines changed

docs/vitepress-theme/guide/vitepress-integration.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,70 @@ export const themeConfig = {
105105
}
106106
```
107107

108+
## Logo Context Menu
109+
110+
The theme includes a context menu (right-click) on the logo that displays your project version and custom menu items.
111+
112+
### Version Display
113+
114+
By default, the context menu automatically shows your project version from `package.json`. This feature is opt-out:
115+
116+
```ts [themeConfig.ts]
117+
export const themeConfig = {
118+
logoContextMenu: {
119+
showVersion: false // Disable version display
120+
}
121+
}
122+
```
123+
124+
The version is automatically extracted from your project's `package.json` by the Nimiq VitePress Vite plugin.
125+
126+
### Custom Menu Items
127+
128+
Add custom menu items to the logo context menu:
129+
130+
```ts [themeConfig.ts]
131+
export const themeConfig = {
132+
logoContextMenu: {
133+
items: [
134+
{
135+
label: 'View on GitHub',
136+
href: 'https://github.com/your-org/your-repo'
137+
},
138+
{
139+
label: 'Report Issue',
140+
onClick: () => {
141+
window.open('https://github.com/your-org/your-repo/issues/new', '_blank')
142+
}
143+
},
144+
{
145+
label: 'Documentation',
146+
href: 'https://docs.example.com'
147+
}
148+
]
149+
}
150+
}
151+
```
152+
153+
### Configuration Options
154+
155+
**`logoContextMenu.showVersion`** (boolean, default: `true`)
156+
157+
- Show the project version from `package.json` in the context menu
158+
- Set to `false` to hide the version
159+
160+
**`logoContextMenu.items`** (array of objects)
161+
162+
- Custom menu items to display
163+
- Each item can have:
164+
- `label` (required): Text to display
165+
- `href` (optional): URL to open in a new tab
166+
- `onClick` (optional): Function to execute when clicked
167+
168+
**Usage:**
169+
170+
Right-click on the logo in the header to see the context menu with your version and custom items.
171+
108172
## Additional Configuration
109173

110174
For a complete list of available VitePress options, refer to the [VitePress Default Theme Config](https://vitepress.dev/reference/default-theme-config) documentation.
Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,125 @@
11
<script setup lang="ts">
2+
import type { LogoContextMenuItem } from '../types'
23
import { useData, withBase } from 'vitepress'
3-
import { computed } from 'vue'
4+
import { computed, ref } from 'vue'
45
56
const { site, theme } = useData()
67
78
const removeNimiqPrefix = (str: string) => str.toLocaleLowerCase().startsWith('nimiq') ? str.slice(6) : str
89
const name = computed(() => removeNimiqPrefix(site.value.title))
10+
11+
// Context menu state
12+
const showContextMenu = ref(false)
13+
const contextMenuX = ref(0)
14+
const contextMenuY = ref(0)
15+
16+
// Get version from Vite config
17+
const version = (window as any).__NIMIQ_VITEPRESS_CONFIG__?.version
18+
19+
// Context menu configuration
20+
const showVersion = computed(() => theme.value.logoContextMenu?.showVersion !== false)
21+
const customItems = computed<LogoContextMenuItem[]>(() => theme.value.logoContextMenu?.items || [])
22+
const hasContextMenu = computed(() => (showVersion.value && version) || customItems.value.length > 0)
23+
24+
function handleContextMenu(event: MouseEvent) {
25+
if (!hasContextMenu.value)
26+
return
27+
28+
event.preventDefault()
29+
contextMenuX.value = event.clientX
30+
contextMenuY.value = event.clientY
31+
showContextMenu.value = true
32+
}
33+
34+
function closeContextMenu() {
35+
showContextMenu.value = false
36+
}
37+
38+
function handleItemClick(item: LogoContextMenuItem) {
39+
if (item.onClick) {
40+
item.onClick()
41+
}
42+
else if (item.href) {
43+
window.open(item.href, '_blank')
44+
}
45+
closeContextMenu()
46+
}
47+
48+
// Close context menu on click outside
49+
if (typeof window !== 'undefined') {
50+
window.addEventListener('click', () => {
51+
if (showContextMenu.value)
52+
closeContextMenu()
53+
})
54+
}
955
</script>
1056

1157
<template>
1258
<div>
13-
<a flex="~ items-center gap-8 shrink-0" w-full text-neutral font-semibold :href="withBase('/')">
59+
<a
60+
flex="~ items-center gap-8 shrink-0"
61+
w-full
62+
text-neutral
63+
font-semibold
64+
:href="withBase('/')"
65+
@contextmenu="handleContextMenu"
66+
>
1467
<img v-if="theme.logo" class="logo" :src="theme.logo">
1568
<div v-else i-nimiq:logos-nimiq-horizontal text-20 dark:i-nimiq:logos-nimiq-white-horizontal />
1669
<span translate-y--1 text-16 font-light tracking-wide>{{ name }}</span>
1770
<span v-if="theme.betaBadge" text-10 font-semibold px-6 py-2 bg-blue-500 text-white rounded-4 translate-y--1>BETA</span>
1871
</a>
72+
73+
<!-- Context Menu -->
74+
<Teleport v-if="showContextMenu" to="body">
75+
<div
76+
fixed
77+
bg-white
78+
dark:bg-neutral-800
79+
border="1 solid neutral-200 dark:neutral-700"
80+
rounded-8
81+
shadow-lg
82+
py-4
83+
min-w-150
84+
z-9999
85+
:style="{ left: `${contextMenuX}px`,
86+
top: `${contextMenuY}px` }"
87+
@click.stop
88+
>
89+
<!-- Version -->
90+
<div
91+
v-if="showVersion && version"
92+
px-12
93+
py-6
94+
text-12
95+
text-neutral-500
96+
dark:text-neutral-400
97+
font-mono
98+
border-b="1 solid neutral-200 dark:neutral-700"
99+
mb-4
100+
>
101+
v{{ version }}
102+
</div>
103+
104+
<!-- Custom Items -->
105+
<button
106+
v-for="(item, index) in customItems"
107+
:key="index"
108+
w-full
109+
px-12
110+
py-8
111+
text-left
112+
text-14
113+
text-neutral-700
114+
dark:text-neutral-300
115+
hover="bg-neutral-100 dark:bg-neutral-700"
116+
transition-colors
117+
cursor-pointer
118+
@click="handleItemClick(item)"
119+
>
120+
{{ item.label }}
121+
</button>
122+
</div>
123+
</Teleport>
19124
</div>
20125
</template>

packages/nimiq-vitepress-theme/src/shim.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ declare module 'mark.js/src/vanilla.js' {
1212

1313
declare const __ASSETS_DIR__: string
1414
declare const __VP_HASH_MAP__: Record<string, string>
15+
declare const __NIMIQ_VITEPRESS_CONFIG__: {
16+
repoURL?: string
17+
contentPath?: string
18+
version?: string
19+
}
1520

1621
declare module 'virtual:nolebase-git-changelog' {
1722
import type { Changelog } from './types'

packages/nimiq-vitepress-theme/src/types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ export interface OutlineAction {
4646
onClick: () => void | Promise<void>
4747
}
4848

49+
export interface LogoContextMenuItem {
50+
label: string
51+
onClick?: () => void
52+
href?: string
53+
}
54+
4955
export interface NimiqVitepressThemeConfig {
5056
/** Array of navigation modules for your documentation */
5157
modules: NimiqVitepressThemeNav[]
@@ -87,6 +93,20 @@ export interface NimiqVitepressThemeConfig {
8793
* @default false
8894
*/
8995
betaBadge?: boolean
96+
/**
97+
* Logo context menu configuration
98+
*/
99+
logoContextMenu?: {
100+
/**
101+
* Show version from package.json in context menu
102+
* @default true
103+
*/
104+
showVersion?: boolean
105+
/**
106+
* Custom menu items to display
107+
*/
108+
items?: LogoContextMenuItem[]
109+
}
90110
}
91111

92112
export interface NimiqVitepressFrontmatter {

packages/nimiq-vitepress-theme/src/vite/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { IncomingMessage } from 'node:http'
22
import type { Plugin, PreviewServerHook, ServerHook } from 'vite'
3+
import fs from 'node:fs'
4+
import path from 'node:path'
5+
import process from 'node:process'
36
import { viteHtmlToMarkdownPlugin } from '@mdream/vite'
47
import { GitChangelog } from '@nolebase/vitepress-plugin-git-changelog/vite'
58
import { groupIconVitePlugin } from '../code-groups/vite'
@@ -28,6 +31,20 @@ export interface NimiqVitepressVitePluginOptions {
2831
gitChangelog?: GitChangelogOptions | false
2932
}
3033

34+
function getProjectVersion(rootDir: string = process.cwd()): string | undefined {
35+
try {
36+
const packageJsonPath = path.resolve(rootDir, 'package.json')
37+
if (fs.existsSync(packageJsonPath)) {
38+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
39+
return packageJson.version
40+
}
41+
}
42+
catch (error) {
43+
console.warn('Failed to read version from package.json:', error)
44+
}
45+
return undefined
46+
}
47+
3148
export function NimiqVitepressVitePlugin({
3249
repoURL,
3350
contentPath = '',
@@ -45,10 +62,14 @@ export function NimiqVitepressVitePlugin({
4562
}
4663
}
4764

65+
// Get project version from package.json
66+
const version = getProjectVersion()
67+
4868
// Store configuration in Vite config for use in composables
4969
const nimiqConfig = {
5070
repoURL,
5171
contentPath,
72+
version,
5273
}
5374

5475
const plugins: Plugin[] = [

0 commit comments

Comments
 (0)