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"
},
"devDependencies": {
"@mdit-vue/plugin-component": "^0.9.4",
"@mdit-vue/plugin-frontmatter": "^0.9.2",
"@mdit-vue/plugin-toc": "^0.9.2",
"@mdit-vue/types": "^0.9.2",
"@mdit-vue/plugin-component": "^0.10.0",
"@mdit-vue/plugin-frontmatter": "^0.10.0",
"@mdit-vue/plugin-headers": "^0.10.0",
"@mdit-vue/plugin-toc": "^0.10.0",
"@mdit-vue/types": "^0.10.0",
"@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-commonjs": "^22.0.2",
"@rollup/plugin-json": "^4.1.0",

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

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

@ -1,20 +1,8 @@
import { Ref, computed, onMounted, onUpdated, onUnmounted } from 'vue'
import { Header, useData } from 'vitepress'
import { useData } from 'vitepress'
import { useAside } from '../composables/aside.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
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(
container: Ref<HTMLElement>,
marker: Ref<HTMLElement>

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

9
types/shared.d.ts vendored

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

Loading…
Cancel
Save