refactor(plugin-md-power): improve md plugins (#562)

This commit is contained in:
pengzhanbo 2025-04-25 15:04:55 +08:00 committed by GitHub
parent 7c05f3abc3
commit b07426dfc6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 577 additions and 564 deletions

View File

@ -18,7 +18,6 @@ permalink: /guide/chart/mermaid/
你需要在你的项目中安装 [mermaid](https://mermaid.js.org/) 库。
::: npm-to
@tab pnpm
```sh
npm install mermaid

View File

@ -40,7 +40,7 @@ export default defineUserConfig({
golang 代码演示默认是只读的,不可编辑。
````md
::: go-repl 自定义标题
::: go-repl title="自定义标题"
```go
// your go code
```
@ -49,10 +49,10 @@ golang 代码演示默认是只读的,不可编辑。
### 可编辑代码演示
如果需要在线编辑并执行,需要将代码块包裹在 `::: go-repl#editable` 容器语法中
如果需要在线编辑并执行,需要将代码块包裹在 `::: go-repl editable` 容器语法中
````md
::: go-repl#editable 自定义标题
::: go-repl editable title="自定义标题"
```go
// your go code
```
@ -103,7 +103,7 @@ func main() {
**输入:**
````md
:::go-repl#editable
:::go-repl editable
```go
package main
@ -120,7 +120,7 @@ func main() {
**输出:**
:::go-repl#editable
:::go-repl editable
```go
package main

View File

@ -40,7 +40,7 @@ export default defineUserConfig({
kotlin 代码演示默认是只读的,不可编辑。
````md
::: kotlin-repl 自定义标题
::: kotlin-repl title="自定义标题"
```kotlin
// your kotlin code
```
@ -49,10 +49,10 @@ kotlin 代码演示默认是只读的,不可编辑。
### 可编辑代码演示
如果需要在线编辑并执行,需要将代码块包裹在 `::: kotlin-repl#editable` 容器语法中
如果需要在线编辑并执行,需要将代码块包裹在 `::: kotlin-repl editable` 容器语法中
````md
::: kotlin-repl#editable 自定义标题
::: kotlin-repl editable title="自定义标题"
```kotlin
// your kotlin code
```
@ -114,7 +114,7 @@ fun main(args: Array<String>) {
**输入:**
````md
::: kotlin-repl#editable
::: kotlin-repl editable
```kotlin
class Contact(val id: Int, var email: String)
@ -128,7 +128,7 @@ fun main(args: Array<String>) {
**输出:**
::: kotlin-repl#editable
::: kotlin-repl editable
```kotlin
class Contact(val id: Int, var email: String)

View File

@ -40,7 +40,7 @@ export default defineUserConfig({
rust 代码演示默认是只读的,不可编辑。
````md
::: rust-repl 自定义标题
::: rust-repl title="自定义标题"
```rust
// your rust code
```
@ -49,10 +49,10 @@ rust 代码演示默认是只读的,不可编辑。
### 可编辑代码演示
如果需要在线编辑并执行,需要将代码块包裹在 `::: rust-repl#editable` 容器语法中
如果需要在线编辑并执行,需要将代码块包裹在 `::: rust-repl editable` 容器语法中
````md
::: rust-repl#editable 自定义标题
::: rust-repl editable title="自定义标题"
```rust
// your rust code
```
@ -66,7 +66,7 @@ rust 代码演示默认是只读的,不可编辑。
**输入:**
````md
::: rust-repl 打印内容
::: rust-repl title="打印内容"
```rust
fn main() {
println!("Hello, world!");
@ -77,7 +77,7 @@ fn main() {
**输出:**
::: rust-repl 打印内容
::: rust-repl title="打印内容"
```rust
fn main() {
@ -156,7 +156,7 @@ fn main() {
**输入:**
````md
::: rust-repl#editable
::: rust-repl editable
```rust
fn main() {
println!("Hello, world!");
@ -167,7 +167,7 @@ fn main() {
**输出:**
::: rust-repl#editable
::: rust-repl editable
```rust
fn main() {

View File

@ -1,10 +1,10 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`artPlayerPlugin > should not work 1`] = `
"<ArtPlayer src="" fullscreen flip playback-rate aspect-ratio setting pip :volume="0.75" width="100%"/><p>@[artPlayer]xxx</p>
"<ArtPlayer src="" fullscreen flip playback-rate aspect-ratio setting pip :volume="0.75" width="100%"/><p>@[artPlayer]xxx</p>
<p>@[ artPlayer]123456</p>
<p>@<a href="/xxx.mp4"> artPlayer</a></p>
<ArtPlayer src="/xxx.xxx" fullscreen flip playback-rate aspect-ratio setting pip type="xxx" :volume="0.75" width="100%"/>"
<ArtPlayer src="/xxx.xxx" fullscreen flip playback-rate aspect-ratio setting pip type="xxx" :volume="0.75" width="100%"/>"
`;
exports[`artPlayerPlugin > should work 1`] = `"<ArtPlayer src="/xxx.mp4" fullscreen flip playback-rate aspect-ratio setting pip :volume="0.75" width="100%"/><ArtPlayer src="/xxx.m3u8" fullscreen flip playback-rate aspect-ratio setting pip loop autoplay muted :volume="0.75" width="100%"/><ArtPlayer src="/xxx.flv" fullscreen flip playback-rate aspect-ratio setting pip autoplay muted :volume="0.55" width="100%"/><ArtPlayer src="/xxx.mpd" fullscreen flip playback-rate aspect-ratio setting pip auto-min poster="xx.jpg" :volume="0.75" width="100%" height="600px" ratio="16:9"/><ArtPlayer src="/xxx" fullscreen flip playback-rate aspect-ratio setting pip type="mp3" :volume="0.75" width="100%"/>"`;
exports[`artPlayerPlugin > should work 1`] = `"<ArtPlayer src="/xxx.mp4" fullscreen flip playback-rate aspect-ratio setting pip :volume="0.75" width="100%"/><ArtPlayer src="/xxx.m3u8" fullscreen flip playback-rate aspect-ratio setting pip autoplay muted loop :volume="0.75" width="100%"/><ArtPlayer src="/xxx.flv" fullscreen flip playback-rate aspect-ratio setting pip autoplay muted :volume="0.55" width="100%"/><ArtPlayer src="/xxx.mpd" fullscreen flip playback-rate aspect-ratio setting pip auto-mini :volume="0.75" poster="xx.jpg" width="100%" height="600px" ratio="16:9"/><ArtPlayer src="/xxx" fullscreen flip playback-rate aspect-ratio setting pip type="mp3" :volume="0.75" width="100%"/>"`;

View File

@ -12,9 +12,9 @@ exports[`artPlayerPlugin > should not work 1`] = `
`;
exports[`artPlayerPlugin > should work 1`] = `
"<p><AudioReader src="/xxx.mp3"></AudioReader> <AudioReader src="/xxx.mp3"></AudioReader></p>
<p><AudioReader src="/xxx.mp3">title</AudioReader></p>
<p><AudioReader src="/xxx.mp3" :start-time="0" :end-time="99" :volume="0.55"></AudioReader></p>
<p>xxx <AudioReader src="/xxx.mp3" type="audio/mp3"></AudioReader> xxx</p>
"<p><AudioReader src="/xxx.mp3" /> <AudioReader src="/xxx.mp3" /></p>
<p><AudioReader src="/xxx.mp3" autoplay title="title" /></p>
<p><AudioReader src="/xxx.mp3" autoplay :start-time="0" :end-time="99" :volume="0.55" /></p>
<p>xxx <AudioReader src="/xxx.mp3" type="audio/mp3" /> xxx</p>
"
`;

View File

@ -1,10 +1,10 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`bilibiliPlugin > should not work 1`] = `
"<VideoBilibili src="https://player.bilibili.com/player.html?autoplay=0&high_quality=1" width="100%" height="" ratio="" title="undefined" /><p>@[bilibili]xxx</p>
"<VideoBilibili src="https://player.bilibili.com/player.html?autoplay=0&high_quality=1" width="100%" /><p>@[bilibili]xxx</p>
<p>@[ bilibili]BV12345</p>
<p>@[bilibili](BV12345</p>
"
`;
exports[`bilibiliPlugin > should work 1`] = `"<VideoBilibili src="https://player.bilibili.com/player.html?bvid=BV12345&autoplay=0&high_quality=1" width="100%" height="" ratio="" title="undefined" /><VideoBilibili src="https://player.bilibili.com/player.html?aid=12432&cid=12345&autoplay=0&high_quality=1" width="100%" height="" ratio="" title="undefined" /><VideoBilibili src="https://player.bilibili.com/player.html?bvid=BV12345&aid=12343&cid=45678&autoplay=0&high_quality=1" width="100%" height="" ratio="" title="undefined" /><VideoBilibili src="https://player.bilibili.com/player.html?bvid=BV12345&p=1&t=1&autoplay=1&high_quality=1" width="100%" height="" ratio="" title="undefined" /><VideoBilibili src="https://player.bilibili.com/player.html?bvid=BV12345&p=1&autoplay=1&high_quality=1" width="100%" height="600px" ratio="" title="undefined" /><VideoBilibili src="https://player.bilibili.com/player.html?bvid=BV12345&p=1&autoplay=1&high_quality=1" width="100%" height="" ratio="16:9" title="undefined" />"`;
exports[`bilibiliPlugin > should work 1`] = `"<VideoBilibili src="https://player.bilibili.com/player.html?bvid=BV12345&autoplay=0&high_quality=1" width="100%" /><VideoBilibili src="https://player.bilibili.com/player.html?aid=12432&cid=12345&autoplay=0&high_quality=1" width="100%" /><VideoBilibili src="https://player.bilibili.com/player.html?bvid=BV12345&aid=12343&cid=45678&autoplay=0&high_quality=1" width="100%" /><VideoBilibili src="https://player.bilibili.com/player.html?bvid=BV12345&p=1&t=1&autoplay=1&high_quality=1" width="100%" /><VideoBilibili src="https://player.bilibili.com/player.html?bvid=BV12345&p=1&autoplay=1&high_quality=1" width="100%" height="600px" /><VideoBilibili src="https://player.bilibili.com/player.html?bvid=BV12345&p=1&autoplay=1&high_quality=1" width="100%" ratio="16:9" />"`;

View File

@ -37,7 +37,7 @@ exports[`caniusePlugin > should not work 8`] = `
"
`;
exports[`caniusePlugin > should work 1`] = `"<CanIUseViewer feature="feature" meta="test-id" past="2" future="1" />"`;
exports[`caniusePlugin > should work 1`] = `"<CanIUseViewer feature="feature" meta="test-id" :past="2" :future="1" />"`;
exports[`caniusePlugin > should work 2`] = `
"<ClientOnly><p><picture>
@ -47,13 +47,13 @@ exports[`caniusePlugin > should work 2`] = `
</picture></p></ClientOnly>"
`;
exports[`caniusePlugin > should work 3`] = `"<CanIUseViewer feature="feature" meta="test-id" past="2" future="1" />"`;
exports[`caniusePlugin > should work 3`] = `"<CanIUseViewer feature="feature" meta="test-id" :past="2" :future="1" />"`;
exports[`caniusePlugin > should work 4`] = `"<CanIUseViewer feature="feature" meta="test-id" past="2" future="0" />"`;
exports[`caniusePlugin > should work 4`] = `"<CanIUseViewer feature="feature" meta="test-id" :past="2" :future="0" />"`;
exports[`caniusePlugin > should work 5`] = `"<CanIUseViewer feature="feature" meta="test-id" past="2" future="0" /><CanIUseViewer feature="feature" meta="test-id" past="2" future="0" />"`;
exports[`caniusePlugin > should work 5`] = `"<CanIUseViewer feature="feature" meta="test-id" :past="2" :future="0" /><CanIUseViewer feature="feature" meta="test-id" :past="2" :future="0" />"`;
exports[`caniusePlugin > should work 6`] = `"<CanIUseViewer feature="feature" meta="test-id" past="2" future="0" />"`;
exports[`caniusePlugin > should work 6`] = `"<CanIUseViewer feature="feature" meta="test-id" :past="2" :future="0" />"`;
exports[`caniusePlugin > should work with options 1`] = `
"<ClientOnly><p><picture>
@ -63,13 +63,13 @@ exports[`caniusePlugin > should work with options 1`] = `
</picture></p></ClientOnly>"
`;
exports[`caniusePlugin > should work with options 2`] = `"<CanIUseViewer feature="feature" meta="test-id" past="2" future="1" />"`;
exports[`caniusePlugin > should work with options 2`] = `"<CanIUseViewer feature="feature" meta="test-id" :past="2" :future="1" />"`;
exports[`caniusePlugin > should work with options 3`] = `"<CanIUseViewer feature="feature" meta="test-id" past="2" future="0" />"`;
exports[`caniusePlugin > should work with options 3`] = `"<CanIUseViewer feature="feature" meta="test-id" :past="2" :future="0" />"`;
exports[`legacyCaniuse > should work 1`] = `"<CanIUseViewer feature="feature" meta="test-id" past="2" future="1" />"`;
exports[`legacyCaniuse > should work 1`] = `"<CanIUseViewer feature="feature" meta="test-id" :past="2" :future="1" />"`;
exports[`legacyCaniuse > should work 2`] = `"<CanIUseViewer feature="feature{-2,4}" meta="test-id" past="2" future="0" />"`;
exports[`legacyCaniuse > should work 2`] = `"<CanIUseViewer feature="feature{-2,4}" meta="test-id" :past="2" :future="0" />"`;
exports[`legacyCaniuse > should work 3`] = `""`;

View File

@ -1,9 +1,9 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`codeSandboxPlugin > should not work 1`] = `
"<CodeSandboxViewer title="" height="500px" width="100%" user="" id="" type="embed" filepath="" :console=false :navbar=true layout="" /><p>@[codesandbox]xxx</p>
"<CodeSandboxViewer width="100%" height="500px" user="" id="" title="" navbar layout="" type="embed" filepath="" /><p>@[codesandbox]xxx</p>
<p>@[codesandbox embed](</p>
"
`;
exports[`codeSandboxPlugin > should work 1`] = `"<CodeSandboxViewer title="" height="500px" width="100%" user="user" id="id" type="embed" filepath="" :console=false :navbar=true layout="" /><CodeSandboxViewer title="" height="500px" width="100%" user="user" id="id" type="embed" filepath="" :console=false :navbar=true layout="" /><CodeSandboxViewer title="" height="500px" width="100%" user="user" id="id" type="button" filepath="" :console=false :navbar=true layout="" /><CodeSandboxViewer title="xxx" height="500px" width="100%" user="user" id="slash" type="embed" filepath="filepath" :console=false :navbar=false layout="Editor+Preview" />"`;
exports[`codeSandboxPlugin > should work 1`] = `"<CodeSandboxViewer width="100%" height="500px" user="user" id="id" title="" navbar layout="" type="embed" filepath="" /><CodeSandboxViewer width="100%" height="500px" user="user" id="id" title="" navbar layout="" type="embed" filepath="" /><CodeSandboxViewer width="100%" height="500px" user="user" id="id" title="" navbar layout="" type="button" filepath="" /><CodeSandboxViewer width="100%" height="500px" user="user" id="slash" title="xxx" layout="Editor+Preview" type="embed" filepath="filepath" />"`;

View File

@ -1,9 +1,9 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`codepenPlugin > should not work 1`] = `
"<CodePenViewer user="" slash="undefined" title="Code Pen" tab="result" width="100%" height="400px" /><p>@[codepen]xxx</p>
"<CodePenViewer title="Code Pen" tab="result" width="100%" height="400px" user="" /><p>@[codepen]xxx</p>
<p>@[codepen preview](</p>
"
`;
exports[`codepenPlugin > should work 1`] = `"<CodePenViewer user="user" slash="slash" title="Code Pen" tab="result" width="100%" height="400px" /><CodePenViewer user="user" slash="slash" title="Code Pen" preview tab="result" width="100%" height="400px" /><CodePenViewer user="user" slash="slash" title="codepen" preview editable tab="css,result" theme="dark" width="100%" height="400px" />"`;
exports[`codepenPlugin > should work 1`] = `"<CodePenViewer title="Code Pen" tab="result" width="100%" height="400px" user="user" slash="slash" /><CodePenViewer title="Code Pen" tab="result" width="100%" height="400px" user="user" slash="slash" preview /><CodePenViewer title="codepen" tab="css,result" width="100%" height="400px" user="user" slash="slash" preview editable theme="dark" />"`;

View File

@ -1,9 +1,9 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`codeSandboxPlugin > should not work 1`] = `
"<JSFiddleViewer source="" title="JS Fiddle" tab="js,css,html,result" width="100%" height="400px" /><p>@[jsfiddle]xxx</p>
"<JSFiddleViewer width="100%" height="400px" source="" title="JS Fiddle" tab="js,css,html,result" /><p>@[jsfiddle]xxx</p>
<p>@[jsfiddle](</p>
"
`;
exports[`codeSandboxPlugin > should work 1`] = `"<JSFiddleViewer source="user/id" title="JS Fiddle" tab="js,css,html,result" width="100%" height="400px" /><JSFiddleViewer source="user/id" title="JS Fiddle" tab="js,css,html,result" width="100%" height="400px" /><JSFiddleViewer source="user/id" title="JS Fiddle" tab="js,css,html,result" width="100%" height="400px" theme="light" /><JSFiddleViewer source="user/id" title="xxx" tab="js,css,html,result" width="100%" height="500px" theme="dark" />"`;
exports[`codeSandboxPlugin > should work 1`] = `"<JSFiddleViewer width="100%" height="400px" source="user/id" title="JS Fiddle" tab="js,css,html,result" /><JSFiddleViewer width="100%" height="400px" source="user/id" title="JS Fiddle" tab="js,css,html,result" /><JSFiddleViewer width="100%" height="400px" source="user/id" title="JS Fiddle" tab="js,css,html,result" theme="light" /><JSFiddleViewer width="100%" height="500px" source="user/id" title="xxx" tab="js,css,html,result" theme="dark" />"`;

View File

@ -1,21 +1,21 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`langReplPlugin > should work with custom options 1`] = `
"<CodeRepl title="go playground"><pre><code class="language-go"><div class="language-go"><pre><code>const a = 1
"<CodeRepl title="go playground"><pre><code class="language-go"><div class="language-go"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl editable title="go playground"><pre><code class="language-go"><div class="language-go"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl title="kotlin playground"><pre><code class="language-kotlin"><div class="language-kotlin"><pre><code>const a = 1
</CodeRepl><CodeRepl title="kotlin playground"><pre><code class="language-kotlin"><div class="language-kotlin"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl editable title="kotlin playground"><pre><code class="language-kotlin"><div class="language-kotlin"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl title="rust playground"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</CodeRepl><CodeRepl title="rust playground"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl editable title="rust playground"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl title="title"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</CodeRepl><CodeRepl title="title"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl editable title=" title"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</CodeRepl><CodeRepl editable title="title"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl>"
`;
@ -45,21 +45,21 @@ exports[`langReplPlugin > should work with custom options 2`] = `
`;
exports[`langReplPlugin > should work with custom theme 1`] = `
"<CodeRepl title="go playground"><pre><code class="language-go"><div class="language-go"><pre><code>const a = 1
"<CodeRepl title="go playground"><pre><code class="language-go"><div class="language-go"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl editable title="go playground"><pre><code class="language-go"><div class="language-go"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl title="kotlin playground"><pre><code class="language-kotlin"><div class="language-kotlin"><pre><code>const a = 1
</CodeRepl><CodeRepl title="kotlin playground"><pre><code class="language-kotlin"><div class="language-kotlin"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl editable title="kotlin playground"><pre><code class="language-kotlin"><div class="language-kotlin"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl title="rust playground"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</CodeRepl><CodeRepl title="rust playground"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl editable title="rust playground"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl title="title"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</CodeRepl><CodeRepl title="title"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl><CodeRepl editable title=" title"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</CodeRepl><CodeRepl editable title="title"><pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
</CodeRepl>"
`;
@ -85,7 +85,7 @@ exports[`langReplPlugin > should work with default options 1`] = `
<pre><code class="language-go"><div class="language-go"><pre><code>const a = 1
</code></pre></div></code></pre>
<p>:::</p>
<p>::: go-repl#editable</p>
<p>::: go-repl editable</p>
<pre><code class="language-go"><div class="language-go"><pre><code>const a = 1
</code></pre></div></code></pre>
<p>:::</p>
@ -93,7 +93,7 @@ exports[`langReplPlugin > should work with default options 1`] = `
<pre><code class="language-kotlin"><div class="language-kotlin"><pre><code>const a = 1
</code></pre></div></code></pre>
<p>:::</p>
<p>::: kotlin-repl#editable</p>
<p>::: kotlin-repl editable</p>
<pre><code class="language-kotlin"><div class="language-kotlin"><pre><code>const a = 1
</code></pre></div></code></pre>
<p>:::</p>
@ -101,15 +101,15 @@ exports[`langReplPlugin > should work with default options 1`] = `
<pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
<p>:::</p>
<p>::: rust-repl#editable</p>
<p>::: rust-repl editable</p>
<pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
<p>:::</p>
<p>::: rust-repl title</p>
<p>::: rust-repl title=&quot;title&quot;</p>
<pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
<p>:::</p>
<p>::: rust-repl#editable title</p>
<p>::: rust-repl editable title=&quot;title&quot;</p>
<pre><code class="language-rust"><div class="language-rust"><pre><code>const a = 1
</code></pre></div></code></pre>
<p>:::</p>

View File

@ -1,12 +1,12 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`pdfPlugin > should not work 1`] = `
"<PDFViewer src="" title="" :page="1" :no-toolbar="false" width="100%" height="" ratio="" :zoom="50" /><p>@[pdf]xxx</p>
"<PDFViewer src="" :page="1" :zoom="50" width="100%" height="" ratio="" title="" /><p>@[pdf]xxx</p>
<p>@<a href=""> pdf</a></p>
<p>@[pdf]</p>
"
`;
exports[`pdfPlugin > should work 1`] = `"<PDFViewer src="foo.pdf" title="foo.pdf" :page="1" :no-toolbar="false" width="100%" height="" ratio="" :zoom="50" />"`;
exports[`pdfPlugin > should work 1`] = `"<PDFViewer src="foo.pdf" :page="1" :zoom="50" width="100%" height="" ratio="" title="foo.pdf" />"`;
exports[`pdfPlugin > should work 2`] = `"<PDFViewer src="foo.pdf" title="foo.pdf" :page="1" :no-toolbar="false" width="100%" height="" ratio="" :zoom="50" /><PDFViewer src="foo.pdf" title="foo.pdf" :page="1" :no-toolbar="true" width="100%" height="" ratio="" :zoom="50" /><PDFViewer src="foo.pdf" title="foo.pdf" :page="2" :no-toolbar="false" width="100%" height="" ratio="" :zoom="50" /><PDFViewer src="foo.pdf" title="foo.pdf" :page="2" :no-toolbar="true" width="100%" height="" ratio="" :zoom="50" /><PDFViewer src="foo.pdf" title="foo.pdf" :page="2" :no-toolbar="true" width="100%" height="600px" ratio="" :zoom="1" /><PDFViewer src="foo.pdf" title="foo.pdf" :page="2" :no-toolbar="true" width="100%" height="" ratio="1:1" :zoom="1" />"`;
exports[`pdfPlugin > should work 2`] = `"<PDFViewer src="foo.pdf" :page="1" :zoom="50" width="100%" height="" ratio="" title="foo.pdf" /><PDFViewer src="foo.pdf" :page="1" no-toolbar :zoom="50" width="100%" height="" ratio="" title="foo.pdf" /><PDFViewer src="foo.pdf" :page="2" :zoom="50" width="100%" height="" ratio="" title="foo.pdf" /><PDFViewer src="foo.pdf" :page="2" no-toolbar :zoom="50" width="100%" height="" ratio="" title="foo.pdf" /><PDFViewer src="foo.pdf" :page="2" no-toolbar :zoom="1" width="100%" height="600px" ratio="" title="foo.pdf" /><PDFViewer src="foo.pdf" :page="2" no-toolbar :zoom="1" width="100%" height="" ratio="1:1" title="foo.pdf" />"`;

View File

@ -4,7 +4,7 @@ exports[`timeline > timelinePlugin() > should work 1`] = `
"<VPTimeline :card="undefined"><VPTimelineItem :card="undefined"><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem><VPTimelineItem :card="undefined"><template #title>这是标题
这也是标题</template><p>这是内容</p>
</VPTimelineItem></VPTimeline><VPTimeline horizontal card line="dashed"><VPTimelineItem time="q1" :card="undefined"><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem></VPTimeline><VPTimeline horizontal line="dashed" card><VPTimelineItem time="q1" :card="undefined"><template #title>这是标题</template><p>这是内容</p>
<ul>
<li>1</li>
<li>2</li>
@ -16,9 +16,9 @@ exports[`timeline > timelinePlugin() > should work 1`] = `
</li>
</ul>
</VPTimelineItem><VPTimelineItem time="q2" color="red"><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem></VPTimeline><VPTimeline :card="undefined" placement="right"><VPTimelineItem type="warning" icon="xxx" card><template #icon><VPIcon name="xxx"/></template><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem></VPTimeline><VPTimeline placement="right" :card="undefined"><VPTimelineItem icon="xxx" card type="warning"><template #icon><VPIcon name="xxx"/></template><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem><VPTimelineItem type="danger" line="dotted" :card="undefined"><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem></VPTimeline><VPTimeline :card="undefined" placement="between"><VPTimelineItem card placement="right"><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem></VPTimeline><VPTimeline placement="between" :card="undefined"><VPTimelineItem card placement="right"><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem><VPTimelineItem card placement="left"><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem><VPTimelineItem :card="undefined"><template #title>这是标题</template><p>这是内容</p>
</VPTimelineItem></VPTimeline>"

View File

@ -1,10 +1,10 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`youtubePlugin > should not work 1`] = `
"<VideoYoutube src="https://www.youtube.com/embed//?" width="100%" height="" ratio="" title="undefined" /><p>@[youtube]xxx</p>
"<VideoYoutube src="https://www.youtube.com/embed//?" width="100%" /><p>@[youtube]xxx</p>
<p>@[ youtube]123456</p>
<p>@<a href="123456"> youtube</a></p>
"
`;
exports[`youtubePlugin > should work 1`] = `"<VideoYoutube src="https://www.youtube.com/embed//123456?" width="100%" height="" ratio="" title="undefined" /><VideoYoutube src="https://www.youtube.com/embed//123456?autoplay=1&loop=1" width="100%" height="" ratio="" title="test" /><VideoYoutube src="https://www.youtube.com/embed//123456?autoplay=1&start=40&end=80" width="100%" height="" ratio="" title="undefined" /><VideoYoutube src="https://www.youtube.com/embed//123456?" width="100%" height="600px" ratio="" title="undefined" /><VideoYoutube src="https://www.youtube.com/embed//123456?" width="100%" height="" ratio="16:9" title="undefined" />"`;
exports[`youtubePlugin > should work 1`] = `"<VideoYoutube src="https://www.youtube.com/embed//123456?" width="100%" /><VideoYoutube src="https://www.youtube.com/embed//123456?autoplay=1&loop=1" width="100%" title="test" /><VideoYoutube src="https://www.youtube.com/embed//123456?autoplay=1&start=40&end=80" width="100%" /><VideoYoutube src="https://www.youtube.com/embed//123456?" width="100%" height="600px" /><VideoYoutube src="https://www.youtube.com/embed//123456?" width="100%" ratio="16:9" />"`;

View File

@ -59,7 +59,7 @@ const a = 1
${FENCE}
:::
::: go-repl#editable
::: go-repl editable
${FENCE}go
const a = 1
${FENCE}
@ -71,7 +71,7 @@ const a = 1
${FENCE}
:::
::: kotlin-repl#editable
::: kotlin-repl editable
${FENCE}kotlin
const a = 1
${FENCE}
@ -83,19 +83,19 @@ const a = 1
${FENCE}
:::
::: rust-repl#editable
::: rust-repl editable
${FENCE}rust
const a = 1
${FENCE}
:::
::: rust-repl title
::: rust-repl title="title"
${FENCE}rust
const a = 1
${FENCE}
:::
::: rust-repl#editable title
::: rust-repl editable title="title"
${FENCE}rust
const a = 1
${FENCE}

View File

@ -3,9 +3,9 @@ import { resolveAttrs } from '../src/node/utils/resolveAttrs.js'
describe('resolveAttrs(info)', () => {
it('should resolve attrs', () => {
expect(resolveAttrs('')).toMatchObject({ rawAttrs: '', attrs: {} })
expect(resolveAttrs('')).toEqual({ rawAttrs: '', attrs: {} })
expect(resolveAttrs('a="1"')).toMatchObject({
expect(resolveAttrs('a="1"')).toEqual({
rawAttrs: 'a="1"',
attrs: { a: '1' },
})
@ -15,21 +15,21 @@ describe('resolveAttrs(info)', () => {
attrs: { a: '1', b: '2' },
})
expect(resolveAttrs('a="1" b="2" c')).toMatchObject({
expect(resolveAttrs('a="1" b="2" c')).toEqual({
rawAttrs: 'a="1" b="2" c',
attrs: { a: '1', b: '2', c: true },
})
expect(resolveAttrs('a b="true" c="false"')).toMatchObject({
expect(resolveAttrs('a b="true" c="false"')).toEqual({
rawAttrs: 'a b="true" c="false"',
attrs: { a: true, b: true, c: false },
})
})
it('should resolve attrs with include `-`', () => {
expect(resolveAttrs('foo-bar="1" fizz-buzz')).toMatchObject({
expect(resolveAttrs('foo-bar="1" fizz-buzz')).toEqual({
rawAttrs: 'foo-bar="1" fizz-buzz',
attrs: { 'fooBar': '1', 'fizzBuzz': true, 'foo-bar': '1', 'fizz-buzz': true },
attrs: { fooBar: '1', fizzBuzz: true },
})
})
})

View File

@ -0,0 +1,47 @@
import { describe, expect, it } from 'vitest'
import { stringifyAttrs } from '../src/node/utils/stringifyAttrs.js'
describe('stringifyAttrs', () => {
it('should handle empty attributes', () => {
expect(stringifyAttrs({})).toBe('')
})
it('should handle single attribute', () => {
expect(stringifyAttrs({ id: 'test' })).toBe(' id="test"')
})
it('should handle multiple attributes', () => {
expect(stringifyAttrs({ id: 'test', class: 'my-class' })).toBe(' id="test" class="my-class"')
})
it('should handle boolean attributes', () => {
expect(stringifyAttrs({ disabled: true, readonly: false, checked: 'true', selected: 'false' })).toBe(' disabled checked')
})
it('should handle null and undefined values', () => {
expect(stringifyAttrs({ id: null, class: undefined })).toBe('')
expect(stringifyAttrs({ id: null, class: undefined, foo: 'undefined', bar: 'null' }, true)).toBe(' :id="null" :class="undefined" :foo="undefined" :bar="null"')
})
it('should handle mixed attribute types', () => {
expect(stringifyAttrs({ id: 'test', disabled: true, title: 'hello' })).toBe(' id="test" disabled title="hello"')
})
it('should ignore prototype properties', () => {
const attrs = Object.create({ prototypeProp: 'value' })
attrs.id = 'test'
expect(stringifyAttrs(attrs)).toBe(' id="test"')
})
it('should handle number attributes', () => {
expect(stringifyAttrs({ id: 1, class: 2 })).toBe(' :id="1" :class="2"')
})
it('should handle like json string values', () => {
expect(stringifyAttrs({ id: '{ "foo": "bar", baz: 1 }', class: '["a", "b"]' })).toBe(' :id="{ \'foo\': \'bar\', baz: 1 }" :class="[\'a\', \'b\']"')
})
it('should handle kebabCase keys', () => {
expect(stringifyAttrs({ 'data-foo': 'bar', 'data-baz': 1, 'fooBaz': 'bar' })).toBe(' data-foo="bar" :data-baz="1" foo-baz="bar"')
})
})

View File

@ -14,12 +14,12 @@ interface MessageData {
const props = withDefaults(defineProps<{
feature: string
past?: string
future?: string
past?: number
future?: number
meta?: string
}>(), {
past: '2',
future: '1',
past: 2,
future: 1,
meta: '',
})

View File

@ -1,5 +1,6 @@
import type { Markdown } from 'vuepress/markdown'
import { resolveAttrs } from '.././utils/resolveAttrs.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { createContainerPlugin } from './createContainer.js'
interface CardAttrs {
@ -21,8 +22,7 @@ export function cardPlugin(md: Markdown) {
createContainerPlugin(md, 'card', {
before(info) {
const { attrs } = resolveAttrs<CardAttrs>(info)
const { title, icon } = attrs
return `<VPCard${title ? ` title="${title}"` : ''}${icon ? ` icon="${icon}"` : ''}>`
return `<VPCard${stringifyAttrs(attrs)}>`
},
after: () => '</VPCard>',
})
@ -55,13 +55,12 @@ export function cardPlugin(md: Markdown) {
createContainerPlugin(md, 'card-masonry', {
before: (info) => {
const { attrs } = resolveAttrs<CardMasonryAttrs>(info)
let cols!: string | number
if (attrs.cols) {
cols = attrs.cols[0] === '{' ? attrs.cols : Number.parseInt(`${attrs.cols}`)
}
const gap = Number.parseInt(`${attrs.gap}`)
if (attrs.cols)
attrs.cols = attrs.cols[0] === '{' ? attrs.cols : Number.parseInt(`${attrs.cols}`)
if (attrs.gap)
attrs.gap = Number(attrs.gap)
return `<VPCardMasonry${cols ? ` :cols="${cols}"` : ''}${gap >= 0 ? ` :gap="${gap}"` : ''}>`
return `<VPCardMasonry${stringifyAttrs(attrs)}>`
},
after: () => '</VPCardMasonry>',
})

View File

@ -8,7 +8,8 @@
*/
import type Token from 'markdown-it/lib/token.mjs'
import type { Markdown } from 'vuepress/markdown'
import { resolveAttrs } from '.././utils/resolveAttrs.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { createContainerPlugin } from './createContainer.js'
interface CollapseMeta {
@ -28,14 +29,14 @@ export function collapsePlugin(md: Markdown): void {
const idx = parseCollapse(tokens, index, attrs)
const { accordion } = attrs
return `<VPCollapse${accordion ? ' accordion' : ''}${idx !== undefined ? ` :index="${idx}"` : ''}>`
return `<VPCollapse${stringifyAttrs({ accordion, index: idx })}>`
},
after: () => `</VPCollapse>`,
})
md.renderer.rules.collapse_item_open = (tokens, idx) => {
const token = tokens[idx]
const { expand, index } = token.meta as CollapseItemMeta
return `<VPCollapseItem${expand ? ' expand' : ''}${` :index="${index}"`}>`
return `<VPCollapseItem${stringifyAttrs({ expand, index })}>`
}
md.renderer.rules.collapse_item_close = () => '</VPCollapseItem>'
md.renderer.rules.collapse_item_title_open = () => '<template #title>'

View File

@ -18,10 +18,10 @@ export function createContainerPlugin(
const token = tokens[index]
const info = token.info.trim().slice(type.length).trim() || ''
if (token.nesting === 1) {
return before?.(info, tokens, index, options, env) || `<div class="custom-container ${type}">`
return before?.(info, tokens, index, options, env) ?? `<div class="custom-container ${type}">`
}
else {
return after?.(info, tokens, index, options, env) || '</div>'
return after?.(info, tokens, index, options, env) ?? '</div>'
}
}

View File

@ -1,32 +1,16 @@
import type markdownIt from 'markdown-it'
import type Token from 'markdown-it/lib/token.mjs'
import type { App } from 'vuepress/core'
import type { ReplEditorData, ReplOptions } from '../../shared/index.js'
import { promises as fs } from 'node:fs'
import { resolveModule } from 'local-pkg'
import container from 'markdown-it-container'
import { colors, logger, path } from 'vuepress/utils'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { createContainerPlugin } from './createContainer.js'
const RE_INFO = /^(#editable)?(.*)$/
function createReplContainer(md: markdownIt, lang: string) {
const type = `${lang}-repl`
const validate = (info: string): boolean => info.trim().startsWith(type)
const render = (tokens: Token[], index: number): string => {
const token = tokens[index]
const info = token.info.trim().slice(type.length).trim() || ''
// :::lang-repl#editable title
const [, editable, title] = info.match(RE_INFO)!
if (token.nesting === 1)
return `<CodeRepl ${editable ? 'editable' : ''} title="${title || `${lang} playground`}">`
else
return '</CodeRepl>'
}
md.use(container, type, { validate, render })
interface CodeReplMeta {
editable?: boolean
title?: string
}
export async function langReplPlugin(app: App, md: markdownIt, {
@ -35,15 +19,24 @@ export async function langReplPlugin(app: App, md: markdownIt, {
kotlin = false,
rust = false,
}: ReplOptions) {
if (kotlin) {
createReplContainer(md, 'kotlin')
}
if (go) {
createReplContainer(md, 'go')
}
if (rust) {
createReplContainer(md, 'rust')
}
const container = (lang: string): void => createContainerPlugin(md, `${lang}-repl`, {
before(info) {
const { attrs } = resolveAttrs<CodeReplMeta>(info)
const { editable, title } = attrs
return `<CodeRepl${stringifyAttrs({ editable, title: title || `${lang} playground` })}>`
},
after: () => '</CodeRepl>',
})
if (kotlin)
container('kotlin')
if (go)
container('go')
if (rust)
container('rust')
theme ??= { light: 'github-light', dark: 'github-dark' }
const data: ReplEditorData = { grammars: {} } as ReplEditorData

View File

@ -22,198 +22,41 @@
* ```
* :::
*/
import type Token from 'markdown-it/lib/token.mjs'
import type { Markdown } from 'vuepress/markdown'
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
import type { NpmToOptions, NpmToPackageManager } from '../../shared/index.js'
import type { CommandConfig, CommandConfigItem } from './npmToPreset.js'
import { isArray } from '@vuepress/helper'
import container from 'markdown-it-container'
import { colors } from 'vuepress/utils'
import { cleanMarkdownEnv } from '../utils/cleanMarkdownEnv.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
type PackageCommand = 'install' | 'add' | 'remove' | 'run' | 'create' | 'init' | 'npx' | 'ci'
interface CommandConfigItem {
cli: string
flags?: Record<string, string>
}
type CommandConfig = Record<Exclude<NpmToPackageManager, 'npm'>, CommandConfigItem | false>
type CommandConfigs = Record<PackageCommand, { pattern: RegExp } & CommandConfig>
const ALLOW_LIST = ['npm', 'pnpm', 'yarn', 'bun', 'deno'] as const
const BOOL_FLAGS: string[] = ['--no-save', '-B', '--save-bundle', '--save-dev', '-D', '--save-prod', '-P', '--save-peer', '-O', '--save-optional', '-E', '--save-exact', '-y', '--yes', '-g', '--global']
const DEFAULT_TABS: NpmToPackageManager[] = ['npm', 'pnpm', 'yarn']
const MANAGERS_CONFIG: CommandConfigs = {
install: {
pattern: /(?:^|\s)npm\s+(?:install|i)$/,
pnpm: { cli: 'pnpm install' },
yarn: { cli: 'yarn' },
bun: { cli: 'bun install' },
deno: { cli: 'deno install' },
},
add: {
pattern: /(?:^|\s)npm\s+(?:install|i|add)(?:\s|$)/,
pnpm: {
cli: 'pnpm add',
flags: {
'--no-save': '', // unsupported
'-B': '', // unsupported
'--save-bundle': '', // unsupported
},
},
yarn: {
cli: 'yarn add',
flags: {
'--save-dev': '--dev',
'--save-prod': '--prod',
'-P': '', // in npm, `-P` same as `--save-prod`. but in yarn, `-P` same as `--peer`
'--save-peer': '--peer',
'--save-optional': '--optional',
'--no-save': '', // unsupported
'--save-exact': '--exact',
'-B': '', // unsupported
'--save-bundle': '', // unsupported
},
},
bun: {
cli: 'bun add',
flags: {
'--save-dev': '--development',
'-P': '', // it's default
'--save-prod': '', // it's default
'--save-peer': '', // unsupported
'-O': '--optional',
'--save-optional': '--optional',
'--no-save': '', // unsupported
'--save-exact': '--exact',
'-B': '', // unsupported
'--save-bundle': '', // unsupported
},
},
deno: {
cli: 'deno add',
flags: {
'-g': '', // unsupported
'--global': '', // unsupported
'--save-dev': '--dev',
'-P': '', // unsupported
'--save-prod': '', // unsupported
'--save-peer': '', // unsupported
'-O': '', // unsupported
'--save-optional': '', // unsupported
'--no-save': '', // unsupported
'-E': '', // unsupported
'--save-exact': '', // unsupported
'-B': '', // unsupported
'--save-bundle': '', // unsupported
},
},
},
run: {
pattern: /(?:^|\s)npm\s+(?:run|run-script|rum|urn)(?:\s|$)/,
pnpm: {
cli: 'pnpm',
flags: {
'-w': '-F', // same as `--workspace`
'--workspace': '--filter', // filter by workspaces
'--': '', // scripts flags
},
},
yarn: {
cli: 'yarn',
flags: {
'-w': '', // unsupported
'--workspace': '', // unsupported
},
},
bun: {
cli: 'bun run',
flags: {
'-w': '--filter', // same as `--workspace`
'--workspace': '--filter', // filter by workspaces
},
},
deno: {
cli: 'deno run',
flags: {
'-w': '', // unsupported
'--workspace': '', // unsupported
},
},
},
create: {
pattern: /(?:^|\s)npm\s+create\s/,
pnpm: { cli: 'pnpm create', flags: { '-y': '', '--yes': '' } },
yarn: { cli: 'yarn create', flags: { '-y': '', '--yes': '' } },
bun: { cli: 'bun create', flags: { '-y': '', '--yes': '' } },
deno: { cli: 'deno run -A ', flags: { '-y': '', '--yes': '' } },
},
init: {
pattern: /(?:^|\s)npm\s+init/,
pnpm: { cli: 'pnpm init', flags: { '-y': '', '--yes': '' } },
yarn: { cli: 'yarn init', flags: { '-y': '', '--yes': '' } },
bun: { cli: 'bun init', flags: { '-y': '', '--yes': '' } },
deno: { cli: 'deno init', flags: { '-y': '', '--yes': '' } },
},
npx: {
pattern: /(?:^|\s)npx\s+/,
pnpm: { cli: 'pnpm dlx' },
yarn: { cli: 'yarn dlx' },
bun: { cli: 'bunx' },
deno: { cli: 'deno run -A' },
},
remove: {
pattern: /(?:^|\s)npm\s+(?:uninstall|r|rm|remove|unlink|un)(?:\s|$)/,
pnpm: {
cli: 'pnpm remove',
flags: { '--no-save': '', '--save': '', '-S': '' },
},
yarn: {
cli: 'yarn remove',
flags: { '--save-dev': '--dev', '--save': '', '-S': '', '-g': '', '--global': '' },
},
bun: {
cli: 'bun remove',
flags: { '--save-dev': '--development', '--save': '', '-S': '', '-g': '', '--global': '' },
},
deno: {
cli: 'deno uninstall',
flags: { '--save-dev': '--dev', '--save': '', '-S': '' },
},
},
ci: {
pattern: /(?:^|\s)npm\s+ci$/,
pnpm: { cli: 'pnpm install --frozen-lockfile' },
yarn: { cli: 'yarn install --immutable' },
bun: { cli: 'bun install --frozen-lockfile' },
deno: { cli: 'deno install --frozen' },
},
}
import { createContainerPlugin } from './createContainer.js'
import { ALLOW_LIST, BOOL_FLAGS, DEFAULT_TABS, MANAGERS_CONFIG } from './npmToPreset.js'
export function npmToPlugins(md: Markdown, options: NpmToOptions = {}): void {
const type = 'npm-to'
const opt = isArray(options) ? { tabs: options } : options
const defaultTabs = opt.tabs?.length ? opt.tabs : DEFAULT_TABS
const render = (tokens: Token[], idx: number): string => {
const { attrs } = resolveAttrs(tokens[idx].info.trim().slice(type.length))
const tabs = (attrs.tabs ? attrs.tabs.split(/,\s*/) : defaultTabs) as NpmToPackageManager[]
if (tokens[idx].nesting === 1) {
createContainerPlugin(md, 'npm-to', {
before: (info, tokens, idx, _opt, env: MarkdownEnv) => {
const { attrs } = resolveAttrs<{ tabs?: string }>(info)
const tabs = (attrs.tabs ? attrs.tabs.split(/,\s*/) : defaultTabs) as NpmToPackageManager[]
const token = tokens[idx + 1]
const info = token.info.trim()
if (token.type === 'fence') {
const content = token.content
token.hidden = true
token.type = 'text'
token.content = ''
const lines = content.split(/(\n|\s*&&\s*)/)
return md.render(resolveNpmTo(lines, info, idx, tabs), {})
return md.render(
resolveNpmTo(lines, token.info.trim(), idx, tabs),
cleanMarkdownEnv(env),
)
}
}
return ''
}
md.use(container, type, { render })
console.warn(`${colors.yellow('[vuepress-plugin-md-power]')} Invalid npm-to container in ${colors.gray(env.filePathRelative || env.filePath)}`)
return ''
},
after: () => '',
})
}
function resolveNpmTo(lines: string[], info: string, idx: number, tabs: NpmToPackageManager[]): string {
@ -279,6 +122,7 @@ interface LineParsed {
}
const LINE_REG = /(.*)(npm|npx)\s+(.*)/
export function parseLine(line: string): false | LineParsed {
const match = line.match(LINE_REG)
if (!match)

View File

@ -0,0 +1,165 @@
import type { NpmToPackageManager } from '../../shared/index.js'
export type PackageCommand = 'install' | 'add' | 'remove' | 'run' | 'create' | 'init' | 'npx' | 'ci'
export interface CommandConfigItem {
cli: string
flags?: Record<string, string>
}
export type CommandConfig = Record<Exclude<NpmToPackageManager, 'npm'>, CommandConfigItem | false>
export type CommandConfigs = Record<PackageCommand, { pattern: RegExp } & CommandConfig>
export const ALLOW_LIST = ['npm', 'pnpm', 'yarn', 'bun', 'deno'] as const
export const BOOL_FLAGS: string[] = ['--no-save', '-B', '--save-bundle', '--save-dev', '-D', '--save-prod', '-P', '--save-peer', '-O', '--save-optional', '-E', '--save-exact', '-y', '--yes', '-g', '--global']
export const DEFAULT_TABS: NpmToPackageManager[] = ['npm', 'pnpm', 'yarn']
export const MANAGERS_CONFIG: CommandConfigs = {
install: {
pattern: /(?:^|\s)npm\s+(?:install|i)$/,
pnpm: { cli: 'pnpm install' },
yarn: { cli: 'yarn' },
bun: { cli: 'bun install' },
deno: { cli: 'deno install' },
},
add: {
pattern: /(?:^|\s)npm\s+(?:install|i|add)(?:\s|$)/,
pnpm: {
cli: 'pnpm add',
flags: {
'--no-save': '', // unsupported
'-B': '', // unsupported
'--save-bundle': '', // unsupported
},
},
yarn: {
cli: 'yarn add',
flags: {
'--save-dev': '--dev',
'--save-prod': '--prod',
'-P': '', // in npm, `-P` same as `--save-prod`. but in yarn, `-P` same as `--peer`
'--save-peer': '--peer',
'--save-optional': '--optional',
'--no-save': '', // unsupported
'--save-exact': '--exact',
'-B': '', // unsupported
'--save-bundle': '', // unsupported
},
},
bun: {
cli: 'bun add',
flags: {
'--save-dev': '--development',
'-P': '', // it's default
'--save-prod': '', // it's default
'--save-peer': '', // unsupported
'-O': '--optional',
'--save-optional': '--optional',
'--no-save': '', // unsupported
'--save-exact': '--exact',
'-B': '', // unsupported
'--save-bundle': '', // unsupported
},
},
deno: {
cli: 'deno add',
flags: {
'-g': '', // unsupported
'--global': '', // unsupported
'--save-dev': '--dev',
'-P': '', // unsupported
'--save-prod': '', // unsupported
'--save-peer': '', // unsupported
'-O': '', // unsupported
'--save-optional': '', // unsupported
'--no-save': '', // unsupported
'-E': '', // unsupported
'--save-exact': '', // unsupported
'-B': '', // unsupported
'--save-bundle': '', // unsupported
},
},
},
run: {
pattern: /(?:^|\s)npm\s+(?:run|run-script|rum|urn)(?:\s|$)/,
pnpm: {
cli: 'pnpm',
flags: {
'-w': '-F', // same as `--workspace`
'--workspace': '--filter', // filter by workspaces
'--': '', // scripts flags
},
},
yarn: {
cli: 'yarn',
flags: {
'-w': '', // unsupported
'--workspace': '', // unsupported
},
},
bun: {
cli: 'bun run',
flags: {
'-w': '--filter', // same as `--workspace`
'--workspace': '--filter', // filter by workspaces
},
},
deno: {
cli: 'deno run',
flags: {
'-w': '', // unsupported
'--workspace': '', // unsupported
},
},
},
create: {
pattern: /(?:^|\s)npm\s+create\s/,
pnpm: { cli: 'pnpm create', flags: { '-y': '', '--yes': '' } },
yarn: { cli: 'yarn create', flags: { '-y': '', '--yes': '' } },
bun: { cli: 'bun create', flags: { '-y': '', '--yes': '' } },
deno: { cli: 'deno run -A ', flags: { '-y': '', '--yes': '' } },
},
init: {
pattern: /(?:^|\s)npm\s+init/,
pnpm: { cli: 'pnpm init', flags: { '-y': '', '--yes': '' } },
yarn: { cli: 'yarn init', flags: { '-y': '', '--yes': '' } },
bun: { cli: 'bun init', flags: { '-y': '', '--yes': '' } },
deno: { cli: 'deno init', flags: { '-y': '', '--yes': '' } },
},
npx: {
pattern: /(?:^|\s)npx\s+/,
pnpm: { cli: 'pnpm dlx' },
yarn: { cli: 'yarn dlx' },
bun: { cli: 'bunx' },
deno: { cli: 'deno run -A' },
},
remove: {
pattern: /(?:^|\s)npm\s+(?:uninstall|r|rm|remove|unlink|un)(?:\s|$)/,
pnpm: {
cli: 'pnpm remove',
flags: { '--no-save': '', '--save': '', '-S': '' },
},
yarn: {
cli: 'yarn remove',
flags: { '--save-dev': '--dev', '--save': '', '-S': '', '-g': '', '--global': '' },
},
bun: {
cli: 'bun remove',
flags: { '--save-dev': '--development', '--save': '', '-S': '', '-g': '', '--global': '' },
},
deno: {
cli: 'deno uninstall',
flags: { '--save-dev': '--dev', '--save': '', '-S': '' },
},
},
ci: {
pattern: /(?:^|\s)npm\s+ci$/,
pnpm: { cli: 'pnpm install --frozen-lockfile' },
yarn: { cli: 'yarn install --immutable' },
bun: { cli: 'bun install --frozen-lockfile' },
deno: { cli: 'deno install --frozen' },
},
}

View File

@ -16,6 +16,7 @@ import type Token from 'markdown-it/lib/token.mjs'
import type { Markdown } from 'vuepress/markdown'
import { isEmptyObject } from '@pengzhanbo/utils'
import { resolveAttrs } from '.././utils/resolveAttrs.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { createContainerPlugin } from './createContainer.js'
export interface TimelineAttrs {
@ -45,36 +46,18 @@ export function timelinePlugin(md: Markdown) {
parseTimeline(tokens, index)
const { attrs } = resolveAttrs<TimelineAttrs>(info)
const { horizontal, card, placement, line } = attrs
return `<VPTimeline${
horizontal ? ' horizontal' : ''
}${
card ? ' card' : ' :card="undefined"'
}${
placement ? ` placement="${placement}"` : ''
}${
line ? ` line="${line}"` : ''
}>`
attrs.card ??= undefined
return `<VPTimeline${stringifyAttrs(attrs, true)}>`
},
after: () => '</VPTimeline>',
})
md.renderer.rules.timeline_item_open = (tokens, idx) => {
const token = tokens[idx]
const { time, type, icon, color, line, card, placement } = token.meta as TimelineItemMeta
return `<VPTimelineItem${
time ? ` time="${time}"` : ''
}${
type ? ` type="${type}"` : ''
}${
color ? ` color="${color}"` : ''
}${
line ? ` line="${line}"` : ''
}${icon ? ` icon="${icon}"` : ''}${
card === 'true' ? ' card' : card === 'false' ? '' : ' :card="undefined"'
}${
placement ? ` placement="${placement}"` : ''
}>${icon ? `<template #icon><VPIcon name="${icon}"/></template>` : ''}`
const attrs = token.meta as TimelineItemMeta
attrs.card ??= undefined
const icon = attrs.icon
return `<VPTimelineItem${stringifyAttrs(attrs, true)}>${icon ? `<template #icon><VPIcon name="${icon}"/></template>` : ''}`
}
md.renderer.rules.timeline_item_close = () => '</VPTimelineItem>'

View File

@ -1,6 +1,7 @@
import type { App } from 'vuepress'
import type { Markdown } from 'vuepress/markdown'
import type { DemoContainerRender, DemoFile, DemoMeta, MarkdownDemoEnv } from '../../shared/demo.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { findFile, readFileSync } from './supports/file.js'
export function markdownEmbed(
@ -23,19 +24,19 @@ export function markdownEmbed(
env.demoFiles.push(demo)
}
return `<VPDemoBasic type="markdown"${title ? ` title="${title}"` : ''}${desc ? ` desc="${desc}"` : ''}${expanded ? ' expanded' : ''}>
return `<VPDemoBasic${stringifyAttrs({ type: 'markdown', title, desc, expanded })}>
${md.render(code, { filepath: env.filePath, filepathRelative: env.filePathRelative })}
<template #code>
${md.render(`\`\`\`md ${codeSetting}\n${code}\n\`\`\``, {})}
</template>
</VPDemoBasic>`
</VPDemoBasic$>`
}
export const markdownContainerRender: DemoContainerRender = {
before(app, md, env, meta, codeMap) {
const { title, desc, expanded = false } = meta
const code = codeMap.md || ''
return `<VPDemoBasic type="markdown"${title ? ` title="${title}"` : ''}${desc ? ` desc="${desc}"` : ''}${expanded ? ' expanded' : ''}>
return `<VPDemoBasic${stringifyAttrs({ type: 'markdown', title, desc, expanded })}>
${md.render(code, { filepath: env.filePath, filepathRelative: env.filePathRelative })}
<template #code>`
},

View File

@ -3,6 +3,7 @@ import type { Markdown } from 'vuepress/markdown'
import type { DemoContainerRender, DemoFile, DemoMeta, MarkdownDemoEnv } from '../../shared/demo.js'
import fs from 'node:fs'
import path from 'node:path'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { compileScript, compileStyle } from './supports/compiler.js'
import { findFile, readFileSync, writeFileSync } from './supports/file.js'
import { insertSetupScript } from './supports/insertScript.js'
@ -126,9 +127,9 @@ export function normalEmbed(
insertSetupScript({ ...demo, path: output }, env)
}
return `<VPDemoNormal :config="${name}"${title ? ` title="${title}"` : ''}${desc ? ` desc="${desc}"` : ''}${expanded ? ' expanded' : ''}>
return `<VPDemoNormal${stringifyAttrs({ config: name, title, desc, expanded })}>
${codeToHtml(md, source, codeSetting)}
</VPDemoNormal>`
</VPDemoNormal$>`
}
export const normalContainerRender: DemoContainerRender = {
@ -148,7 +149,7 @@ export const normalContainerRender: DemoContainerRender = {
const source = parseContainerCode(codeMap)
compileCode(source, output)
return `<VPDemoNormal :config="${name}"${title ? ` title="${title}"` : ''}${desc ? ` desc="${desc}"` : ''}${expanded ? ' expanded' : ''}>`
return `<VPDemoNormal${stringifyAttrs({ config: name, title, desc, expanded })}>`
},
after: () => '</VPDemoNormal>',

View File

@ -1,26 +0,0 @@
# demo 嵌入
demo 目前主要聚焦于实现 vue component demo不考虑其他场景。
此功能提供两种方式来嵌入 demo。
方式一 嵌入语法:
@[demo vue](/xxx.vue)
方式二 容器语法:
::: demo vue
```vue
<script setup>
</script>
<template>
<div>1</div>
</template>
<style>
</style>
``
:::

View File

@ -2,6 +2,7 @@ import type { App } from 'vuepress'
import type { Markdown } from 'vuepress/markdown'
import type { DemoContainerRender, DemoFile, DemoMeta, MarkdownDemoEnv } from '../../shared/demo.js'
import path from 'node:path'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { findFile, readFileSync, writeFileSync } from './supports/file.js'
import { insertSetupScript } from './supports/insertScript.js'
@ -30,12 +31,12 @@ export function vueEmbed(
insertSetupScript(demo, env)
}
return `<VPDemoBasic type="vue"${title ? ` title="${title}"` : ''}${desc ? ` desc="${desc}"` : ''}${expanded ? ' expanded' : ''}>
return `<VPDemoBasic${stringifyAttrs({ type: 'vue', title, desc, expanded })}>
<${name} />
<template #code>
${md.render(`\`\`\`${ext}${codeSetting}\n${code}\n\`\`\``, {})}
</template>
</VPDemoBasic>`
</VPDemoBasic$>`
}
const target = 'md-power/demo/vue'
@ -101,7 +102,7 @@ export const vueContainerRender: DemoContainerRender = {
}
}
return `<VPDemoBasic type="vue"${title ? ` title="${title}"` : ''}${desc ? ` desc="${desc}"` : ''}${expanded ? ' expanded' : ''}>
return `<VPDemoBasic${stringifyAttrs({ type: 'vue', title, desc, expanded })}>
<${componentName} />
<template #code>\n`
},

View File

@ -1,6 +1,16 @@
import type { PluginWithOptions } from 'markdown-it'
import type { RuleInline } from 'markdown-it/lib/parser_inline.mjs'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { stringifyAttrs } from '../../utils/stringifyAttrs.js'
interface AudioReaderTokenMeta {
src?: string
startTime?: number
endTime?: number
type?: string
volume?: number
title?: string
}
const audioReader: RuleInline = (state, silent) => {
const max = state.posMax
@ -52,28 +62,11 @@ const audioReader: RuleInline = (state, silent) => {
state.pos = labelStart
state.posMax = labelEnd
const info = state.src.slice(labelStart, labelEnd).trim()
const { attrs } = resolveAttrs(info)
const { attrs } = resolveAttrs<AudioReaderTokenMeta>(info)
const tokenOpen = state.push('audio_reader_open', 'AudioReader', 1)
tokenOpen.info = info
tokenOpen.attrs = [['src', href]]
if (attrs.startTime)
tokenOpen.attrs.push([':start-time', attrs.startTime])
if (attrs.endTime)
tokenOpen.attrs.push([':end-time', attrs.endTime])
if (attrs.type)
tokenOpen.attrs.push(['type', attrs.type])
if (attrs.volume)
tokenOpen.attrs.push([':volume', attrs.volume])
if (attrs.title)
state.push('text', '', 0).content = attrs.title
state.push('audio_reader_close', 'AudioReader', -1)
const token = state.push('audio_reader', 'AudioReader', 0)
token.info = info
token.meta = { src: href, ...attrs } as AudioReaderTokenMeta
}
state.pos = pos + 1
@ -81,5 +74,20 @@ const audioReader: RuleInline = (state, silent) => {
return true
}
export const audioReaderPlugin: PluginWithOptions<never> = md =>
export const audioReaderPlugin: PluginWithOptions<never> = (md) => {
md.renderer.rules.audio_reader = (tokens, idx) => {
const meta = (tokens[idx].meta ?? {}) as AudioReaderTokenMeta
if (meta.startTime)
meta.startTime = Number(meta.startTime)
if (meta.endTime)
meta.endTime = Number(meta.endTime)
if (meta.volume)
meta.volume = Number(meta.volume)
return `<AudioReader${stringifyAttrs(meta)} />`
}
md.inline.ruler.before('link', 'audio-reader', audioReader)
}

View File

@ -4,10 +4,10 @@
*/
import type { PluginWithOptions } from 'markdown-it'
import type MarkdownIt from 'markdown-it'
import type Token from 'markdown-it/lib/token.mjs'
import type { CanIUseMode, CanIUseOptions, CanIUseTokenMeta } from '../../shared/index.js'
import container from 'markdown-it-container'
import { createContainerPlugin } from '../container/createContainer.js'
import { nanoid } from '../utils/nanoid.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { createEmbedRuleBlock } from './createEmbedRuleBlock.js'
const UNDERLINE_RE = /_+/g
@ -51,27 +51,14 @@ export function legacyCaniuse(
const isMode = (mode: CanIUseMode): boolean => modeMap.includes(mode)
mode = isMode(mode) ? mode : modeMap[0]
const type = 'caniuse'
const validateReg = new RegExp(`^${type}`)
const validate = (info: string): boolean => {
return validateReg.test(info.trim())
}
const render = (tokens: Token[], index: number): string => {
const token = tokens[index]
if (token.nesting === 1) {
const info = token.info.trim().slice(type.length).trim() || ''
createContainerPlugin(md, 'caniuse', {
before: (info) => {
const feature = info.split(/\s+/)[0]
const versions = info.match(/\{(.*)\}/)?.[1] || ''
return feature ? resolveCanIUse({ feature, mode, versions }) : ''
}
else {
return ''
}
}
md.use(container, type, { validate, render })
},
after: () => '',
})
}
function resolveCanIUse({ feature, mode, versions }: CanIUseTokenMeta): string {
@ -92,7 +79,7 @@ function resolveCanIUse({ feature, mode, versions }: CanIUseTokenMeta): string {
const { past, future } = resolveVersions(versions)
const meta = nanoid()
return `<CanIUseViewer feature="${feature}" meta="${meta}" past="${past}" future="${future}" />`
return `<CanIUseViewer${stringifyAttrs({ feature, meta, past, future })} />`
}
function resolveVersions(versions: string): { past: number, future: number } {

View File

@ -7,6 +7,7 @@ import type { PluginWithOptions } from 'markdown-it'
import type { CodeSandboxTokenMeta } from '../../../shared/index.js'
import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { stringifyAttrs } from '../../utils/stringifyAttrs.js'
import { createEmbedRuleBlock } from '../createEmbedRuleBlock.js'
export const codeSandboxPlugin: PluginWithOptions<never> = (md) => {
@ -31,8 +32,6 @@ export const codeSandboxPlugin: PluginWithOptions<never> = (md) => {
filepath,
}
},
content({ title, height, width, user, id, type, filepath, console, navbar, layout }) {
return `<CodeSandboxViewer title="${title}" height="${height}" width="${width}" user="${user}" id="${id}" type="${type}" filepath="${filepath}" :console=${console} :navbar=${navbar} layout="${layout}" />`
},
content: meta => `<CodeSandboxViewer${stringifyAttrs(meta)} />`,
})
}

View File

@ -7,6 +7,7 @@ import type { PluginWithOptions } from 'markdown-it'
import type { CodepenTokenMeta } from '../../../shared/index.js'
import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { stringifyAttrs } from '../../utils/stringifyAttrs.js'
import { createEmbedRuleBlock } from '../createEmbedRuleBlock.js'
export const codepenPlugin: PluginWithOptions<never> = (md) => {
@ -14,20 +15,21 @@ export const codepenPlugin: PluginWithOptions<never> = (md) => {
type: 'codepen',
syntaxPattern: /^@\[codepen([^\]]*)\]\(([^)]*)\)/,
meta: ([, info, source]) => {
const { width, height, title, tab, ...rest } = resolveAttrs<CodepenTokenMeta>(info).attrs
const { width, height, title, tab, preview, editable, theme } = resolveAttrs<CodepenTokenMeta>(info).attrs
const [user, slash] = source.split('/')
return {
title: title || 'Code Pen',
tab: tab || 'result',
width: width ? parseRect(width) : '100%',
height: height ? parseRect(height) : '400px',
user,
slash,
title: title || 'Code Pen',
tab: tab || 'result',
...rest,
preview,
editable,
theme,
}
},
content: ({ title, height, width, user, slash, preview, editable, tab, theme }) =>
`<CodePenViewer user="${user}" slash="${slash}" title="${title}"${preview ? ' preview' : ''}${editable ? ' editable' : ''} tab="${tab}"${theme ? ` theme="${theme}"` : ''} width="${width}" height="${height}" />`,
content: meta => `<CodePenViewer${stringifyAttrs(meta)} />`,
})
}

View File

@ -6,6 +6,7 @@ import type { PluginWithOptions } from 'markdown-it'
import type { JSFiddleTokenMeta } from '../../../shared/index.js'
import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { stringifyAttrs } from '../../utils/stringifyAttrs.js'
import { createEmbedRuleBlock } from '../createEmbedRuleBlock.js'
export const jsfiddlePlugin: PluginWithOptions<never> = (md) => {
@ -24,7 +25,6 @@ export const jsfiddlePlugin: PluginWithOptions<never> = (md) => {
theme,
}
},
content: ({ title, height, width, source, tab, theme }) =>
`<JSFiddleViewer source="${source}" title="${title}" tab="${tab}" width="${width}" height="${height}"${theme ? ` theme="${theme}"` : ''} />`,
content: meta => `<JSFiddleViewer${stringifyAttrs(meta)} />`,
})
}

View File

@ -7,6 +7,7 @@ import type { PluginWithOptions } from 'markdown-it'
import type { ReplitTokenMeta } from '../../../shared/index.js'
import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { stringifyAttrs } from '../../utils/stringifyAttrs.js'
import { createEmbedRuleBlock } from '../createEmbedRuleBlock.js'
export const replitPlugin: PluginWithOptions<never> = (md) => {
@ -23,8 +24,6 @@ export const replitPlugin: PluginWithOptions<never> = (md) => {
theme: attrs.theme || '',
}
},
content({ title, height, width, source, theme }) {
return `<ReplitViewer title="${title || ''}" height="${height}" width="${width}" source="${source}" theme="${theme}" />`
},
content: meta => `<ReplitViewer${stringifyAttrs(meta)} />`,
})
}

View File

@ -8,6 +8,7 @@ import type { PDFTokenMeta } from '../../shared/index.js'
import { path } from 'vuepress/utils'
import { parseRect } from '../utils/parseRect.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { stringifyAttrs } from '../utils/stringifyAttrs.js'
import { createEmbedRuleBlock } from './createEmbedRuleBlock.js'
export const pdfPlugin: PluginWithOptions<never> = (md) => {
@ -28,8 +29,6 @@ export const pdfPlugin: PluginWithOptions<never> = (md) => {
title: path.basename(src || ''),
}
},
content({ title, src, page, noToolbar, width, height, ratio, zoom }) {
return `<PDFViewer src="${src}" title="${title}" :page="${page}" :no-toolbar="${noToolbar}" width="${width}" height="${height}" ratio="${ratio}" :zoom="${zoom}" />`
},
content: meta => `<PDFViewer${stringifyAttrs(meta)} />`,
})
}

View File

@ -8,6 +8,7 @@ import { isPackageExists } from 'local-pkg'
import { colors } from 'vuepress/utils'
import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { stringifyAttrs } from '../../utils/stringifyAttrs.js'
import { createEmbedRuleBlock } from '../createEmbedRuleBlock.js'
const installed = {
@ -29,6 +30,8 @@ export const artPlayerPlugin: PluginWithOptions<never> = (md) => {
checkSupportType(attrs.type ?? url.split('.').pop())
return {
url,
type: attrs.type,
autoplay: attrs.autoplay ?? false,
muted: attrs.muted ?? attrs.autoplay ?? false,
autoMini: attrs.autoMini ?? false,
@ -36,28 +39,13 @@ export const artPlayerPlugin: PluginWithOptions<never> = (md) => {
volume: typeof attrs.volume !== 'undefined' ? Number(attrs.volume) : 0.75,
poster: attrs.poster,
width: attrs.width ? parseRect(attrs.width) : '100%',
height: attrs.height ? parseRect(attrs.height) : '',
ratio: attrs.ratio ? parseRect(`${attrs.ratio}`) : '',
type: attrs.type,
url,
height: attrs.height ? parseRect(attrs.height) : undefined,
ratio: attrs.ratio ? parseRect(`${attrs.ratio}`) : undefined,
}
},
content({ autoMini, autoplay, loop, muted, poster, url, type, volume, width, height, ratio }) {
return `<ArtPlayer src="${url}" fullscreen flip playback-rate aspect-ratio setting pip ${
loop ? ' loop' : ''
}${
type ? ` type="${type}"` : ''
}${
autoMini ? ' auto-min' : ''
}${autoplay ? ' autoplay' : ''}${
muted || autoplay ? ' muted' : ''
}${
poster ? ` poster="${poster}"` : ''
} :volume="${volume}" width="${width}"${
height ? ` height="${height}"` : ''
}${
ratio ? ` ratio="${ratio}"` : ''
}/>`
content({ url, ...meta }) {
meta.muted = meta.muted || meta.autoplay
return `<ArtPlayer src="${url}" fullscreen flip playback-rate aspect-ratio setting pip${stringifyAttrs(meta)}/>`
},
})
}

View File

@ -9,6 +9,7 @@ import type { BilibiliTokenMeta } from '../../../shared/index.js'
import { URLSearchParams } from 'node:url'
import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { stringifyAttrs } from '../../utils/stringifyAttrs.js'
import { timeToSeconds } from '../../utils/timeToSeconds.js'
import { createEmbedRuleBlock } from '../createEmbedRuleBlock.js'
@ -35,39 +36,35 @@ export const bilibiliPlugin: PluginWithOptions<never> = (md) => {
time: timeToSeconds(attrs.time),
title: attrs.title,
width: attrs.width ? parseRect(attrs.width) : '100%',
height: attrs.height ? parseRect(attrs.height) : '',
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
height: attrs.height ? parseRect(attrs.height) : undefined,
ratio: attrs.ratio ? parseRect(attrs.ratio) : undefined,
}
},
content(meta) {
const params = new URLSearchParams()
if (meta.bvid) {
if (meta.bvid)
params.set('bvid', meta.bvid)
}
if (meta.aid) {
if (meta.aid)
params.set('aid', meta.aid)
}
if (meta.cid) {
if (meta.cid)
params.set('cid', meta.cid)
}
if (meta.page) {
if (meta.page)
params.set('p', meta.page.toString())
}
if (meta.time) {
if (meta.time)
params.set('t', meta.time.toString())
}
params.set('autoplay', meta.autoplay ? '1' : '0')
params.set('high_quality', '1')
const source = `${BILIBILI_LINK}?${params.toString()}`
const { width, height, ratio, title } = meta
return `<VideoBilibili src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`
return `<VideoBilibili${stringifyAttrs({ src: source, width, height, ratio, title })} />`
},
})
}

View File

@ -6,6 +6,7 @@ import type { YoutubeTokenMeta } from '../../../shared/index.js'
import { URLSearchParams } from 'node:url'
import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { stringifyAttrs } from '../../utils/stringifyAttrs.js'
import { timeToSeconds } from '../../utils/timeToSeconds.js'
import { createEmbedRuleBlock } from '..//createEmbedRuleBlock.js'
@ -27,32 +28,29 @@ export const youtubePlugin: PluginWithOptions<never> = (md) => {
end: timeToSeconds(attrs.end),
title: attrs.title,
width: attrs.width ? parseRect(attrs.width) : '100%',
height: attrs.height ? parseRect(attrs.height) : '',
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
height: attrs.height ? parseRect(attrs.height) : undefined,
ratio: attrs.ratio ? parseRect(attrs.ratio) : undefined,
}
},
content(meta) {
const params = new URLSearchParams()
if (meta.autoplay) {
if (meta.autoplay)
params.set('autoplay', '1')
}
if (meta.loop) {
if (meta.loop)
params.set('loop', '1')
}
if (meta.start) {
if (meta.start)
params.set('start', meta.start.toString())
}
if (meta.end) {
if (meta.end)
params.set('end', meta.end.toString())
}
const source = `${YOUTUBE_LINK}/${meta.id}?${params.toString()}`
const { width, height, ratio, title } = meta
return `<VideoYoutube src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`
return `<VideoYoutube${stringifyAttrs({ src: source, width, height, ratio, title })} />`
},
})
}

View File

@ -167,7 +167,8 @@ export const annotationPlugin: PluginSimple = (md) => {
data.sources.map((source, i) => {
const annotation = data.rendered[i] ??= md.render(source, env)
return `<template #item-${i}>${annotation}</template>`
}).join('')}</Annotation>`
}).join('')
}</Annotation>`
}
md.inline.ruler.before('image', 'annotation_ref', annotationRef)

View File

@ -4,78 +4,76 @@
import type { PluginWithOptions } from 'markdown-it'
import type { RuleInline } from 'markdown-it/lib/parser_inline.mjs'
export const plotPlugin: PluginWithOptions<never> = md =>
md.inline.ruler.before('emphasis', 'plot', createTokenizer())
const plotDef: RuleInline = (state, silent) => {
let found = false
const max = state.posMax
const start = state.pos
function createTokenizer(): RuleInline {
return (state, silent) => {
let found = false
const max = state.posMax
const start = state.pos
if (
state.src.charCodeAt(start) !== 0x21
|| state.src.charCodeAt(start + 1) !== 0x21
) {
return false
}
const next = state.src.charCodeAt(start + 2)
// - !! xxx | !!!xxx
// ^ | ^
if (next === 0x20 || next === 0x21)
return false
/* istanbul ignore if -- @preserve */
if (silent)
return false
// - !!!!
if (max - start < 5)
return false
state.pos = start + 2
while (state.pos < max) {
if (state.src.charCodeAt(state.pos) === 0x21
&& state.src.charCodeAt(state.pos + 1) === 0x21) {
found = true
break
}
state.md.inline.skipToken(state)
}
if (
!found
|| start + 2 === state.pos
// - !!xxx !!
// ^
|| state.src.charCodeAt(state.pos - 1) === 0x20
) {
state.pos = start
return false
}
const content = state.src.slice(start + 2, state.pos)
// found!
state.posMax = state.pos
state.pos = start + 2
const open = state.push('plot_open', 'Plot', 1)
open.markup = '!!'
const text = state.push('text', '', 0)
text.content = content
const close = state.push('plot_close', 'Plot', -1)
close.markup = '!!'
state.pos = state.posMax + 2
state.posMax = max
return true
if (
state.src.charCodeAt(start) !== 0x21
|| state.src.charCodeAt(start + 1) !== 0x21
) {
return false
}
const next = state.src.charCodeAt(start + 2)
// - !! xxx | !!!xxx
// ^ | ^
if (next === 0x20 || next === 0x21)
return false
/* istanbul ignore if -- @preserve */
if (silent)
return false
// - !!!!
if (max - start < 5)
return false
state.pos = start + 2
while (state.pos < max) {
if (state.src.charCodeAt(state.pos) === 0x21
&& state.src.charCodeAt(state.pos + 1) === 0x21) {
found = true
break
}
state.md.inline.skipToken(state)
}
if (
!found
|| start + 2 === state.pos
// - !!xxx !!
// ^
|| state.src.charCodeAt(state.pos - 1) === 0x20
) {
state.pos = start
return false
}
const content = state.src.slice(start + 2, state.pos)
// found!
state.posMax = state.pos
state.pos = start + 2
const token = state.push('plot_inline', 'Plot', 0)
token.markup = '!!'
token.content = content
state.pos = state.posMax + 2
state.posMax = max
return true
}
export const plotPlugin: PluginWithOptions<never> = (md) => {
md.renderer.rules.plot_inline = (tokens, idx) => {
const token = tokens[idx]
return `<Plot>${token.content}</Plot>`
}
md.inline.ruler.before('emphasis', 'plot', plotDef)
}

View File

@ -1,3 +1,5 @@
import { camelCase } from '@pengzhanbo/utils'
const RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w-]+)(?:=\s*(?<quote>['"])(?<value>.+?)\k<quote>)?(?:\s+|$)/
export function resolveAttrs<T extends Record<string, any> = Record<string, any>>(info: string): {
@ -16,26 +18,16 @@ export function resolveAttrs<T extends Record<string, any> = Record<string, any>
// eslint-disable-next-line no-cond-assign
while (matched = info.match(RE_ATTR_VALUE)) {
const { attr, value } = matched.groups!
attrs[attr] = value ?? true
const { attr, value = true } = matched.groups!
let v = typeof value === 'string' ? value.trim() : value
if (v === 'true')
v = true
else if (v === 'false')
v = false
attrs[camelCase(attr)] = v
info = info.slice(matched[0].length)
}
Object.keys(attrs).forEach((key) => {
let value = attrs[key]
value = typeof value === 'string' ? value.trim() : value
if (value === 'true')
value = true
else if (value === 'false')
value = false
attrs[key] = value
if (key.includes('-')) {
const _key = key.replace(/-(\w)/g, (_, c) => c.toUpperCase())
attrs[_key] = value
}
})
return { attrs: attrs as T, rawAttrs }
}

View File

@ -0,0 +1,37 @@
import { isBoolean, isNull, isNumber, isString, isUndefined, kebabCase } from '@pengzhanbo/utils'
export function stringifyAttrs<T extends object = object>(
attrs: T,
withUndefined = false,
): string {
const result = Object.entries(attrs)
.map(([key, value]) => {
const k = kebabCase(String(key))
if (isUndefined(value) || value === 'undefined')
return withUndefined ? `:${k}="undefined"` : ''
if (isNull(value) || value === 'null')
return withUndefined ? `:${k}="null"` : ''
if (value === 'true')
value = true
if (value === 'false')
value = false
if (isBoolean(value))
return value ? `${k}` : ''
if (isNumber(value))
return `:${k}="${value}"`
// like object or array
if (isString(value) && (value[0] === '{' || value[0] === '['))
return `:${k}="${value.replaceAll('\"', '\'')}"`
return `${k}="${String(value)}"`
})
.filter(Boolean)
.join(' ')
return result ? ` ${result}` : ''
}

16
pnpm-lock.yaml generated
View File

@ -177,8 +177,8 @@ catalogs:
specifier: ^0.16.0
version: 0.16.0
'@pengzhanbo/utils':
specifier: ^2.0.0
version: 2.0.0
specifier: ^2.1.0
version: 2.1.0
'@vueuse/core':
specifier: ^13.1.0
version: 13.1.0
@ -469,7 +469,7 @@ importers:
version: 0.10.1
'@pengzhanbo/utils':
specifier: catalog:prod
version: 2.0.0
version: 2.1.0
cac:
specifier: catalog:prod
version: 6.7.14
@ -599,7 +599,7 @@ importers:
version: 0.16.0(markdown-it@14.1.0)
'@pengzhanbo/utils':
specifier: catalog:prod
version: 2.0.0
version: 2.1.0
'@vuepress/helper':
specifier: catalog:vuepress
version: 2.0.0-rc.94(typescript@5.8.3)(vuepress@2.0.0-rc.21(@vuepress/bundler-vite@2.0.0-rc.21(@types/node@22.14.1)(jiti@2.4.2)(less@4.3.0)(sass-embedded@1.86.3)(sass@1.86.3)(stylus@0.64.0)(typescript@5.8.3)(yaml@2.7.0))(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)))
@ -732,7 +732,7 @@ importers:
version: 4.3.0(vue@3.5.13(typescript@5.8.3))
'@pengzhanbo/utils':
specifier: catalog:prod
version: 2.0.0
version: 2.1.0
'@vuepress-plume/plugin-fonts':
specifier: workspace:*
version: link:../plugins/plugin-fonts
@ -1957,8 +1957,8 @@ packages:
'@pengzhanbo/utils@1.2.0':
resolution: {integrity: sha512-M3sN7SQs6PY/J9lB8U6meyUMhv8qs4iIOU/sG6QWRp4nm9ySM8nvX9GOJpo+RuVGcgcH3CO8qlQugBUa5qADSw==}
'@pengzhanbo/utils@2.0.0':
resolution: {integrity: sha512-CGMZYQm4TSvaKxFKJ2JMUxz/GjiHPquf6T18Pr/hVMhzatAdwyfleqOMcUaVy/dxLXpE4Lp6ONY2H4rgylWyNg==}
'@pengzhanbo/utils@2.1.0':
resolution: {integrity: sha512-mdcNoYZ6S9EhRqAIpjnD2dcFxaP7E9JdMrP2z5uXuEesddNcmQ4GvEs/wcyxKmFXqeFdL88fJu7l8a6hNN4zPQ==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
@ -8273,7 +8273,7 @@ snapshots:
'@pengzhanbo/utils@1.2.0': {}
'@pengzhanbo/utils@2.0.0': {}
'@pengzhanbo/utils@2.1.0': {}
'@pkgjs/parseargs@0.11.0':
optional: true

View File

@ -75,7 +75,7 @@ catalogs:
'@mdit/plugin-sup': ^0.16.0
'@mdit/plugin-tab': ^0.16.0
'@mdit/plugin-tasklist': ^0.16.0
'@pengzhanbo/utils': ^2.0.0
'@pengzhanbo/utils': ^2.1.0
'@vueuse/core': ^13.1.0
'@vueuse/integrations': ^13.1.0
bcrypt-ts: ^6.0.0