refactor: externalize markdown headers plugin (#1230)

pull/1235/head
meteorlxy 2 years ago committed by GitHub
parent 03f8c5ed9c
commit 71358ebce7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -92,10 +92,11 @@
"vue": "^3.2.37" "vue": "^3.2.37"
}, },
"devDependencies": { "devDependencies": {
"@mdit-vue/plugin-component": "^0.9.4", "@mdit-vue/plugin-component": "^0.10.0",
"@mdit-vue/plugin-frontmatter": "^0.9.2", "@mdit-vue/plugin-frontmatter": "^0.10.0",
"@mdit-vue/plugin-toc": "^0.9.2", "@mdit-vue/plugin-headers": "^0.10.0",
"@mdit-vue/types": "^0.9.2", "@mdit-vue/plugin-toc": "^0.10.0",
"@mdit-vue/types": "^0.10.0",
"@rollup/plugin-alias": "^3.1.9", "@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-commonjs": "^22.0.2", "@rollup/plugin-commonjs": "^22.0.2",
"@rollup/plugin-json": "^4.1.0", "@rollup/plugin-json": "^4.1.0",

@ -6,10 +6,11 @@ importers:
specifiers: specifiers:
'@docsearch/css': ^3.2.1 '@docsearch/css': ^3.2.1
'@docsearch/js': ^3.2.1 '@docsearch/js': ^3.2.1
'@mdit-vue/plugin-component': ^0.9.4 '@mdit-vue/plugin-component': ^0.10.0
'@mdit-vue/plugin-frontmatter': ^0.9.2 '@mdit-vue/plugin-frontmatter': ^0.10.0
'@mdit-vue/plugin-toc': ^0.9.2 '@mdit-vue/plugin-headers': ^0.10.0
'@mdit-vue/types': ^0.9.2 '@mdit-vue/plugin-toc': ^0.10.0
'@mdit-vue/types': ^0.10.0
'@rollup/plugin-alias': ^3.1.9 '@rollup/plugin-alias': ^3.1.9
'@rollup/plugin-commonjs': ^22.0.2 '@rollup/plugin-commonjs': ^22.0.2
'@rollup/plugin-json': ^4.1.0 '@rollup/plugin-json': ^4.1.0
@ -90,10 +91,11 @@ importers:
vite: 3.0.8 vite: 3.0.8
vue: 3.2.37 vue: 3.2.37
devDependencies: devDependencies:
'@mdit-vue/plugin-component': 0.9.4 '@mdit-vue/plugin-component': 0.10.0
'@mdit-vue/plugin-frontmatter': 0.9.2 '@mdit-vue/plugin-frontmatter': 0.10.0
'@mdit-vue/plugin-toc': 0.9.2 '@mdit-vue/plugin-headers': 0.10.0
'@mdit-vue/types': 0.9.2 '@mdit-vue/plugin-toc': 0.10.0
'@mdit-vue/types': 0.10.0
'@rollup/plugin-alias': 3.1.9_rollup@2.78.0 '@rollup/plugin-alias': 3.1.9_rollup@2.78.0
'@rollup/plugin-commonjs': 22.0.2_rollup@2.78.0 '@rollup/plugin-commonjs': 22.0.2_rollup@2.78.0
'@rollup/plugin-json': 4.1.0_rollup@2.78.0 '@rollup/plugin-json': 4.1.0_rollup@2.78.0
@ -396,41 +398,50 @@ packages:
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dev: true dev: true
/@mdit-vue/plugin-component/0.9.4: /@mdit-vue/plugin-component/0.10.0:
resolution: {integrity: sha512-Sc32sjJiXbCxvOTwPc+h6pcT7zEQ1sWBUZ94iODkVPW75HmM7Ir1GC4JN4VwmuiVmjSG/W3caHlVnioG1ePGJw==} resolution: {integrity: sha512-cfxmPVcp6880TRUgpT3eUjem1gCkg3vsBHOcjOoiD2gAu3hWg48d3woz5+F9WVrAhv8P6wpDYBzFqt29D6D4MQ==}
dependencies: dependencies:
'@types/markdown-it': 12.2.3 '@types/markdown-it': 12.2.3
markdown-it: 13.0.1 markdown-it: 13.0.1
dev: true dev: true
/@mdit-vue/plugin-frontmatter/0.9.2: /@mdit-vue/plugin-frontmatter/0.10.0:
resolution: {integrity: sha512-qHVgr6v0sH4C2KPD/q/EMUQBGLjuxpS24gyqLHwKHzg+o977CJGS0UA2TymGybHTliaIqmRiQ9DGkIzqO7sJCQ==} resolution: {integrity: sha512-rJa4QM04YKRH9Edpr07BZvOjzOH2BwkPkalIa8YFIsZkCXLmrPpLsQteXbRLTkLGHLXnniW4V4tn5Y7bf7J74g==}
dependencies: dependencies:
'@mdit-vue/types': 0.9.2 '@mdit-vue/types': 0.10.0
'@types/markdown-it': 12.2.3 '@types/markdown-it': 12.2.3
gray-matter: 4.0.3 gray-matter: 4.0.3
markdown-it: 13.0.1 markdown-it: 13.0.1
dev: true dev: true
/@mdit-vue/plugin-toc/0.9.2: /@mdit-vue/plugin-headers/0.10.0:
resolution: {integrity: sha512-Du3fFz+YezlXex9Syn+bc8ADDRx/kBfBokeHXfd/qCW5XqS7I4THLR/IRqlvi9CgIZ0dx7bJv0avxil9EX1PoQ==} resolution: {integrity: sha512-DPrQyv83jVxX3FwmCnemVeBsSdtH4Hz+geDMwbzATtaqzaYDDpuAxoeiLGpTg41EpLe2SPDk94N3OOh0cdV0Lw==}
dependencies: dependencies:
'@mdit-vue/shared': 0.9.2 '@mdit-vue/shared': 0.10.0
'@mdit-vue/types': 0.9.2 '@mdit-vue/types': 0.10.0
'@types/markdown-it': 12.2.3 '@types/markdown-it': 12.2.3
markdown-it: 13.0.1 markdown-it: 13.0.1
dev: true dev: true
/@mdit-vue/shared/0.9.2: /@mdit-vue/plugin-toc/0.10.0:
resolution: {integrity: sha512-05Nk/o+kJCgeAa7oBGJOIazJq+6n0+VR4jPhzV3LGc9TyuMEqnrH5XzmBoy45vzyyoe7pGxJ/PBDxq4HebQHtQ==} resolution: {integrity: sha512-P9aNy4jtqfjI08wUYGT/HVd5x/IpTjgSnNdJ3lU52qAO5AeFsW3v4gt+NmW0lO8We0S2YDEONRHBuBN6r40y6A==}
dependencies: dependencies:
'@mdit-vue/types': 0.9.2 '@mdit-vue/shared': 0.10.0
'@mdit-vue/types': 0.10.0
'@types/markdown-it': 12.2.3 '@types/markdown-it': 12.2.3
markdown-it: 13.0.1 markdown-it: 13.0.1
dev: true dev: true
/@mdit-vue/types/0.9.2: /@mdit-vue/shared/0.10.0:
resolution: {integrity: sha512-SuoxzZHS2/9bEqeJ+bjj2xBLjoZhRo6Ww/GVqNZS2ji9rkoM2teA0kbwSmj0X6Kf00K9HnLs6T0dtDtqpBqEHA==} resolution: {integrity: sha512-rUyu0NVNbaEg4DUiQenh/fam1MLdkItdzEVScN7vP0UzDWOwmGaKwkhlMmkSTW80H63ZlKst0fPe9LaGHImSZg==}
dependencies:
'@mdit-vue/types': 0.10.0
'@types/markdown-it': 12.2.3
markdown-it: 13.0.1
dev: true
/@mdit-vue/types/0.10.0:
resolution: {integrity: sha512-ROz5zVKt3COpuWUYFnpJh5kIXit9SQeMtimGBlwKJL1xEBNPG3QKD3VZzez5Ng/dBCApianCQhNVZGCza82Myw==}
dev: true dev: true
/@nodelib/fs.scandir/2.1.5: /@nodelib/fs.scandir/2.1.5:
@ -2087,7 +2098,7 @@ packages:
dev: true dev: true
/extend-shallow/2.0.1: /extend-shallow/2.0.1:
resolution: {integrity: sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=} resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dependencies: dependencies:
is-extendable: 0.1.1 is-extendable: 0.1.1
@ -2418,7 +2429,7 @@ packages:
dev: true dev: true
/is-extendable/0.1.1: /is-extendable/0.1.1:
resolution: {integrity: sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=} resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
@ -3581,7 +3592,7 @@ packages:
dev: true dev: true
/sprintf-js/1.0.3: /sprintf-js/1.0.3:
resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
dev: true dev: true
/string-argv/0.3.1: /string-argv/0.3.1:
@ -3657,7 +3668,7 @@ packages:
dev: true dev: true
/strip-bom-string/1.0.0: /strip-bom-string/1.0.0:
resolution: {integrity: sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=} resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true

@ -1,8 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref } from 'vue'
import { useData } from 'vitepress' import { useData } from 'vitepress'
import { import {
resolveHeaders,
useOutline, useOutline,
useActiveAnchor useActiveAnchor
} from '../composables/outline.js' } from '../composables/outline.js'
@ -16,10 +15,6 @@ const marker = ref()
useActiveAnchor(container, marker) useActiveAnchor(container, marker)
const resolvedHeaders = computed(() => {
return resolveHeaders(page.value.headers)
})
function handleClick({ target: el }: Event) { function handleClick({ target: el }: Event) {
const id = '#' + (el as HTMLAnchorElement).href!.split('#')[1] const id = '#' + (el as HTMLAnchorElement).href!.split('#')[1]
const heading = document.querySelector<HTMLAnchorElement>( const heading = document.querySelector<HTMLAnchorElement>(
@ -45,16 +40,15 @@ function handleClick({ target: el }: Event) {
<ul class="root"> <ul class="root">
<li <li
v-for="{ text, link, children, hidden } in resolvedHeaders" v-for="{ title, link, children } in page.headers"
v-show="!hidden"
> >
<a class="outline-link" :href="link" @click="handleClick"> <a class="outline-link" :href="link" @click="handleClick">
{{ text }} {{ title }}
</a> </a>
<ul v-if="children && frontmatter.outline === 'deep'"> <ul v-if="children && frontmatter.outline === 'deep'">
<li v-for="{ text, link, hidden } in children" v-show="!hidden"> <li v-for="{ title, link } in children">
<a class="outline-link nested" :href="link" @click="handleClick"> <a class="outline-link nested" :href="link" @click="handleClick">
{{ text }} {{ title }}
</a> </a>
</li> </li>
</ul> </ul>

@ -1,20 +1,8 @@
import { Ref, computed, onMounted, onUpdated, onUnmounted } from 'vue' import { Ref, computed, onMounted, onUpdated, onUnmounted } from 'vue'
import { Header, useData } from 'vitepress' import { useData } from 'vitepress'
import { useAside } from '../composables/aside.js' import { useAside } from '../composables/aside.js'
import { throttleAndDebounce } from '../support/utils.js' import { throttleAndDebounce } from '../support/utils.js'
interface HeaderWithChildren extends Header {
children?: Header[]
hidden?: boolean
}
interface MenuItemWithLinkAndChildren {
text: string
link: string
children?: MenuItemWithLinkAndChildren[]
hidden?: boolean
}
// magic number to avoid repeated retrieval // magic number to avoid repeated retrieval
const PAGE_OFFSET = 56 const PAGE_OFFSET = 56
@ -30,37 +18,6 @@ export function useOutline() {
} }
} }
export function resolveHeaders(headers: Header[]) {
return mapHeaders(groupHeaders(headers))
}
function groupHeaders(headers: Header[]): HeaderWithChildren[] {
headers = headers.map((h) => Object.assign({}, h))
let lastH2: HeaderWithChildren | undefined
for (const h of headers) {
if (h.level === 2) {
lastH2 = h
} else if (lastH2 && h.level <= 3) {
;(lastH2.children || (lastH2.children = [])).push(h)
}
}
return headers.filter((h) => h.level === 2)
}
function mapHeaders(
headers: HeaderWithChildren[]
): MenuItemWithLinkAndChildren[] {
return headers.map((header) => ({
text: header.title,
link: `#${header.slug}`,
children: header.children ? mapHeaders(header.children) : undefined,
hidden: header.hidden
}))
}
export function useActiveAnchor( export function useActiveAnchor(
container: Ref<HTMLElement>, container: Ref<HTMLElement>,
marker: Ref<HTMLElement> marker: Ref<HTMLElement>

@ -7,6 +7,10 @@ import {
frontmatterPlugin, frontmatterPlugin,
type FrontmatterPluginOptions type FrontmatterPluginOptions
} from '@mdit-vue/plugin-frontmatter' } from '@mdit-vue/plugin-frontmatter'
import {
headersPlugin,
type HeadersPluginOptions
} from '@mdit-vue/plugin-headers'
import { tocPlugin, type TocPluginOptions } from '@mdit-vue/plugin-toc' import { tocPlugin, type TocPluginOptions } from '@mdit-vue/plugin-toc'
import { IThemeRegistration } from 'shiki' import { IThemeRegistration } from 'shiki'
import { highlight } from './plugins/highlight' import { highlight } from './plugins/highlight'
@ -18,7 +22,6 @@ import { snippetPlugin } from './plugins/snippet'
import { hoistPlugin } from './plugins/hoist' import { hoistPlugin } from './plugins/hoist'
import { preWrapperPlugin } from './plugins/preWrapper' import { preWrapperPlugin } from './plugins/preWrapper'
import { linkPlugin } from './plugins/link' import { linkPlugin } from './plugins/link'
import { headingPlugin } from './plugins/headings'
import { imagePlugin } from './plugins/image' import { imagePlugin } from './plugins/image'
import { Header } from '../shared' import { Header } from '../shared'
@ -37,6 +40,7 @@ export interface MarkdownOptions extends MarkdownIt.Options {
disable?: boolean disable?: boolean
} }
frontmatter?: FrontmatterPluginOptions frontmatter?: FrontmatterPluginOptions
headers?: HeadersPluginOptions
theme?: ThemeOptions theme?: ThemeOptions
toc?: TocPluginOptions toc?: TocPluginOptions
externalLinks?: Record<string, string> externalLinks?: Record<string, string>
@ -45,7 +49,6 @@ export interface MarkdownOptions extends MarkdownIt.Options {
export interface MarkdownParsedData { export interface MarkdownParsedData {
hoistedTags?: string[] hoistedTags?: string[]
links?: string[] links?: string[]
headers?: Header[]
} }
export interface MarkdownRenderer extends MarkdownIt { export interface MarkdownRenderer extends MarkdownIt {
@ -75,7 +78,6 @@ export const createMarkdownRenderer = async (
.use(snippetPlugin, srcDir) .use(snippetPlugin, srcDir)
.use(hoistPlugin) .use(hoistPlugin)
.use(containerPlugin) .use(containerPlugin)
.use(headingPlugin)
.use(imagePlugin) .use(imagePlugin)
.use( .use(
linkPlugin, linkPlugin,
@ -100,6 +102,10 @@ export const createMarkdownRenderer = async (
.use(frontmatterPlugin, { .use(frontmatterPlugin, {
...options.frontmatter ...options.frontmatter
} as FrontmatterPluginOptions) } as FrontmatterPluginOptions)
.use(headersPlugin, {
slugify,
...options.headers
} as HeadersPluginOptions)
.use(tocPlugin, { .use(tocPlugin, {
slugify, slugify,
...options.toc ...options.toc

@ -1,23 +0,0 @@
import { MarkdownRenderer } from '../markdown'
import { deeplyParseHeader } from '../../utils/parseHeader'
import { slugify } from './slugify'
import MarkdownIt from 'markdown-it'
export const headingPlugin = (md: MarkdownIt, include = ['h2', 'h3']) => {
md.renderer.rules.heading_open = (tokens, i, options, env, self) => {
const token = tokens[i]
if (include.includes(token.tag)) {
const title = tokens[i + 1].content
const idAttr = token.attrs!.find(([name]) => name === 'id')
const slug = idAttr && idAttr[1]
const data = (md as MarkdownRenderer).__data
const headers = data.headers || (data.headers = [])
headers.push({
level: parseInt(token.tag.slice(1), 10),
title: deeplyParseHeader(title),
slug: slug || slugify(title)
})
}
return self.renderToken(tokens, i, options)
}
}

@ -83,7 +83,7 @@ export async function createMarkdownToVueRenderFn(
} }
const html = md.render(src, env) const html = md.render(src, env)
const data = md.__data const data = md.__data
const { content = '', frontmatter = {} } = env const { content = '', frontmatter = {}, headers = [] } = env
// validate data.links // validate data.links
const deadLinks: string[] = [] const deadLinks: string[] = []
@ -133,7 +133,7 @@ export async function createMarkdownToVueRenderFn(
titleTemplate: frontmatter.titleTemplate as any, titleTemplate: frontmatter.titleTemplate as any,
description: inferDescription(frontmatter), description: inferDescription(frontmatter),
frontmatter, frontmatter,
headers: data.headers || [], headers,
relativePath relativePath
} }

9
types/shared.d.ts vendored

@ -1,5 +1,5 @@
// types shared between server and client // types shared between server and client
import type { MarkdownItHeader } from '@mdit-vue/types'
export type { DefaultTheme } from './default-theme.js' export type { DefaultTheme } from './default-theme.js'
export interface PageData { export interface PageData {
@ -12,12 +12,7 @@ export interface PageData {
lastUpdated?: number lastUpdated?: number
} }
export interface Header { export type Header = MarkdownItHeader
level: number
title: string
slug: string
}
export type CleanUrlsMode = export type CleanUrlsMode =
| 'disabled' | 'disabled'
| 'without-subfolders' | 'without-subfolders'

Loading…
Cancel
Save