mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
feat(theme): add support for iconify localization
This commit is contained in:
parent
2fc28878fa
commit
5d0d626eef
@ -10,6 +10,7 @@ import { parseRect } from '../../utils/parseRect.js'
|
||||
|
||||
export interface IconCacheItem {
|
||||
className: string
|
||||
background: boolean
|
||||
content: string
|
||||
}
|
||||
|
||||
@ -18,6 +19,8 @@ const iconDataCache = new Map<string, any>()
|
||||
const URL_CONTENT_RE = /(url\([\s\S]+?\))/
|
||||
const CSS_PATH = 'internal/md-power/icons.css'
|
||||
|
||||
let locate: ((name: string) => any) | undefined
|
||||
|
||||
function resolveOption(opt?: boolean | IconsOptions): Required<IconsOptions> {
|
||||
const options = typeof opt === 'object' ? opt : {}
|
||||
options.prefix ??= 'vp-mdi'
|
||||
@ -65,19 +68,18 @@ export function createIconCSSWriter(app: App, opt?: boolean | IconsOptions) {
|
||||
if (!isInstalled)
|
||||
return
|
||||
|
||||
if (cache.has(iconName))
|
||||
return cache.get(iconName)!.className
|
||||
if (cache.has(iconName)) {
|
||||
const item = cache.get(iconName)!
|
||||
return `${item.className}${item.background ? ' bg' : ''}`
|
||||
}
|
||||
|
||||
const item: IconCacheItem = {
|
||||
className: `${prefix}-${nanoid()}`,
|
||||
content: '',
|
||||
...genIcon(iconName),
|
||||
}
|
||||
cache.set(iconName, item)
|
||||
genIconContent(iconName, (content) => {
|
||||
item.content = content
|
||||
writeCss()
|
||||
})
|
||||
return item.className
|
||||
writeCss()
|
||||
return `${item.className}${item.background ? ' bg' : ''}`
|
||||
}
|
||||
|
||||
async function initIcon() {
|
||||
@ -89,6 +91,11 @@ export function createIconCSSWriter(app: App, opt?: boolean | IconsOptions) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!locate) {
|
||||
const mod = await interopDefault(import('@iconify/json'))
|
||||
locate = mod.locate
|
||||
}
|
||||
|
||||
return await writeCss()
|
||||
}
|
||||
|
||||
@ -97,12 +104,13 @@ export function createIconCSSWriter(app: App, opt?: boolean | IconsOptions) {
|
||||
|
||||
function getDefaultContent(options: Required<IconsOptions>) {
|
||||
const { prefix, size, color } = options
|
||||
return `[class^="${prefix}-"],
|
||||
[class*=" ${prefix}-"] {
|
||||
return `[class^="${prefix}-"] {
|
||||
display: inline-block;
|
||||
width: ${size};
|
||||
height: ${size};
|
||||
vertical-align: middle;
|
||||
}
|
||||
[class^="${prefix}-"]:not(.bg) {
|
||||
color: inherit;
|
||||
background-color: ${color};
|
||||
-webkit-mask: var(--svg) no-repeat;
|
||||
@ -110,24 +118,29 @@ function getDefaultContent(options: Required<IconsOptions>) {
|
||||
-webkit-mask-size: 100% 100%;
|
||||
mask-size: 100% 100%;
|
||||
}
|
||||
[class^="${prefix}-"].bg {
|
||||
background-color: transparent;
|
||||
background-image: var(--svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
let locate: ((name: string) => any) | undefined
|
||||
|
||||
async function genIconContent(iconName: string, cb: (content: string) => void) {
|
||||
function genIcon(iconName: string): {
|
||||
content: string
|
||||
background: boolean
|
||||
} {
|
||||
if (!locate) {
|
||||
const mod = await interopDefault(import('@iconify/json'))
|
||||
locate = mod.locate
|
||||
return { content: '', background: false }
|
||||
}
|
||||
|
||||
const [collect, name] = iconName.split(':')
|
||||
let iconJson: any = iconDataCache.get(collect)
|
||||
if (!iconJson) {
|
||||
const filename = locate(collect)
|
||||
|
||||
try {
|
||||
iconJson = JSON.parse(await fs.readFile(filename, 'utf-8'))
|
||||
iconJson = JSON.parse(fs.readFileSync(filename, 'utf-8'))
|
||||
iconDataCache.set(collect, iconJson)
|
||||
}
|
||||
catch {
|
||||
@ -135,14 +148,19 @@ async function genIconContent(iconName: string, cb: (content: string) => void) {
|
||||
}
|
||||
}
|
||||
const data = getIconData(iconJson, name)
|
||||
if (!data)
|
||||
return logger.error(`[plugin-md-power] Can not read icon in ${collect}, ${name} is missing!`)
|
||||
if (!data) {
|
||||
logger.error(`[plugin-md-power] Can not read icon in ${collect}, ${name} is missing!`)
|
||||
return { content: '', background: false }
|
||||
}
|
||||
|
||||
const content = getIconContentCSS(data, {
|
||||
height: data.height || 24,
|
||||
})
|
||||
const match = content.match(URL_CONTENT_RE)
|
||||
return cb(match ? match[1] : '')
|
||||
return {
|
||||
content: match ? match[1] : '',
|
||||
background: !data.body.includes('currentColor'),
|
||||
}
|
||||
}
|
||||
|
||||
function existsSync(fp: string) {
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@ -274,9 +274,6 @@ importers:
|
||||
'@vuepress-plume/plugin-fonts':
|
||||
specifier: workspace:*
|
||||
version: link:../plugins/plugin-fonts
|
||||
'@vuepress-plume/plugin-iconify':
|
||||
specifier: workspace:*
|
||||
version: link:../plugins/plugin-iconify
|
||||
'@vuepress-plume/plugin-search':
|
||||
specifier: workspace:*
|
||||
version: link:../plugins/plugin-search
|
||||
@ -403,9 +400,6 @@ packages:
|
||||
peerDependencies:
|
||||
'@algolia/client-search': '>= 4.9.1 < 6'
|
||||
algoliasearch: '>= 4.9.1 < 6'
|
||||
peerDependenciesMeta:
|
||||
'@algolia/client-search':
|
||||
optional: true
|
||||
|
||||
'@algolia/cache-browser-local-storage@4.20.0':
|
||||
resolution: {integrity: sha512-uujahcBt4DxduBTvYdwO3sBfHuJvJokiC3BP1+O70fglmE1ShkH8lpXqZBac1rrU3FnNYSUs4pL9lBdTKeRPOQ==}
|
||||
@ -5708,9 +5702,8 @@ snapshots:
|
||||
|
||||
'@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.20.0)(algoliasearch@4.20.0)':
|
||||
dependencies:
|
||||
algoliasearch: 4.20.0
|
||||
optionalDependencies:
|
||||
'@algolia/client-search': 4.20.0
|
||||
algoliasearch: 4.20.0
|
||||
|
||||
'@algolia/cache-browser-local-storage@4.20.0':
|
||||
dependencies:
|
||||
|
||||
@ -73,7 +73,6 @@
|
||||
"@pengzhanbo/utils": "^1.1.2",
|
||||
"@vuepress-plume/plugin-content-update": "workspace:*",
|
||||
"@vuepress-plume/plugin-fonts": "workspace:*",
|
||||
"@vuepress-plume/plugin-iconify": "workspace:*",
|
||||
"@vuepress-plume/plugin-search": "workspace:*",
|
||||
"@vuepress-plume/plugin-shikiji": "workspace:*",
|
||||
"@vuepress/helper": "2.0.0-rc.39",
|
||||
|
||||
@ -2,35 +2,83 @@
|
||||
import { computed } from 'vue'
|
||||
import { isLinkHttp } from 'vuepress/shared'
|
||||
import { withBase } from 'vuepress/client'
|
||||
import VPIconify from '@theme/VPIconify.vue'
|
||||
import { useIconsData } from '../composables/index.js'
|
||||
|
||||
const props = defineProps<{
|
||||
name: string | { svg: string }
|
||||
size?: string | number
|
||||
color?: string
|
||||
}>()
|
||||
|
||||
const isLink = computed(() =>
|
||||
typeof props.name === 'string' && (isLinkHttp(props.name) || props.name[0] === '/'),
|
||||
)
|
||||
const isSvg = computed(() => typeof props.name === 'object' && !!props.name.svg)
|
||||
const iconsData = useIconsData()
|
||||
|
||||
const type = computed(() => {
|
||||
if (typeof props.name === 'string' && (isLinkHttp(props.name) || props.name[0] === '/')) {
|
||||
return 'link'
|
||||
}
|
||||
if (typeof props.name === 'object' && !!props.name.svg) {
|
||||
return 'svg'
|
||||
}
|
||||
if (typeof props.name === 'string' && iconsData.value[props.name]) {
|
||||
return 'local'
|
||||
}
|
||||
return 'remote'
|
||||
})
|
||||
|
||||
const svg = computed(() => {
|
||||
if (isSvg.value)
|
||||
if (type.value === 'svg')
|
||||
return (props.name as { svg: string }).svg
|
||||
|
||||
return ''
|
||||
})
|
||||
const link = computed(() => {
|
||||
if (isLink.value) {
|
||||
if (type.value === 'link') {
|
||||
const link = props.name as string
|
||||
return isLinkHttp(link) ? link : withBase(link)
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const className = computed(() => {
|
||||
if (type.value === 'local') {
|
||||
const name = props.name as string
|
||||
return iconsData.value[name] || ''
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const size = computed(() => {
|
||||
const size = props.size
|
||||
if (!size)
|
||||
return undefined
|
||||
if (String(Number(size)) === size)
|
||||
return `${size}px`
|
||||
|
||||
return size
|
||||
})
|
||||
|
||||
const style = computed(() => ({
|
||||
'background-color': props.color,
|
||||
'width': size.value,
|
||||
'height': size.value,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img v-if="isLink" class="vp__img" :src="link" alt="">
|
||||
<span v-else-if="isSvg" class="vp-iconify" v-html="svg" />
|
||||
<Icon v-else :name="name" />
|
||||
<img v-if="type === 'link'" class="vp__img" :src="link" alt="" :style="{ height: size }">
|
||||
<span
|
||||
v-else-if="type === 'svg'"
|
||||
class="vp-iconify"
|
||||
:style="style"
|
||||
v-html="svg"
|
||||
/>
|
||||
<span
|
||||
v-else-if="type === 'local' && className"
|
||||
class="vp-iconify" :class="[className]"
|
||||
:style="style"
|
||||
/>
|
||||
<VPIconify v-else :name="(name as string)" :size="size" :color="color" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
75
theme/src/client/components/VPIconify.vue
Normal file
75
theme/src/client/components/VPIconify.vue
Normal file
@ -0,0 +1,75 @@
|
||||
<script lang="ts" setup>
|
||||
import type { IconifyIcon } from '@iconify/vue/offline'
|
||||
import { Icon as OfflineIcon } from '@iconify/vue/offline'
|
||||
import { loadIcon } from '@iconify/vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
name?: string
|
||||
size?: string | number
|
||||
color?: string
|
||||
}>(),
|
||||
{
|
||||
name: '',
|
||||
size: '',
|
||||
color: '',
|
||||
},
|
||||
)
|
||||
|
||||
const icon = ref<IconifyIcon | null>(null)
|
||||
const loaded = ref(false)
|
||||
|
||||
async function loadIconComponent() {
|
||||
if (icon.value)
|
||||
return
|
||||
|
||||
if (!__VUEPRESS_SSR__) {
|
||||
try {
|
||||
loaded.value = false
|
||||
icon.value = await loadIcon(props.name)
|
||||
}
|
||||
finally {
|
||||
loaded.value = true
|
||||
}
|
||||
}
|
||||
else {
|
||||
loaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.name, loadIconComponent, { immediate: true })
|
||||
|
||||
const size = computed(() => {
|
||||
const size = props.size || '1em'
|
||||
if (String(Number(size)) === size)
|
||||
return `${size}px`
|
||||
|
||||
return size
|
||||
})
|
||||
const color = computed(() => props.color || 'currentColor')
|
||||
|
||||
const bind = computed<any>(() => ({
|
||||
icon: icon.value,
|
||||
color: props.color,
|
||||
height: size.value,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<span v-if="!loaded" class="vp-iconify" :style="{ color, width: size, height: size }" />
|
||||
<OfflineIcon
|
||||
v-else-if="icon"
|
||||
class="vp-iconify"
|
||||
v-bind="bind"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.vp-iconify {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
@ -44,6 +44,7 @@ onBeforeUnmount(() => {
|
||||
.group + .group {
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
transition: border var(--t-color);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
|
||||
16
theme/src/client/composables/icons.ts
Normal file
16
theme/src/client/composables/icons.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { icons } from '@internal/iconify'
|
||||
import { ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
type IconsData = Record<string, string>
|
||||
type IconsDataRef = Ref<IconsData>
|
||||
|
||||
const iconsData: IconsDataRef = ref(icons)
|
||||
|
||||
export const useIconsData = (): IconsDataRef => iconsData
|
||||
|
||||
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
|
||||
__VUE_HMR_RUNTIME__.updateIcons = (data: IconsData) => {
|
||||
iconsData.value = data
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
export * from './theme-data.js'
|
||||
export * from './dark-mode.js'
|
||||
export * from './data.js'
|
||||
export * from './icons.js'
|
||||
|
||||
export * from './scroll-promise.js'
|
||||
export * from './scroll-behavior.js'
|
||||
|
||||
@ -5,6 +5,7 @@ import VPCard from '@theme/global/VPCard.vue'
|
||||
import VPLinkCard from '@theme/global/VPLinkCard.vue'
|
||||
import VPBadge from '@theme/global/VPBadge.vue'
|
||||
import VPCardGrid from '@theme/global/VPCardGrid.vue'
|
||||
import VPIcon from '@theme/VPIcon.vue'
|
||||
|
||||
export function globalComponents(app: App) {
|
||||
app.component('Badge', VPBadge)
|
||||
@ -36,13 +37,8 @@ export function globalComponents(app: App) {
|
||||
return null
|
||||
})
|
||||
|
||||
app.component('Icon', (props) => {
|
||||
const Iconify = app.component('Iconify')
|
||||
if (Iconify)
|
||||
return h(Iconify, props)
|
||||
|
||||
return null
|
||||
})
|
||||
app.component('Icon', VPIcon)
|
||||
app.component('VPIcon', VPIcon)
|
||||
|
||||
/** @deprecated */
|
||||
app.component('HomeBox', VPHomeBox)
|
||||
|
||||
7
theme/src/client/shim.d.ts
vendored
7
theme/src/client/shim.d.ts
vendored
@ -58,3 +58,10 @@ declare module '@internal/encrypt' {
|
||||
encrypt,
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@internal/iconify' {
|
||||
const icons: Record<string, string>
|
||||
export {
|
||||
icons,
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ html:not(.dark) .vp-code span {
|
||||
margin: 16px -24px;
|
||||
overflow-x: auto;
|
||||
background-color: var(--vp-code-block-bg);
|
||||
transition: background-color 0.5s;
|
||||
transition: background-color var(--t-color);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
[class^="vpi-"],
|
||||
[class*=" vpi-"],
|
||||
.vp-icon {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
[class^="vpi-"].bg,
|
||||
[class*=" vpi-"].bg,
|
||||
.vp-icon.bg {
|
||||
background-color: transparent;
|
||||
background-image: var(--icon);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ import { docsearchPlugin } from '@vuepress/plugin-docsearch'
|
||||
import { gitPlugin } from '@vuepress/plugin-git'
|
||||
import { photoSwipePlugin } from '@vuepress/plugin-photo-swipe'
|
||||
import { nprogressPlugin } from '@vuepress/plugin-nprogress'
|
||||
import { iconifyPlugin } from '@vuepress-plume/plugin-iconify'
|
||||
import { shikiPlugin } from '@vuepress-plume/plugin-shikiji'
|
||||
import { commentPlugin } from '@vuepress/plugin-comment'
|
||||
import { type MarkdownEnhancePluginOptions, mdEnhancePlugin } from 'vuepress-plugin-md-enhance'
|
||||
@ -41,7 +40,6 @@ export function getPlugins({
|
||||
|
||||
const plugins: PluginConfig = [
|
||||
|
||||
iconifyPlugin(),
|
||||
fontsPlugin(),
|
||||
contentUpdatePlugin(),
|
||||
activeHeaderLinksPlugin({
|
||||
|
||||
@ -5,6 +5,7 @@ import { prepareArticleTagColors } from './prepareArticleTagColor.js'
|
||||
import { preparedBlogData } from './prepareBlogData.js'
|
||||
import { prepareEncrypt } from './prepareEncrypt.js'
|
||||
import { prepareSidebar } from './prepareSidebar.js'
|
||||
import { prepareIcons } from './prepareIcons.js'
|
||||
|
||||
export async function prepareData(
|
||||
app: App,
|
||||
@ -15,6 +16,7 @@ export async function prepareData(
|
||||
preparedBlogData(app, localeOptions, encrypt),
|
||||
prepareSidebar(app, localeOptions),
|
||||
prepareEncrypt(app, encrypt),
|
||||
prepareIcons(app, localeOptions),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
196
theme/src/node/prepare/prepareIcons.ts
Normal file
196
theme/src/node/prepare/prepareIcons.ts
Normal file
@ -0,0 +1,196 @@
|
||||
import type { App, Page } from 'vuepress'
|
||||
import { isArray, isString, uniq } from '@pengzhanbo/utils'
|
||||
import { fs } from 'vuepress/utils'
|
||||
import { entries, isLinkAbsolute, isLinkHttp, isPlainObject } from '@vuepress/helper'
|
||||
import { isPackageExists } from 'local-pkg'
|
||||
import { getIconContentCSS, getIconData } from '@iconify/utils'
|
||||
import type { NavItem, PlumeThemeLocaleOptions, Sidebar } from '../../shared/index.js'
|
||||
import { interopDefault, logger, nanoid, resolveContent, writeTemp } from '../utils/index.js'
|
||||
|
||||
interface IconData {
|
||||
className: string
|
||||
background?: boolean
|
||||
content: string
|
||||
}
|
||||
|
||||
type CollectMap = Record<string, string[]>
|
||||
type IconDataMap = Record<string, IconData>
|
||||
|
||||
const ICON_REGEXP = /<(?:VP)?Icon(?:ify)?([^>]*)>/g
|
||||
const ICON_NAME_REGEXP = /name="([^"]+)"/
|
||||
const URL_CONTENT_REGEXP = /(url\([\s\S]+\))/
|
||||
const JS_FILENAME = 'internal/iconify.js'
|
||||
const CSS_FILENAME = 'internal/iconify.css'
|
||||
|
||||
const isInstalled = isPackageExists('@iconify/json')
|
||||
let locate!: ((name: string) => any)
|
||||
|
||||
// { iconName: { className, content } }
|
||||
const cache: IconDataMap = {}
|
||||
|
||||
export async function prepareIcons(app: App, localeOptions: PlumeThemeLocaleOptions) {
|
||||
if (!isInstalled) {
|
||||
await writeTemp(app, JS_FILENAME, resolveContent(app, { name: 'icons', content: '{}' }))
|
||||
return
|
||||
}
|
||||
|
||||
const iconList: string[] = []
|
||||
app.pages.forEach(page => iconList.push(...getIconsWithPage(page)))
|
||||
iconList.push(...getIconWithThemeConfig(localeOptions))
|
||||
|
||||
const collectMap: CollectMap = {}
|
||||
uniq(iconList).filter(icon => !cache[icon]).forEach((iconName) => {
|
||||
const [collect, name] = iconName.split(':')
|
||||
if (!collectMap[collect])
|
||||
collectMap[collect] = []
|
||||
|
||||
collectMap[collect].push(name)
|
||||
})
|
||||
|
||||
if (!locate) {
|
||||
const mod = await interopDefault(import('@iconify/json'))
|
||||
locate = mod.locate
|
||||
}
|
||||
|
||||
const unknownList = (await Promise.all(
|
||||
entries(collectMap).map(([collect, names]) => resolveCollect(collect, names)),
|
||||
)).flat()
|
||||
|
||||
if (unknownList.length) {
|
||||
logger.warn(`[iconify] Unknown icons: ${unknownList.join(', ')}`)
|
||||
}
|
||||
|
||||
let cssCode = ''
|
||||
const map: Record<string, string> = {}
|
||||
for (const [iconName, { className, content, background }] of entries(cache)) {
|
||||
map[iconName] = `${className}${background ? ' bg' : ''}`
|
||||
cssCode += `.${className} {\n --icon: ${content};\n}\n`
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
writeTemp(app, CSS_FILENAME, cssCode),
|
||||
writeTemp(app, JS_FILENAME, resolveContent(app, {
|
||||
name: 'icons',
|
||||
content: map,
|
||||
before: `import './iconify.css'`,
|
||||
})),
|
||||
])
|
||||
}
|
||||
|
||||
function getIconsWithPage(page: Page): string[] {
|
||||
const list = page.contentRendered
|
||||
.match(ICON_REGEXP)?.map(match => match.match(ICON_NAME_REGEXP)?.[1])
|
||||
.filter(Boolean) as string[] || []
|
||||
|
||||
if (page.frontmatter.icon && isString(page.frontmatter.icon)) {
|
||||
list.push(page.frontmatter.icon)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
function getIconWithThemeConfig(localeOptions: PlumeThemeLocaleOptions): string[] {
|
||||
const list: string[] = []
|
||||
// navbar notes sidebar
|
||||
const locales = localeOptions.locales || {}
|
||||
entries(locales).forEach(([, { navbar, sidebar, notes }]) => {
|
||||
if (navbar) {
|
||||
list.push(...getIconWithNavbar(navbar))
|
||||
}
|
||||
const sidebarList: Sidebar[] = Object.values(sidebar || {}) as Sidebar[]
|
||||
if (notes) {
|
||||
notes.notes.forEach((note) => {
|
||||
if (note.sidebar)
|
||||
sidebarList.push(note.sidebar)
|
||||
})
|
||||
}
|
||||
sidebarList.forEach(sidebar => list.push(...getIconWithSidebar(sidebar)))
|
||||
})
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
function getIconWithNavbar(navbar: NavItem[]): string[] {
|
||||
const list: string[] = []
|
||||
navbar.forEach((item) => {
|
||||
if (typeof item !== 'string') {
|
||||
if (typeof item.icon === 'string' && !isLinkHttp(item.icon) && !isLinkAbsolute(item.icon))
|
||||
list.push(item.icon)
|
||||
if (item.items?.length)
|
||||
list.push(...getIconWithNavbar(item.items))
|
||||
}
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
function getIconWithSidebar(sidebar: Sidebar): string[] {
|
||||
const list: string[] = []
|
||||
if (isArray(sidebar)) {
|
||||
sidebar.forEach((item) => {
|
||||
if (typeof item !== 'string') {
|
||||
if (typeof item.icon === 'string' && !isLinkHttp(item.icon) && !isLinkAbsolute(item.icon))
|
||||
list.push(item.icon)
|
||||
if (item.items?.length)
|
||||
list.push(...getIconWithSidebar(item.items))
|
||||
}
|
||||
})
|
||||
}
|
||||
else if (isPlainObject(sidebar)) {
|
||||
entries(sidebar).forEach(([, item]) => {
|
||||
if (typeof item !== 'string') {
|
||||
if (isArray(item)) {
|
||||
list.push(...getIconWithSidebar(item))
|
||||
}
|
||||
else if (item.items?.length) {
|
||||
list.push(...getIconWithSidebar(item.items))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
async function resolveCollect(collect: string, names: string[]) {
|
||||
const filepath = locate(collect)
|
||||
const config = await readJSON(filepath)
|
||||
|
||||
if (!config) {
|
||||
logger.warn(`[iconify] Can not find icon collect: ${collect}!`)
|
||||
return []
|
||||
}
|
||||
|
||||
const unknownList: string[] = []
|
||||
|
||||
for (const name of names) {
|
||||
const data = getIconData(config, name)
|
||||
const icon = `${collect}:${name}`
|
||||
if (!data) {
|
||||
unknownList.push(icon)
|
||||
}
|
||||
else if (!cache[icon]) {
|
||||
const content = getIconContentCSS(data, {
|
||||
height: data.height || 24,
|
||||
})
|
||||
const matched = content.match(URL_CONTENT_REGEXP)?.[1] ?? ''
|
||||
/**
|
||||
* @see - https://iconify.design/docs/libraries/utils/get-icon-css.html#options
|
||||
*/
|
||||
const background = !data.body.includes('currentColor')
|
||||
cache[icon] = {
|
||||
className: `vpi-${nanoid()}`,
|
||||
background,
|
||||
content: matched,
|
||||
}
|
||||
}
|
||||
}
|
||||
return unknownList
|
||||
}
|
||||
|
||||
async function readJSON(filepath: string) {
|
||||
try {
|
||||
return await fs.readJSON(filepath, 'utf-8')
|
||||
}
|
||||
catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -7,5 +7,6 @@ export const logger = new Logger(THEME_NAME)
|
||||
export * from './hash.js'
|
||||
export * from './path.js'
|
||||
export * from './package.js'
|
||||
export * from './interopDefault.js'
|
||||
export * from './resolveContent.js'
|
||||
export * from './writeTemp.js'
|
||||
|
||||
6
theme/src/node/utils/interopDefault.ts
Normal file
6
theme/src/node/utils/interopDefault.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export type Awaitable<T> = T | Promise<T>
|
||||
|
||||
export async function interopDefault<T>(m: Awaitable<T>): Promise<T extends { default: infer U } ? U : T> {
|
||||
const resolved = await m
|
||||
return (resolved as any).default || resolved
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user