diff --git a/docs/.vitepress/config/shared.ts b/docs/.vitepress/config/shared.ts index 3b967321..3b2c1b8a 100644 --- a/docs/.vitepress/config/shared.ts +++ b/docs/.vitepress/config/shared.ts @@ -44,9 +44,25 @@ export const shared = defineConfig({ logo: { src: '/vitepress-logo-mini.svg', width: 24, height: 24 }, socialLinks: [ - { icon: 'github', link: 'https://github.com/vuejs/vitepress' } + { + icon: { + id: 'github', + symbol: ` + + ` + }, + link: 'https://github.com/vuejs/vitepress' + }, + { + icon: { + svg: `npm` + }, + 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 e12889d5..22fdaaa1 100644 --- a/src/client/shim.d.ts +++ b/src/client/shim.d.ts @@ -4,6 +4,7 @@ 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' diff --git a/src/client/theme-default/components/VPSVGSpriteIcon.vue b/src/client/theme-default/components/VPSVGSpriteIcon.vue new file mode 100644 index 00000000..9c751730 --- /dev/null +++ b/src/client/theme-default/components/VPSVGSpriteIcon.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/client/theme-default/components/VPSocialLink.vue b/src/client/theme-default/components/VPSocialLink.vue index ae7b097a..1627a15e 100644 --- a/src/client/theme-default/components/VPSocialLink.vue +++ b/src/client/theme-default/components/VPSocialLink.vue @@ -1,7 +1,6 @@ diff --git a/src/client/theme-default/components/icons/VPIconAlignJustify.vue b/src/client/theme-default/components/icons/VPIconAlignJustify.vue index 46f45a94..ef3b32ff 100644 --- a/src/client/theme-default/components/icons/VPIconAlignJustify.vue +++ b/src/client/theme-default/components/icons/VPIconAlignJustify.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconAlignLeft.vue b/src/client/theme-default/components/icons/VPIconAlignLeft.vue index 90ad6c16..c152d290 100644 --- a/src/client/theme-default/components/icons/VPIconAlignLeft.vue +++ b/src/client/theme-default/components/icons/VPIconAlignLeft.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconAlignRight.vue b/src/client/theme-default/components/icons/VPIconAlignRight.vue index 74635d5f..2a14012a 100644 --- a/src/client/theme-default/components/icons/VPIconAlignRight.vue +++ b/src/client/theme-default/components/icons/VPIconAlignRight.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconArrowLeft.vue b/src/client/theme-default/components/icons/VPIconArrowLeft.vue index cf7fb4e9..d107c721 100644 --- a/src/client/theme-default/components/icons/VPIconArrowLeft.vue +++ b/src/client/theme-default/components/icons/VPIconArrowLeft.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconArrowRight.vue b/src/client/theme-default/components/icons/VPIconArrowRight.vue index 2d6b6451..11065fb0 100644 --- a/src/client/theme-default/components/icons/VPIconArrowRight.vue +++ b/src/client/theme-default/components/icons/VPIconArrowRight.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconChevronDown.vue b/src/client/theme-default/components/icons/VPIconChevronDown.vue index e5744e6c..7bb6ca87 100644 --- a/src/client/theme-default/components/icons/VPIconChevronDown.vue +++ b/src/client/theme-default/components/icons/VPIconChevronDown.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconChevronLeft.vue b/src/client/theme-default/components/icons/VPIconChevronLeft.vue index 5564685b..595450cf 100644 --- a/src/client/theme-default/components/icons/VPIconChevronLeft.vue +++ b/src/client/theme-default/components/icons/VPIconChevronLeft.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconChevronRight.vue b/src/client/theme-default/components/icons/VPIconChevronRight.vue index fd96f192..2b85b43a 100644 --- a/src/client/theme-default/components/icons/VPIconChevronRight.vue +++ b/src/client/theme-default/components/icons/VPIconChevronRight.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconChevronUp.vue b/src/client/theme-default/components/icons/VPIconChevronUp.vue index 3b6e11be..9e45b989 100644 --- a/src/client/theme-default/components/icons/VPIconChevronUp.vue +++ b/src/client/theme-default/components/icons/VPIconChevronUp.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconEdit.vue b/src/client/theme-default/components/icons/VPIconEdit.vue index 2c5d4296..f7096914 100644 --- a/src/client/theme-default/components/icons/VPIconEdit.vue +++ b/src/client/theme-default/components/icons/VPIconEdit.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconHeart.vue b/src/client/theme-default/components/icons/VPIconHeart.vue index 0906e8ae..db553a79 100644 --- a/src/client/theme-default/components/icons/VPIconHeart.vue +++ b/src/client/theme-default/components/icons/VPIconHeart.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconLanguages.vue b/src/client/theme-default/components/icons/VPIconLanguages.vue index 4b40f2c7..69255083 100644 --- a/src/client/theme-default/components/icons/VPIconLanguages.vue +++ b/src/client/theme-default/components/icons/VPIconLanguages.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconMinus.vue b/src/client/theme-default/components/icons/VPIconMinus.vue index 0d8bc0a7..ef580742 100644 --- a/src/client/theme-default/components/icons/VPIconMinus.vue +++ b/src/client/theme-default/components/icons/VPIconMinus.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconMinusSquare.vue b/src/client/theme-default/components/icons/VPIconMinusSquare.vue index e9b49e04..2a91496a 100644 --- a/src/client/theme-default/components/icons/VPIconMinusSquare.vue +++ b/src/client/theme-default/components/icons/VPIconMinusSquare.vue @@ -1,11 +1,7 @@ diff --git a/src/client/theme-default/components/icons/VPIconMoon.vue b/src/client/theme-default/components/icons/VPIconMoon.vue index 5329c484..ac3169ce 100644 --- a/src/client/theme-default/components/icons/VPIconMoon.vue +++ b/src/client/theme-default/components/icons/VPIconMoon.vue @@ -1,10 +1,7 @@ + diff --git a/src/client/theme-default/components/icons/VPIconMoreHorizontal.vue b/src/client/theme-default/components/icons/VPIconMoreHorizontal.vue index 737602cc..9395beb1 100644 --- a/src/client/theme-default/components/icons/VPIconMoreHorizontal.vue +++ b/src/client/theme-default/components/icons/VPIconMoreHorizontal.vue @@ -1,12 +1,8 @@ diff --git a/src/client/theme-default/components/icons/VPIconPlus.vue b/src/client/theme-default/components/icons/VPIconPlus.vue index 61b3c229..550013a4 100644 --- a/src/client/theme-default/components/icons/VPIconPlus.vue +++ b/src/client/theme-default/components/icons/VPIconPlus.vue @@ -1,12 +1,8 @@ diff --git a/src/client/theme-default/components/icons/VPIconPlusSquare.vue b/src/client/theme-default/components/icons/VPIconPlusSquare.vue index b502818f..4a8600a5 100644 --- a/src/client/theme-default/components/icons/VPIconPlusSquare.vue +++ b/src/client/theme-default/components/icons/VPIconPlusSquare.vue @@ -1,12 +1,8 @@ diff --git a/src/client/theme-default/components/icons/VPIconSun.vue b/src/client/theme-default/components/icons/VPIconSun.vue index 9fb4db15..d2ef6a7b 100644 --- a/src/client/theme-default/components/icons/VPIconSun.vue +++ b/src/client/theme-default/components/icons/VPIconSun.vue @@ -1,10 +1,7 @@ + diff --git a/src/client/theme-default/components/social-icons/VPDiscordIcon.vue b/src/client/theme-default/components/social-icons/VPDiscordIcon.vue new file mode 100644 index 00000000..dae25e7b --- /dev/null +++ b/src/client/theme-default/components/social-icons/VPDiscordIcon.vue @@ -0,0 +1,6 @@ + + + diff --git a/src/client/theme-default/components/social-icons/VPFacebookIcon.vue b/src/client/theme-default/components/social-icons/VPFacebookIcon.vue new file mode 100644 index 00000000..bd1230ab --- /dev/null +++ b/src/client/theme-default/components/social-icons/VPFacebookIcon.vue @@ -0,0 +1,6 @@ + + + diff --git a/src/client/theme-default/components/social-icons/VPGitHubIcon.vue b/src/client/theme-default/components/social-icons/VPGitHubIcon.vue new file mode 100644 index 00000000..a46c8674 --- /dev/null +++ b/src/client/theme-default/components/social-icons/VPGitHubIcon.vue @@ -0,0 +1,6 @@ + + + diff --git a/src/client/theme-default/components/social-icons/VPInstagramIcon.vue b/src/client/theme-default/components/social-icons/VPInstagramIcon.vue new file mode 100644 index 00000000..02631ce8 --- /dev/null +++ b/src/client/theme-default/components/social-icons/VPInstagramIcon.vue @@ -0,0 +1,6 @@ + + + diff --git a/src/client/theme-default/components/social-icons/VPLinkedInIcon.vue b/src/client/theme-default/components/social-icons/VPLinkedInIcon.vue new file mode 100644 index 00000000..55eb79e0 --- /dev/null +++ b/src/client/theme-default/components/social-icons/VPLinkedInIcon.vue @@ -0,0 +1,6 @@ + + + diff --git a/src/client/theme-default/components/social-icons/VPMastodonIcon.vue b/src/client/theme-default/components/social-icons/VPMastodonIcon.vue new file mode 100644 index 00000000..fdcfc9fb --- /dev/null +++ b/src/client/theme-default/components/social-icons/VPMastodonIcon.vue @@ -0,0 +1,6 @@ + + + diff --git a/src/client/theme-default/components/social-icons/VPNpmIcon.vue b/src/client/theme-default/components/social-icons/VPNpmIcon.vue new file mode 100644 index 00000000..db69fe6a --- /dev/null +++ b/src/client/theme-default/components/social-icons/VPNpmIcon.vue @@ -0,0 +1,6 @@ + + + diff --git a/src/client/theme-default/components/social-icons/VPSlackIcon.vue b/src/client/theme-default/components/social-icons/VPSlackIcon.vue new file mode 100644 index 00000000..9c09804a --- /dev/null +++ b/src/client/theme-default/components/social-icons/VPSlackIcon.vue @@ -0,0 +1,6 @@ + + + diff --git a/src/client/theme-default/components/social-icons/VPTwitterIcon.vue b/src/client/theme-default/components/social-icons/VPTwitterIcon.vue new file mode 100644 index 00000000..46dac478 --- /dev/null +++ b/src/client/theme-default/components/social-icons/VPTwitterIcon.vue @@ -0,0 +1,7 @@ + + + + diff --git a/src/client/theme-default/components/social-icons/VPXIcon.vue b/src/client/theme-default/components/social-icons/VPXIcon.vue new file mode 100644 index 00000000..0b7132f9 --- /dev/null +++ b/src/client/theme-default/components/social-icons/VPXIcon.vue @@ -0,0 +1,7 @@ + + + + diff --git a/src/client/theme-default/components/social-icons/VPYouTubeIcon.vue b/src/client/theme-default/components/social-icons/VPYouTubeIcon.vue new file mode 100644 index 00000000..2621c6ae --- /dev/null +++ b/src/client/theme-default/components/social-icons/VPYouTubeIcon.vue @@ -0,0 +1,7 @@ + + + + diff --git a/src/client/theme-default/components/sprite-social-icons/VPDiscordIcon.vue b/src/client/theme-default/components/sprite-social-icons/VPDiscordIcon.vue new file mode 100644 index 00000000..353cb209 --- /dev/null +++ b/src/client/theme-default/components/sprite-social-icons/VPDiscordIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/sprite-social-icons/VPFacebookIcon.vue b/src/client/theme-default/components/sprite-social-icons/VPFacebookIcon.vue new file mode 100644 index 00000000..4058d7c3 --- /dev/null +++ b/src/client/theme-default/components/sprite-social-icons/VPFacebookIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/sprite-social-icons/VPGitHubIcon.vue b/src/client/theme-default/components/sprite-social-icons/VPGitHubIcon.vue new file mode 100644 index 00000000..54ef1d11 --- /dev/null +++ b/src/client/theme-default/components/sprite-social-icons/VPGitHubIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/sprite-social-icons/VPInstagramIcon.vue b/src/client/theme-default/components/sprite-social-icons/VPInstagramIcon.vue new file mode 100644 index 00000000..c81dc2fa --- /dev/null +++ b/src/client/theme-default/components/sprite-social-icons/VPInstagramIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/sprite-social-icons/VPLinkedInIcon.vue b/src/client/theme-default/components/sprite-social-icons/VPLinkedInIcon.vue new file mode 100644 index 00000000..7fd926d6 --- /dev/null +++ b/src/client/theme-default/components/sprite-social-icons/VPLinkedInIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/sprite-social-icons/VPMastodonIcon.vue b/src/client/theme-default/components/sprite-social-icons/VPMastodonIcon.vue new file mode 100644 index 00000000..c54c0b97 --- /dev/null +++ b/src/client/theme-default/components/sprite-social-icons/VPMastodonIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/sprite-social-icons/VPNpmIcon.vue b/src/client/theme-default/components/sprite-social-icons/VPNpmIcon.vue new file mode 100644 index 00000000..f002c7df --- /dev/null +++ b/src/client/theme-default/components/sprite-social-icons/VPNpmIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/sprite-social-icons/VPSlackIcon.vue b/src/client/theme-default/components/sprite-social-icons/VPSlackIcon.vue new file mode 100644 index 00000000..1360bde4 --- /dev/null +++ b/src/client/theme-default/components/sprite-social-icons/VPSlackIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/sprite-social-icons/VPTwitterIcon.vue b/src/client/theme-default/components/sprite-social-icons/VPTwitterIcon.vue new file mode 100644 index 00000000..ccbfd3ad --- /dev/null +++ b/src/client/theme-default/components/sprite-social-icons/VPTwitterIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/sprite-social-icons/VPXIcon.vue b/src/client/theme-default/components/sprite-social-icons/VPXIcon.vue new file mode 100644 index 00000000..b72457cd --- /dev/null +++ b/src/client/theme-default/components/sprite-social-icons/VPXIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/components/sprite-social-icons/VPYouTubeIcon.vue b/src/client/theme-default/components/sprite-social-icons/VPYouTubeIcon.vue new file mode 100644 index 00000000..cf3cd889 --- /dev/null +++ b/src/client/theme-default/components/sprite-social-icons/VPYouTubeIcon.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/client/theme-default/support/socialIcons.ts b/src/client/theme-default/support/socialIcons.ts deleted file mode 100644 index 18c4bf8c..00000000 --- a/src/client/theme-default/support/socialIcons.ts +++ /dev/null @@ -1,23 +0,0 @@ -// used under CC0 1.0 from https://simpleicons.org/ -export const icons = { - discord: - 'Discord', - facebook: - 'Facebook', - github: - 'GitHub', - instagram: - 'Instagram', - linkedin: - 'LinkedIn', - mastodon: - 'Mastodon', - npm: 'npm', - slack: - 'Slack', - twitter: - 'Twitter', - x: 'X', - youtube: - 'YouTube' -} as const diff --git a/src/node/plugin.ts b/src/node/plugin.ts index d5dfdc58..d7f582b4 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -153,7 +153,9 @@ 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 + __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: !!process.env.DEBUG, + __SOCIAL_SVG_SPRITE_ICONS__: + site.themeConfig?.enableSocialLinksSVGSprite === true }, optimizeDeps: { // force include vue to avoid duplicated copies when linked + optimized @@ -445,7 +447,7 @@ export async function createVitePressPlugin( ...(userViteConfig?.plugins || []), await localSearchPlugin(siteConfig), staticDataPlugin, - svgSpritePlugin, + svgSpritePlugin(siteConfig), await dynamicRoutesPlugin(siteConfig) ] } diff --git a/src/node/plugins/svgSpritePlugin.ts b/src/node/plugins/svgSpritePlugin.ts index e20f14d5..919cecc5 100644 --- a/src/node/plugins/svgSpritePlugin.ts +++ b/src/node/plugins/svgSpritePlugin.ts @@ -1,20 +1,66 @@ import type { Plugin } from 'vite' +import type { SiteConfig } from '../siteConfig' +import type { DefaultTheme } from '../index' + +export function svgSpritePlugin(siteConfig: SiteConfig) { + let useSvgSprites: Record = {} + return { + name: 'vitepress:svg-sprite', + enforce: 'pre', + configResolved() { + useSvgSprites = prepareSvgSprites(useSvgSprites, siteConfig) + }, + generateBundle(_options, bundle) { + Object.entries(useSvgSprites).forEach(([fileName, source]) => { + bundle[fileName] = { + type: 'asset', + name: fileName, + source, + fileName, + needsCodeReference: true + } + }) + }, + configureServer(server) { + const base = siteConfig.site.base ?? server.config.base ?? '/' + server.middlewares.use((req, res, next) => { + const url = req.url + if (!url) { + return next() + } + const filename = new URL(url, 'http://localhost').pathname.replace( + base, + '' + ) + const sprite = useSvgSprites[filename] + if (!sprite) { + return next() + } + + res.setHeader('Content-Type', 'image/svg+xml') + res.statusCode = 200 + res.write(sprite, 'utf-8') + res.end() + }) + } + } satisfies Plugin +} const svgSprite = ` - ` -// TODO: include social icons? -const socialSvgSprite = ` +const localSearchSvgSprite = ` ` -const svgSpriteMap: Record = { - 'vp-icons-sprite.svg': svgSprite, - 'vp-icons-social-sprite.svg': socialSvgSprite +const socialSvgSpriteSymbols = { + discord: ` + + `, + facebook: ` + + `, + github: ` + + `, + instagram: ` + + `, + linkedin: ` + + `, + mastodon: ` + + `, + npm: ` + + `, + slack: ` + + `, + twitter: ` + + `, + x: ` + + `, + youtube: ` + + ` } -export const svgSpritePlugin: Plugin = { - name: 'vitepress:svg-sprite', - enforce: 'pre', - generateBundle(_options, bundle) { - Object.entries(svgSpriteMap).forEach(([fileName, source]) => { - bundle[fileName] = { - type: 'asset', - name: fileName, - source, - fileName, - needsCodeReference: true - } - }) - }, - configureServer(server) { - // TODO: review base path => SiteConfig - const base = server.config.base ?? '/' - server.middlewares.use((req, res, next) => { - const url = req.url - if (!url) { - return next() - } - const filename = new URL(url, 'http://localhost').pathname.replace( - base, - '' +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 = '' + } + } + + return sprite + }, + {} as Record + ) ) - const sprite = svgSpriteMap[filename] - if (!sprite) { - return next() - } + entries.unshift(` +`) + entries.push('') + sprites['vp-social-icons-sprite.svg'] = entries.join('') + } + } - res.setHeader('Content-Type', 'image/svg+xml') - res.statusCode = 200 - res.write(sprite, 'utf-8') - res.end() - }) + if (siteConfig.site.themeConfig?.search?.provider === 'local') { + sprites['vp-local-search-icons-sprite.svg'] = localSearchSvgSprite } + + return sprites } diff --git a/types/default-theme.d.ts b/types/default-theme.d.ts index d6636643..2c3aeef4 100644 --- a/types/default-theme.d.ts +++ b/types/default-theme.d.ts @@ -89,6 +89,19 @@ 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. */ @@ -327,6 +340,11 @@ export namespace DefaultTheme { ariaLabel?: string } + export interface SpriteSvg { + id: string + symbol: string + } + export type SocialLinkIcon = | 'discord' | 'facebook' @@ -340,6 +358,7 @@ export namespace DefaultTheme { | 'x' | 'youtube' | { svg: string } + | SpriteSvg // footer --------------------------------------------------------------------