refactor(theme): social icon support all iconify icons, close #781 (#790)

This commit is contained in:
pengzhanbo 2025-12-12 20:40:50 +08:00 committed by GitHub
parent c42a601467
commit 95d345bf6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 328 additions and 138 deletions

View File

@ -392,31 +392,55 @@ export default defineUserConfig({
将作为 图标链接 展示在 导航栏最右侧。 将作为 图标链接 展示在 导航栏最右侧。
图标可选值: 支持 [Iconify](https://icon-sets.iconify.design/) 任意图标,直接使用 iconify name 即可自动加载。
- `'github'`
- `'gitlab'` 对于 `simple-icons` 集合下的图标,可以省略 `simple-icons:` 前缀,如 `simple-icons:github` 可以简写为 `github`
- `'npm'`
- `'docker'` 常见的社交图标示例:
- `'discord'`
- `'telegram'` ::: flex
- `'facebook'`
- `'instagram'` <div style="flex: 1">
- `'linkedin'`
- `'mastodon'` - discord ::simple-icons:discord::
- `'slack'` - telegram ::simple-icons:telegram::
- `'twitter'` - facebook ::simple-icons:facebook::
- `'x'` - github ::simple-icons:github::
- `'youtube'` - instagram ::simple-icons:instagram::
- `'juejin'` - linkedin ::simple-icons:linkedin::
- `'stackoverflow'` - mastodon ::simple-icons:mastodon::
- `'qq'` - npm ::simple-icons:npm::
- `'weibo'` - slack ::simple-icons:slack::
- `'bilibili'` - twitter ::simple-icons:twitter::
- `'zhihu'` - x ::simple-icons:x::
- `'douban'` - youtube ::simple-icons:youtube::
- `'steam'` - bluesky ::simple-icons:bluesky::
- `'xbox'` - tiktok ::simple-icons:tiktok::
- `{ svg: string, name?: string }`: 自定义图标,传入 svg 源码字符串,可选 `name` 字段,用于配置 [`navbarSocialInclude`](#navbarsocialinclude)
</div><div style="flex: 1">
- qq ::simple-icons:qq::
- weibo ::simple-icons:sinaweibo::
- bilibili ::simple-icons:bilibili::
- gitlab ::simple-icons:gitlab::
- docker ::simple-icons:docker::
- juejin ::simple-icons:juejin::
- zhihu ::simple-icons:zhihu::
- douban ::simple-icons:douban::
- steam ::simple-icons:steam::
- stackoverflow ::simple-icons:stackoverflow::
- xbox ::simple-icons:xbox::
- kuaishou ::simple-icons:kuaishou::
- twitch ::simple-icons:twitch::
- xiaohongshu ::simple-icons:xiaohongshu::
</div>
:::
[你可以在这里查看 **simple-icons** 所有可用图标](https://icon-sets.iconify.design/simple-icons/){.readmore}
如果 **Iconify** 无法满足你的需求,可以传入 `{ svg: string, name?: string }`的格式,使用自定义图标,传入 svg 源码字符串,可选 `name` 字段,用于配置 [`navbarSocialInclude`](#navbarsocialinclude)
示例: 示例:
@ -424,8 +448,10 @@ export default defineUserConfig({
export default defineUserConfig({ export default defineUserConfig({
theme: plumeTheme({ theme: plumeTheme({
social: [ social: [
// 使用 iconify name
{ icon: 'github', link: 'https://github.com/zhangsan' }, { icon: 'github', link: 'https://github.com/zhangsan' },
{ {
// 使用自定义图标
icon: { svg: '<svg>xxxxx</svg>', name: 'xxx' }, icon: { svg: '<svg>xxxxx</svg>', name: 'xxx' },
link: 'https://xxx.com' link: 'https://xxx.com'
}, },

View File

@ -397,31 +397,57 @@ export default defineUserConfig({
Displayed as icon links on the far right of the navbar. Displayed as icon links on the far right of the navbar.
Available icon options: Supports any icon from [Iconify](https://icon-sets.iconify.design/). Simply use the iconify name to load it automatically.
- `'github'`
- `'gitlab'` For icons in the `simple-icons` collection, you can omit the `simple-icons:` prefix.
- `'npm'` For example, `simple-icons:github` can be abbreviated as `github`.
- `'docker'`
- `'discord'` Examples of common social icons:
- `'telegram'`
- `'facebook'` ::: flex
- `'instagram'`
- `'linkedin'` <div style="flex: 1">
- `'mastodon'`
- `'slack'` - discord ::simple-icons:discord::
- `'twitter'` - telegram ::simple-icons:telegram::
- `'x'` - facebook ::simple-icons:facebook::
- `'youtube'` - github ::simple-icons:github::
- `'juejin'` - instagram ::simple-icons:instagram::
- `'stackoverflow'` - linkedin ::simple-icons:linkedin::
- `'qq'` - mastodon ::simple-icons:mastodon::
- `'weibo'` - npm ::simple-icons:npm::
- `'bilibili'` - slack ::simple-icons:slack::
- `'zhihu'` - twitter ::simple-icons:twitter::
- `'douban'` - x ::simple-icons:x::
- `'steam'` - youtube ::simple-icons:youtube::
- `'xbox'` - bluesky ::simple-icons:bluesky::
- `{ svg: string, name?: string }`: Custom icon, pass the SVG source string. The optional `name` field is used to configure [`navbarSocialInclude`](#navbarsocialinclude). - tiktok ::simple-icons:tiktok::
</div><div style="flex: 1">
- qq ::simple-icons:qq::
- weibo ::simple-icons:sinaweibo::
- bilibili ::simple-icons:bilibili::
- gitlab ::simple-icons:gitlab::
- docker ::simple-icons:docker::
- juejin ::simple-icons:juejin::
- zhihu ::simple-icons:zhihu::
- douban ::simple-icons:douban::
- steam ::simple-icons:steam::
- stackoverflow ::simple-icons:stackoverflow::
- xbox ::simple-icons:xbox::
- kuaishou ::simple-icons:kuaishou::
- twitch ::simple-icons:twitch::
- xiaohongshu ::simple-icons:xiaohongshu::
</div>
:::
[You can view all available icons for **simple-icons** here](https://icon-sets.iconify.design/simple-icons/){.readmore}
If **Iconify** does not meet your needs, you can pass in the format `{ svg: string, name?: string }` to use a custom icon.
Pass in the SVG source code string, with the optional `name` field for configuring [`navbarSocialInclude`](#navbarsocialinclude).
Example: Example:
@ -429,8 +455,10 @@ Example:
export default defineUserConfig({ export default defineUserConfig({
theme: plumeTheme({ theme: plumeTheme({
social: [ social: [
// use iconify name
{ icon: 'github', link: 'https://github.com/zhangsan' }, { icon: 'github', link: 'https://github.com/zhangsan' },
{ {
// use custom icon
icon: { svg: '<svg>xxxxx</svg>', name: 'xxx' }, icon: { svg: '<svg>xxxxx</svg>', name: 'xxx' },
link: 'https://xxx.com' link: 'https://xxx.com'
}, },

View File

@ -450,9 +450,15 @@ export default defineUserConfig({
type: 'post', type: 'post',
dir: 'blog', dir: 'blog',
title: 'Blog', title: 'Blog',
// [!code hl:4] // [!code hl:9]
social: [ social: [
{ icon: 'github', link: 'https://github.com/pengzhanbo' }, // use iconify name
{ icon: 'github', link: 'https://github.com/zhangsan' },
{
// use custom icon
icon: { svg: '<svg>xxxxx</svg>', name: 'xxx' },
link: 'https://xxx.com'
},
], ],
} }
] ]
@ -471,9 +477,15 @@ export default defineThemeConfig({
type: 'post', type: 'post',
dir: 'blog', dir: 'blog',
title: 'Blog', title: 'Blog',
// [!code hl:4] // [!code hl:9]
social: [ social: [
{ icon: 'github', link: 'https://github.com/pengzhanbo' }, // use iconify name
{ icon: 'github', link: 'https://github.com/zhangsan' },
{
// use custom icon
icon: { svg: '<svg>xxxxx</svg>', name: 'xxx' },
link: 'https://xxx.com'
},
], ],
} }
] ]
@ -482,43 +494,58 @@ export default defineThemeConfig({
::: :::
### Built-in Icon Library Supports any icon from [Iconify](https://icon-sets.iconify.design/). Simply use the iconify name to load it automatically.
For icons in the `simple-icons` collection, the `simple-icons:` prefix can be omitted.
For example, `simple-icons:github` can be abbreviated as `github`.
Examples of common social icons:
::: flex ::: flex
<div style="flex: 1"> <div style="flex: 1">
- discord - discord ::simple-icons:discord::
- telegram - telegram ::simple-icons:telegram::
- facebook - facebook ::simple-icons:facebook::
- github - github ::simple-icons:github::
- instagram - instagram ::simple-icons:instagram::
- linkedin - linkedin ::simple-icons:linkedin::
- mastodon - mastodon ::simple-icons:mastodon::
- npm - npm ::simple-icons:npm::
- slack - slack ::simple-icons:slack::
- twitter - twitter ::simple-icons:twitter::
- x - x ::simple-icons:x::
- youtube - youtube ::simple-icons:youtube::
- bluesky ::simple-icons:bluesky::
- tiktok ::simple-icons:tiktok::
</div><div style="flex: 1"> </div><div style="flex: 1">
- qq - qq ::simple-icons:qq::
- weibo - weibo ::simple-icons:sinaweibo::
- bilibili - bilibili ::simple-icons:bilibili::
- gitlab - gitlab ::simple-icons:gitlab::
- docker - docker ::simple-icons:docker::
- juejin - juejin ::simple-icons:juejin::
- zhihu - zhihu ::simple-icons:zhihu::
- douban - douban ::simple-icons:douban::
- steam - steam ::simple-icons:steam::
- stackoverflow - stackoverflow ::simple-icons:stackoverflow::
- xbox - xbox ::simple-icons:xbox::
- kuaishou ::simple-icons:kuaishou::
- twitch ::simple-icons:twitch::
- xiaohongshu ::simple-icons:xiaohongshu::
</div> </div>
::: :::
[You can view all available icons of **simple-icons** here](https://icon-sets.iconify.design/simple-icons/){.readmore}
If **Iconify** cannot meet your needs, you can pass in the format `{ svg: string, name?: string }`
to use custom icons by providing the SVG source code string.
## Article Cover Configuration ## Article Cover Configuration
The article list page supports cover image display with various layout and size options. The article list page supports cover image display with various layout and size options.

View File

@ -429,7 +429,7 @@ export default defineThemeConfig({
## 社交链接 ## 社交链接
个人信息区域支持社交链接配置,未配置时继承[主题默认 social 设置](../../config/theme.md#social)。 个人信息区域支持社交链接配置,未配置时继承 [主题默认 social 设置](../../config/theme.md#social)。
::: code-tabs#config ::: code-tabs#config
@ -446,9 +446,15 @@ export default defineUserConfig({
type: 'post', type: 'post',
dir: 'blog', dir: 'blog',
title: '博客', title: '博客',
// [!code hl:4] // [!code hl:9]
social: [ social: [
{ icon: 'github', link: 'https://github.com/pengzhanbo' }, // 使用 iconify name
{ icon: 'github', link: 'https://github.com/zhangsan' },
{
// 使用自定义图标
icon: { svg: '<svg>xxxxx</svg>', name: 'xxx' },
link: 'https://xxx.com'
},
], ],
} }
] ]
@ -467,9 +473,15 @@ export default defineThemeConfig({
type: 'post', type: 'post',
dir: 'blog', dir: 'blog',
title: '博客', title: '博客',
// [!code hl:4] // [!code hl:9]
social: [ social: [
{ icon: 'github', link: 'https://github.com/pengzhanbo' }, // 使用 iconify name
{ icon: 'github', link: 'https://github.com/zhangsan' },
{
// 使用自定义图标
icon: { svg: '<svg>xxxxx</svg>', name: 'xxx' },
link: 'https://xxx.com'
},
], ],
} }
] ]
@ -478,43 +490,56 @@ export default defineThemeConfig({
::: :::
### 内置图标库 支持 [Iconify](https://icon-sets.iconify.design/) 任意图标,直接使用 iconify name 即可自动加载。
对于 `simple-icons` 集合下的图标,可以省略 `simple-icons:` 前缀,如 `simple-icons:github` 可以简写为 `github`
常见的社交图标示例:
::: flex ::: flex
<div style="flex: 1"> <div style="flex: 1">
- discord - discord ::simple-icons:discord::
- telegram - telegram ::simple-icons:telegram::
- facebook - facebook ::simple-icons:facebook::
- github - github ::simple-icons:github::
- instagram - instagram ::simple-icons:instagram::
- linkedin - linkedin ::simple-icons:linkedin::
- mastodon - mastodon ::simple-icons:mastodon::
- npm - npm ::simple-icons:npm::
- slack - slack ::simple-icons:slack::
- twitter - twitter ::simple-icons:twitter::
- x - x ::simple-icons:x::
- youtube - youtube ::simple-icons:youtube::
- bluesky ::simple-icons:bluesky::
- tiktok ::simple-icons:tiktok::
</div><div style="flex: 1"> </div><div style="flex: 1">
- qq - qq ::simple-icons:qq::
- weibo - weibo ::simple-icons:sinaweibo::
- bilibili - bilibili ::simple-icons:bilibili::
- gitlab - gitlab ::simple-icons:gitlab::
- docker - docker ::simple-icons:docker::
- juejin - juejin ::simple-icons:juejin::
- zhihu - zhihu ::simple-icons:zhihu::
- douban - douban ::simple-icons:douban::
- steam - steam ::simple-icons:steam::
- stackoverflow - stackoverflow ::simple-icons:stackoverflow::
- xbox - xbox ::simple-icons:xbox::
- kuaishou ::simple-icons:kuaishou::
- twitch ::simple-icons:twitch::
- xiaohongshu ::simple-icons:xiaohongshu::
</div> </div>
::: :::
[你可以在这里查看 **simple-icons** 所有可用图标](https://icon-sets.iconify.design/simple-icons/){.readmore}
如果 **Iconify** 无法满足你的需求,可以传入 `{ svg: string, name?: string }`的格式,使用自定义图标,传入 svg 源码字符串。
## 文章封面配置 ## 文章封面配置
文章列表页支持封面图展示,提供多种布局和尺寸选项。 文章列表页支持封面图展示,提供多种布局和尺寸选项。

View File

@ -3,7 +3,7 @@ import type { IconifyIcon } from '@iconify/vue/offline'
import { loadIcon } from '@iconify/vue' import { loadIcon } from '@iconify/vue'
import { Icon as OfflineIcon } from '@iconify/vue/offline' import { Icon as OfflineIcon } from '@iconify/vue/offline'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { useIconsData } from '../composables/index.js' import { normalizeIconClassname } from '../composables/index.js'
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
@ -20,15 +20,13 @@ const { name, size, color, prefix, extra } = defineProps<{
const icon = ref<IconifyIcon | null>(null) const icon = ref<IconifyIcon | null>(null)
const loaded = ref(false) const loaded = ref(false)
const iconsData = useIconsData()
const iconName = computed(() => { const iconName = computed(() => {
if (name.includes(':')) if (name.includes(':'))
return name return name
return prefix ? `${prefix}:${name}` : name return prefix ? `${prefix}:${name}` : name
}) })
const localIconName = computed(() => iconsData.value[iconName.value]) const localIconName = computed(() => normalizeIconClassname(iconName.value))
async function loadRemoteIcon() { async function loadRemoteIcon() {
if (icon.value) if (icon.value)

View File

@ -1,6 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { SocialLinkIcon } from '../../shared/index.js' import type { SocialLinkIcon } from '../../shared/index.js'
import VPIcon from '@theme/VPIcon.vue'
import { computed } from 'vue' import { computed } from 'vue'
import { socialFallbacks } from '../composables/index.js'
const { icon, link, ariaLabel } = defineProps<{ const { icon, link, ariaLabel } = defineProps<{
icon: SocialLinkIcon icon: SocialLinkIcon
@ -8,10 +10,22 @@ const { icon, link, ariaLabel } = defineProps<{
ariaLabel?: string ariaLabel?: string
}>() }>()
const svg = computed(() => { const iconName = computed(() => {
if (typeof icon === 'object') if (typeof icon === 'string') {
return icon.svg const name = socialFallbacks[icon] || icon
return `<span class="vpi-social-${icon}" />` if (name.includes(':'))
return name
return `simple-icons:${name}`
}
return icon
})
const label = computed(() => {
if (ariaLabel)
return ariaLabel
if (typeof icon === 'string')
return icon.includes(':') ? icon.split(':')[1] : icon
return icon.name
}) })
</script> </script>
@ -19,9 +33,12 @@ const svg = computed(() => {
<a <a
class="vp-social-link no-icon" class="vp-social-link no-icon"
:href="link" :href="link"
:aria-label="ariaLabel ?? (typeof icon === 'string' ? icon : '')" :aria-label="label"
target="_blank" rel="noopener" v-html="svg" :title="label"
/> target="_blank" rel="noopener"
>
<VPIcon :name="iconName" />
</a>
</template> </template>
<style scoped> <style scoped>
@ -40,7 +57,7 @@ const svg = computed(() => {
} }
.vp-social-link > :deep(svg), .vp-social-link > :deep(svg),
.vp-social-link > :deep([class^="vpi-social-"]) { .vp-social-link > :deep([class*="vpi-"]) {
width: 20px; width: 20px;
height: 20px; height: 20px;
fill: currentcolor; fill: currentcolor;

View File

@ -2,15 +2,26 @@ import type { Ref } from 'vue'
import { icons } from '@internal/iconify' import { icons } from '@internal/iconify'
import { ref } from 'vue' import { ref } from 'vue'
type IconsData = Record<string, string> type NeedBackgroundIcons = string[]
type IconsDataRef = Ref<IconsData> type IconsDataRef = Ref<NeedBackgroundIcons>
const iconsData: IconsDataRef = ref(icons) const iconsData: IconsDataRef = ref(icons)
export const useIconsData = (): IconsDataRef => iconsData export const useIconsData = (): IconsDataRef => iconsData
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) { if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
__VUE_HMR_RUNTIME__.updateIcons = (data: IconsData) => { __VUE_HMR_RUNTIME__.updateIcons = (data: NeedBackgroundIcons) => {
iconsData.value = data iconsData.value = data
} }
} }
// 旧版本内置图标别名,映射回 simple-icons 集合中的名称
export const socialFallbacks: Record<string, string> = {
twitter: 'x',
weibo: 'sinaweibo',
}
export function normalizeIconClassname(icon: string): string {
const [collect, name] = icon.split(':')
return `vpi-${collect}-${name}${iconsData.value.includes(icon) ? ' bg' : ''}`
}

View File

@ -74,7 +74,7 @@ declare module '@internal/encrypt' {
} }
declare module '@internal/iconify' { declare module '@internal/iconify' {
const icons: Record<string, string> const icons: string[]
export { export {
icons, icons,
} }

View File

@ -3,7 +3,8 @@
@import url("./vars.css"); @import url("./vars.css");
@import url("./normalize.css"); @import url("./normalize.css");
@import url("./icons.css"); @import url("./icons.css");
@import url("./social-icons.css");
/* @import url("./social-icons.css"); */
@import url("./compat.css"); @import url("./compat.css");
@import url("./utils.css"); @import url("./utils.css");
@import url("./content.css"); @import url("./content.css");

View File

@ -1,6 +1,6 @@
import type { App, Page } from 'vuepress' import type { App, Page } from 'vuepress'
import type { IconOptions } from 'vuepress-plugin-md-power' import type { IconOptions } from 'vuepress-plugin-md-power'
import type { ThemeHomeConfig, ThemeNavItem, ThemeOptions, ThemeSidebar } from '../../shared/index.js' import type { FriendGroup, FriendsItem, SocialLink, ThemeHomeConfig, ThemeNavItem, ThemeOptions, ThemeSidebar } from '../../shared/index.js'
import type { FsCache } from '../utils/index.js' import type { FsCache } from '../utils/index.js'
import { getIconContentCSS, getIconData } from '@iconify/utils' import { getIconContentCSS, getIconData } from '@iconify/utils'
import { isArray, uniq } from '@pengzhanbo/utils' import { isArray, uniq } from '@pengzhanbo/utils'
@ -8,7 +8,7 @@ import { entries, isLinkAbsolute, isLinkHttp, isPlainObject } from '@vuepress/he
import { isPackageExists } from 'local-pkg' import { isPackageExists } from 'local-pkg'
import { fs } from 'vuepress/utils' import { fs } from 'vuepress/utils'
import { getThemeConfig } from '../loadConfig/index.js' import { getThemeConfig } from '../loadConfig/index.js'
import { createFsCache, interopDefault, logger, nanoid, perf, resolveContent, writeTemp } from '../utils/index.js' import { createFsCache, interopDefault, logger, perf, resolveContent, writeTemp } from '../utils/index.js'
interface IconData { interface IconData {
className: string className: string
@ -34,11 +34,17 @@ let fsCache: FsCache<IconDataMap> | null = null
// { iconName: { className, content } } // { iconName: { className, content } }
const cache: IconDataMap = {} const cache: IconDataMap = {}
// 旧版本内置图标别名,映射回 simple-icons 集合中的名称
const socialFallbacks: Record<string, string> = {
twitter: 'x',
weibo: 'sinaweibo',
}
export async function prepareIcons(app: App): Promise<void> { export async function prepareIcons(app: App): Promise<void> {
perf.mark('prepare:icons:total') perf.mark('prepare:icons:total')
const options = getThemeConfig() const options = getThemeConfig()
if (!isInstalled) { if (!isInstalled) {
await writeTemp(app, JS_FILENAME, resolveContent(app, { name: 'icons', content: '{}' })) await writeTemp(app, JS_FILENAME, resolveContent(app, { name: 'icons', content: '[]' }))
return return
} }
if (!fsCache && app.env.isDev) { if (!fsCache && app.env.isDev) {
@ -86,9 +92,10 @@ export async function prepareIcons(app: App): Promise<void> {
perf.log('prepare:icons:imports') perf.log('prepare:icons:imports')
let cssCode = '' let cssCode = ''
const map: Record<string, string> = {} const shouldBackground: string[] = []
for (const [iconName, { className, content, background }] of entries(cache)) { for (const [iconName, { className, content, background }] of entries(cache)) {
map[iconName] = `${className}${background ? ' bg' : ''}` if (background)
shouldBackground.push(iconName)
cssCode += `.${className} {\n --icon: ${content};\n}\n` cssCode += `.${className} {\n --icon: ${content};\n}\n`
} }
@ -96,7 +103,7 @@ export async function prepareIcons(app: App): Promise<void> {
writeTemp(app, CSS_FILENAME, cssCode), writeTemp(app, CSS_FILENAME, cssCode),
writeTemp(app, JS_FILENAME, resolveContent(app, { writeTemp(app, JS_FILENAME, resolveContent(app, {
name: 'icons', name: 'icons',
content: map, content: shouldBackground,
before: `import './iconify.css'`, before: `import './iconify.css'`,
})), })),
]) ])
@ -106,10 +113,11 @@ export async function prepareIcons(app: App): Promise<void> {
perf.log('prepare:icons:total') perf.log('prepare:icons:total')
} }
function isIconify(icon: any): icon is string { function isIconify(icon: unknown): icon is string {
if (!icon || typeof icon !== 'string' || isLinkAbsolute(icon) || isLinkHttp(icon)) if (!icon || typeof icon !== 'string' || isLinkAbsolute(icon) || isLinkHttp(icon))
return false return false
return icon[0] !== '{' && ICONIFY_NAME.test(icon) const ic = icon.trim()
return ic[0] !== '{' && ICONIFY_NAME.test(ic)
} }
function withPrefix(icon: string, prefix?: string): string { function withPrefix(icon: string, prefix?: string): string {
@ -130,7 +138,7 @@ function getIconsWithPage(page: Page, { provider = 'iconify', prefix }: IconOpti
} }
const addIcon = (icon: unknown): void => { const addIcon = (icon: unknown): void => {
if (isIconify(icon) && (provider === 'iconify' || icon.startsWith('iconify'))) { if (icon && isIconify(icon) && (provider === 'iconify' || icon.startsWith('iconify'))) {
list.push(withPrefix(icon.replace(/^iconify /, ''), prefix)) list.push(withPrefix(icon.replace(/^iconify /, ''), prefix))
} }
} }
@ -153,30 +161,58 @@ function getIconsWithPage(page: Page, { provider = 'iconify', prefix }: IconOpti
} }
} }
} }
if (fm.pageLayout === 'friends') {
const socialList: SocialLink[] = []
if ((fm.list as FriendsItem[])?.length) {
for (const { socials } of fm.list as FriendsItem[]) {
socialList.push(...(socials || []))
}
}
if ((fm.groups as FriendGroup[])?.length) {
for (const { list } of fm.groups as FriendGroup[]) {
if (!list?.length)
continue
for (const { socials } of list as FriendsItem[]) {
socialList.push(...(socials || []))
}
}
}
socialList.forEach(social => addIcon(getIconWithSocial(social)))
}
return list return list
} }
function getIconWithThemeConfig(options: ThemeOptions, { provider = 'iconify', prefix }: IconOptions): string[] { function getIconWithThemeConfig(options: ThemeOptions, { provider = 'iconify', prefix }: IconOptions): string[] {
const list: string[] = [] const list: string[] = []
// navbar notes sidebar // navbar / doc collection sidebar / social
const locales = options.locales || {} const locales = options.locales || {}
entries(locales).forEach(([, { navbar, sidebar, collections }]) => { entries(locales).forEach(([, { navbar, sidebar, collections, social }]) => {
// navbar icon
if (navbar) { if (navbar) {
list.push(...getIconWithNavbar(navbar)) list.push(...getIconWithNavbar(navbar))
} }
// social
const socialList: SocialLink[] = social ? [...social] : []
// sidebar icon
const sidebarList: ThemeSidebar[] = Object.values(sidebar || {}) as ThemeSidebar[] const sidebarList: ThemeSidebar[] = Object.values(sidebar || {}) as ThemeSidebar[]
if (collections?.length) { if (collections?.length) {
collections.forEach((collection) => { collections.forEach((collection) => {
if (collection.type === 'doc' && collection.sidebar) if (collection.type === 'doc' && collection.sidebar)
sidebarList.push(collection.sidebar) sidebarList.push(collection.sidebar)
if (collection.type === 'post' && collection.social)
socialList.push(...collection.social)
}) })
} }
sidebarList.forEach(sidebar => list.push(...getIconWithSidebar(sidebar))) sidebarList.forEach(sidebar => list.push(...getIconWithSidebar(sidebar)))
// social
socialList.forEach(social => list.push(getIconWithSocial(social)))
}) })
const addIcon = (icon: unknown): string | void => { const addIcon = (icon: unknown): string | void => {
if (isIconify(icon) && (provider === 'iconify' || icon.startsWith('iconify'))) { if (icon && isIconify(icon) && (provider === 'iconify' || icon.startsWith('iconify'))) {
return withPrefix(icon.replace(/^iconify /, ''), prefix) return withPrefix(icon.replace(/^iconify /, ''), prefix)
} }
} }
@ -224,6 +260,15 @@ function getIconWithSidebar(sidebar: ThemeSidebar): string[] {
return list return list
} }
function getIconWithSocial({ icon }: SocialLink): string {
if (!icon || typeof icon !== 'string')
return ''
const name = socialFallbacks[icon] || icon
if (name.includes(':'))
return name
return `simple-icons:${name}`
}
async function resolveCollect(collect: string, names: string[]) { async function resolveCollect(collect: string, names: string[]) {
const filepath = locate(collect) const filepath = locate(collect)
const config = await readJSON(filepath) const config = await readJSON(filepath)
@ -251,7 +296,7 @@ async function resolveCollect(collect: string, names: string[]) {
*/ */
const background = !data.body.includes('currentColor') const background = !data.body.includes('currentColor')
cache[icon] = { cache[icon] = {
className: `vpi-${nanoid()}`, className: normalizeClassname(icon),
background, background,
content: matched, content: matched,
} }
@ -260,6 +305,11 @@ async function resolveCollect(collect: string, names: string[]) {
return unknownList return unknownList
} }
function normalizeClassname(icon: string): string {
const [collect, name] = icon.split(':')
return `vpi-${collect}-${name}`
}
async function readJSON(filepath: string) { async function readJSON(filepath: string) {
try { try {
return await fs.readJSON(filepath, 'utf-8') return await fs.readJSON(filepath, 'utf-8')

View File

@ -1,3 +1,4 @@
import type { LiteralUnion } from '../utils.js'
/** /**
* *
*/ */
@ -10,7 +11,7 @@ export interface SocialLink {
/** /**
* *
*/ */
export type SocialLinkIcon = SocialLinkIconUnion | { svg: string, name?: string } export type SocialLinkIcon = LiteralUnion<SocialLinkIconUnion> | { svg: string, name?: string }
export type SocialLinkIconUnion export type SocialLinkIconUnion
= | 'discord' = | 'discord'
@ -36,3 +37,9 @@ export type SocialLinkIconUnion
| 'steam' | 'steam'
| 'stackoverflow' | 'stackoverflow'
| 'xbox' | 'xbox'
| 'tiktok'
| 'kuaishou'
| 'bytedance'
| 'xiaohongshu'
| 'bluesky'
| 'gmail'