diff --git a/docs/.vitepress/config/shared.ts b/docs/.vitepress/config/shared.ts index 3b2c1b8a..3b967321 100644 --- a/docs/.vitepress/config/shared.ts +++ b/docs/.vitepress/config/shared.ts @@ -44,25 +44,9 @@ export const shared = defineConfig({ logo: { src: '/vitepress-logo-mini.svg', width: 24, height: 24 }, socialLinks: [ - { - icon: { - id: 'github', - symbol: ` - - ` - }, - link: 'https://github.com/vuejs/vitepress' - }, - { - icon: { - svg: `npm` - }, - link: 'https://github.com/vuejs/vitepress' - } + { icon: 'github', link: 'https://github.com/vuejs/vitepress' } ], - enableSocialLinksSVGSprite: true, - search: { provider: 'algolia', options: { diff --git a/src/client/shim.d.ts b/src/client/shim.d.ts index 22fdaaa1..f7bc356a 100644 --- a/src/client/shim.d.ts +++ b/src/client/shim.d.ts @@ -4,7 +4,6 @@ declare const __ALGOLIA__: boolean declare const __CARBON__: boolean declare const __VUE_PROD_DEVTOOLS__: boolean declare const __ASSETS_DIR__: string -declare const __SOCIAL_SVG_SPRITE_ICONS__: boolean declare module '*.vue' { import type { DefineComponent } from 'vue' @@ -34,3 +33,7 @@ declare module 'mark.js/src/vanilla.js' { const mark: typeof Mark export default mark } + +declare module 'virtual:vp-social-icons' { + export const socialIcons: Record +} diff --git a/src/client/theme-default/components/VPLocalSearchBox.vue b/src/client/theme-default/components/VPLocalSearchBox.vue index 36eeb84d..9298b11d 100644 --- a/src/client/theme-default/components/VPLocalSearchBox.vue +++ b/src/client/theme-default/components/VPLocalSearchBox.vue @@ -32,6 +32,14 @@ import { escapeRegExp } from '../../shared' import { useData } from '../composables/data' import { LRUCache } from '../support/lru' import { createSearchTranslate } from '../support/translation' +import VPLocalSearchIcon from "./search-icons/VPLocalSearchIcon.vue"; +import VPLocalSearchBackIcon from "./search-icons/VPLocalSearchBackIcon.vue"; +import VPLocalSearchToggleIcon from "./search-icons/VPLocalSearchToggleIcon.vue"; +import VPLocalSearchClearIcon from "./search-icons/VPLocalSearchClearIcon.vue"; +import VPLocalSearchTilesIcon from "./search-icons/VPLocalSearchTilesIcon.vue"; +import VPLocalSearchNavUpIcon from "./search-icons/VPLocalSearchNavUpIcon.vue"; +import VPLocalSearchNavDownIcon from "./search-icons/VPLocalSearchNavDownIcon.vue"; +import VPLocalSearchSelectIcon from "./search-icons/VPLocalSearchSelectIcon.vue"; const emit = defineEmits<{ (e: 'close'): void @@ -430,24 +438,7 @@ function formMarkRegex(terms: Set) { id="localsearch-label" for="localsearch-input" > - +
) { selectedIndex > -1 && (showDetailedList = !showDetailedList) " > - + @@ -568,16 +517,7 @@ function formMarkRegex(terms: Set) { class="title" > - - - + @@ -606,45 +546,16 @@ function formMarkRegex(terms: Set) {
- - - + - - - + {{ $t('modal.footer.navigateText') }} - - - - - - + {{ $t('modal.footer.selectText') }} diff --git a/src/client/theme-default/components/VPNavBarSearchButton.vue b/src/client/theme-default/components/VPNavBarSearchButton.vue index 2c943363..d72c9932 100644 --- a/src/client/theme-default/components/VPNavBarSearchButton.vue +++ b/src/client/theme-default/components/VPNavBarSearchButton.vue @@ -1,6 +1,7 @@ @@ -57,7 +35,7 @@ const svg = computed(() => { +import VPIcon from '../VPSVGSpriteIcon.vue'; + + + diff --git a/src/client/theme-default/components/search-icons/VPLocalSearchBackIcon.vue b/src/client/theme-default/components/search-icons/VPLocalSearchBackIcon.vue new file mode 100644 index 00000000..a91a8ad3 --- /dev/null +++ b/src/client/theme-default/components/search-icons/VPLocalSearchBackIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/search-icons/VPLocalSearchClearIcon.vue b/src/client/theme-default/components/search-icons/VPLocalSearchClearIcon.vue new file mode 100644 index 00000000..f14f148a --- /dev/null +++ b/src/client/theme-default/components/search-icons/VPLocalSearchClearIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/search-icons/VPLocalSearchIcon.vue b/src/client/theme-default/components/search-icons/VPLocalSearchIcon.vue new file mode 100644 index 00000000..679b80f5 --- /dev/null +++ b/src/client/theme-default/components/search-icons/VPLocalSearchIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/search-icons/VPLocalSearchNavDownIcon.vue b/src/client/theme-default/components/search-icons/VPLocalSearchNavDownIcon.vue new file mode 100644 index 00000000..3c3db85f --- /dev/null +++ b/src/client/theme-default/components/search-icons/VPLocalSearchNavDownIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/search-icons/VPLocalSearchNavUpIcon.vue b/src/client/theme-default/components/search-icons/VPLocalSearchNavUpIcon.vue new file mode 100644 index 00000000..67edf4b9 --- /dev/null +++ b/src/client/theme-default/components/search-icons/VPLocalSearchNavUpIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/search-icons/VPLocalSearchSelectIcon.vue b/src/client/theme-default/components/search-icons/VPLocalSearchSelectIcon.vue new file mode 100644 index 00000000..c6d8d372 --- /dev/null +++ b/src/client/theme-default/components/search-icons/VPLocalSearchSelectIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/search-icons/VPLocalSearchTilesIcon.vue b/src/client/theme-default/components/search-icons/VPLocalSearchTilesIcon.vue new file mode 100644 index 00000000..c30acd1f --- /dev/null +++ b/src/client/theme-default/components/search-icons/VPLocalSearchTilesIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/search-icons/VPLocalSearchToggleIcon.vue b/src/client/theme-default/components/search-icons/VPLocalSearchToggleIcon.vue new file mode 100644 index 00000000..d5cd164d --- /dev/null +++ b/src/client/theme-default/components/search-icons/VPLocalSearchToggleIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/support/socialIcons.ts b/src/client/theme-default/support/socialIcons.ts new file mode 100644 index 00000000..189ccc84 --- /dev/null +++ b/src/client/theme-default/support/socialIcons.ts @@ -0,0 +1,2 @@ +// keep this module empty: transformed by vitepress:svg-sprite plugin +export const socialIcons: Record = {} diff --git a/src/node/plugin.ts b/src/node/plugin.ts index d7f582b4..53d7812b 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -153,9 +153,7 @@ export async function createVitePressPlugin( !!site.themeConfig?.algolia, // legacy __CARBON__: !!site.themeConfig?.carbonAds, __ASSETS_DIR__: JSON.stringify(siteConfig.assetsDir), - __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: !!process.env.DEBUG, - __SOCIAL_SVG_SPRITE_ICONS__: - site.themeConfig?.enableSocialLinksSVGSprite === true + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: !!process.env.DEBUG }, optimizeDeps: { // force include vue to avoid duplicated copies when linked + optimized diff --git a/src/node/plugins/svgSpritePlugin.ts b/src/node/plugins/svgSpritePlugin.ts index 919cecc5..0d333dd6 100644 --- a/src/node/plugins/svgSpritePlugin.ts +++ b/src/node/plugins/svgSpritePlugin.ts @@ -4,11 +4,31 @@ import type { DefaultTheme } from '../index' export function svgSpritePlugin(siteConfig: SiteConfig) { let useSvgSprites: Record = {} + let virtual: [name: string, path: string][] = [] + const virtualName = 'virtual:vp-social-icons' + const resolvedVirtualName = `\0${virtualName}` return { name: 'vitepress:svg-sprite', enforce: 'pre', configResolved() { useSvgSprites = prepareSvgSprites(useSvgSprites, siteConfig) + virtual = prepareSVGIcons(siteConfig) + }, + resolveId(id: string) { + if (id === virtualName) { + return resolvedVirtualName + } + }, + load(id) { + if (id === resolvedVirtualName) { + if (virtual.length === 0) { + return 'export const socialIcons = {}' + } + return `${virtual.map(([name, path]) => `import ${name} from ${JSON.stringify(path)}`).join('\n')} + +export const socialIcons = {${virtual.map(([name]) => name).join(',')}} +` + } }, generateBundle(_options, bundle) { Object.entries(useSvgSprites).forEach(([fileName, source]) => { @@ -46,6 +66,45 @@ export function svgSpritePlugin(siteConfig: SiteConfig) { } satisfies Plugin } +function prepareSVGIcons({ site }: SiteConfig) { + const socialIconPrefix = + site.themeConfig?.enableSocialLinksSVGSprite === true + ? 'vitepress/dist/client/theme-default/components/sprite-social-icons/' + : 'vitepress/dist/client/theme-default/components/social-icons/' + const socialIcons: DefaultTheme.SocialLink[] = + site.themeConfig?.socialLinks ?? [] + + return socialIcons + .filter((s) => typeof s.icon === 'string') + .map((s) => { + let icon: string = '' + switch (s.icon) { + case 'x': + icon = 'X' + break + case 'github': + icon = 'GitHub' + break + case 'youtube': + icon = 'YouTube' + break + case 'discord': + case 'facebook': + case 'instagram': + case 'mastodon': + case 'npm': + case 'slack': + case 'twitter': + icon = s.icon.charAt(0).toUpperCase() + s.icon.slice(1) + break + } + return [s.icon, `${socialIconPrefix}VP${icon}Icon.vue`] as [ + name: string, + path: string + ] + }) +} + const svgSprite = ` @@ -131,11 +190,6 @@ const svgSprite = ` ` -const localSearchSvgSprite = ` - - -` - const socialSvgSpriteSymbols = { discord: ` @@ -172,44 +226,99 @@ const socialSvgSpriteSymbols = { ` } +const localSearchSvgSprite = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +` + +const DocSearchIcon = ` + + + ` + function prepareSvgSprites( sprites: Record, siteConfig: SiteConfig ) { sprites['vp-icons-sprite.svg'] = svgSprite - if (siteConfig.site.themeConfig?.enableSocialLinksSVGSprite) { - const socialLinks: DefaultTheme.SocialLink[] | undefined = - siteConfig.site.themeConfig.socialLinks - if (socialLinks?.length) { - const entries = Object.values( - socialLinks.reduce( - (sprite, l) => { - if (l.icon) { - if (typeof l.icon !== 'object') { - if (l.icon in socialSvgSpriteSymbols) { - sprite[l.icon] = socialSvgSpriteSymbols[l.icon] - } - } else if ('id' in l.icon && 'symbol' in l.icon) { - sprite[l.icon.id] = l.icon.symbol - // we don't need the symbol in the site configuration - l.icon.symbol = '' + const socialLinks: DefaultTheme.SocialLink[] | undefined = + siteConfig.site.themeConfig.socialLinks + if (socialLinks?.length) { + const entries = Object.values( + socialLinks.reduce( + (sprite, l) => { + if (l.icon) { + if (typeof l.icon !== 'object') { + if (l.icon in socialSvgSpriteSymbols) { + sprite[l.icon] = socialSvgSpriteSymbols[l.icon] } + } else if ('id' in l.icon && 'symbol' in l.icon) { + sprite[l.icon.id] = l.icon.symbol + // we don't need the symbol in the site configuration + l.icon.symbol = '' } + } - return sprite - }, - {} as Record - ) + return sprite + }, + {} as Record ) - entries.unshift(` + ) + entries.unshift(` `) - entries.push('') - sprites['vp-social-icons-sprite.svg'] = entries.join('') - } + entries.push('') + sprites['vp-social-icons-sprite.svg'] = entries.join('') } if (siteConfig.site.themeConfig?.search?.provider === 'local') { - sprites['vp-local-search-icons-sprite.svg'] = localSearchSvgSprite + sprites['vp-local-search-icons-sprite.svg'] = + ` + + ${DocSearchIcon.trim()} + ${localSearchSvgSprite.trim()} + +` + } else { + sprites['vp-local-search-icons-sprite.svg'] = + ` + + ${DocSearchIcon.trim()} + +` } return sprites diff --git a/types/default-theme.d.ts b/types/default-theme.d.ts index 2c3aeef4..c852955a 100644 --- a/types/default-theme.d.ts +++ b/types/default-theme.d.ts @@ -89,19 +89,6 @@ export namespace DefaultTheme { */ socialLinks?: SocialLink[] - /** - * Should VitePress generate and use SVG Sprite to render SVG social links?. - * - * To enable this feature, you need to provide `{ sprite: string; symbol: string }` in `socialLinks` entries if you're using custom `svg`: - * - the symbol is the svg content: replace svg with symbol tag: `` => `` - * - remove any xmlns and role attributes - * - include id attribute, must match the sprite value - * - remove title and desc tags if present - * - * @default false - */ - enableSocialLinksSVGSprite?: boolean - /** * The footer configuration. */