diff --git a/__tests__/e2e/client-component/Slots.vue b/__tests__/e2e/client-component/Slots.vue
new file mode 100644
index 000000000..5e19a0def
--- /dev/null
+++ b/__tests__/e2e/client-component/Slots.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/__tests__/e2e/client-component/client-component.test.ts b/__tests__/e2e/client-component/client-component.test.ts
new file mode 100644
index 000000000..10a4941fe
--- /dev/null
+++ b/__tests__/e2e/client-component/client-component.test.ts
@@ -0,0 +1,78 @@
+describe('client components', () => {
+ beforeEach(async () => {
+ await goto('/client-component/')
+ await page.waitForSelector('#forwarded.client-component')
+ })
+
+ test('forwards default and scoped slots and keeps them reactive', async () => {
+ const header = page.locator('#forwarded header')
+ const content = page.locator('#forwarded .default-slot')
+
+ expect(await header.textContent()).toBe('scoped value 0')
+ expect((await content.textContent())?.trim()).toBe('default content 0')
+ expect(await page.locator('#forwarded footer').textContent()).toBe('')
+
+ await page.locator('#increment').click()
+
+ await expect.poll(() => header.textContent()).toBe('scoped value 1')
+ expect((await content.textContent())?.trim()).toBe('default content 1')
+ expect(await page.locator('#forwarded footer').textContent()).toBe(
+ 'conditional slot'
+ )
+
+ await page.locator('#increment').click()
+
+ await expect.poll(() => header.textContent()).toBe('scoped value 2')
+ expect(await page.locator('#forwarded footer').textContent()).toBe('')
+ })
+
+ test('forwards slots when args only provide props', async () => {
+ expect(await page.locator('#with-props header').textContent()).toBe(
+ 'from args'
+ )
+ expect(
+ (await page.locator('#with-props .default-slot').textContent())?.trim()
+ ).toBe('content with props')
+ expect(await page.locator('#empty-args .default-slot').textContent()).toBe(
+ 'content with empty args'
+ )
+ expect(await page.locator('#null-props .default-slot').textContent()).toBe(
+ 'content with null props'
+ )
+ })
+
+ test('preserves explicitly provided slots and shorthand children', async () => {
+ expect(await page.locator('#explicit header').textContent()).toBe(
+ 'explicit header'
+ )
+ expect(await page.locator('#explicit .default-slot').textContent()).toBe(
+ 'explicit default'
+ )
+
+ for (const [id, content] of [
+ ['string', 'string child'],
+ ['array', 'array child'],
+ ['vnode', 'vnode child'],
+ ['function', 'function child'],
+ ['number', '42'],
+ ['null', 'default fallback'],
+ ['undefined', 'default fallback'],
+ ['multiple', 'firstsecond']
+ ]) {
+ expect(
+ await page.locator(`#legacy-${id} .default-slot`).textContent()
+ ).toBe(content)
+ }
+ })
+
+ test.runIf(!!process.env.VITE_TEST_BUILD)(
+ 'does not render the component or its slots on the server',
+ async () => {
+ const response = await page.request.get(page.url())
+ const html = await response.text()
+
+ expect(html).not.toContain('class="client-component"')
+ expect(html).not.toContain('default content 0')
+ }
+ )
+})
diff --git a/__tests__/e2e/client-component/index.md b/__tests__/e2e/client-component/index.md
new file mode 100644
index 000000000..4740dc7b8
--- /dev/null
+++ b/__tests__/e2e/client-component/index.md
@@ -0,0 +1,57 @@
+
+
+# Client Components
+
+
+
+
+ {{ message }} {{ count }}
+ conditional slot
+ default content {{ count }}
+
+
+
+ {{ message }}
+ content with props
+
+
+content with empty args
+
+content with null props
+
+
+ ignored header
+ ignored default
+
+
+
+ ignored content
+
diff --git a/__tests__/e2e/theme-image/image.test.ts b/__tests__/e2e/theme-image/image.test.ts
new file mode 100644
index 000000000..1822edaff
--- /dev/null
+++ b/__tests__/e2e/theme-image/image.test.ts
@@ -0,0 +1,38 @@
+describe('themeable image alt overrides', () => {
+ beforeEach(async () => {
+ await goto('/theme-image/')
+ })
+
+ test.each(['light', 'dark'])('uses the alt prop in %s mode', async (mode) => {
+ await page.evaluate((mode) => {
+ document.documentElement.classList.toggle('dark', mode === 'dark')
+ }, mode)
+
+ expect(
+ await page.locator('.image-override img:visible').getAttribute('alt')
+ ).toBe('Override description')
+ expect(
+ await page.locator('.image-decorative img:visible').getAttribute('alt')
+ ).toBe('')
+ expect(
+ await page.locator('.image-fallback img:visible').getAttribute('alt')
+ ).toBe('Image description')
+ })
+
+ test('updates both images when the alt prop changes', async () => {
+ const descriptions = () =>
+ page
+ .locator('.image-override img')
+ .evaluateAll((images) =>
+ images.map((image) => image.getAttribute('alt'))
+ )
+
+ await page.getByRole('button', { name: 'Update description' }).click()
+ await expect
+ .poll(descriptions)
+ .toEqual(['Updated description', 'Updated description'])
+
+ await page.getByRole('button', { name: 'Clear description' }).click()
+ await expect.poll(descriptions).toEqual(['', ''])
+ })
+})
diff --git a/__tests__/e2e/theme-image/index.md b/__tests__/e2e/theme-image/index.md
new file mode 100644
index 000000000..c7ea85f4c
--- /dev/null
+++ b/__tests__/e2e/theme-image/index.md
@@ -0,0 +1,28 @@
+
+
+# Themeable image descriptions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/__tests__/unit/client/theme-default/components/VPImage.test.ts b/__tests__/unit/client/theme-default/components/VPImage.test.ts
new file mode 100644
index 000000000..66719d376
--- /dev/null
+++ b/__tests__/unit/client/theme-default/components/VPImage.test.ts
@@ -0,0 +1,41 @@
+// @ts-expect-error the unit typecheck uses tsc; Vite compiles this SFC at runtime
+import VPImage from 'client/theme-default/components/VPImage.vue'
+import { createSSRApp } from 'vue'
+import { renderToString } from 'vue/server-renderer'
+
+vi.mock('vitepress', () => ({ withBase: (path: string) => path }))
+
+const images = [
+ { name: 'string', image: 'logo.svg', fallback: '', count: 1 },
+ {
+ name: 'single source',
+ image: { src: 'logo.svg', alt: 'Image description' },
+ fallback: 'Image description',
+ count: 1
+ },
+ {
+ name: 'themed sources',
+ image: { light: 'light.svg', dark: 'dark.svg', alt: 'Image description' },
+ fallback: 'Image description',
+ count: 2
+ },
+ {
+ name: 'themed sources without alt',
+ image: { light: 'light.svg', dark: 'dark.svg' },
+ fallback: '',
+ count: 2
+ }
+]
+
+describe.each(images)('VPImage with $name', ({ image, fallback, count }) => {
+ test.each(['Override description', '', undefined])(
+ 'respects the alt prop %j',
+ async (alt) => {
+ const html = await renderToString(createSSRApp(VPImage, { image, alt }))
+ const descriptions = [...html.matchAll(/ alt(?:="([^"]*)")?/g)].map(
+ ([, value]) => value ?? ''
+ )
+ expect(descriptions).toEqual(Array(count).fill(alt ?? fallback))
+ }
+ )
+})
diff --git a/__tests__/unit/node/markdown/plugins/containers.test.ts b/__tests__/unit/node/markdown/plugins/containers.test.ts
index bd48c5814..ffee6c777 100644
--- a/__tests__/unit/node/markdown/plugins/containers.test.ts
+++ b/__tests__/unit/node/markdown/plugins/containers.test.ts
@@ -8,13 +8,18 @@ import type { MarkdownEnv } from 'node/shared'
async function render(
src: string,
options: MarkdownOptions = {},
- env?: Partial
+ env?: Partial,
+ base = '/'
) {
disposeMdItInstance()
- const md = await createMarkdownRenderer('.', {
- highlight: (code) => code,
- ...options
- })
+ const md = await createMarkdownRenderer(
+ '.',
+ {
+ highlight: (code) => code,
+ ...options
+ },
+ base
+ )
return md.renderAsync(src, env)
}
@@ -114,6 +119,48 @@ describe('node/markdown/plugins/containers', () => {
`)
})
+ test.each([
+ ['/', true, true, '/guide/start'],
+ ['./', false, true, '../guide/start.html'],
+ ['./', true, true, '../guide/start'],
+ ['./', false, false, '/guide/start.html']
+ ])(
+ 'uses page URL settings in titles (base: %s, cleanUrls: %s, relativizeUrls: %s)',
+ async (base, cleanUrls, relativizeUrls, href) => {
+ const src = [
+ '::: tip [Guide][guide]',
+ '[Guide][guide]',
+ ':::',
+ '',
+ '::: details [Guide][guide]',
+ 'content',
+ ':::',
+ '',
+ '[guide]: /guide/start.md'
+ ].join('\n')
+ const html = await render(
+ src,
+ {},
+ { cleanUrls, relativePath: 'nested/page.md', relativizeUrls },
+ base
+ )
+ const link = `Guide`
+ expect(html).toContain(`${link}
`)
+ expect(html).toContain(`${link}`)
+ expect(html).toContain(`${link}
`)
+ }
+ )
+
+ test('keeps page footnotes out of container titles', async () => {
+ const html = await render(
+ '::: tip [Guide](/guide.md)\nText[^1]\n:::\n\n[^1]: A note'
+ )
+ expect(html).toContain(
+ 'Guide
'
+ )
+ expect(html.match(/class="footnotes"/g)).toHaveLength(1)
+ })
+
test('respects custom labels from container options', async () => {
const src = '::: tip\n提示内容\n:::\n\n::: details\n详情内容\n:::'
expect(
diff --git a/__tests__/unit/node/markdownToVue-cache.test.ts b/__tests__/unit/node/markdownToVue-cache.test.ts
new file mode 100644
index 000000000..fc9920531
--- /dev/null
+++ b/__tests__/unit/node/markdownToVue-cache.test.ts
@@ -0,0 +1,70 @@
+import { mkdtemp, rm, writeFile } from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import path from 'node:path'
+
+import { resolveConfig } from 'node/config'
+import { disposeMdItInstance } from 'node/markdown/markdown'
+import { clearCache, createMarkdownToVueRenderFn } from 'node/markdownToVue'
+
+describe('node/markdownToVue cache invalidation', () => {
+ let root: string
+
+ beforeEach(async () => {
+ clearCache()
+ disposeMdItInstance()
+ root = await mkdtemp(path.join(tmpdir(), 'vitepress-md-cache-'))
+ })
+
+ afterEach(async () => {
+ clearCache()
+ disposeMdItInstance()
+ await rm(root, { recursive: true, force: true })
+ })
+
+ test.each([
+ ['include', ''],
+ ['snippet', '<<< ./shared.md{txt}']
+ ])(
+ 'invalidates every source version when its %s changes',
+ async (_, importSrc) => {
+ const file = path.join(root, 'page.md')
+ const otherFile = path.join(root, 'other-page.md')
+ const sharedFile = path.join(root, 'shared.md')
+ const sources = ['First', 'Second', 'Third'].map(
+ (title) => `# ${title}\n\n${importSrc}\n`
+ )
+ await writeFile(file, sources[0])
+ await writeFile(otherFile, '# Other page')
+ await writeFile(sharedFile, 'old shared content')
+
+ const config = await resolveConfig(root, 'serve', 'development')
+ const render = await createMarkdownToVueRenderFn(
+ config.srcDir,
+ { highlight: (code) => code },
+ '/',
+ false,
+ false,
+ config
+ )
+
+ // Editing the page creates several cached versions of the same path.
+ const first = await render(sources[0], file)
+ expect(await render(sources[0], file)).toBe(first)
+ await render(sources[1], file)
+ const other = await render('# Other page', otherFile)
+ await render(sources[2], file)
+
+ await writeFile(sharedFile, 'updated shared content')
+ // The include/snippet HMR handler invalidates each importing page by path.
+ clearCache('page.md')
+
+ // Undoing a page edit must not revive a stale version of the dependency.
+ for (const src of [...sources].reverse()) {
+ const result = await render(src, file)
+ expect(result.vueSrc).toContain('updated shared content')
+ expect(result.vueSrc).not.toContain('old shared content')
+ }
+ expect(await render('# Other page', otherFile)).toBe(other)
+ }
+ )
+})
diff --git a/docs/en/guide/i18n.md b/docs/en/guide/i18n.md
index e166804ef..f688a62bb 100644
--- a/docs/en/guide/i18n.md
+++ b/docs/en/guide/i18n.md
@@ -55,7 +55,7 @@ interface LocaleSpecificConfig {
Set `dir: false` at the site or locale level when application code manages the `` attribute at runtime. VitePress will omit the attribute from generated HTML and will not overwrite it during client-side navigation.
-Refer [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) interface for details on customizing the placeholder texts of the default theme. Don't override `themeConfig.algolia` or `themeConfig.carbonAds` at locale-level. Refer [Algolia docs](../reference/default-theme-search#i18n) for using multilingual search.
+Refer [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) interface for details on customizing the placeholder texts of the default theme. Don't override `themeConfig.algolia` or `themeConfig.carbonAds` at locale-level. Refer [Algolia docs](../reference/default-theme-search#algolia-search-i18n) for using multilingual search.
**Pro tip:** Config file can be stored at `docs/.vitepress/config/index.ts` too. It might help you organize stuff by creating a configuration file per locale and then merge and export them from `index.ts`.
diff --git a/docs/fa/guide/i18n.md b/docs/fa/guide/i18n.md
index 5fd64c4fb..ac199cc5e 100644
--- a/docs/fa/guide/i18n.md
+++ b/docs/fa/guide/i18n.md
@@ -53,7 +53,7 @@ interface LocaleSpecificConfig {
}
```
-برای جزئیات درباره تنظیمات پیشفرض، به رابط `DefaultTheme.Config` در [اینجا](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) مراجعه کنید. لطفاً خصوصیات `themeConfig.algolia` یا `themeConfig.carbonAds` را در سطح زبان تغییر ندهید. برای استفاده از جستجوی چندزبانه، به [مستندات Algolia](../reference/default-theme-search#i18n) مراجعه کنید.
+برای جزئیات درباره تنظیمات پیشفرض، به رابط `DefaultTheme.Config` در [اینجا](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) مراجعه کنید. لطفاً خصوصیات `themeConfig.algolia` یا `themeConfig.carbonAds` را در سطح زبان تغییر ندهید. برای استفاده از جستجوی چندزبانه، به [مستندات Algolia](../reference/default-theme-search#algolia-search-i18n) مراجعه کنید.
**نکته حرفهای:** فایل پیکربندی را میتوانید در `docs/.vitepress/config/index.ts` نیز ذخیره کنید. این کار به شما کمک میکند که با ایجاد یک فایل پیکربندی برای هر زبان و سپس ادغام و صدور آنها از `index.ts`، موارد را سازماندهی کنید.
diff --git a/docs/ja/guide/i18n.md b/docs/ja/guide/i18n.md
index 95995ced6..f74f7e2e1 100644
--- a/docs/ja/guide/i18n.md
+++ b/docs/ja/guide/i18n.md
@@ -53,7 +53,7 @@ interface LocaleSpecificConfig {
}
```
-デフォルトテーマのプレースホルダ文言のカスタマイズについては [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) を参照してください。`themeConfig.algolia` や `themeConfig.carbonAds` をロケール単位で上書きしないでください。多言語検索の利用は [Algolia ドキュメント](../reference/default-theme-search#i18n) を参照。
+デフォルトテーマのプレースホルダ文言のカスタマイズについては [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) を参照してください。`themeConfig.algolia` や `themeConfig.carbonAds` をロケール単位で上書きしないでください。多言語検索の利用は [Algolia ドキュメント](../reference/default-theme-search#algolia-search-i18n) を参照。
**プロ向けヒント:** 設定ファイルは `docs/.vitepress/config/index.ts` に置くこともできます。ロケールごとに設定ファイルを作成して、`index.ts` でマージ・エクスポートすると整理しやすくなります。
diff --git a/docs/ko/guide/i18n.md b/docs/ko/guide/i18n.md
index cd449f51e..0fed88f36 100644
--- a/docs/ko/guide/i18n.md
+++ b/docs/ko/guide/i18n.md
@@ -53,7 +53,7 @@ interface LocaleSpecificConfig {
}
```
-기본 테마의 플레이스홀더 텍스트를 커스텀하는 방법에 대한 자세한 내용은 [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) 인터페이스를 참고하십시오. 로케일 수준에서 `themeConfig.algolia` 또는 `themeConfig.carbonAds`를 재정의하지 마십시오. 다국어 검색을 사용하려면 [Algolia docs](../reference/default-theme-search#i18n)를 참조하십시오.
+기본 테마의 플레이스홀더 텍스트를 커스텀하는 방법에 대한 자세한 내용은 [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) 인터페이스를 참고하십시오. 로케일 수준에서 `themeConfig.algolia` 또는 `themeConfig.carbonAds`를 재정의하지 마십시오. 다국어 검색을 사용하려면 [Algolia docs](../reference/default-theme-search#algolia-search-i18n)를 참조하십시오.
**전문가 팁:** 구성 파일은 `docs/.vitepress/config/index.ts`로 저장할 수도 있습니다. 로케일마다 구성 파일을 생성한 후 `index.ts`에서 병합하고 내보내어 내용을 구성하는 데 도움이 될 수 있습니다.
diff --git a/docs/pt/guide/i18n.md b/docs/pt/guide/i18n.md
index f4e3b0923..999c35a0c 100644
--- a/docs/pt/guide/i18n.md
+++ b/docs/pt/guide/i18n.md
@@ -53,7 +53,7 @@ interface LocaleSpecificConfig {
}
```
-Consulte a interface [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) para obter detalhes sobre a personalização dos textos marcadores do tema padrão. Não substitua `themeConfig.algolia` ou `themeConfig.carbonAds` no nível do idioma. Consulte a [documentação Algolia](../reference/default-theme-search#i18n) para usar a pesquisa multilínguas.
+Consulte a interface [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) para obter detalhes sobre a personalização dos textos marcadores do tema padrão. Não substitua `themeConfig.algolia` ou `themeConfig.carbonAds` no nível do idioma. Consulte a [documentação Algolia](../reference/default-theme-search#algolia-search-i18n) para usar a pesquisa multilínguas.
**Dica profissional:** O arquivo de configuração pode ser armazenado em `docs/.vitepress/config/index.ts` também. Isso pode ajudar a organizar as coisas criando um arquivo de configuração por idioma e então mesclá-los e exportá-los a partir de `index.ts`.
diff --git a/docs/zh/guide/i18n.md b/docs/zh/guide/i18n.md
index 96b16705d..f5c9e4026 100644
--- a/docs/zh/guide/i18n.md
+++ b/docs/zh/guide/i18n.md
@@ -53,7 +53,7 @@ interface LocaleSpecificConfig {
}
```
-有关自定义默认主题的文本占位符的信息,请参考 [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) 接口。不要在 locale 级别覆盖 `themeConfig.algolia` 或 `themeConfig.carbonAds`。想获取多语言搜索的信息,请参考 [Algolia 文档](../reference/default-theme-search#i18n)。
+有关自定义默认主题的文本占位符的信息,请参考 [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) 接口。不要在 locale 级别覆盖 `themeConfig.algolia` 或 `themeConfig.carbonAds`。想获取多语言搜索的信息,请参考 [Algolia 文档](../reference/default-theme-search#algolia-search-i18n)。
**提示**:配置文件也可以是 `docs/.vitepress/config/index.ts`。通过为每个语言环境创建一个配置文件,然后从 `index.ts` 合并并导出它们,可以更好地组织文件。
diff --git a/src/client/app/utils.ts b/src/client/app/utils.ts
index 63e4708a6..437837574 100644
--- a/src/client/app/utils.ts
+++ b/src/client/app/utils.ts
@@ -1,5 +1,12 @@
import { tryOnUnmounted } from '@vueuse/core'
-import { h, onMounted, shallowRef, type AsyncComponentLoader } from 'vue'
+import {
+ h,
+ isVNode,
+ onMounted,
+ shallowRef,
+ type AsyncComponentLoader,
+ type SetupContext
+} from 'vue'
import {
EXTERNAL_URL_RE,
@@ -116,7 +123,7 @@ export function defineClientComponent(
cb?: () => Awaitable
) {
return {
- setup() {
+ setup(_: unknown, { slots }: SetupContext) {
const comp = shallowRef()
onMounted(async () => {
let res = await loader()
@@ -127,7 +134,23 @@ export function defineClientComponent(
comp.value = res
await cb?.()
})
- return () => (comp.value ? h(comp.value, ...(args ?? [])) : null)
+ return () => {
+ if (!comp.value) return null
+
+ const props = args?.[0]
+ // Forward slots unless args already supply children in an h() overload.
+ if (
+ (!args || args.length < 2) &&
+ (props == null ||
+ (typeof props === 'object' &&
+ !Array.isArray(props) &&
+ !isVNode(props)))
+ ) {
+ return h(comp.value, props, slots)
+ }
+
+ return h(comp.value, ...(args ?? []))
+ }
}
}
}
diff --git a/src/client/theme-default/components/VPFlyout.vue b/src/client/theme-default/components/VPFlyout.vue
index be1b8d68b..419a7f8a1 100644
--- a/src/client/theme-default/components/VPFlyout.vue
+++ b/src/client/theme-default/components/VPFlyout.vue
@@ -167,7 +167,7 @@ useEventListener('pointerdown', (e) => {
.text {
display: flex;
align-items: center;
- line-height: var(--vp-nav-height);
+ line-height: 1.5;
font-size: 0.875rem;
font-weight: 500;
color: var(--vp-c-text-1);
diff --git a/src/client/theme-default/components/VPHero.vue b/src/client/theme-default/components/VPHero.vue
index d6586235a..f594d8ae7 100644
--- a/src/client/theme-default/components/VPHero.vue
+++ b/src/client/theme-default/components/VPHero.vue
@@ -305,6 +305,7 @@ const { heroImageSlotExists } = inject(
width: 12rem;
height: 12rem;
background-image: var(--vp-home-hero-image-background-image);
+ will-change: filter;
filter: var(--vp-home-hero-image-filter);
transform: translate(-50%, -50%);
}
diff --git a/src/client/theme-default/components/VPImage.vue b/src/client/theme-default/components/VPImage.vue
index e2d606210..fb04af238 100644
--- a/src/client/theme-default/components/VPImage.vue
+++ b/src/client/theme-default/components/VPImage.vue
@@ -23,13 +23,13 @@ defineOptions({ inheritAttrs: false })
diff --git a/src/client/theme-default/styles/components/vp-doc.css b/src/client/theme-default/styles/components/vp-doc.css
index ec6a34692..a5ba52f7c 100644
--- a/src/client/theme-default/styles/components/vp-doc.css
+++ b/src/client/theme-default/styles/components/vp-doc.css
@@ -439,6 +439,7 @@
}
.vp-doc [class*='language-'] .has-focused-lines .line:not(.has-focus) {
+ will-change: filter;
filter: blur(0.095rem);
opacity: 0.4;
transition:
diff --git a/src/node/markdown/plugins/containers.ts b/src/node/markdown/plugins/containers.ts
index 88c7c87bc..1e8925ebd 100644
--- a/src/node/markdown/plugins/containers.ts
+++ b/src/node/markdown/plugins/containers.ts
@@ -138,7 +138,13 @@ function createOpenRender(
if (noTitle) return `\n`
const title = md.renderInline(
info || titlesFor(titles, env.localeIndex)[name],
- { references: env.references, frontmatter: env.frontmatter }
+ {
+ references: env.references,
+ frontmatter: env.frontmatter,
+ cleanUrls: env.cleanUrls,
+ relativePath: env.relativePath,
+ relativizeUrls: env.relativizeUrls
+ }
)
if (name === 'details')
return `${title}
\n`
diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts
index e21e27a32..06ce309bd 100644
--- a/src/node/markdownToVue.ts
+++ b/src/node/markdownToVue.ts
@@ -59,7 +59,9 @@ export function clearCache(relativePath?: string) {
return
}
- cache.find((_, key) => key.endsWith(`:${relativePath}`) && cache.delete(key))
+ for (const key of cache.keys()) {
+ if (key.endsWith(`:${relativePath}`)) cache.delete(key)
+ }
}
function normalizeDriveLetter(file: string) {