feat(plugin-md-power): add @[caniuse](feature) syntax supported

This commit is contained in:
pengzhanbo 2024-03-28 15:38:32 +08:00
parent b77a6334d7
commit 53dfcb83b1
5 changed files with 297 additions and 0 deletions

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (C) 2021 - PRESENT by pengzhanbo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,69 @@
{
"name": "@vuepress-plume/plugin-md-power",
"type": "module",
"version": "1.0.0-rc.47",
"description": "The Plugin for VuePres 2 - markdown power",
"author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT",
"homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git",
"directory": "plugins/plugin-md-power"
},
"bugs": {
"url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues"
},
"exports": {
".": {
"types": "./lib/node/index.d.ts",
"import": "./lib/node/index.js"
},
"./client": {
"types": "./lib/client/index.d.ts",
"import": "./lib/client/index.js"
},
"./package.json": "./package.json"
},
"main": "lib/node/index.js",
"types": "./lib/node/index.d.ts",
"files": [
"lib"
],
"scripts": {
"build": "pnpm run copy && pnpm run ts",
"clean": "rimraf --glob ./lib ./*.tsbuildinfo",
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
"ts": "tsc -b tsconfig.build.json"
},
"peerDependencies": {
"@iconify/json": "^2",
"vuepress": "2.0.0-rc.9"
},
"peerDependenciesMeta": {
"@iconify/json": {
"optional": true
}
},
"dependencies": {
"@iconify/utils": "^2.1.22",
"@vueuse/core": "^10.9.0",
"local-pkg": "^0.5.0",
"markdown-it-container": "^4.0.0",
"nanoid": "^5.0.6",
"vue": "^3.4.21"
},
"devDependencies": {
"@iconify/json": "^2.2.196",
"@types/markdown-it": "^13.0.7"
},
"publishConfig": {
"access": "public"
},
"keyword": [
"VuePress",
"vuepress plugin",
"markdown power",
"vuepress-plugin-md-power"
]
}

View File

@ -0,0 +1,179 @@
/**
* @[caniuse embed{1,2,3,4}](feature_name)
* @[caniuse image](feature_name)
*/
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'
// @[caniuse]()
const minLength = 12
// char codes of '@[caniuse'
const START_CODES = [64, 91, 99, 97, 110, 105, 117, 115, 101]
// regexp to match the import syntax
const SYNTAX_RE = /^@\[caniuse(?:\s*?(embed|image)?(?:{([0-9,\-]*?)})?)\]\(([^)]*)\)/
function createCanIUseRuleBlock(defaultMode: CanIUseMode): RuleBlock {
return (state, startLine, endLine, silent) => {
const pos = state.bMarks[startLine] + state.tShift[startLine]
const max = state.eMarks[startLine]
// return false if the length is shorter than min length
if (pos + minLength > max)
return false
// check if it's matched the start
for (let i = 0; i < START_CODES.length; i += 1) {
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
return false
}
// check if it's matched the syntax
const match = state.src.slice(pos, max).match(SYNTAX_RE)
if (!match)
return false
// return true as we have matched the syntax
if (silent)
return true
const [, mode, versions = '', feature] = match
const meta: CanIUseTokenMeta = {
feature,
mode: (mode as CanIUseMode) || defaultMode,
versions,
}
const token = state.push('caniuse', '', 0)
token.meta = meta
token.map = [startLine, startLine + 1]
token.info = mode || defaultMode
state.line = startLine + 1
return true
}
}
function resolveCanIUse({ feature, mode, versions }: CanIUseTokenMeta): string {
if (!feature)
return ''
if (mode === 'image') {
const link = 'https://caniuse.bitsofco.de/image/'
const alt = `Data on support for the ${feature} feature across the major browsers from caniuse.com`
return `<ClientOnly><p><picture>
<source type="image/webp" srcset="${link}${feature}.webp">
<source type="image/png" srcset="${link}${feature}.png">
<img src="${link}${feature}.jpg" alt="${alt}" width="100%">
</picture></p></ClientOnly>`
}
const periods = resolveVersions(versions)
const accessible = 'false'
const image = 'none'
const url = 'https://caniuse.bitsofco.de/embed/index.html'
const src = `${url}?feat=${feature}&periods=${periods}&accessible-colours=${accessible}&image-base=${image}`
return `<ClientOnly><div class="ciu_embed" style="margin:16px 0" data-feature="${feature}"><iframe src="${src}" frameborder="0" width="100%" height="400px" title="Can I use${feature}"></iframe></div></ClientOnly>`
}
function resolveVersions(versions: string): string {
if (!versions)
return 'future_1,current,past_1,past_2'
const list = versions
.split(',')
.map(v => Number(v.trim()))
.filter(v => !Number.isNaN(v) && v >= -5 && v <= 3)
list.push(0)
const uniq = [...new Set(list)].sort((a, b) => b - a)
const result: string[] = []
uniq.forEach((v) => {
if (v < 0)
result.push(`past_${Math.abs(v)}`)
if (v === 0)
result.push('current')
if (v > 0)
result.push(`future_${v}`)
})
return result.join(',')
}
/**
* @example
* ```md
* @[caniuse](feature_name)
* ```
*/
export const caniusePlugin: PluginWithOptions<CanIUseOptions> = (
md,
{ mode = 'embed' }: CanIUseOptions = {},
): void => {
md.block.ruler.before(
'import_code',
'caniuse',
createCanIUseRuleBlock(mode),
{
alt: ['paragraph', 'reference', 'blockquote', 'list'],
},
)
md.renderer.rules.caniuse = (tokens, index) => {
const token = tokens[index]
const content = resolveCanIUse(token.meta)
token.content = content
return content
}
}
/**
* @deprecated use caniuse plugin
*
*
* @example
* ```md
* :::caniuse <feature_name>
* :::
* ```
*/
export function legacyCaniuse(
md: Markdown,
{ mode = 'embed' }: CanIUseOptions = {},
): void {
const modeMap: CanIUseMode[] = ['image', 'embed']
const isMode = (mode: CanIUseMode): boolean => modeMap.includes(mode)
mode = isMode(mode) ? mode : modeMap[0]
const type = 'caniuse'
const validateReg = new RegExp(`^${type}\\s+(.*)$`)
const validate = (info: string): boolean => {
return validateReg.test(info.trim())
}
const render = (tokens: Token[], index: number): string => {
const token = tokens[index]
if (token.nesting === 1) {
const info = token.info.trim().slice(type.length).trim() || ''
const feature = info.split(/\s+/)[0]
const versions = info.match(/\{(.*)\}/)?.[1] || ''
return feature ? resolveCanIUse({ feature, mode, versions }) : ''
}
else {
return ''
}
}
md.use(container, type, { validate, render })
}

View File

@ -0,0 +1,20 @@
export type CanIUseMode = 'embed' | 'image'
export interface CanIUseTokenMeta {
feature: string
mode: CanIUseMode
versions: string
}
export interface CanIUseOptions {
/**
*
*
* embed iframe嵌入
*
* image
*
* @default 'embed'
*/
mode?: CanIUseMode
}

View File

@ -0,0 +1,8 @@
{
"extends": "../tsconfig.build.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
},
"include": ["./src"]
}