feat(plugin-md-power): add :[icon]: syntax supported

This commit is contained in:
pengzhanbo 2024-03-29 00:38:14 +08:00
parent b8379e2245
commit c2fc5ad7b1
35 changed files with 668 additions and 39 deletions

View File

@ -54,6 +54,7 @@
"vue"
],
"cSpell.words": [
"bilibili",
"bumpp",
"caniuse",
"colours",

View File

@ -66,6 +66,13 @@ export const theme: Theme = themePlume({
mermaid: true,
flowchart: true,
},
markdownPower: {
pdf: true,
caniuse: true,
bilibili: true,
youtube: true,
icons: true,
},
comment: {
provider: 'Giscus',
comment: true,

View File

@ -12,6 +12,7 @@
"vuepress": "2.0.0-rc.9"
},
"dependencies": {
"@iconify/json": "^2.2.196",
"@vuepress/bundler-vite": "2.0.0-rc.9",
"anywhere": "^1.6.0",
"chart.js": "^4.4.2",

View File

@ -1,5 +1,5 @@
{
"name": "@vuepress-plume/plugin-md-power",
"name": "vuepress-plugin-md-power",
"type": "module",
"version": "1.0.0-rc.47",
"description": "The Plugin for VuePres 2 - markdown power",

View File

@ -0,0 +1,20 @@
let isBind = false
export function setupCanIUse(): void {
if (isBind)
return
isBind = true
window.addEventListener('message', (message) => {
const data = message.data
if (typeof data === 'string' && data.includes('ciu_embed')) {
const [, feature, height] = data.split(':')
const el = document.querySelector(`.ciu_embed[data-feature="${feature}"]:not([data-skip])`)
if (el) {
const h = Number.parseInt(height) + 30
; (el.childNodes[0] as any).height = `${h}px`
el.setAttribute('data-skip', 'true')
}
}
})
}

View File

@ -0,0 +1,55 @@
import type { MaybeRef } from '@vueuse/core'
import { useEventListener } from '@vueuse/core'
import type { Ref, ShallowRef, ToRefs } from 'vue'
import { computed, isRef, onMounted, ref, shallowRef, toValue, watch } from 'vue'
import type { SizeOptions } from '../../shared/size.js'
export interface SizeInfo<T extends HTMLElement> {
el: ShallowRef<T | undefined>
width: Ref<string>
height: Ref<string>
resize: () => void
}
export function useSize<T extends HTMLElement>(
options: ToRefs<SizeOptions>,
extraHeight: MaybeRef<number> = 0,
): SizeInfo<T> {
const el = shallowRef<T>()
const width = computed(() => toValue(options.width) || '100%')
const height = ref('auto')
const getRadio = (ratio: number | string | undefined): number => {
if (typeof ratio === 'string') {
const [width, height] = ratio.split(':')
const parsedRadio = Number(width) / Number(height)
if (!Number.isNaN(parsedRadio))
return parsedRadio
}
return typeof ratio === 'number' ? ratio : 16 / 9
}
const getHeight = (width: number): string => {
const height = toValue(options.height)
const ratio = getRadio(toValue(options.ratio))
return height || `${Number(width) / ratio + toValue(extraHeight)}px`
}
const resize = (): void => {
if (el.value)
height.value = getHeight(el.value.offsetWidth)
}
onMounted(() => {
resize()
if (isRef(extraHeight))
watch(extraHeight, resize)
useEventListener('orientationchange', resize)
useEventListener('resize', resize)
})
return { el, width, height, resize }
}

View File

@ -0,0 +1,33 @@
import { defineClientConfig } from 'vuepress/client'
import type { ClientConfig } from 'vuepress/client'
import { pluginOptions } from './options.js'
import { setupCanIUse } from './composables/setupCanIUse.js'
import PDFViewer from './components/PDFViewer.vue'
import Bilibili from './components/Bilibili.vue'
import Youtube from './components/Youtube.vue'
import '@internal/md-power/icons.css'
declare const __VUEPRESS_SSR__: boolean
export default defineClientConfig({
enhance({ router, app }) {
if (pluginOptions.pdf)
app.component('PDFViewer', PDFViewer)
if (pluginOptions.bilibili)
app.component('VideoBilibili', Bilibili)
if (pluginOptions.youtube)
app.component('VideoYoutube', Youtube)
if (__VUEPRESS_SSR__)
return
if (pluginOptions.caniuse) {
router.afterEach(() => {
setupCanIUse()
})
}
},
}) as ClientConfig

View File

@ -0,0 +1 @@
export * from '../shared/index.js'

View File

@ -0,0 +1,5 @@
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
declare const __MD_POWER_INJECT_OPTIONS__: MarkdownPowerPluginOptions
export const pluginOptions = __MD_POWER_INJECT_OPTIONS__

View File

@ -0,0 +1,6 @@
declare module '*.vue' {
import type { ComponentOptions } from 'vue'
const comp: ComponentOptions
export default comp
}

View File

@ -0,0 +1,15 @@
export function checkIsMobile(ua: string): boolean {
return /\b(?:Android|iPhone)/i.test(ua)
}
export function checkIsSafari(ua: string): boolean {
return /version\/([\w.]+) .*(mobile ?safari|safari)/i.test(ua)
}
export function checkIsiPad(ua: string): boolean {
return [
/\((ipad);[-\w),; ]+apple/i,
/applecoremedia\/[\w.]+ \((ipad)/i,
/\b(ipad)\d\d?,\d\d?[;\]].+ios/i,
].some(item => item.test(ua))
}

View File

@ -0,0 +1,6 @@
import { isLinkHttp } from 'vuepress/shared'
import { withBase } from 'vuepress/client'
export function normalizeLink(url: string): string {
return isLinkHttp(url) ? url : withBase(url)
}

View File

@ -6,7 +6,7 @@ import type { PluginWithOptions, Token } from 'markdown-it'
import type { RuleBlock } from 'markdown-it/lib/parser_block.js'
import type { Markdown } from 'vuepress/markdown'
import container from 'markdown-it-container'
import type { CanIUseMode, CanIUseOptions, CanIUseTokenMeta } from '../shared/index.js'
import type { CanIUseMode, CanIUseOptions, CanIUseTokenMeta } from '../../shared/index.js'
// @[caniuse]()
const minLength = 12

View File

@ -0,0 +1,2 @@
export * from './writer.js'
export * from './plugin.js'

View File

@ -0,0 +1,96 @@
/**
* :[mdi:11]:
* :[mdi:11 24px]:
* :[mid:11 /#ccc]:
* :[fluent-mdl2:toggle-filled 128px/#fff]:
*/
import type { PluginWithOptions } from 'markdown-it'
import type { RuleInline } from 'markdown-it/lib/parser_inline.js'
import { parseRect } from '../../utils/parseRect.js'
type AddIcon = (iconName: string) => string | undefined
function createTokenizer(addIcon: AddIcon): RuleInline {
return (state, silent) => {
let found = false
const max = state.posMax
const start = state.pos
if (state.src.slice(start, start + 2) !== ':[')
return false
if (silent)
return false
// :[]:
if (max - start < 5)
return false
state.pos = start + 2
while (state.pos < max) {
if (state.src.slice(state.pos, state.pos + 2) === ']:') {
found = true
break
}
state.md.inline.skipToken(state)
}
if (!found || start + 2 === state.pos) {
state.pos = start
return false
}
const content = state.src.slice(start + 2, state.pos)
// 不允许前后带有空格
if (/^\s|\s$/.test(content)) {
state.pos = start
return false
}
// found!
state.posMax = state.pos
state.pos = start + 2
const [iconName, options = ''] = content.split(/\s+/)
const [size, color] = options.split('/')
const open = state.push('iconify_open', 'span', 1)
open.markup = ':['
const className = addIcon(iconName)
if (className)
open.attrSet('class', className)
let style = ''
if (size)
style += `width:${parseRect(size)};height:${parseRect(size)};`
if (color)
style += `color:${color};`
if (style)
open.attrSet('style', style)
const text = state.push('text', '', 0)
text.content = className ? '' : iconName
const close = state.push('iconify_close', 'span', -1)
close.markup = ']:'
state.pos = state.posMax + 2
state.posMax = max
return true
}
}
export const iconsPlugin: PluginWithOptions<AddIcon> = (
md,
addIcon = () => '',
) => {
md.inline.ruler.before('emphasis', 'iconify', createTokenizer(addIcon))
}

View File

@ -0,0 +1,128 @@
import type { App } from 'vuepress/core'
import { getIconContentCSS, getIconData } from '@iconify/utils'
import { fs, logger } from 'vuepress/utils'
import { isPackageExists } from 'local-pkg'
import { customAlphabet } from 'nanoid'
import type { IconsOptions } from '../../../shared/icons.js'
import { interopDefault } from '../../utils/package.js'
import { parseRect } from '../../utils/parseRect.js'
export interface IconCacheItem {
className: string
content: string
}
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 8)
const iconDataCache = new Map<string, any>()
const URL_CONTENT_RE = /(url\([^]+?\))/
function resolveOption(opt?: boolean | IconsOptions): Required<IconsOptions> {
const options = typeof opt === 'object' ? opt : {}
options.prefix ??= 'vp-mdi'
options.color = options.color === 'currentColor' || !options.color ? 'currentcolor' : options.color
options.size = options.size ? parseRect(`${options.size}`) : '1em'
return options as Required<IconsOptions>
}
export function createIconCSSWriter(app: App, opt?: boolean | IconsOptions) {
const cache = new Map<string, IconCacheItem>()
const isInstalled = isPackageExists('@iconify/json')
const write = (content: string) => app.writeTemp('internal/md-power/icons.css', content)
const options = resolveOption(opt)
const prefix = options.prefix
const defaultContent = getDefaultContent(options)
async function writeCss() {
let css = defaultContent
for (const [, { content, className }] of cache)
css += `.${className} {\n --svg: ${content};\n}\n`
await write(css)
}
function addIcon(iconName: string) {
if (!isInstalled)
return
if (cache.has(iconName))
return cache.get(iconName)!.className
const item: IconCacheItem = {
className: `${prefix}-${nanoid()}`,
content: '',
}
cache.set(iconName, item)
genIconContent(iconName, (content) => {
item.content = content
writeCss()
})
return item.className
}
async function initIcon() {
if (!opt)
return await write('')
if (!isInstalled) {
logger.error('[plugin-md-power]: `@iconify/json` not found! Please install `@iconify/json` first.')
return
}
return await writeCss()
}
return { addIcon, writeCss, initIcon }
}
function getDefaultContent(options: Required<IconsOptions>) {
const { prefix, size, color } = options
return `[class^="${prefix}-"],
[class*=" ${prefix}-"] {
display: inline-block;
width: ${size};
height: ${size};
vertical-align: middle;
color: inherit;
background-color: ${color};
-webkit-mask: var(--svg) no-repeat;
mask: var(--svg) no-repeat;
-webkit-mask-size: 100% 100%;
mask-size: 100% 100%;
}
`
}
let locate: ((name: string) => any) | undefined
async function genIconContent(iconName: string, cb: (content: string) => void) {
if (!locate) {
const mod = await interopDefault(import('@iconify/json'))
locate = mod.locate
}
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'))
iconDataCache.set(collect, iconJson)
}
catch (e) {
logger.warn(`[plugin-md-power] Can not find icon, ${collect} is missing!`)
}
}
const data = getIconData(iconJson, name)
if (!data)
return logger.error(`[plugin-md-power] Can not read icon in ${collect}, ${name} is missing!`)
const content = getIconContentCSS(data, {
height: data.height || 24,
})
const match = content.match(URL_CONTENT_RE)
return cb(match ? match[1] : '')
}

View File

@ -0,0 +1,2 @@
export * from './plugin.js'
export * from '../shared/index.js'

View File

@ -0,0 +1,6 @@
declare module 'markdown-it-container' {
import type { PluginWithParams } from 'markdown-it'
const container: PluginWithParams
export = container
}

View File

@ -0,0 +1,58 @@
import type { Plugin } from 'vuepress/core'
import { getDirname, path } from 'vuepress/utils'
import type { CanIUseOptions, MarkdownPowerPluginOptions } from '../shared/index.js'
import { caniusePlugin, legacyCaniuse } from './features/caniuse.js'
import { pdfPlugin } from './features/pdf.js'
import { createIconCSSWriter, iconsPlugin } from './features/icons/index.js'
import { bilibiliPlugin } from './features/video/bilibili.js'
import { youtubePlugin } from './features/video/youtube.js'
const __dirname = getDirname(import.meta.url)
export function markdownPowerPlugin(options: MarkdownPowerPluginOptions = {}): Plugin {
return (app) => {
const { initIcon, addIcon } = createIconCSSWriter(app, options.icons)
return {
name: '@vuepress-plume/plugin-md-power',
clientConfigFile: path.resolve(__dirname, '../client/config.js'),
define: {
__MD_POWER_INJECT_OPTIONS__: options,
},
onInitialized: async () => await initIcon(),
extendsMarkdown(md) {
if (options.caniuse) {
const caniuse = options.caniuse === true ? {} : options.caniuse
// @[caniuse](feature_name)
md.use<CanIUseOptions>(caniusePlugin, caniuse)
// 兼容旧语法
legacyCaniuse(md, caniuse)
}
if (options.pdf) {
// @[pdf](url)
md.use(pdfPlugin)
}
if (options.icons) {
// :[collect:name]:
md.use(iconsPlugin, addIcon)
}
if (options.bilibili) {
// @[bilibili](bvid aid cid)
md.use(bilibiliPlugin)
}
if (options.youtube) {
// @[youtube](id)
md.use(youtubePlugin)
}
},
}
}
}

View 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
}

View File

@ -0,0 +1,6 @@
export function parseRect(str: string, unit = 'px'): string {
if (Number.parseFloat(str) === Number(str))
return `${str}${unit}`
return str
}

View File

@ -0,0 +1,41 @@
const RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w\d-]+)(?:=\s*(?<quote>['"])(?<value>.+?)\k<quote>)?(?:\s+|$)/
export function resolveAttrs(info: string): {
attrs: Record<string, any>
rawAttrs: string
} {
info = info.trim()
if (!info)
return { rawAttrs: '', attrs: {} }
const attrs: Record<string, string | boolean> = {}
const rawAttrs = info
let matched: RegExpMatchArray | null
// eslint-disable-next-line no-cond-assign
while (matched = info.match(RE_ATTR_VALUE)) {
const { attr, value } = matched.groups || {}
attrs[attr] = value ?? true
info = info.slice(matched[0].length)
}
Object.keys(attrs).forEach((key) => {
let value = attrs[key]
value = typeof value === 'string' ? value.trim() : value
if (value === 'true')
value = true
else if (value === 'false')
value = false
attrs[key] = value
if (key.includes('-')) {
const _key = key.replace(/-(\w)/g, (_, c) => c.toUpperCase())
attrs[_key] = value
}
})
return { attrs, rawAttrs }
}

View File

@ -0,0 +1,11 @@
export function timeToSeconds(time: string): number {
if (!time)
return 0
if (Number.parseFloat(time) === Number(time))
return Number(time)
const [s, m, h] = time.split(':').reverse().map(n => Number(n) || 0)
return s + m * 60 + h * 3600
}

View File

@ -0,0 +1,19 @@
export interface IconsOptions {
/**
* The prefix of the icon className
* @default 'vp-mdi'
*/
prefix?: string
/**
* The size of the icon
* @default '1em'
*/
size?: string | number
/**
* The color of the icon
* @default 'currentColor'
*/
color?: string
}

View File

@ -0,0 +1,3 @@
export * from './caniuse.js'
export * from './pdf.js'
export * from './plugin.js'

View File

@ -0,0 +1,11 @@
import type { CanIUseOptions } from './caniuse.js'
import type { PDFOptions } from './pdf.js'
import type { IconsOptions } from './icons.js'
export interface MarkdownPowerPluginOptions {
caniuse?: boolean | CanIUseOptions
pdf?: boolean | PDFOptions
icons?: boolean | IconsOptions
bilibili?: boolean
youtube?: boolean
}

View File

@ -0,0 +1,5 @@
export interface SizeOptions {
width?: string
height?: string
ratio?: number | string
}

View File

@ -0,0 +1,25 @@
import type { SizeOptions } from './size'
export interface VideoOptions {
bilibili?: boolean
youtube?: boolean
}
export interface BilibiliTokenMeta extends SizeOptions {
title?: string
bvid?: string
aid?: string
cid?: string
autoplay?: boolean
time?: string | number
page?: number
}
export interface YoutubeTokenMeta extends SizeOptions {
title?: string
id: string
autoplay?: boolean
loop?: boolean
start?: string | number
end?: string | number
}

View File

@ -15,7 +15,8 @@
{ "path": "./plugin-page-collection/tsconfig.build.json" },
{ "path": "./plugin-shikiji/tsconfig.build.json" },
{ "path": "./plugin-content-update/tsconfig.build.json" },
{ "path": "./plugin-search/tsconfig.build.json" }
{ "path": "./plugin-search/tsconfig.build.json" },
{ "path": "./plugin-md-power/tsconfig.build.json" }
],
"files": []
}

87
pnpm-lock.yaml generated
View File

@ -79,6 +79,9 @@ importers:
docs:
dependencies:
'@iconify/json':
specifier: ^2.2.196
version: 2.2.196
'@vuepress/bundler-vite':
specifier: 2.0.0-rc.9
version: 2.0.0-rc.9(@types/node@20.9.1)(typescript@5.4.3)
@ -202,6 +205,37 @@ importers:
specifier: 2.0.0-rc.9
version: 2.0.0-rc.9(@vuepress/bundler-vite@2.0.0-rc.9)(typescript@5.4.3)(vue@3.4.21)
plugins/plugin-md-power:
dependencies:
'@iconify/utils':
specifier: ^2.1.22
version: 2.1.22
'@vueuse/core':
specifier: ^10.9.0
version: 10.9.0(vue@3.4.21)
local-pkg:
specifier: ^0.5.0
version: 0.5.0
markdown-it-container:
specifier: ^4.0.0
version: 4.0.0
nanoid:
specifier: ^5.0.6
version: 5.0.6
vue:
specifier: ^3.4.21
version: 3.4.21(typescript@5.4.3)
vuepress:
specifier: 2.0.0-rc.9
version: 2.0.0-rc.9(@vuepress/bundler-vite@2.0.0-rc.9)(typescript@5.4.3)(vue@3.4.21)
devDependencies:
'@iconify/json':
specifier: ^2.2.196
version: 2.2.196
'@types/markdown-it':
specifier: ^13.0.7
version: 13.0.7
plugins/plugin-netlify-functions:
dependencies:
'@iarna/toml':
@ -346,9 +380,6 @@ importers:
'@vuepress-plume/plugin-blog-data':
specifier: workspace:*
version: link:../plugins/plugin-blog-data
'@vuepress-plume/plugin-caniuse':
specifier: workspace:*
version: link:../plugins/plugin-caniuse
'@vuepress-plume/plugin-content-update':
specifier: workspace:*
version: link:../plugins/plugin-content-update
@ -439,6 +470,9 @@ importers:
vuepress-plugin-md-enhance:
specifier: 2.0.0-rc.32
version: 2.0.0-rc.32(katex@0.16.9)(markdown-it@14.1.0)(typescript@5.4.3)(vuepress@2.0.0-rc.9)
vuepress-plugin-md-power:
specifier: workspace:*
version: link:../plugins/plugin-md-power
packages:
@ -586,6 +620,17 @@ packages:
engines: {node: '>=18.0.0', npm: '>=9.0.0', pnpm: '>= 8.6.0'}
dev: true
/@antfu/install-pkg@0.1.1:
resolution: {integrity: sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==}
dependencies:
execa: 5.1.1
find-up: 5.0.0
dev: false
/@antfu/utils@0.7.7:
resolution: {integrity: sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==}
dev: false
/@babel/code-frame@7.22.13:
resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==}
engines: {node: '>=6.9.0'}
@ -1705,8 +1750,27 @@ packages:
resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==}
dev: false
/@iconify/json@2.2.196:
resolution: {integrity: sha512-hRZ0pq77N+mkAbZvFi/pfsKcspA8PyGSASc6zQoq6n/RSLxb8xAgORatVHyDl0ow7shcS+dvyiZI8xmr6yI2WA==}
dependencies:
'@iconify/types': 2.0.0
pathe: 1.1.2
/@iconify/types@2.0.0:
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
/@iconify/utils@2.1.22:
resolution: {integrity: sha512-6UHVzTVXmvO8uS6xFF+L/QTSpTzA/JZxtgU+KYGFyDYMEObZ1bu/b5l+zNJjHy+0leWjHI+C0pXlzGvv3oXZMA==}
dependencies:
'@antfu/install-pkg': 0.1.1
'@antfu/utils': 0.7.7
'@iconify/types': 2.0.0
debug: 4.3.4(supports-color@9.2.2)
kolorist: 1.8.0
local-pkg: 0.5.0
mlly: 1.6.1
transitivePeerDependencies:
- supports-color
dev: false
/@iconify/vue@4.1.1(vue@3.4.21):
@ -3958,7 +4022,7 @@ packages:
'@vue/shared': 3.4.21
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.0.2
source-map-js: 1.2.0
/@vue/compiler-dom@3.4.21:
resolution: {integrity: sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==}
@ -3976,8 +4040,8 @@ packages:
'@vue/shared': 3.4.21
estree-walker: 2.0.2
magic-string: 0.30.7
postcss: 8.4.35
source-map-js: 1.0.2
postcss: 8.4.38
source-map-js: 1.2.0
/@vue/compiler-ssr@3.4.21:
resolution: {integrity: sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==}
@ -8360,7 +8424,6 @@ packages:
dependencies:
locate-path: 6.0.0
path-exists: 4.0.0
dev: true
/find-up@6.3.0:
resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==}
@ -10134,6 +10197,10 @@ packages:
resolution: {integrity: sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==}
dev: true
/kolorist@1.8.0:
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
dev: false
/kuler@2.0.0:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
dev: false
@ -10320,7 +10387,6 @@ packages:
dependencies:
mlly: 1.6.1
pkg-types: 1.0.3
dev: true
/locate-path@2.0.0:
resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==}
@ -10341,7 +10407,6 @@ packages:
engines: {node: '>=10'}
dependencies:
p-locate: 5.0.0
dev: true
/locate-path@7.2.0:
resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
@ -11498,7 +11563,7 @@ packages:
resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==}
dependencies:
acorn: 8.10.0
pathe: 1.1.1
pathe: 1.1.2
pkg-types: 1.0.3
ufo: 1.3.1
@ -12278,7 +12343,6 @@ packages:
engines: {node: '>=10'}
dependencies:
p-limit: 3.1.0
dev: true
/p-locate@6.0.0:
resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
@ -12754,6 +12818,7 @@ packages:
nanoid: 3.3.7
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
/postcss@8.4.38:
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}

View File

@ -59,7 +59,6 @@
"@vuepress-plume/plugin-auto-frontmatter": "workspace:*",
"@vuepress-plume/plugin-baidu-tongji": "workspace:*",
"@vuepress-plume/plugin-blog-data": "workspace:*",
"@vuepress-plume/plugin-caniuse": "workspace:*",
"@vuepress-plume/plugin-content-update": "workspace:*",
"@vuepress-plume/plugin-copy-code": "workspace:*",
"@vuepress-plume/plugin-iconify": "workspace:*",
@ -88,6 +87,7 @@
"nanoid": "^5.0.6",
"vue": "^3.4.21",
"vue-router": "4.3.0",
"vuepress-plugin-md-enhance": "2.0.0-rc.32"
"vuepress-plugin-md-enhance": "2.0.0-rc.32",
"vuepress-plugin-md-power": "workspace:*"
}
}

View File

@ -1,17 +0,0 @@
const colorList = [
'var(--vp-c-brand-1)',
'var(--vp-c-brand-2)',
'var(--vp-c-green-1)',
'var(--vp-c-green-2)',
'var(--vp-c-green-3)',
'var(--vp-c-yellow-1)',
'var(--vp-c-yellow-2)',
'var(--vp-c-yellow-3)',
'var(--vp-c-red-1)',
'var(--vp-c-red-2)',
'var(--vp-c-red-3)',
]
export function getRandomColor() {
return colorList[Math.floor(Math.random() * colorList.length)]
}

View File

@ -10,7 +10,6 @@ import { themeDataPlugin } from '@vuepress/plugin-theme-data'
import { autoFrontmatterPlugin } from '@vuepress-plume/plugin-auto-frontmatter'
import { baiduTongjiPlugin } from '@vuepress-plume/plugin-baidu-tongji'
import { blogDataPlugin } from '@vuepress-plume/plugin-blog-data'
import { caniusePlugin } from '@vuepress-plume/plugin-caniuse'
import { copyCodePlugin } from '@vuepress-plume/plugin-copy-code'
import { iconifyPlugin } from '@vuepress-plume/plugin-iconify'
import { notesDataPlugin } from '@vuepress-plume/plugin-notes-data'
@ -22,6 +21,7 @@ import { seoPlugin } from '@vuepress/plugin-seo'
import { sitemapPlugin } from '@vuepress/plugin-sitemap'
import { contentUpdatePlugin } from '@vuepress-plume/plugin-content-update'
import { searchPlugin } from '@vuepress-plume/plugin-search'
import { markdownPowerPlugin } from 'vuepress-plugin-md-power'
import type {
PlumeThemeEncrypt,
PlumeThemeLocaleOptions,
@ -139,9 +139,6 @@ export function setupPlugins(
}))
}
if (options.caniuse !== false)
plugins.push(caniusePlugin(options.caniuse || { mode: 'embed' }))
if (options.externalLinkIcon !== false) {
plugins.push(externalLinkIconPlugin({
locales: Object.entries(localeOptions.locales || {}).reduce(
@ -205,6 +202,13 @@ export function setupPlugins(
))
}
if (options.markdownPower !== false) {
plugins.push(markdownPowerPlugin({
caniuse: options.caniuse,
...options.markdownPower || {},
}))
}
if (options.comment)
plugins.push(commentPlugin(options.comment))

View File

@ -2,18 +2,20 @@ import type { DocsearchOptions } from '@vuepress/plugin-docsearch'
import type { SearchPluginOptions } from '@vuepress-plume/plugin-search'
import type { AutoFrontmatterOptions } from '@vuepress-plume/plugin-auto-frontmatter'
import type { BaiduTongjiOptions } from '@vuepress-plume/plugin-baidu-tongji'
import type { CanIUsePluginOptions } from '@vuepress-plume/plugin-caniuse'
import type { CopyCodeOptions } from '@vuepress-plume/plugin-copy-code'
import type { ShikiPluginOptions } from '@vuepress-plume/plugin-shikiji'
import type { CommentPluginOptions } from '@vuepress/plugin-comment'
import type { MarkdownEnhanceOptions } from 'vuepress-plugin-md-enhance'
import type { ReadingTimePluginOptions } from '@vuepress/plugin-reading-time'
import type { MarkdownPowerPluginOptions } from 'vuepress-plugin-md-power'
export interface PlumeThemePluginOptions {
/**
* @deprecated `plugin-md-power`
*
* can-i-use
*/
caniuse?: false | CanIUsePluginOptions
caniuse?: false
/**
* external-link-icon
@ -54,6 +56,8 @@ export interface PlumeThemePluginOptions {
markdownEnhance?: false | MarkdownEnhanceOptions
markdownPower?: false | MarkdownPowerPluginOptions
comment?: false | CommentPluginOptions
sitemap?: false

View File

@ -17,6 +17,9 @@
"@vuepress-plume/*/client": ["./plugins/*/src/client/index.ts"],
"vuepress-plugin-netlify-functions": [
"./plugins/plugin-netlify-functions/src/node/index.ts"
],
"vuepress-plugin-md-power": [
"./plugins/plugin-md-power/src/node/index.ts"
]
},
"types": ["webpack-env", "vite/client"]