feat(plugin-md-power): add :[icon]: syntax supported
This commit is contained in:
parent
b8379e2245
commit
c2fc5ad7b1
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -54,6 +54,7 @@
|
||||
"vue"
|
||||
],
|
||||
"cSpell.words": [
|
||||
"bilibili",
|
||||
"bumpp",
|
||||
"caniuse",
|
||||
"colours",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
55
plugins/plugin-md-power/src/client/composables/size.ts
Normal file
55
plugins/plugin-md-power/src/client/composables/size.ts
Normal 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 }
|
||||
}
|
||||
33
plugins/plugin-md-power/src/client/config.ts
Normal file
33
plugins/plugin-md-power/src/client/config.ts
Normal 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
|
||||
1
plugins/plugin-md-power/src/client/index.ts
Normal file
1
plugins/plugin-md-power/src/client/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from '../shared/index.js'
|
||||
5
plugins/plugin-md-power/src/client/options.ts
Normal file
5
plugins/plugin-md-power/src/client/options.ts
Normal 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__
|
||||
6
plugins/plugin-md-power/src/client/shim.d.ts
vendored
Normal file
6
plugins/plugin-md-power/src/client/shim.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
declare module '*.vue' {
|
||||
import type { ComponentOptions } from 'vue'
|
||||
|
||||
const comp: ComponentOptions
|
||||
export default comp
|
||||
}
|
||||
15
plugins/plugin-md-power/src/client/utils/is.ts
Normal file
15
plugins/plugin-md-power/src/client/utils/is.ts
Normal 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))
|
||||
}
|
||||
6
plugins/plugin-md-power/src/client/utils/link.ts
Normal file
6
plugins/plugin-md-power/src/client/utils/link.ts
Normal 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)
|
||||
}
|
||||
@ -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
|
||||
2
plugins/plugin-md-power/src/node/features/icons/index.ts
Normal file
2
plugins/plugin-md-power/src/node/features/icons/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './writer.js'
|
||||
export * from './plugin.js'
|
||||
96
plugins/plugin-md-power/src/node/features/icons/plugin.ts
Normal file
96
plugins/plugin-md-power/src/node/features/icons/plugin.ts
Normal 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))
|
||||
}
|
||||
128
plugins/plugin-md-power/src/node/features/icons/writer.ts
Normal file
128
plugins/plugin-md-power/src/node/features/icons/writer.ts
Normal 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] : '')
|
||||
}
|
||||
2
plugins/plugin-md-power/src/node/index.ts
Normal file
2
plugins/plugin-md-power/src/node/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './plugin.js'
|
||||
export * from '../shared/index.js'
|
||||
6
plugins/plugin-md-power/src/node/markdown-it-container.d.ts
vendored
Normal file
6
plugins/plugin-md-power/src/node/markdown-it-container.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
declare module 'markdown-it-container' {
|
||||
import type { PluginWithParams } from 'markdown-it'
|
||||
|
||||
const container: PluginWithParams
|
||||
export = container
|
||||
}
|
||||
58
plugins/plugin-md-power/src/node/plugin.ts
Normal file
58
plugins/plugin-md-power/src/node/plugin.ts
Normal 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)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
6
plugins/plugin-md-power/src/node/utils/package.ts
Normal file
6
plugins/plugin-md-power/src/node/utils/package.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
|
||||
}
|
||||
6
plugins/plugin-md-power/src/node/utils/parseRect.ts
Normal file
6
plugins/plugin-md-power/src/node/utils/parseRect.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export function parseRect(str: string, unit = 'px'): string {
|
||||
if (Number.parseFloat(str) === Number(str))
|
||||
return `${str}${unit}`
|
||||
|
||||
return str
|
||||
}
|
||||
41
plugins/plugin-md-power/src/node/utils/resolveAttrs.ts
Normal file
41
plugins/plugin-md-power/src/node/utils/resolveAttrs.ts
Normal 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 }
|
||||
}
|
||||
11
plugins/plugin-md-power/src/node/utils/timeToSeconds.ts
Normal file
11
plugins/plugin-md-power/src/node/utils/timeToSeconds.ts
Normal 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
|
||||
}
|
||||
19
plugins/plugin-md-power/src/shared/icons.ts
Normal file
19
plugins/plugin-md-power/src/shared/icons.ts
Normal 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
|
||||
}
|
||||
3
plugins/plugin-md-power/src/shared/index.ts
Normal file
3
plugins/plugin-md-power/src/shared/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './caniuse.js'
|
||||
export * from './pdf.js'
|
||||
export * from './plugin.js'
|
||||
11
plugins/plugin-md-power/src/shared/plugin.ts
Normal file
11
plugins/plugin-md-power/src/shared/plugin.ts
Normal 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
|
||||
}
|
||||
5
plugins/plugin-md-power/src/shared/size.ts
Normal file
5
plugins/plugin-md-power/src/shared/size.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface SizeOptions {
|
||||
width?: string
|
||||
height?: string
|
||||
ratio?: number | string
|
||||
}
|
||||
25
plugins/plugin-md-power/src/shared/video.ts
Normal file
25
plugins/plugin-md-power/src/shared/video.ts
Normal 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
|
||||
}
|
||||
@ -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
87
pnpm-lock.yaml
generated
@ -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==}
|
||||
|
||||
@ -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:*"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)]
|
||||
}
|
||||
@ -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))
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user