├── .npmrc ├── CONTRIBUTING.md ├── docs ├── public │ ├── og.png │ ├── oxc.svg │ └── logo.svg ├── test.md ├── .vitepress │ ├── theme │ │ └── index.ts │ └── config.ts ├── package.json ├── getting-started.md ├── showcase.md ├── index.md ├── assets │ └── vitepress.svg └── features.md ├── eslint.config.js ├── src ├── index.ts ├── helper.ts ├── utils.ts ├── vite.ts ├── builtin.ts ├── markdown.ts └── codegen.ts ├── pnpm-workspace.yaml ├── .gitignore ├── tsconfig.json ├── .github └── workflows │ ├── cr.yml │ ├── release.yml │ └── ci.yml ├── LICENSE ├── .vscode └── settings.json ├── test ├── codegen.test.ts └── __snapshots__ │ └── codegen.test.ts.snap ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | shell-emulator=true 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please refer to https://github.com/antfu/contribute 2 | -------------------------------------------------------------------------------- /docs/public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuyinws/vitepress-plugin-group-icons/HEAD/docs/public/og.png -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import antfu from '@antfu/eslint-config' 3 | 4 | export default antfu() 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './builtin' 2 | export * from './helper' 3 | export * from './markdown' 4 | export * from './vite' 5 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - playground 3 | - docs 4 | - packages/* 5 | - examples/* 6 | 7 | onlyBuiltDependencies: 8 | - rolldown 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .DS_Store 3 | .idea 4 | *.log 5 | *.tgz 6 | coverage 7 | dist 8 | lib-cov 9 | logs 10 | node_modules 11 | temp 12 | docs/.vitepress/cache 13 | .vercel 14 | -------------------------------------------------------------------------------- /src/helper.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'node:fs' 2 | import { dirname, resolve } from 'node:path' 3 | import { fileURLToPath } from 'node:url' 4 | 5 | export function localIconLoader(url: string, path: string) { 6 | return readFileSync(resolve(dirname(fileURLToPath(url)), path), 'utf-8') 7 | } 8 | -------------------------------------------------------------------------------- /docs/test.md: -------------------------------------------------------------------------------- 1 | ``` [nuxt.config.ts] 2 | test 3 | ``` 4 | 5 | ``` [named-icon ~vscode-icons:default-folder~] 6 | test 7 | ``` 8 | 9 | ::: code-group 10 | 11 | ``` [Docker ~vscode-icons:file-type-docker2~] 12 | Docker 13 | ``` 14 | 15 | ``` [Docker2 ~vscode-icons:file-type-docker2~] 16 | Docker 17 | ``` 18 | 19 | ::: 20 | 21 | ```ts [users/[id]/index.ts] 22 | // users/[id]/index.ts 23 | ``` 24 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function isSetEqual(set1: Set, set2: Set) { 2 | try { 3 | if (set1.size !== set2.size) { 4 | return false 5 | } 6 | for (const item of set1) { 7 | if (!set2.has(item)) { 8 | return false 9 | } 10 | } 11 | return true 12 | } 13 | catch { 14 | return false 15 | } 16 | } 17 | 18 | export const namedIconMatchRegex = /\s~([^~]+)~/ 19 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import type { EnhanceAppContext } from 'vitepress' 2 | import TwoslashFloatingVue from '@shikijs/vitepress-twoslash/client' 3 | import Theme from 'vitepress/theme' 4 | import 'virtual:group-icons.css' 5 | import '@shikijs/vitepress-twoslash/style.css' 6 | 7 | export default { 8 | extends: Theme, 9 | enhanceApp({ app }: EnhanceAppContext) { 10 | app.use(TwoslashFloatingVue) 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "description": "", 6 | "author": "", 7 | "license": "ISC", 8 | "keywords": [], 9 | "main": "index.js", 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "devDependencies": { 14 | "@iconify-json/devicon": "^1.1.50", 15 | "@shikijs/vitepress-twoslash": "^1.17.6", 16 | "vite-plugin-inspect": "^0.8.5", 17 | "vitepress-plugin-group-icons": "workspace:*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2019", 4 | "lib": ["ESNext", "DOM"], 5 | "baseUrl": ".", 6 | "rootDir": ".", 7 | "module": "ESNext", 8 | "moduleResolution": "node", 9 | "strict": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitReturns": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "declaration": true, 15 | "sourceMap": true, 16 | "esModuleInterop": true, 17 | "verbatimModuleSyntax": true, 18 | "skipLibCheck": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/cr.yml: -------------------------------------------------------------------------------- 1 | name: CR 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [main] 7 | paths-ignore: 8 | - '.github/**' 9 | - 'tests/**' 10 | - 'docs/**' 11 | - '*.md' 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 20 23 | 24 | - name: Setup 25 | run: npm i -g @antfu/ni 26 | 27 | - name: Install 28 | run: nci 29 | 30 | - name: Build 31 | run: nr build 32 | 33 | - run: nlx pkg-pr-new publish 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | permissions: 4 | id-token: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | tags: 10 | - 'v*' 11 | 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | - uses: pnpm/action-setup@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: lts/* 23 | registry-url: https://registry.npmjs.org/ 24 | 25 | - run: pnpm dlx changelogithub 26 | env: 27 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 28 | 29 | # # Uncomment the following lines to publish to npm on CI 30 | # 31 | # - run: pnpm install 32 | # - run: pnpm publish -r --access public 33 | # env: 34 | # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 35 | # NPM_CONFIG_PROVENANCE: true 36 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Install 4 | 5 | ::: code-group 6 | 7 | ```sh [npm] 8 | npm install vitepress-plugin-group-icons 9 | ``` 10 | 11 | ```sh [yarn] 12 | yarn add vitepress-plugin-group-icons 13 | ``` 14 | 15 | ```sh [pnpm] 16 | pnpm add vitepress-plugin-group-icons 17 | ``` 18 | 19 | ```sh [bun] 20 | bun add vitepress-plugin-group-icons 21 | ``` 22 | 23 | ::: 24 | 25 | ## Configuration 26 | 27 | ```ts {2,7,12} [.vitepress/config.ts] 28 | import { defineConfig } from 'vitepress' 29 | import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons' 30 | 31 | export default defineConfig({ 32 | markdown: { 33 | config(md) { 34 | md.use(groupIconMdPlugin) 35 | }, 36 | }, 37 | vite: { 38 | plugins: [ 39 | groupIconVitePlugin() 40 | ], 41 | } 42 | }) 43 | ``` 44 | 45 | ```ts {2} [.vitepress/theme/index.ts] 46 | import Theme from 'vitepress/theme' 47 | import 'virtual:group-icons.css' 48 | 49 | export default Theme 50 | ``` 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-PRESENT Leo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Disable the default formatter, use eslint instead 3 | "prettier.enable": false, 4 | "editor.formatOnSave": false, 5 | 6 | // Auto fix 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": "explicit", 9 | "source.organizeImports": "never" 10 | }, 11 | 12 | // Silent the stylistic rules in you IDE, but still auto fix them 13 | "eslint.rules.customizations": [ 14 | { "rule": "style/*", "severity": "off" }, 15 | { "rule": "*-indent", "severity": "off" }, 16 | { "rule": "*-spacing", "severity": "off" }, 17 | { "rule": "*-spaces", "severity": "off" }, 18 | { "rule": "*-order", "severity": "off" }, 19 | { "rule": "*-dangle", "severity": "off" }, 20 | { "rule": "*-newline", "severity": "off" }, 21 | { "rule": "*quotes", "severity": "off" }, 22 | { "rule": "*semi", "severity": "off" } 23 | ], 24 | 25 | // Enable eslint for all supported languages 26 | "eslint.validate": [ 27 | "javascript", 28 | "javascriptreact", 29 | "typescript", 30 | "typescriptreact", 31 | "vue", 32 | "html", 33 | "markdown", 34 | "json", 35 | "jsonc", 36 | "yaml" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /docs/showcase.md: -------------------------------------------------------------------------------- 1 | # Showcase 2 | 3 | This plugin is used in the following projects: 4 | (no particular order) 5 | 6 | - [Vite](https://vite.dev/guide/#scaffolding-your-first-vite-project) 7 | - [Vite PWA](https://vite-pwa-org.netlify.app/guide/#scaffolding-your-first-vite-pwa-project) 8 | - [VitePress](https://vitepress.dev/guide/getting-started#prerequisites) 9 | - [UnoCSS](https://unocss.dev/guide/style-reset#installation) 10 | - [Unplugin](https://unplugin.unjs.io/guide/#install-package) 11 | - [Vitest](https://main.vitest.dev/guide/#adding-vitest-to-your-project) 12 | - [Vue Macros](https://vue-macros.dev/guide/getting-started.html#installation) 13 | - [Rolldown](https://rolldown.rs/contrib-guide/setup-the-project#prerequisites) 14 | - [Unplugin Turbo Console](https://utc.yuy1n.io/guide/getting-started.html#install) 15 | - [OXC](https://oxc.rs/docs/guide/usage/linter.html#installation) 16 | - [TresJS](https://docs.tresjs.org/guide/) 17 | - [Hono](https://hono.dev/docs/#quick-start) 18 | - [Pinia Plugin Persistedstate](https://prazdevs.github.io/pinia-plugin-persistedstate/guide/#installation) 19 | - [More ...](https://github.com/search?q=vitepress-plugin-group-icons+path%3Apackage.json&type=code) 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: pnpm/action-setup@v4 18 | with: 19 | run_install: false 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: lts/* 23 | cache: pnpm 24 | 25 | - run: pnpm i -g @antfu/ni 26 | - run: nci 27 | - run: nr lint 28 | - run: nr typecheck 29 | 30 | test: 31 | runs-on: ${{ matrix.os }} 32 | 33 | strategy: 34 | matrix: 35 | node: [lts/*] 36 | os: [ubuntu-latest, windows-latest, macos-latest] 37 | fail-fast: false 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: pnpm/action-setup@v4 42 | with: 43 | run_install: false 44 | - name: Set node ${{ matrix.node }} 45 | uses: actions/setup-node@v4 46 | with: 47 | node-version: ${{ matrix.node }} 48 | cache: pnpm 49 | 50 | - run: pnpm i -g @antfu/ni 51 | - run: nci 52 | - run: nr build 53 | - run: nr test 54 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Vitepress Plugin Group Icons" 7 | tagline: Enhance code blocks features for VitePress. 8 | 9 | actions: 10 | - theme: brand 11 | text: Getting Started 12 | link: /getting-started 13 | 14 | - theme: alt 15 | text: GitHub 16 | link: https://github.com/yuyinws/vitepress-plugin-group-icons 17 | image: 18 | src: /logo.svg 19 | alt: Vitepress Plugin Group Icons 20 | features: 21 | - icon: 💡 22 | title: Icons 23 | details: Automatically fill the missing product icons 24 | link: /features#💡-fill-icons 25 | 26 | - icon: 🪧 27 | title: Title Bar 28 | details: Add Title Bar to your code blocks 29 | link: /features#🪧-title-bar 30 | --- 31 | 32 | ::: code-group 33 | 34 | ```sh [npm] 35 | npm install vitepress-plugin-group-icons 36 | ``` 37 | 38 | ```sh [yarn] 39 | yarn add vitepress-plugin-group-icons 40 | ``` 41 | 42 | ```sh [pnpm] 43 | pnpm add vitepress-plugin-group-icons 44 | ``` 45 | 46 | ```sh [bun] 47 | bun add vitepress-plugin-group-icons 48 | ``` 49 | 50 | ::: 51 | 52 | 73 | -------------------------------------------------------------------------------- /src/vite.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin, ViteDevServer } from 'vite' 2 | import { createFilter } from 'vite' 3 | import { generateCSS } from './codegen' 4 | import { isSetEqual } from './utils' 5 | 6 | export interface Options { 7 | customIcon?: Record 8 | defaultLabels?: string[] 9 | } 10 | 11 | const filter = createFilter( 12 | [/\.md$/, /\.md\?vue/, /\.md\?v=/], 13 | ) 14 | 15 | export function groupIconVitePlugin(options?: Options): Plugin { 16 | const virtualCssId = 'virtual:group-icons.css' 17 | const resolvedVirtualCssId = `\0${virtualCssId}` 18 | const combinedRegex = /\bdata-title=\\"([^"]*)\\"|\bdata-title="([^"]*)"|"data-title":\s*"([^"]*)"/g 19 | const matches = new Set() 20 | 21 | let oldMatches: Set 22 | let server: ViteDevServer | undefined 23 | 24 | options = options || { customIcon: {} } 25 | 26 | function handleUpdateModule() { 27 | const mod = server?.moduleGraph.getModuleById(resolvedVirtualCssId) 28 | if (mod) { 29 | server!.moduleGraph.invalidateModule(mod) 30 | server!.reloadModule(mod) 31 | } 32 | } 33 | 34 | return { 35 | name: 'vitepress-plugin-group-icons', 36 | enforce: 'post', 37 | resolveId(id) { 38 | if (id === virtualCssId) { 39 | return resolvedVirtualCssId 40 | } 41 | return undefined 42 | }, 43 | 44 | configureServer(_server) { 45 | server = _server 46 | }, 47 | 48 | async load(id) { 49 | if (id === resolvedVirtualCssId) { 50 | const { css } = await generateCSS(matches, options) 51 | oldMatches = new Set(matches) 52 | return css 53 | } 54 | 55 | return undefined 56 | }, 57 | transform(code, id) { 58 | if (!filter(id)) 59 | return 60 | 61 | while (true) { 62 | const match = combinedRegex.exec(code) 63 | if (!match) 64 | break 65 | 66 | matches.add(match[1] || match[2] || match[3]) 67 | } 68 | 69 | if (!isSetEqual(matches, oldMatches)) { 70 | handleUpdateModule() 71 | } 72 | }, 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test/codegen.test.ts: -------------------------------------------------------------------------------- 1 | import { builtinIcons, localIconLoader } from 'src' 2 | import { generateCSS } from 'src/codegen' 3 | import { describe, expect, it } from 'vitest' 4 | 5 | describe('generate css', () => { 6 | it('builtin icon', async () => { 7 | const labels = new Set(['npm', 'yarn']) 8 | expect(await generateCSS(labels, { 9 | customIcon: {}, 10 | })).toMatchSnapshot() 11 | }) 12 | 13 | it('custom icon', { timeout: 0 }, async () => { 14 | const labels = new Set(['vitepress', 'ae', 'oxc']) 15 | expect(await generateCSS(labels, { 16 | customIcon: { 17 | ae: 'logos:adobe-after-effects', 18 | vitepress: localIconLoader(import.meta.url, '../docs/assets/vitepress.svg'), 19 | }, 20 | })).toMatchSnapshot() 21 | }) 22 | 23 | it('duplicate label', async () => { 24 | const labels = new Set(['foo.ts', 'bar.ts']) 25 | expect(await generateCSS(labels, { 26 | customIcon: {}, 27 | })).toMatchSnapshot() 28 | }) 29 | 30 | it('default labels', async () => { 31 | const labels = new Set(['npm']) 32 | expect(await generateCSS(labels, { 33 | customIcon: {}, 34 | defaultLabels: ['yarn'], 35 | })).toMatchSnapshot() 36 | }) 37 | 38 | it('default labels with custom icon', async () => { 39 | const labels = new Set(['npm']) 40 | expect(await generateCSS(labels, { 41 | customIcon: { 42 | curl: 'logos:curl', 43 | }, 44 | defaultLabels: ['curl'], 45 | })).toMatchSnapshot() 46 | }) 47 | 48 | it('default labels with all builtin icons', async () => { 49 | const labels = new Set([]) 50 | expect(await generateCSS(labels, { 51 | defaultLabels: Object.keys(builtinIcons).slice(0, 3), 52 | })).toMatchSnapshot() 53 | }) 54 | 55 | it('empty icon', async () => { 56 | const labels = new Set(['reactive']) 57 | expect(await generateCSS(labels, { 58 | customIcon: { 59 | reactive: '', 60 | }, 61 | })).toMatchSnapshot() 62 | }) 63 | 64 | it('named icon', async () => { 65 | const labels = new Set(['named-icon ~vscode-icons:default-folder~']) 66 | expect(await generateCSS(labels, { 67 | customIcon: {}, 68 | })).toMatchSnapshot() 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vitepress-plugin-group-icons", 3 | "type": "module", 4 | "version": "1.6.5", 5 | "packageManager": "pnpm@10.18.3", 6 | "author": "Leo ", 7 | "license": "MIT", 8 | "homepage": "https://github.com/yuyinws/vitepress-plugin-group-icons#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/yuyinws/vitepress-plugin-group-icons.git" 12 | }, 13 | "bugs": "https://github.com/yuyinws/vitepress-plugin-group-icons/issues", 14 | "keywords": [], 15 | "sideEffects": false, 16 | "exports": { 17 | ".": { 18 | "types": "./dist/index.d.ts", 19 | "import": "./dist/index.js" 20 | } 21 | }, 22 | "main": "./dist/index.js", 23 | "module": "./dist/index.js", 24 | "types": "./dist/index.d.ts", 25 | "typesVersions": { 26 | "*": { 27 | "*": [ 28 | "./dist/*", 29 | "./dist/index.d.ts" 30 | ] 31 | } 32 | }, 33 | "files": [ 34 | "dist" 35 | ], 36 | "scripts": { 37 | "build": "tsdown", 38 | "dev": "tsdown --watch", 39 | "lint": "eslint .", 40 | "prepublishOnly": "nr build", 41 | "release": "bumpp && npm publish", 42 | "start": "esno src/index.ts", 43 | "test": "vitest", 44 | "typecheck": "tsc --noEmit", 45 | "prepare": "simple-git-hooks", 46 | "docs:dev": "vitepress dev docs", 47 | "docs:build": "vitepress build docs", 48 | "docs:preview": "vitepress preview docs" 49 | }, 50 | "peerDependencies": { 51 | "vite": ">=3" 52 | }, 53 | "peerDependenciesMeta": { 54 | "vite": { 55 | "optional": true 56 | } 57 | }, 58 | "dependencies": { 59 | "@iconify-json/logos": "^1.2.9", 60 | "@iconify-json/vscode-icons": "^1.2.32", 61 | "@iconify/utils": "^3.0.2" 62 | }, 63 | "devDependencies": { 64 | "@antfu/eslint-config": "^6.0.0", 65 | "@antfu/ni": "^26.1.0", 66 | "@types/markdown-it": "^14.1.2", 67 | "@types/node": "^22.18.10", 68 | "bumpp": "^10.3.1", 69 | "eslint": "^9.37.0", 70 | "esno": "^4.8.0", 71 | "lint-staged": "^16.2.4", 72 | "simple-git-hooks": "^2.13.1", 73 | "tsdown": "^0.15.7", 74 | "typescript": "^5.9.3", 75 | "vite": "^7.1.10", 76 | "vitepress": "^1.6.4", 77 | "vitest": "^3.2.4" 78 | }, 79 | "simple-git-hooks": { 80 | "pre-commit": "pnpm lint-staged" 81 | }, 82 | "lint-staged": { 83 | "*": "eslint --fix" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /docs/public/oxc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/assets/vitepress.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { transformerTwoslash } from '@shikijs/vitepress-twoslash' 2 | import Inspect from 'vite-plugin-inspect' 3 | import { defineConfig } from 'vitepress' 4 | import { groupIconMdPlugin, groupIconVitePlugin, localIconLoader } from 'vitepress-plugin-group-icons' 5 | 6 | // https://vitepress.dev/reference/site-config 7 | export default defineConfig({ 8 | title: 'Vitepress Plugin Group Icons', 9 | description: 'Automatically fill the missing product icon for code groups.', 10 | themeConfig: { 11 | socialLinks: [ 12 | { icon: 'github', link: 'https://github.com/yuyinws/vitepress-plugin-group-icons' }, 13 | ], 14 | nav: [ 15 | { text: 'Features', link: '/features' }, 16 | { text: 'Showcase', link: '/showcase' }, 17 | ], 18 | sidebar: [ 19 | { 20 | text: 'Guide', 21 | items: [ 22 | { text: 'Getting Started', link: '/getting-started' }, 23 | { text: 'Features', link: '/features' }, 24 | ], 25 | }, 26 | { 27 | text: 'Showcase', 28 | link: '/showcase', 29 | }, 30 | ], 31 | 32 | }, 33 | markdown: { 34 | config(md) { 35 | md.use(groupIconMdPlugin, { 36 | titleBar: { 37 | includeSnippet: true, 38 | }, 39 | }) 40 | }, 41 | codeTransformers: [ 42 | transformerTwoslash(), 43 | ], 44 | }, 45 | head: [ 46 | ['meta', { property: 'og:title', content: 'Vitepress Plugin Group Icons' }], 47 | ['meta', { property: 'og:description', content: 'Automatically fill the missing product icon for code groups.' }], 48 | ['meta', { property: 'og:image', content: `https://vpgi.vercel.app//og.png` }], 49 | ['meta', { property: 'og:type', content: 'website' }], 50 | ['meta', { property: 'og:url', content: 'https://vpgi.vercel.app/' }], 51 | ['meta', { property: 'twitter:card', content: 'summary_large_image' }], 52 | ['meta', { property: 'twitter:image', content: `https://vpgi.vercel.app/og.png` }], 53 | ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }], 54 | ['meta', { name: 'theme-color', content: '#4FC08D' }], 55 | ], 56 | vite: { 57 | plugins: [ 58 | groupIconVitePlugin({ 59 | customIcon: { 60 | '.mdx': 'vscode-icons:file-type-light-mdx', 61 | 'babel': 'vscode-icons:file-type-light-babel2', 62 | 'vitepress': localIconLoader(import.meta.url, '../assets/vitepress.svg'), 63 | 'unplugin': 'https://unplugin.unjs.io/logo_light.svg', 64 | }, 65 | }), 66 | Inspect(), 67 | ], 68 | }, 69 | }) 70 | -------------------------------------------------------------------------------- /src/builtin.ts: -------------------------------------------------------------------------------- 1 | export const builtinIcons = { 2 | // package managers 3 | 'pnpm': 'vscode-icons:file-type-light-pnpm', 4 | 'npm': 'vscode-icons:file-type-npm', 5 | 'yarn': 'vscode-icons:file-type-yarn', 6 | 'bun': 'vscode-icons:file-type-bun', 7 | 'deno': 'vscode-icons:file-type-deno', 8 | // frameworks 9 | 'vue': 'vscode-icons:file-type-vue', 10 | 'svelte': 'vscode-icons:file-type-svelte', 11 | 'angular': 'vscode-icons:file-type-angular', 12 | 'react': 'vscode-icons:file-type-reactjs', 13 | 'next': 'vscode-icons:file-type-light-next', 14 | 'nuxt': 'vscode-icons:file-type-nuxt', 15 | 'solid': 'logos:solidjs-icon', 16 | 'astro': 'vscode-icons:file-type-light-astro', 17 | 'qwik': 'logos:qwik-icon', 18 | 'ember': 'vscode-icons:file-type-ember', 19 | // bundlers 20 | 'rollup': 'vscode-icons:file-type-rollup', 21 | 'webpack': 'vscode-icons:file-type-webpack', 22 | 'vite': 'vscode-icons:file-type-vite', 23 | 'esbuild': 'vscode-icons:file-type-esbuild', 24 | // configuration files 25 | 'package.json': 'vscode-icons:file-type-node', 26 | 'tsconfig.json': 'vscode-icons:file-type-tsconfig', 27 | '.npmrc': 'vscode-icons:file-type-npm', 28 | '.editorconfig': 'vscode-icons:file-type-editorconfig', 29 | '.eslintrc': 'vscode-icons:file-type-eslint', 30 | '.eslintignore': 'vscode-icons:file-type-eslint', 31 | 'eslint.config': 'vscode-icons:file-type-eslint', 32 | '.gitignore': 'vscode-icons:file-type-git', 33 | '.gitattributes': 'vscode-icons:file-type-git', 34 | '.env': 'vscode-icons:file-type-dotenv', 35 | '.env.example': 'vscode-icons:file-type-dotenv', 36 | '.vscode': 'vscode-icons:file-type-vscode', 37 | 'tailwind.config': 'vscode-icons:file-type-tailwind', 38 | 'uno.config': 'vscode-icons:file-type-unocss', 39 | 'unocss.config': 'vscode-icons:file-type-unocss', 40 | '.oxlintrc': 'vscode-icons:file-type-oxlint', 41 | 'vue.config': 'vscode-icons:file-type-vueconfig', 42 | // filename extensions 43 | '.mts': 'vscode-icons:file-type-typescript', 44 | '.cts': 'vscode-icons:file-type-typescript', 45 | '.ts': 'vscode-icons:file-type-typescript', 46 | '.tsx': 'vscode-icons:file-type-typescript', 47 | '.mjs': 'vscode-icons:file-type-js', 48 | '.cjs': 'vscode-icons:file-type-js', 49 | '.json': 'vscode-icons:file-type-json', 50 | '.js': 'vscode-icons:file-type-js', 51 | '.jsx': 'vscode-icons:file-type-js', 52 | '.md': 'vscode-icons:file-type-markdown', 53 | '.py': 'vscode-icons:file-type-python', 54 | '.ico': 'vscode-icons:file-type-favicon', 55 | '.html': 'vscode-icons:file-type-html', 56 | '.css': 'vscode-icons:file-type-css', 57 | '.scss': 'vscode-icons:file-type-scss', 58 | '.yml': 'vscode-icons:file-type-light-yaml', 59 | '.yaml': 'vscode-icons:file-type-light-yaml', 60 | '.php': 'vscode-icons:file-type-php', 61 | '.gjs': 'vscode-icons:file-type-glimmer', 62 | '.gts': 'vscode-icons:file-type-glimmer', 63 | } 64 | -------------------------------------------------------------------------------- /src/markdown.ts: -------------------------------------------------------------------------------- 1 | import type Markdown from 'markdown-it' 2 | import { namedIconMatchRegex } from './utils' 3 | 4 | interface MdPluginOptions { 5 | titleBar: { 6 | /** 7 | * Whether the title bar is included in the [Snippets](https://vitepress.dev/guide/markdown#import-code-snippets) 8 | * 9 | * @defaultValue false 10 | */ 11 | includeSnippet?: boolean 12 | } 13 | } 14 | 15 | export function groupIconMdPlugin(md: Markdown, options?: MdPluginOptions) { 16 | const _options = options || { titleBar: { includeSnippet: false } } 17 | 18 | // code group rule 19 | const labelRE = /]+\bdata-title\b)[^>]*>(.*?)<\/label>/g 20 | const codeGroupOpenRule = md.renderer.rules['container_code-group_open'] 21 | if (codeGroupOpenRule) { 22 | md.renderer.rules['container_code-group_open'] = (...args) => { 23 | return codeGroupOpenRule(...args).replace( 24 | labelRE, 25 | (match: string, label: string) => 26 | `