feat(plugin-md-power): rename demo-wrapper container to window (#858)

This commit is contained in:
pengzhanbo 2026-02-25 21:48:39 +08:00 committed by GitHub
parent f7d3546962
commit ce32605aee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 454 additions and 321 deletions

View File

@ -81,7 +81,7 @@ content right
**demo wrapper** **demo wrapper**
::: demo-wrapper title="Demo" no-padding height="200px" ::: window title="Demo" height="200px"
<style scoped> <style scoped>
.open-door { .open-door {
display: flex; display: flex;

View File

@ -95,9 +95,9 @@ H~2~O
- vscode - <Icon name="skill-icons:vscode-dark" size="2em" /> - vscode - <Icon name="skill-icons:vscode-dark" size="2em" />
- twitter - <Icon name="skill-icons:twitter" size="2em" /> - twitter - <Icon name="skill-icons:twitter" size="2em" />
**demo wrapper** **示例容器**
::: demo-wrapper title="示例" no-padding height="200px" ::: window title="示例" height="200px"
<style scoped> <style scoped>
.open-door { .open-door {
display: flex; display: flex;

View File

@ -102,9 +102,9 @@ H~2~O
- vscode - <Icon name="skill-icons:vscode-dark" size="2em" /> - vscode - <Icon name="skill-icons:vscode-dark" size="2em" />
- twitter - <Icon name="skill-icons:twitter" size="2em" /> - twitter - <Icon name="skill-icons:twitter" size="2em" />
**demo wrapper** **示例容器**
::: demo-wrapper title="示例" no-padding height="200px" ::: window title="示例" height="200px"
<style scoped> <style scoped>
.open-door { .open-door {
display: flex; display: flex;

View File

@ -129,8 +129,7 @@ config:
**Result:** **Result:**
:::demo-wrapper img no-padding ::: window
![banner](/images/custom-banner.jpg) ![banner](/images/custom-banner.jpg)
::: :::
@ -246,7 +245,7 @@ config:
**Result:** **Result:**
:::demo-wrapper img no-padding ::: window
<img src="/images/custom-doc-hero.jpg" alt="Theme Plume" /> <img src="/images/custom-doc-hero.jpg" alt="Theme Plume" />
::: :::
@ -359,7 +358,7 @@ config:
**Result:** **Result:**
:::demo-wrapper img no-padding :::window
<img src="/images/custom-features.jpg" alt="custom-features" /> <img src="/images/custom-features.jpg" alt="custom-features" />
::: :::
@ -432,11 +431,11 @@ config:
**Result:** **Result:**
:::demo-wrapper img no-padding :::window
<img src="/images/custom-image-text.jpg" alt="image-text" /> <img src="/images/custom-image-text.jpg" alt="image-text" />
::: :::
:::demo-wrapper img no-padding :::window
<img src="/images/custom-text-image.jpg" alt="text-image" /> <img src="/images/custom-text-image.jpg" alt="text-image" />
::: :::
@ -505,7 +504,7 @@ config:
**Result:** **Result:**
:::demo-wrapper img no-padding :::window
<img src="/images/custom-profile.jpg" alt="profile" /> <img src="/images/custom-profile.jpg" alt="profile" />
::: :::
@ -551,7 +550,7 @@ yarn add vuepress@next vuepress-theme-plume
**Result:** **Result:**
:::demo-wrapper img no-padding :::window
<img src="/images/custom-content.jpg" alt="content" /> <img src="/images/custom-content.jpg" alt="content" />
::: :::

View File

@ -1,9 +1,12 @@
--- ---
title: Demo Container title: Demo Container
createTime: 2025/10/08 14:47:12 createTime: 2025/10/08 14:47:12
icon: icon-park-outline:eyes icon: fxemoji:window
permalink: /en/guide/markdown/demo-wrapper/ permalink: /en/guide/markdown/window/
outline: 2 outline: 2
badge:
text: Change
type: warning
--- ---
## Overview ## Overview
@ -14,7 +17,7 @@ separately from other content. The theme supports adding demo containers in Mark
## Syntax ## Syntax
````md ````md
::: demo-wrapper ::: window
Add your demo here Add your demo here
::: :::
```` ````
@ -31,20 +34,20 @@ Add your demo here
Containing only images: Containing only images:
```md ```md
::: demo-wrapper img no-padding ::: window
![hero](/images/custom-hero.jpg) ![hero](/images/custom-hero.jpg)
::: :::
``` ```
**Output:** **Output:**
::: demo-wrapper img no-padding ::: window
![hero](/images/custom-hero.jpg) ![hero](/images/custom-hero.jpg)
::: :::
Containing markdown syntax: Containing markdown syntax:
```md ```md
::: demo-wrapper title="Title" ::: window title="Title"
### Level 3 Heading ### Level 3 Heading
This is content inside the demo container. This is content inside the demo container.
@ -52,7 +55,7 @@ This is content inside the demo container.
``` ```
**Output:** **Output:**
::: demo-wrapper title="Title" ::: window title="Title"
### Level 3 Heading ### Level 3 Heading
@ -62,7 +65,7 @@ This is content inside the demo container.
Containing HTML/Vue code: Containing HTML/Vue code:
```md ```md
::: demo-wrapper ::: window
<h1 class="your-demo-title">This is a heading</h1> <h1 class="your-demo-title">This is a heading</h1>
<p class="your-demo-paragraph">This is a paragraph</p> <p class="your-demo-paragraph">This is a paragraph</p>
@ -78,7 +81,7 @@ Containing HTML/Vue code:
``` ```
**Output:** **Output:**
::: demo-wrapper ::: window
<h1 class="your-demo-title">This is a heading</h1> <h1 class="your-demo-title">This is a heading</h1>
<p class="your-demo-paragraph">This is a paragraph</p> <p class="your-demo-paragraph">This is a paragraph</p>

View File

@ -144,18 +144,18 @@ Included via `<!-- @include: ./foo.snippet.md#snippet -->`.
Using `<!-- @include: ./foo.snippet.md -->` to include the entire file: Using `<!-- @include: ./foo.snippet.md -->` to include the entire file:
:::: demo-wrapper title="Include by file" :::: window title="Include by file"
<!-- @include: ../../snippet/include-2.snippet.md --> <!-- @include: ../../snippet/include-2.snippet.md -->
:::: ::::
Using `<!-- @include: ./foo.snippet.md{5-7} -->` to include lines 5-7 of the file: Using `<!-- @include: ./foo.snippet.md{5-7} -->` to include lines 5-7 of the file:
:::: demo-wrapper title="Include by lines" :::: window title="Include by lines"
<!-- @include: ../../snippet/include-2.snippet.md{5-7} --> <!-- @include: ../../snippet/include-2.snippet.md{5-7} -->
:::: ::::
Using `<!-- @include: ./foo.snippet.md#snippet -->` to include the `snippet` region: Using `<!-- @include: ./foo.snippet.md#snippet -->` to include the `snippet` region:
:::: demo-wrapper title="Include by file region" :::: window title="Include by file region"
<!-- @include: ../../snippet/include-2.snippet.md#snippet --> <!-- @include: ../../snippet/include-2.snippet.md#snippet -->
:::: ::::

View File

@ -12,7 +12,7 @@ This could be to pique readers' curiosity or simply to add a bit of reading diff
To satisfy this playful intention, the theme provides a fun little feature called **"plot" text**. It looks like this: To satisfy this playful intention, the theme provides a fun little feature called **"plot" text**. It looks like this:
:::demo-wrapper :::window
Did you know that !!Lu Xun!! once said: "!!I never said this!!!" It was an enlightening revelation that deeply Did you know that !!Lu Xun!! once said: "!!I never said this!!!" It was an enlightening revelation that deeply
inspired me, filling me with unparalleled strength! So, !!I turned over in bed!!! inspired me, filling me with unparalleled strength! So, !!I turned over in bed!!!
::: :::
@ -102,7 +102,7 @@ Did you know that !!Lu Xun!! once said: "!!I never said this!!!" It was an enlig
**Output**: **Output**:
:::demo-wrapper :::window
Did you know that !!Lu Xun!! once said: "!!I never said this!!!" It was an enlightening revelation that Did you know that !!Lu Xun!! once said: "!!I never said this!!!" It was an enlightening revelation that
deeply inspired me, filling me with unparalleled strength! So, !!I turned over in bed!!!! deeply inspired me, filling me with unparalleled strength! So, !!I turned over in bed!!!!
::: :::
@ -118,7 +118,7 @@ Blur effect + click: !!Click to see me!!{.blur .click}
**Output**: **Output**:
:::demo-wrapper :::window
Mask effect + hover: !!Hover to see me!!{.mask .hover} Mask effect + hover: !!Hover to see me!!{.mask .hover}
Mask effect + click: !!Click to see me!!{.mask .click} Mask effect + click: !!Click to see me!!{.mask .click}

View File

@ -128,7 +128,7 @@ config:
**效果:** **效果:**
:::demo-wrapper img no-padding :::window
![banner](/images/custom-banner.jpg) ![banner](/images/custom-banner.jpg)
::: :::
@ -245,7 +245,7 @@ config:
**效果:** **效果:**
:::demo-wrapper img no-padding :::window
<img src="/images/custom-doc-hero.jpg" alt="Theme Plume" /> <img src="/images/custom-doc-hero.jpg" alt="Theme Plume" />
::: :::
@ -358,7 +358,7 @@ config:
**效果:** **效果:**
:::demo-wrapper img no-padding :::window
<img src="/images/custom-features.jpg" alt="custom-features" /> <img src="/images/custom-features.jpg" alt="custom-features" />
::: :::
@ -431,11 +431,11 @@ config:
**效果:** **效果:**
:::demo-wrapper img no-padding :::window
<img src="/images/custom-image-text.jpg" alt="image-text" /> <img src="/images/custom-image-text.jpg" alt="image-text" />
::: :::
:::demo-wrapper img no-padding :::window
<img src="/images/custom-text-image.jpg" alt="text-image" /> <img src="/images/custom-text-image.jpg" alt="text-image" />
::: :::
@ -504,7 +504,7 @@ config:
**效果:** **效果:**
:::demo-wrapper img no-padding :::window
<img src="/images/custom-profile.jpg" alt="profile" /> <img src="/images/custom-profile.jpg" alt="profile" />
::: :::
@ -550,7 +550,7 @@ yarn add vuepress@next vuepress-theme-plume
**效果:** **效果:**
:::demo-wrapper img no-padding :::window
<img src="/images/custom-content.jpg" alt="content" /> <img src="/images/custom-content.jpg" alt="content" />
::: :::

View File

@ -1,20 +1,23 @@
--- ---
title: 示例容器 title: 示例容器
createTime: 2024/09/30 14:47:12 createTime: 2024/09/30 14:47:12
icon: icon-park-outline:eyes icon: fxemoji:window
permalink: /guide/markdown/demo-wrapper/ permalink: /guide/markdown/window/
outline: 2 outline: 2
badge:
text: 变更
type: warning
--- ---
## 概述 ## 概述
有时候,你可能需要在 内容中补充一些 示例,但期望能与 其它内容 分隔开来呈现。 有时候,你可能需要在 内容中补充一些 示例,但期望能与 其它内容 分隔开来呈现。
主题支持在 Markdown 文件中添加示例容器。 主题支持在 Markdown 文件中添加示例窗口容器。
## 语法 ## 语法
````md ````md
::: demo-wrapper ::: window
添加你的示例 添加你的示例
::: :::
```` ````
@ -22,29 +25,28 @@ outline: 2
## 选项 ## 选项
- `title="xxx"`:标题 - `title="xxx"`:标题
- `no-padding`:不添加内边距 - `height="200px"`: 高度
- `img`: 仅包含图片时使用 - `gap="20px"`: 左右内边距
- `height="xxx"`: 高度
## 示例 ## 示例
仅包含图片: 仅包含图片:
```md ```md
::: demo-wrapper img no-padding ::: window
![hero](/images/custom-hero.jpg) ![hero](/images/custom-hero.jpg)
::: :::
``` ```
**输出:** **输出:**
::: demo-wrapper img no-padding ::: window
![hero](/images/custom-hero.jpg) ![hero](/images/custom-hero.jpg)
::: :::
包含 markdown 语法: 包含 markdown 语法:
```md ```md
::: demo-wrapper title="标题" ::: window title="标题"
### 三级标题 ### 三级标题
这是示例容器中的内容。 这是示例容器中的内容。
@ -52,7 +54,7 @@ outline: 2
``` ```
**输出:** **输出:**
::: demo-wrapper title="标题" ::: window title="标题"
### 三级标题 ### 三级标题
@ -62,8 +64,8 @@ outline: 2
包含 html /vue 代码: 包含 html /vue 代码:
```md ```md
::: demo-wrapper ::: window
<h1 class="your-demo-title">这是标题</h1> <h2 class="your-demo-title">这是标题</h2>
<p class="your-demo-paragraph">这是段落</p> <p class="your-demo-paragraph">这是段落</p>
<style> <style>
@ -78,9 +80,9 @@ outline: 2
``` ```
**输出:** **输出:**
::: demo-wrapper ::: window
<h1 class="your-demo-title">这是标题</h1> <h2 class="your-demo-title">这是标题</h2>
<p class="your-demo-paragraph">这是段落</p> <p class="your-demo-paragraph">这是段落</p>
<style> <style>

View File

@ -144,18 +144,18 @@ export default defineUserConfig({
使用 `<!-- @include: ./foo.snippet.md -->` 导入文件: 使用 `<!-- @include: ./foo.snippet.md -->` 导入文件:
:::: demo-wrapper title="Include by file" :::: window title="Include by file"
<!-- @include: ../../snippet/include-2.snippet.md --> <!-- @include: ../../snippet/include-2.snippet.md -->
:::: ::::
使用 `<!-- @include: ./foo.snippet.md{5-7} -->` 导入文件内的 5 到 7 行: 使用 `<!-- @include: ./foo.snippet.md{5-7} -->` 导入文件内的 5 到 7 行:
:::: demo-wrapper title="Include by lines" :::: window title="Include by lines"
<!-- @include: ../../snippet/include-2.snippet.md{5-7} --> <!-- @include: ../../snippet/include-2.snippet.md{5-7} -->
:::: ::::
使用 `<!-- @include: ./foo.snippet.md#snippet -->` 导入 `snippet` 区域 使用 `<!-- @include: ./foo.snippet.md#snippet -->` 导入 `snippet` 区域
:::: demo-wrapper title="Include by file region" :::: window title="Include by file region"
<!-- @include: ../../snippet/include-2.snippet.md#snippet --> <!-- @include: ../../snippet/include-2.snippet.md#snippet -->
:::: ::::

View File

@ -12,7 +12,7 @@ permalink: /guide/markdown/plot/
为了满足这种小小的心思,主题提供了一个 **“隐秘”文本** 的有趣小功能。它看起来像这样: 为了满足这种小小的心思,主题提供了一个 **“隐秘”文本** 的有趣小功能。它看起来像这样:
:::demo-wrapper :::window
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的 你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
力量!于是,!!我在床上翻了个身!! 力量!于是,!!我在床上翻了个身!!
::: :::
@ -102,7 +102,7 @@ plot:
**输出** **输出**
:::demo-wrapper :::window
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!!” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的 你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!!” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
力量!于是,!!我在床上翻了个身!! 力量!于是,!!我在床上翻了个身!!
::: :::
@ -118,7 +118,7 @@ plot:
**输出** **输出**
:::demo-wrapper :::window
遮罩层效果 + 鼠标悬停:!!鼠标悬停看到我了!!{.mask .hover} 遮罩层效果 + 鼠标悬停:!!鼠标悬停看到我了!!{.mask .hover}
遮罩层效果 + 点击:!!点击看到我了!!{.mask .click} 遮罩层效果 + 点击:!!点击看到我了!!{.mask .click}

View File

@ -97,9 +97,9 @@ H~2~O
- vscode - <Icon name="skill-icons:vscode-dark" size="2em" /> - vscode - <Icon name="skill-icons:vscode-dark" size="2em" />
- twitter - <Icon name="skill-icons:twitter" size="2em" /> - twitter - <Icon name="skill-icons:twitter" size="2em" />
**demo wrapper** **示例容器**
::: demo-wrapper title="示例" no-padding height="200px" ::: window title="示例" height="200px"
<style scoped> <style scoped>
.open-door { .open-door {
display: flex; display: flex;

View File

@ -1,33 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`demoWrapperPlugin > should work 1`] = `
"<div class="demo-wrapper">
<div class="demo-head">
<div class="demo-ctrl"><i></i><i></i><i></i></div>
</div>
<div class="demo-container" >
<p>content</p>
</div></div><div class="demo-wrapper has-title">
<div class="demo-head">
<div class="demo-ctrl"><i></i><i></i><i></i></div>
<h4 class="demo-title"><p>test</p></h4>
</div>
<div class="demo-container" >
<p>content</p>
</div></div><div class="demo-wrapper only-img no-padding has-height">
<div class="demo-head">
<div class="demo-ctrl"><i></i><i></i><i></i></div>
</div>
<div class="demo-container" style="--demo-container-height: 100px;">
<p><a href="/img.jpg">xxx</a></p>
</div></div><div class="demo-wrapper only-img no-padding has-height">
<div class="demo-head">
<div class="demo-ctrl"><i></i><i></i><i></i></div>
</div>
<div class="demo-container" style="--demo-container-height: 100px;">
<p><a href="/img.jpg">xxx</a></p>
</div></div>"
`;

View File

@ -0,0 +1,80 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`windowPlugin > legacy demo-wrapper container 1`] = `
"<article class="window-wrapper">
<header class="window-header">
<div class="window-left"><i></i><i></i><i></i></div>
<div class="window-right"><i class="vpi-window-share"></i><i class="vpi-window-add"></i><i class="vpi-window-copy"></i></div>
</header>
<section class="window-content" style="--window-gap:20px;"><p>content</p>
</section></article><article class="window-wrapper has-title">
<header class="window-header">
<div class="window-left"><i></i><i></i><i></i></div>
<div class="window-center"><h4 class="window-title ignore-header"><span>test</span><i class="vpi-window-reload"></i></h4></div>
<div class="window-right"><i class="vpi-window-share"></i><i class="vpi-window-add"></i><i class="vpi-window-copy"></i></div>
</header>
<section class="window-content" style="--window-gap:20px;"><p>content</p>
</section></article><article class="window-wrapper">
<header class="window-header">
<div class="window-left"><i></i><i></i><i></i></div>
<div class="window-right"><i class="vpi-window-share"></i><i class="vpi-window-add"></i><i class="vpi-window-copy"></i></div>
</header>
<section class="window-content" style="--window-gap:20px;--window-height:100px"><p><img src="/img.jpg" alt="xxx"></p>
</section></article><article class="window-wrapper">
<header class="window-header">
<div class="window-left"><i></i><i></i><i></i></div>
<div class="window-right"><i class="vpi-window-share"></i><i class="vpi-window-add"></i><i class="vpi-window-copy"></i></div>
</header>
<section class="window-content" style="--window-gap:20px;--window-height:100px"><p><img src="/img.jpg" alt="xxx"></p>
</section></article>"
`;
exports[`windowPlugin > should work 1`] = `
"<article class="window-wrapper">
<header class="window-header">
<div class="window-left"><i></i><i></i><i></i></div>
<div class="window-right"><i class="vpi-window-share"></i><i class="vpi-window-add"></i><i class="vpi-window-copy"></i></div>
</header>
<section class="window-content" style="--window-gap:20px;"><p>content</p>
</section></article><article class="window-wrapper has-title">
<header class="window-header">
<div class="window-left"><i></i><i></i><i></i></div>
<div class="window-center"><h4 class="window-title ignore-header"><span>test</span><i class="vpi-window-reload"></i></h4></div>
<div class="window-right"><i class="vpi-window-share"></i><i class="vpi-window-add"></i><i class="vpi-window-copy"></i></div>
</header>
<section class="window-content" style="--window-gap:20px;"><p>content</p>
</section></article><article class="window-wrapper">
<header class="window-header">
<div class="window-left"><i></i><i></i><i></i></div>
<div class="window-right"><i class="vpi-window-share"></i><i class="vpi-window-add"></i><i class="vpi-window-copy"></i></div>
</header>
<section class="window-content" style="--window-gap:0;--window-height:100px"><img src="/img.jpg" alt="xxx"></section></article><article class="window-wrapper">
<header class="window-header">
<div class="window-left"><i></i><i></i><i></i></div>
<div class="window-right"><i class="vpi-window-share"></i><i class="vpi-window-add"></i><i class="vpi-window-copy"></i></div>
</header>
<section class="window-content" style="--window-gap:20px;--window-height:100px"><img src="/img.jpg" alt="xxx"></section></article><article class="window-wrapper">
<header class="window-header">
<div class="window-left"><i></i><i></i><i></i></div>
<div class="window-right"><i class="vpi-window-share"></i><i class="vpi-window-add"></i><i class="vpi-window-copy"></i></div>
</header>
<section class="window-content" style="--window-gap:0;"> <img src="/img.jpg" alt="xxx">
</section></article><article class="window-wrapper">
<header class="window-header">
<div class="window-left"><i></i><i></i><i></i></div>
<div class="window-right"><i class="vpi-window-share"></i><i class="vpi-window-add"></i><i class="vpi-window-copy"></i></div>
</header>
<section class="window-content" style="--window-gap:0;"> <picture>
<source srcset="/img.jpg" type="image/webp" />
<img src="/img.jpg" alt="xxx" />
</picture>
</section></article>"
`;

View File

@ -1,29 +0,0 @@
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'
import { demoWrapperPlugin } from '../src/node/container/demoWrapper.js'
describe('demoWrapperPlugin', () => {
const md = new MarkdownIt().use(demoWrapperPlugin)
it('should work', () => {
const code = `\
::: demo-wrapper
content
:::
::: demo-wrapper title="test"
content
:::
::: demo-wrapper no-padding img height="100px"
[xxx](/img.jpg)
:::
::: demo-wrapper no-padding img height="100"
[xxx](/img.jpg)
:::
`
expect(md.render(code)).toMatchSnapshot()
})
})

View File

@ -0,0 +1,61 @@
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'
import { windowPlugin } from '../src/node/container/window.js'
describe('windowPlugin', () => {
const md = new MarkdownIt({ html: true }).use(windowPlugin)
it('should work', () => {
const code = `\
::: window
content
:::
::: window title="test"
content
:::
::: window height="100px"
![xxx](/img.jpg)
:::
::: window height="100" gap="20px"
![xxx](/img.jpg)
:::
::: window
<img src="/img.jpg" alt="xxx">
:::
::: window
<picture>
<source srcset="/img.jpg" type="image/webp" />
<img src="/img.jpg" alt="xxx" />
</picture>
:::
`
expect(md.render(code)).toMatchSnapshot()
})
it('legacy demo-wrapper container', () => {
const code = `\
::: demo-wrapper
content
:::
::: demo-wrapper title="test"
content
:::
::: demo-wrapper height="100px"
![xxx](/img.jpg)
:::
::: demo-wrapper height="100" gap="20px"
![xxx](/img.jpg)
:::
`
expect(md.render(code)).toMatchSnapshot()
})
})

View File

@ -1,140 +0,0 @@
.vp-doc .demo-wrapper {
display: flex;
flex-direction: column;
min-height: 40px;
margin: 40px -16px;
border: solid 1px var(--vp-c-divider);
border-radius: 8px;
box-shadow: var(--vp-shadow-2);
transition: var(--vp-t-color);
transition-property: border, box-shadow;
}
.vp-doc .demo-wrapper .demo-head {
display: flex;
align-items: center;
justify-content: flex-start;
min-height: 0;
border-bottom: solid 1px var(--vp-c-divider);
transition: border-bottom var(--vp-t-color);
}
.vp-doc .demo-wrapper .demo-container {
min-height: 0;
padding: 20px;
font-size: 14px;
line-height: 22px;
background-color: var(--vp-c-bg-alt);
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
transition: background-color var(--vp-t-color);
}
.vp-doc .demo-wrapper.has-title .demo-head {
border-bottom-color: transparent;
}
.vp-doc .demo-wrapper.only-img {
overflow: hidden;
}
.vp-doc .demo-wrapper.only-img img {
display: block;
}
.vp-doc .demo-wrapper.only-img .demo-container,
.vp-doc .demo-wrapper.no-padding .demo-container {
padding: 0;
}
.vp-doc .demo-wrapper.has-height .demo-container {
height: var(--demo-container-height);
overflow-y: auto;
}
.vp-doc .demo-wrapper .demo-ctrl {
display: flex;
gap: 5px;
align-items: center;
justify-content: flex-start;
padding: 5px 0 5px 8px;
}
.vp-doc .demo-wrapper .demo-ctrl i {
display: inline-block;
width: 10px;
height: 10px;
background-color: #ccc;
border-radius: 100%;
transition: background-color var(--vp-t-color);
}
.vp-doc .demo-wrapper .demo-ctrl i:nth-child(1) {
background-color: var(--vp-c-danger-3);
}
.vp-doc .demo-wrapper .demo-ctrl i:nth-child(2) {
background-color: var(--vp-c-warning-3);
}
.vp-doc .demo-wrapper .demo-ctrl i:nth-child(3) {
background-color: var(--vp-c-green-3);
}
.vp-doc .demo-wrapper .demo-title {
position: relative;
min-width: 0;
padding: 0 16px;
margin: 0 20px -1px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-2);
background-color: var(--vp-c-bg-alt);
border-top-left-radius: 8px;
border-top-right-radius: 8px;
transition: var(--vp-t-color);
transition-property: color, background-color;
}
.vp-doc .demo-wrapper .demo-title p {
max-width: 100%;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
text-wrap: nowrap;
}
.vp-doc .demo-wrapper .demo-title::after,
.vp-doc .demo-wrapper .demo-title::before {
position: absolute;
bottom: 0;
z-index: 1;
width: 8px;
height: 8px;
content: " ";
transition: background var(--vp-t-color);
}
.vp-doc .demo-wrapper .demo-title::before {
left: 100%;
background: radial-gradient(16px at right top, transparent 50%, var(--vp-c-bg-alt) 50%);
}
.vp-doc .demo-wrapper .demo-title::after {
right: 100%;
background: radial-gradient(16px at left top, transparent 50%, var(--vp-c-bg-alt) 50%);
}
.vp-doc .demo-wrapper .demo-container > *:first-child {
margin-top: 0;
}
.vp-doc .demo-wrapper .demo-container > *:last-child {
margin-bottom: 0;
}
@media (min-width: 419px) {
.vp-doc .demo-wrapper {
margin: 40px 0;
}
}

View File

@ -1,4 +1,4 @@
@charset "UTF-8"; @charset "UTF-8";
@import url("./demo-wrapper.css"); @import url("./window.css");
@import url("./steps.css"); @import url("./steps.css");

View File

@ -0,0 +1,156 @@
.window-wrapper {
display: flex;
flex-direction: column;
min-height: 40px;
margin: 16px -16px;
border: solid 1px var(--vp-c-divider);
border-radius: 8px;
box-shadow: var(--vp-shadow-2);
transition: var(--vp-t-color);
transition-property: border, box-shadow;
}
@media (min-width: 419px) {
.window-wrapper {
margin: 16px 0;
}
}
.window-wrapper .window-header {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
height: 28px;
background-color: rgb(241 242 243);
border-bottom: solid 1px var(--vp-c-divider);
border-top-left-radius: 8px;
border-top-right-radius: 8px;
transition: border-bottom var(--vp-t-color), background-color var(--vp-t-color);
}
[data-theme="dark"] .window-wrapper .window-header {
background-color: rgb(52 53 54);
}
.window-wrapper.has-title .window-header {
height: 36px;
}
.window-wrapper .window-left {
display: flex;
gap: 5px;
align-items: center;
justify-content: flex-start;
padding: 5px 0 5px 8px;
}
.window-wrapper .window-left i {
display: inline-block;
width: 10px;
height: 10px;
background-color: #ccc;
border-radius: 100%;
transition: background-color var(--vp-t-color);
}
.window-wrapper .window-left i:nth-child(1) {
background-color: var(--vp-c-danger-3);
}
.window-wrapper .window-left i:nth-child(2) {
background-color: var(--vp-c-warning-3);
}
.window-wrapper .window-left i:nth-child(3) {
background-color: var(--vp-c-green-3);
}
.window-wrapper .window-right {
display: flex;
gap: 5px;
align-items: center;
justify-content: flex-end;
padding-right: 8px;
color: var(--vp-c-text-3);
}
.window-wrapper .window-center {
display: flex;
flex: 1;
justify-content: center;
min-width: 0;
}
.window-wrapper .window-title {
display: inline-flex;
gap: 8px;
align-items: center;
justify-content: flex-start;
min-width: 200px;
max-width: 100%;
padding: 0 8px;
margin: 0;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-2);
border: solid 1px var(--vp-c-divider);
border-radius: 8px;
transition: border-color var(--vp-t-color);
}
@media (min-width: 640px) {
.window-wrapper .window-title {
min-width: 320px;
}
}
.window-wrapper .window-title span {
flex: 1;
max-width: 100%;
margin: 0;
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
text-wrap: nowrap;
}
.window-wrapper .window-title [class*="vpi-window-"] {
color: var(--vp-c-text-3);
transition: color var(--vp-t-color);
}
.window-wrapper .window-content {
height: var(--window-height, auto);
min-height: 0;
padding: 0 var(--window-gap, 20px);
overflow: auto;
font-size: 14px;
line-height: 22px;
background-color: var(--vp-c-bg);
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
transition: background-color var(--vp-t-color);
}
.window-wrapper .window-content > img:only-child {
display: block;
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
}
.vpi-window-share {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M11.293 2.293a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1-1.414 1.414L13 5.414V15a1 1 0 1 1-2 0V5.414L9.707 6.707a1 1 0 0 1-1.414-1.414zM4 11a2 2 0 0 1 2-2h2a1 1 0 0 1 0 2H6v9h12v-9h-2a1 1 0 1 1 0-2h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z'/%3E%3C/svg%3E");
}
.vpi-window-add {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 4a1 1 0 0 1 1 1v6h6a1 1 0 1 1 0 2h-6v6a1 1 0 1 1-2 0v-6H5a1 1 0 1 1 0-2h6V5a1 1 0 0 1 1-1'/%3E%3C/svg%3E");
}
.vpi-window-copy {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M2 4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v4h4a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2v-4H4a2 2 0 0 1-2-2zm8 12v4h10V10h-4v4a2 2 0 0 1-2 2zm4-2V4H4v10z'/%3E%3C/svg%3E");
}
.vpi-window-reload {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath d='M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747'/%3E%3Cpath d='M20 4v5h-5'/%3E%3C/g%3E%3C/svg%3E");
}

View File

@ -1,57 +0,0 @@
import type { Markdown } from 'vuepress/markdown'
import { resolveAttrs } from '.././utils/resolveAttrs.js'
import { createContainerPlugin } from './createContainer.js'
/**
* Demo wrapper attributes
*
*
*/
interface DemoWrapperAttrs {
title?: string
img?: string
noPadding?: boolean
height?: string
}
/**
* Demo wrapper plugin - Enable demo wrapper container
*
* -
*
* Syntax: :::demo-wrapper img no-padding title="xxx" height="100px"
* :::demo-wrapper img no-padding title="xxx" height="100px"
*
* @param md - Markdown instance / Markdown
*/
export function demoWrapperPlugin(md: Markdown): void {
createContainerPlugin(md, 'demo-wrapper', {
before: (info: string) => {
const { attrs } = resolveAttrs<DemoWrapperAttrs>(info)
const wrapperClasses: string[] = ['demo-wrapper']
let containerStyle = ''
if (attrs.title)
wrapperClasses.push('has-title')
if (attrs.img)
wrapperClasses.push('only-img')
if (attrs.noPadding)
wrapperClasses.push('no-padding')
if (attrs.height) {
const h = Number.parseFloat(attrs.height) === Number(attrs.height) ? `${attrs.height}px` : attrs.height
containerStyle += `--demo-container-height: ${h};`
wrapperClasses.push('has-height')
}
return `<div class="${wrapperClasses.join(' ')}">
<div class="demo-head">
<div class="demo-ctrl"><i></i><i></i><i></i></div>
${attrs.title ? `<h4 class="demo-title"><p>${attrs.title}</p></h4>` : ''}
</div>
<div class="demo-container" ${containerStyle ? `style="${containerStyle}"` : ''}>\n`
},
after: () => '</div></div>',
})
}

View File

@ -10,7 +10,6 @@ import { chatPlugin } from './chat.js'
import { codeTabs } from './codeTabs.js' import { codeTabs } from './codeTabs.js'
import { codeTreePlugin } from './codeTree.js' import { codeTreePlugin } from './codeTree.js'
import { collapsePlugin } from './collapse.js' import { collapsePlugin } from './collapse.js'
import { demoWrapperPlugin } from './demoWrapper.js'
import { encryptPlugin } from './encrypt.js' import { encryptPlugin } from './encrypt.js'
import { fieldPlugin } from './field.js' import { fieldPlugin } from './field.js'
import { fileTreePlugin } from './fileTree.js' import { fileTreePlugin } from './fileTree.js'
@ -20,6 +19,7 @@ import { stepsPlugin } from './steps.js'
import { tablePlugin } from './table.js' import { tablePlugin } from './table.js'
import { tabs } from './tabs.js' import { tabs } from './tabs.js'
import { timelinePlugin } from './timeline.js' import { timelinePlugin } from './timeline.js'
import { windowPlugin } from './window.js'
/** /**
* Container plugin - Register all container plugins * Container plugin - Register all container plugins
@ -44,8 +44,8 @@ export async function containerPlugin(
// ::: code-tabs // ::: code-tabs
codeTabs(md, options.codeTabs) codeTabs(md, options.codeTabs)
// ::: demo-wrapper // ::: window
demoWrapperPlugin(md) windowPlugin(md)
// ::: steps // ::: steps
stepsPlugin(md) stepsPlugin(md)

View File

@ -0,0 +1,91 @@
import type Token from 'markdown-it/lib/token.mjs'
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
import { isNumber, isString } from '@pengzhanbo/utils'
import { colors } from 'vuepress/utils'
import { logger } from '../utils/logger.js'
import { parseRect } from '../utils/parseRect.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { type ContainerOptions, createContainerPlugin } from './createContainer.js'
interface WindowAttrs {
title?: string
height?: string
gap?: string
/** @deprecated */
noPadding?: boolean
}
const RE_IMAGE_SYNTAX = /^!?\[[^\]]*\]\([^)]+\)$/
const render: NonNullable<ContainerOptions['before']> = (info, tokens, idx) => {
const elms: Token[] = []
for (let i = idx + 1; i < tokens.length; i++) {
if (tokens[i].type === 'container_window_close')
break
elms.push(tokens[i])
}
const { attrs } = resolveAttrs<WindowAttrs>(info)
let onlyImg = false
if (elms.length === 1) {
const { type } = elms[0]
const content = elms[0].content.trim()
if (type === 'html_block'
&& (content.startsWith('<img') || content.startsWith('<picture'))) {
onlyImg = true
}
}
if (elms.length === 3) {
const [op, img, cp] = elms
if (op.type === 'paragraph_open'
&& cp.type === 'paragraph_close'
&& img.type === 'inline'
&& RE_IMAGE_SYNTAX.test(img.content.trim())) {
op.type = 'text'
cp.type = 'text'
onlyImg = true
}
}
const { title, height, noPadding } = attrs
const gap = isString(attrs.gap) || isNumber(attrs.gap)
? parseRect(attrs.gap)
: (onlyImg || noPadding) ? '0' : '20px'
const classes: string[] = ['window-wrapper']
title && classes.push('has-title')
return `<article class="${classes.join(' ')}">
<header class="window-header">
<div class="window-left"><i></i><i></i><i></i></div>
${title ? `<div class="window-center"><h4 class="window-title ignore-header"><span>${title}</span><i class="vpi-window-reload"></i></h4></div>` : ''}
<div class="window-right"><i class="vpi-window-share"></i><i class="vpi-window-add"></i><i class="vpi-window-copy"></i></div>
</header>
<section class="window-content" style="--window-gap:${gap};${height ? `--window-height:${parseRect(height)}` : ''}">`
}
/**
* window plugin - Enable window container
*
* -
*
* Syntax: :::window title="xxx" height="100px" gap="20px"
* :::window title="xxx" height="100px" gap="20px"
*
* @param md - Markdown instance / Markdown
*/
export function windowPlugin(md: Markdown): void {
const after = () => '</section></article>'
createContainerPlugin(md, 'window', {
before: render,
after,
})
// legacy demo-wrapper container, keep for compatibility
createContainerPlugin(md, 'demo-wrapper', {
before: (info, tokens, idx, _, env: MarkdownEnv) => {
logger.warn('container', `::: demo-wrapper container is deprecated, please use ::: window container instead. (${colors.gray(env.filePathRelative || '')})`)
return render(info, tokens, idx, _, env)
},
after,
})
}