mio-previewer 0.2.71 → 0.2.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,13 @@
1
1
  # mio-previewer
2
2
 
3
- [中文文档](./README.zh-CN.md) | English
3
+ A Vue 3 markdown rendering engine engineered for **AI streaming responses**. It transforms standard Markdown output into reactive Vue VNodes, enabling smooth, incremental DOM updates without the performance penalties of `v-html`.
4
4
 
5
- A Vue 3 markdown renderer optimized for streaming updates with powerful plugin system. Features real-time rendering, syntax highlighting, math formulas, diagrams, and more.
5
+ **Core Value Proposition:**
6
6
 
7
- **Key Features:**
8
- - 🚀 Streaming-friendly real-time rendering
9
- - 🎨 Built-in syntax highlighting (Prism.js, 20+ languages)
10
- - 📐 Math formulas support (KaTeX)
11
- - 📊 Diagram rendering (Mermaid)
12
- - 🔌 Extensible plugin system
13
- - 📦 Tree-shakeable & lightweight
14
- - 🎯 TypeScript support
7
+ - ⚡️ **VNode-Based Incremental Rendering**: Bypasses the "destroy & recreate" cycle of `v-html`. By converting AST to VNodes, we leverage Vue's diffing algorithm to update only the changed text nodes during streaming.
8
+ - 🧩 **Component-Level Interception**: Intercepts Markdown tokens (like code blocks or math) and renders them as fully interactive Vue components, not just static HTML.
9
+ - 🌊 **Stream-Optimized**: Features like smart cursor tracking and anti-jitter logic ensure a buttery-smooth reading experience while the AI is "typing".
10
+ - 🛡 **Security First**: AST-based transformation naturally prevents XSS attacks compared to raw HTML injection.
15
11
 
16
12
  ## Installation
17
13
 
@@ -33,11 +29,11 @@ yarn add mio-previewer
33
29
  </template>
34
30
 
35
31
  <script setup>
36
- import { ref } from 'vue'
37
- import { MdRenderer } from 'mio-previewer'
38
- import 'mio-previewer/dist/mio-previewer.css'
32
+ import { ref } from "vue";
33
+ import { MdRenderer } from "mio-previewer";
34
+ import "mio-previewer/dist/mio-previewer.css";
39
35
 
40
- const markdown = ref('# Hello World\n\nThis is **markdown**!')
36
+ const markdown = ref("# Hello World\n\nThis is **markdown**!");
41
37
  </script>
42
38
  ```
43
39
 
@@ -47,32 +43,29 @@ Perfect for AI chatbots or real-time content:
47
43
 
48
44
  ```vue
49
45
  <template>
50
- <MdRenderer
51
- :md="streamContent"
52
- :isStreaming="isStreaming"
53
- />
46
+ <MdRenderer :md="streamContent" :isStreaming="isStreaming" />
54
47
  </template>
55
48
 
56
49
  <script setup>
57
- import { ref } from 'vue'
58
- import { MdRenderer } from 'mio-previewer'
59
- import 'mio-previewer/dist/mio-previewer.css'
50
+ import { ref } from "vue";
51
+ import { MdRenderer } from "mio-previewer";
52
+ import "mio-previewer/dist/mio-previewer.css";
60
53
 
61
- const streamContent = ref('')
62
- const isStreaming = ref(true)
54
+ const streamContent = ref("");
55
+ const isStreaming = ref(true);
63
56
 
64
57
  // Simulate streaming
65
- const text = '# Streaming Demo\n\nContent appears **gradually**...'
66
- let index = 0
58
+ const text = "# Streaming Demo\n\nContent appears **gradually**...";
59
+ let index = 0;
67
60
 
68
61
  const interval = setInterval(() => {
69
62
  if (index < text.length) {
70
- streamContent.value += text[index++]
63
+ streamContent.value += text[index++];
71
64
  } else {
72
- isStreaming.value = false
73
- clearInterval(interval)
65
+ isStreaming.value = false;
66
+ clearInterval(interval);
74
67
  }
75
- }, 50)
68
+ }, 50);
76
69
  </script>
77
70
  ```
78
71
 
@@ -84,9 +77,9 @@ const interval = setInterval(() => {
84
77
 
85
78
  ```vue
86
79
  <script setup>
87
- import { MdRenderer } from 'mio-previewer'
88
- import { katexPlugin } from 'mio-previewer/plugins/markdown-it'
89
- import 'mio-previewer/dist/mio-previewer.css'
80
+ import { MdRenderer } from "mio-previewer";
81
+ import { katexPlugin } from "mio-previewer/plugins/markdown-it";
82
+ import "mio-previewer/dist/mio-previewer.css";
90
83
 
91
84
  const markdown = `
92
85
  # Math Example
@@ -97,18 +90,13 @@ Block:
97
90
  $$
98
91
  \\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}
99
92
  $$
100
- `
93
+ `;
101
94
 
102
- const markdownItPlugins = [
103
- { plugin: katexPlugin }
104
- ]
95
+ const markdownItPlugins = [{ plugin: katexPlugin }];
105
96
  </script>
106
97
 
107
98
  <template>
108
- <MdRenderer
109
- :md="markdown"
110
- :markdownItPlugins="markdownItPlugins"
111
- />
99
+ <MdRenderer :md="markdown" :markdownItPlugins="markdownItPlugins" />
112
100
  </template>
113
101
  ```
114
102
 
@@ -116,9 +104,9 @@ const markdownItPlugins = [
116
104
 
117
105
  ```vue
118
106
  <script setup>
119
- import { MdRenderer } from 'mio-previewer'
120
- import { AlertPlugin } from 'mio-previewer/plugins/markdown-it'
121
- import 'mio-previewer/dist/mio-previewer.css'
107
+ import { MdRenderer } from "mio-previewer";
108
+ import { AlertPlugin } from "mio-previewer/plugins/markdown-it";
109
+ import "mio-previewer/dist/mio-previewer.css";
122
110
 
123
111
  const markdown = `
124
112
  ::: info
@@ -136,18 +124,13 @@ This is an **info** alert with markdown support!
136
124
  ::: success
137
125
  ✅ Success message
138
126
  :::
139
- `
127
+ `;
140
128
 
141
- const markdownItPlugins = [
142
- { plugin: AlertPlugin }
143
- ]
129
+ const markdownItPlugins = [{ plugin: AlertPlugin }];
144
130
  </script>
145
131
 
146
132
  <template>
147
- <MdRenderer
148
- :md="markdown"
149
- :markdownItPlugins="markdownItPlugins"
150
- />
133
+ <MdRenderer :md="markdown" :markdownItPlugins="markdownItPlugins" />
151
134
  </template>
152
135
  ```
153
136
 
@@ -155,9 +138,9 @@ const markdownItPlugins = [
155
138
 
156
139
  ```vue
157
140
  <script setup>
158
- import { MdRenderer } from 'mio-previewer'
159
- import { CodeBlockPlugin } from 'mio-previewer/plugins/custom'
160
- import 'mio-previewer/dist/mio-previewer.css'
141
+ import { MdRenderer } from "mio-previewer";
142
+ import { CodeBlockPlugin } from "mio-previewer/plugins/custom";
143
+ import "mio-previewer/dist/mio-previewer.css";
161
144
 
162
145
  const markdown = `
163
146
  \`\`\`javascript
@@ -170,16 +153,13 @@ function hello() {
170
153
  def greet():
171
154
  print("Hello from Python!")
172
155
  \`\`\`
173
- `
156
+ `;
174
157
 
175
- const customPlugins = [CodeBlockPlugin]
158
+ const customPlugins = [CodeBlockPlugin];
176
159
  </script>
177
160
 
178
161
  <template>
179
- <MdRenderer
180
- :md="markdown"
181
- :customPlugins="customPlugins"
182
- />
162
+ <MdRenderer :md="markdown" :customPlugins="customPlugins" />
183
163
  </template>
184
164
  ```
185
165
 
@@ -187,9 +167,9 @@ const customPlugins = [CodeBlockPlugin]
187
167
 
188
168
  ```vue
189
169
  <script setup>
190
- import { MdRenderer } from 'mio-previewer'
191
- import { mermaidPlugin } from 'mio-previewer/plugins/custom'
192
- import 'mio-previewer/dist/mio-previewer.css'
170
+ import { MdRenderer } from "mio-previewer";
171
+ import { mermaidPlugin } from "mio-previewer/plugins/custom";
172
+ import "mio-previewer/dist/mio-previewer.css";
193
173
 
194
174
  const markdown = `
195
175
  \`\`\`mermaid
@@ -198,16 +178,13 @@ graph TD
198
178
  B -->|Yes| C[Continue]
199
179
  B -->|No| D[Stop]
200
180
  \`\`\`
201
- `
181
+ `;
202
182
 
203
- const customPlugins = [mermaidPlugin]
183
+ const customPlugins = [mermaidPlugin];
204
184
  </script>
205
185
 
206
186
  <template>
207
- <MdRenderer
208
- :md="markdown"
209
- :customPlugins="customPlugins"
210
- />
187
+ <MdRenderer :md="markdown" :customPlugins="customPlugins" />
211
188
  </template>
212
189
  ```
213
190
 
@@ -224,32 +201,28 @@ For other plugin styles (KaTeX, Mermaid, Prism), we intentionally do NOT auto-lo
224
201
 
225
202
  ```js
226
203
  // example (app's main.js)
227
- import 'katex/dist/katex.min.css' // if you use the KaTeX plugin
228
- import 'mermaid/dist/mermaid.min.css' // if you use the Mermaid plugin
229
- import 'prismjs/themes/prism.css' // if you use the CodeBlock/Prism plugin
204
+ import "katex/dist/katex.min.css"; // if you use the KaTeX plugin
205
+ import "mermaid/dist/mermaid.min.css"; // if you use the Mermaid plugin
206
+ import "prismjs/themes/prism.css"; // if you use the CodeBlock/Prism plugin
230
207
  ```
231
208
 
232
209
  This keeps the library flexible and avoids network/CDN reliance in restricted environments.
233
210
 
234
-
235
211
  #### Emoji Support
236
212
 
237
213
  ```vue
238
214
  <script setup>
239
- import { MdRenderer } from 'mio-previewer'
240
- import { EmojiPlugin } from 'mio-previewer/plugins/custom'
241
- import 'mio-previewer/dist/mio-previewer.css'
215
+ import { MdRenderer } from "mio-previewer";
216
+ import { EmojiPlugin } from "mio-previewer/plugins/custom";
217
+ import "mio-previewer/dist/mio-previewer.css";
242
218
 
243
- const markdown = 'Hello :smile: Welcome! :tada: :rocket:'
219
+ const markdown = "Hello :smile: Welcome! :tada: :rocket:";
244
220
 
245
- const customPlugins = [EmojiPlugin]
221
+ const customPlugins = [EmojiPlugin];
246
222
  </script>
247
223
 
248
224
  <template>
249
- <MdRenderer
250
- :md="markdown"
251
- :customPlugins="customPlugins"
252
- />
225
+ <MdRenderer :md="markdown" :customPlugins="customPlugins" />
253
226
  </template>
254
227
  ```
255
228
 
@@ -257,11 +230,15 @@ const customPlugins = [EmojiPlugin]
257
230
 
258
231
  ```vue
259
232
  <script setup>
260
- import { ref } from 'vue'
261
- import { MdRenderer } from 'mio-previewer'
262
- import { AlertPlugin, katexPlugin } from 'mio-previewer/plugins/markdown-it'
263
- import { mermaidPlugin, CodeBlockPlugin, EmojiPlugin } from 'mio-previewer/plugins/custom'
264
- import 'mio-previewer/dist/mio-previewer.css'
233
+ import { ref } from "vue";
234
+ import { MdRenderer } from "mio-previewer";
235
+ import { AlertPlugin, katexPlugin } from "mio-previewer/plugins/markdown-it";
236
+ import {
237
+ mermaidPlugin,
238
+ CodeBlockPlugin,
239
+ EmojiPlugin,
240
+ } from "mio-previewer/plugins/custom";
241
+ import "mio-previewer/dist/mio-previewer.css";
265
242
 
266
243
  const markdown = ref(`# Complete Demo :rocket:
267
244
 
@@ -287,23 +264,16 @@ graph LR
287
264
  \`\`\`
288
265
 
289
266
  Great work! :thumbsup: :100:
290
- `)
267
+ `);
291
268
 
292
- const customPlugins = [
293
- mermaidPlugin,
294
- CodeBlockPlugin,
295
- EmojiPlugin
296
- ]
269
+ const customPlugins = [mermaidPlugin, CodeBlockPlugin, EmojiPlugin];
297
270
 
298
- const markdownItPlugins = [
299
- { plugin: AlertPlugin },
300
- { plugin: katexPlugin }
301
- ]
271
+ const markdownItPlugins = [{ plugin: AlertPlugin }, { plugin: katexPlugin }];
302
272
  </script>
303
273
 
304
274
  <template>
305
- <MdRenderer
306
- :md="markdown"
275
+ <MdRenderer
276
+ :md="markdown"
307
277
  :customPlugins="customPlugins"
308
278
  :markdownItPlugins="markdownItPlugins"
309
279
  />
@@ -316,59 +286,60 @@ const markdownItPlugins = [
316
286
 
317
287
  ```javascript
318
288
  const HighlightPlugin = {
319
- name: 'highlight',
289
+ name: "highlight",
320
290
  priority: 50,
321
291
  test: (node) => {
322
- return node.type === 'tag' &&
323
- node.name === 'mark'
292
+ return node.type === "tag" && node.name === "mark";
324
293
  },
325
294
  render: (node, renderChildren, h) => {
326
- return h('mark', {
327
- style: {
328
- backgroundColor: '#ffeb3b',
329
- padding: '2px 4px',
330
- borderRadius: '2px'
331
- }
332
- }, renderChildren())
333
- }
334
- }
295
+ return h(
296
+ "mark",
297
+ {
298
+ style: {
299
+ backgroundColor: "#ffeb3b",
300
+ padding: "2px 4px",
301
+ borderRadius: "2px",
302
+ },
303
+ },
304
+ renderChildren(),
305
+ );
306
+ },
307
+ };
335
308
 
336
309
  // Use it
337
- const customPlugins = [HighlightPlugin]
310
+ const customPlugins = [HighlightPlugin];
338
311
  ```
339
312
 
340
313
  #### Custom Markdown-it Plugin
341
314
 
342
315
  ```javascript
343
316
  function customContainerPlugin(md) {
344
- md.use(require('markdown-it-container'), 'note', {
317
+ md.use(require("markdown-it-container"), "note", {
345
318
  render: (tokens, idx) => {
346
319
  if (tokens[idx].nesting === 1) {
347
- return '<div class="note">\n'
320
+ return '<div class="note">\n';
348
321
  } else {
349
- return '</div>\n'
322
+ return "</div>\n";
350
323
  }
351
- }
352
- })
324
+ },
325
+ });
353
326
  }
354
327
 
355
- const markdownItPlugins = [
356
- { plugin: customContainerPlugin }
357
- ]
328
+ const markdownItPlugins = [{ plugin: customContainerPlugin }];
358
329
  ```
359
330
 
360
331
  ## API Reference
361
332
 
362
333
  ### MdRenderer Props
363
334
 
364
- | Prop | Type | Default | Description |
365
- |------|------|---------|-------------|
366
- | `md` | `string` | `''` | Markdown content to render |
367
- | `isStreaming` | `boolean` | `false` | Show cursor during streaming |
368
- | `useWorker` | `boolean` | `false` | Use Web Worker for parsing |
369
- | `customPlugins` | `CustomPlugin[]` | `[]` | Custom rendering plugins |
370
- | `markdownItPlugins` | `MarkdownItPluginConfig[]` | `[]` | Markdown-it plugins |
371
- | `markdownItOptions` | `object` | `{}` | Markdown-it options |
335
+ | Prop | Type | Default | Description |
336
+ | ------------------- | -------------------------- | ------- | ---------------------------- |
337
+ | `md` | `string` | `''` | Markdown content to render |
338
+ | `isStreaming` | `boolean` | `false` | Show cursor during streaming |
339
+ | `useWorker` | `boolean` | `false` | Use Web Worker for parsing |
340
+ | `customPlugins` | `CustomPlugin[]` | `[]` | Custom rendering plugins |
341
+ | `markdownItPlugins` | `MarkdownItPluginConfig[]` | `[]` | Markdown-it plugins |
342
+ | `markdownItOptions` | `object` | `{}` | Markdown-it options |
372
343
 
373
344
  ### Plugin Types
374
345
 
@@ -376,14 +347,14 @@ const markdownItPlugins = [
376
347
 
377
348
  ```typescript
378
349
  interface CustomPlugin {
379
- name?: string
380
- priority?: number // Higher = earlier execution
381
- test: (node: ASTNode) => boolean
350
+ name?: string;
351
+ priority?: number; // Higher = earlier execution
352
+ test: (node: ASTNode) => boolean;
382
353
  render: (
383
354
  node: ASTNode,
384
355
  renderChildren: () => VNode[],
385
- h: typeof import('vue').h
386
- ) => VNode | string | null
356
+ h: typeof import("vue").h,
357
+ ) => VNode | string | null;
387
358
  }
388
359
  ```
389
360
 
@@ -391,8 +362,8 @@ interface CustomPlugin {
391
362
 
392
363
  ```typescript
393
364
  interface MarkdownItPluginConfig {
394
- plugin: (md: MarkdownIt, options?: any) => void
395
- options?: any
365
+ plugin: (md: MarkdownIt, options?: any) => void;
366
+ options?: any;
396
367
  }
397
368
  ```
398
369
 
@@ -400,18 +371,18 @@ interface MarkdownItPluginConfig {
400
371
 
401
372
  ### Markdown-it Plugins (Syntax)
402
373
 
403
- | Plugin | Import Path | Description |
404
- |--------|-------------|-------------|
374
+ | Plugin | Import Path | Description |
375
+ | ------------- | ----------------------------------- | ------------------------------------------- |
405
376
  | `AlertPlugin` | `mio-previewer/plugins/markdown-it` | Alert boxes (info, warning, error, success) |
406
- | `katexPlugin` | `mio-previewer/plugins/markdown-it` | Math formulas with KaTeX |
377
+ | `katexPlugin` | `mio-previewer/plugins/markdown-it` | Math formulas with KaTeX |
407
378
 
408
379
  ### Custom Plugins (Rendering)
409
380
 
410
- | Plugin | Import Path | Description |
411
- |--------|-------------|-------------|
412
- | `mermaidPlugin` | `mio-previewer/plugins/custom` | Diagram rendering with Mermaid |
413
- | `CodeBlockPlugin` | `mio-previewer/plugins/custom` | Syntax highlighting with Prism |
414
- | `EmojiPlugin` | `mio-previewer/plugins/custom` | Emoji code replacement |
381
+ | Plugin | Import Path | Description |
382
+ | ------------------- | ------------------------------ | -------------------------------------------------------------------------- |
383
+ | `mermaidPlugin` | `mio-previewer/plugins/custom` | Diagram rendering with Mermaid |
384
+ | `CodeBlockPlugin` | `mio-previewer/plugins/custom` | Syntax highlighting with Prism |
385
+ | `EmojiPlugin` | `mio-previewer/plugins/custom` | Emoji code replacement |
415
386
  | `imageViewerPlugin` | `mio-previewer/plugins/custom` | Image preview with zoom & gestures ([docs](./docs/IMAGE_VIEWER_PLUGIN.md)) |
416
387
 
417
388
  ## Advanced Usage
@@ -421,18 +392,15 @@ interface MarkdownItPluginConfig {
421
392
  ```vue
422
393
  <script setup>
423
394
  const markdownItOptions = {
424
- html: true, // Enable HTML tags
425
- linkify: true, // Auto-convert URLs
395
+ html: true, // Enable HTML tags
396
+ linkify: true, // Auto-convert URLs
426
397
  typographer: true, // Smart quotes, dashes
427
- breaks: false // Convert \n to <br>
428
- }
398
+ breaks: false, // Convert \n to <br>
399
+ };
429
400
  </script>
430
401
 
431
402
  <template>
432
- <MdRenderer
433
- :md="markdown"
434
- :markdownItOptions="markdownItOptions"
435
- />
403
+ <MdRenderer :md="markdown" :markdownItOptions="markdownItOptions" />
436
404
  </template>
437
405
  ```
438
406
 
@@ -442,10 +410,7 @@ For better performance with large documents:
442
410
 
443
411
  ```vue
444
412
  <template>
445
- <MdRenderer
446
- :md="largeMarkdown"
447
- :useWorker="true"
448
- />
413
+ <MdRenderer :md="largeMarkdown" :useWorker="true" />
449
414
  </template>
450
415
  ```
451
416
 
@@ -492,7 +457,7 @@ mio-previewer/
492
457
  ## Browser Support
493
458
 
494
459
  - Chrome/Edge: Latest 2 versions
495
- - Firefox: Latest 2 versions
460
+ - Firefox: Latest 2 versions
496
461
  - Safari: Latest 2 versions
497
462
 
498
463
  ## Documentation
@@ -500,6 +465,7 @@ mio-previewer/
500
465
  📚 **[Complete Documentation →](./docs/README.md)**
501
466
 
502
467
  ### Quick Links
468
+
503
469
  - [Plugin System Guide](./docs/PLUGINS.md) - Complete plugin system documentation
504
470
  - [Customize Code Block Styles](./docs/CUSTOMIZE_CODEBLOCK_STYLE.md) - Code block theming
505
471
  - [KaTeX Configuration](./docs/KATEX_DELIMITERS.md) - Math formula setup
@@ -514,4 +480,3 @@ MIT
514
480
  - [GitHub Repository](https://github.com/Pretend-to/mio-previewer)
515
481
  - [npm Package](https://www.npmjs.com/package/mio-previewer)
516
482
  - [Documentation Center](./docs/README.md)
517
-
package/README.zh-CN.md CHANGED
@@ -1,17 +1,15 @@
1
- # mio-previewer
1
+ ### mio-previewer
2
2
 
3
3
  [English](./README.md) | 中文文档
4
4
 
5
- 一个针对流式更新优化的 Vue 3 Markdown 渲染器,具有强大的插件系统。支持实时渲染、语法高亮、数学公式、图表等功能。
5
+ 一个专门为 **AI 流式对话 (Streaming Responses)** 设计的 Vue 3 Markdown 渲染引擎。它将标准的 Markdown 文本转换为可响应的 Vue VNode 树,利用 Vue 的 Diff 算法实现丝滑的增量 DOM 更新,彻底规避 `v-html` 带来的性能抖动和状态丢失。
6
6
 
7
- **核心特性:**
8
- - 🚀 支持流式实时渲染
9
- - 🎨 内置语法高亮(Prism.js,20+ 种语言)
10
- - 📐 数学公式支持(KaTeX)
11
- - 📊 图表渲染(Mermaid)
12
- - 🔌 可扩展的插件系统
13
- - 📦 支持 Tree-shaking,轻量级
14
- - 🎯 TypeScript 支持
7
+ **核心价值:**
8
+
9
+ - ⚡️ **基于 VNode 的增量渲染**:打破了 `v-html` “全量销毁再重建”的魔咒。通过将 AST 映射为 VNode,在流式更新时仅更新末尾变化的文本节点,实现 O(1) 级的局部 DOM 操作。
10
+ - 🧩 **组件化拦截 (Component Interception)**:能够精准拦截 Markdown 标记(如代码块、数学公式、图表),并将其替换为功能完备的交互式 Vue 组件,而非死板的静态 HTML。
11
+ - 🌊 **极致流式体验**:内置智能光标追踪和防抖渲染算法,确保 AI 在逐字吐出内容时,页面视觉稳定、不抖动、不闪烁。
12
+ - 🛡 **内核级安全**:由于采用 AST 转换而非直接字符串注入,天然物理隔离了 XSS 攻击风险。
15
13
 
16
14
  ## 安装
17
15
 
@@ -36,7 +34,7 @@ yarn add mio-previewer
36
34
  impor## 浏览器支持
37
35
 
38
36
  - Chrome/Edge: 最新 2 个版本
39
- - Firefox: 最新 2 个版本
37
+ - Firefox: 最新 2 个版本
40
38
  - Safari: 最新 2 个版本
41
39
 
42
40
  ## 文档
@@ -72,32 +70,29 @@ const markdown = ref('# Hello World\n\n这是 **Markdown** 渲染!')
72
70
 
73
71
  ```vue
74
72
  <template>
75
- <MdRenderer
76
- :md="streamContent"
77
- :isStreaming="isStreaming"
78
- />
73
+ <MdRenderer :md="streamContent" :isStreaming="isStreaming" />
79
74
  </template>
80
75
 
81
76
  <script setup>
82
- import { ref } from 'vue'
83
- import { MdRenderer } from 'mio-previewer'
84
- import 'mio-previewer/dist/mio-previewer.css'
77
+ import { ref } from "vue";
78
+ import { MdRenderer } from "mio-previewer";
79
+ import "mio-previewer/dist/mio-previewer.css";
85
80
 
86
- const streamContent = ref('')
87
- const isStreaming = ref(true)
81
+ const streamContent = ref("");
82
+ const isStreaming = ref(true);
88
83
 
89
84
  // 模拟流式输出
90
- const text = '# 流式演示\n\n内容**逐步**出现...'
91
- let index = 0
85
+ const text = "# 流式演示\n\n内容**逐步**出现...";
86
+ let index = 0;
92
87
 
93
88
  const interval = setInterval(() => {
94
89
  if (index < text.length) {
95
- streamContent.value += text[index++]
90
+ streamContent.value += text[index++];
96
91
  } else {
97
- isStreaming.value = false
98
- clearInterval(interval)
92
+ isStreaming.value = false;
93
+ clearInterval(interval);
99
94
  }
100
- }, 50)
95
+ }, 50);
101
96
  </script>
102
97
  ```
103
98
 
@@ -130,10 +125,7 @@ const markdownItPlugins = [
130
125
  </script>
131
126
 
132
127
  <template>
133
- <MdRenderer
134
- :md="markdown"
135
- :markdownItPlugins="markdownItPlugins"
136
- />
128
+ <MdRenderer :md="markdown" :markdownItPlugins="markdownItPlugins" />
137
129
  </template>
138
130
  ```
139
131
 
@@ -169,10 +161,7 @@ const markdownItPlugins = [
169
161
  </script>
170
162
 
171
163
  <template>
172
- <MdRenderer
173
- :md="markdown"
174
- :markdownItPlugins="markdownItPlugins"
175
- />
164
+ <MdRenderer :md="markdown" :markdownItPlugins="markdownItPlugins" />
176
165
  </template>
177
166
  ```
178
167
 
@@ -201,10 +190,7 @@ const customPlugins = [CodeBlockPlugin]
201
190
  </script>
202
191
 
203
192
  <template>
204
- <MdRenderer
205
- :md="markdown"
206
- :customPlugins="customPlugins"
207
- />
193
+ <MdRenderer :md="markdown" :customPlugins="customPlugins" />
208
194
  </template>
209
195
  ```
210
196
 
@@ -229,10 +215,7 @@ const customPlugins = [mermaidPlugin]
229
215
  </script>
230
216
 
231
217
  <template>
232
- <MdRenderer
233
- :md="markdown"
234
- :customPlugins="customPlugins"
235
- />
218
+ <MdRenderer :md="markdown" :customPlugins="customPlugins" />
236
219
  </template>
237
220
  ```
238
221
 
@@ -240,20 +223,17 @@ const customPlugins = [mermaidPlugin]
240
223
 
241
224
  ```vue
242
225
  <script setup>
243
- import { MdRenderer } from 'mio-previewer'
244
- import { EmojiPlugin } from 'mio-previewer/plugins/custom'
245
- import 'mio-previewer/dist/mio-previewer.css'
226
+ import { MdRenderer } from "mio-previewer";
227
+ import { EmojiPlugin } from "mio-previewer/plugins/custom";
228
+ import "mio-previewer/dist/mio-previewer.css";
246
229
 
247
- const markdown = '你好 :smile: 欢迎! :tada: :rocket:'
230
+ const markdown = "你好 :smile: 欢迎! :tada: :rocket:";
248
231
 
249
- const customPlugins = [EmojiPlugin]
232
+ const customPlugins = [EmojiPlugin];
250
233
  </script>
251
234
 
252
235
  <template>
253
- <MdRenderer
254
- :md="markdown"
255
- :customPlugins="customPlugins"
256
- />
236
+ <MdRenderer :md="markdown" :customPlugins="customPlugins" />
257
237
  </template>
258
238
  ```
259
239
 
@@ -306,8 +286,8 @@ const markdownItPlugins = [
306
286
  </script>
307
287
 
308
288
  <template>
309
- <MdRenderer
310
- :md="markdown"
289
+ <MdRenderer
290
+ :md="markdown"
311
291
  :customPlugins="customPlugins"
312
292
  :markdownItPlugins="markdownItPlugins"
313
293
  />
@@ -320,59 +300,60 @@ const markdownItPlugins = [
320
300
 
321
301
  ```javascript
322
302
  const HighlightPlugin = {
323
- name: 'highlight',
303
+ name: "highlight",
324
304
  priority: 50,
325
305
  test: (node) => {
326
- return node.type === 'tag' &&
327
- node.name === 'mark'
306
+ return node.type === "tag" && node.name === "mark";
328
307
  },
329
308
  render: (node, renderChildren, h) => {
330
- return h('mark', {
331
- style: {
332
- backgroundColor: '#ffeb3b',
333
- padding: '2px 4px',
334
- borderRadius: '2px'
335
- }
336
- }, renderChildren())
337
- }
338
- }
309
+ return h(
310
+ "mark",
311
+ {
312
+ style: {
313
+ backgroundColor: "#ffeb3b",
314
+ padding: "2px 4px",
315
+ borderRadius: "2px",
316
+ },
317
+ },
318
+ renderChildren(),
319
+ );
320
+ },
321
+ };
339
322
 
340
323
  // 使用插件
341
- const customPlugins = [HighlightPlugin]
324
+ const customPlugins = [HighlightPlugin];
342
325
  ```
343
326
 
344
327
  #### 自定义 Markdown-it 插件
345
328
 
346
329
  ```javascript
347
330
  function customContainerPlugin(md) {
348
- md.use(require('markdown-it-container'), 'note', {
331
+ md.use(require("markdown-it-container"), "note", {
349
332
  render: (tokens, idx) => {
350
333
  if (tokens[idx].nesting === 1) {
351
- return '<div class="note">\n'
334
+ return '<div class="note">\n';
352
335
  } else {
353
- return '</div>\n'
336
+ return "</div>\n";
354
337
  }
355
- }
356
- })
338
+ },
339
+ });
357
340
  }
358
341
 
359
- const markdownItPlugins = [
360
- { plugin: customContainerPlugin }
361
- ]
342
+ const markdownItPlugins = [{ plugin: customContainerPlugin }];
362
343
  ```
363
344
 
364
345
  ## API 参考
365
346
 
366
347
  ### MdRenderer 属性
367
348
 
368
- | 属性 | 类型 | 默认值 | 说明 |
369
- |------|------|--------|------|
370
- | \`md\` | \`string\` | \`''\` | 要渲染的 Markdown 内容 |
371
- | \`isStreaming\` | \`boolean\` | \`false\` | 流式模式时显示光标 |
372
- | \`useWorker\` | \`boolean\` | \`false\` | 使用 Web Worker 解析 |
373
- | \`customPlugins\` | \`CustomPlugin[]\` | \`[]\` | 自定义渲染插件 |
374
- | \`markdownItPlugins\` | \`MarkdownItPluginConfig[]\` | \`[]\` | Markdown-it 插件 |
375
- | \`markdownItOptions\` | \`object\` | \`{}\` | Markdown-it 配置选项 |
349
+ | 属性 | 类型 | 默认值 | 说明 |
350
+ | --------------------- | ---------------------------- | --------- | ---------------------- |
351
+ | \`md\` | \`string\` | \`''\` | 要渲染的 Markdown 内容 |
352
+ | \`isStreaming\` | \`boolean\` | \`false\` | 流式模式时显示光标 |
353
+ | \`useWorker\` | \`boolean\` | \`false\` | 使用 Web Worker 解析 |
354
+ | \`customPlugins\` | \`CustomPlugin[]\` | \`[]\` | 自定义渲染插件 |
355
+ | \`markdownItPlugins\` | \`MarkdownItPluginConfig[]\` | \`[]\` | Markdown-it 插件 |
356
+ | \`markdownItOptions\` | \`object\` | \`{}\` | Markdown-it 配置选项 |
376
357
 
377
358
  ### 插件类型
378
359
 
@@ -380,14 +361,14 @@ const markdownItPlugins = [
380
361
 
381
362
  ```typescript
382
363
  interface CustomPlugin {
383
- name?: string
384
- priority?: number // 数字越大优先级越高
385
- test: (node: ASTNode) => boolean
364
+ name?: string;
365
+ priority?: number; // 数字越大优先级越高
366
+ test: (node: ASTNode) => boolean;
386
367
  render: (
387
368
  node: ASTNode,
388
369
  renderChildren: () => VNode[],
389
- h: typeof import('vue').h
390
- ) => VNode | string | null
370
+ h: typeof import("vue").h,
371
+ ) => VNode | string | null;
391
372
  }
392
373
  ```
393
374
 
@@ -395,8 +376,8 @@ interface CustomPlugin {
395
376
 
396
377
  ```typescript
397
378
  interface MarkdownItPluginConfig {
398
- plugin: (md: MarkdownIt, options?: any) => void
399
- options?: any
379
+ plugin: (md: MarkdownIt, options?: any) => void;
380
+ options?: any;
400
381
  }
401
382
  ```
402
383
 
@@ -404,18 +385,18 @@ interface MarkdownItPluginConfig {
404
385
 
405
386
  ### Markdown-it 插件(语法)
406
387
 
407
- | 插件 | 导入路径 | 说明 |
408
- |------|----------|------|
409
- | \`AlertPlugin\` | \`mio-previewer/plugins/markdown-it\` | 警告框(info、warning、error、success)|
410
- | \`katexPlugin\` | \`mio-previewer/plugins/markdown-it\` | KaTeX 数学公式 |
388
+ | 插件 | 导入路径 | 说明 |
389
+ | --------------- | ------------------------------------- | --------------------------------------- |
390
+ | \`AlertPlugin\` | \`mio-previewer/plugins/markdown-it\` | 警告框(info、warning、error、success) |
391
+ | \`katexPlugin\` | \`mio-previewer/plugins/markdown-it\` | KaTeX 数学公式 |
411
392
 
412
393
  ### 自定义插件(渲染)
413
394
 
414
- | 插件 | 导入路径 | 说明 |
415
- |------|----------|------|
416
- | \`mermaidPlugin\` | \`mio-previewer/plugins/custom\` | Mermaid 图表渲染 |
417
- | \`CodeBlockPlugin\` | \`mio-previewer/plugins/custom\` | Prism 语法高亮 |
418
- | \`EmojiPlugin\` | \`mio-previewer/plugins/custom\` | Emoji 代码替换 |
395
+ | 插件 | 导入路径 | 说明 |
396
+ | ------------------- | -------------------------------- | ---------------- |
397
+ | \`mermaidPlugin\` | \`mio-previewer/plugins/custom\` | Mermaid 图表渲染 |
398
+ | \`CodeBlockPlugin\` | \`mio-previewer/plugins/custom\` | Prism 语法高亮 |
399
+ | \`EmojiPlugin\` | \`mio-previewer/plugins/custom\` | Emoji 代码替换 |
419
400
 
420
401
  ## 高级用法
421
402
 
@@ -424,18 +405,15 @@ interface MarkdownItPluginConfig {
424
405
  ```vue
425
406
  <script setup>
426
407
  const markdownItOptions = {
427
- html: true, // 启用 HTML 标签
428
- linkify: true, // 自动转换 URL
408
+ html: true, // 启用 HTML 标签
409
+ linkify: true, // 自动转换 URL
429
410
  typographer: true, // 智能引号、破折号
430
- breaks: false // 将 \n 转换为 <br>
431
- }
411
+ breaks: false, // 将 \n 转换为 <br>
412
+ };
432
413
  </script>
433
414
 
434
415
  <template>
435
- <MdRenderer
436
- :md="markdown"
437
- :markdownItOptions="markdownItOptions"
438
- />
416
+ <MdRenderer :md="markdown" :markdownItOptions="markdownItOptions" />
439
417
  </template>
440
418
  ```
441
419
 
@@ -445,10 +423,7 @@ const markdownItOptions = {
445
423
 
446
424
  ```vue
447
425
  <template>
448
- <MdRenderer
449
- :md="largeMarkdown"
450
- :useWorker="true"
451
- />
426
+ <MdRenderer :md="largeMarkdown" :useWorker="true" />
452
427
  </template>
453
428
  ```
454
429
 
@@ -1,4 +1,4 @@
1
- code[class*=language-],pre[class*=language-]{color:#ccc;background:none;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#999}.token.punctuation{color:#ccc}.token.tag,.token.attr-name,.token.namespace,.token.deleted{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.number,.token.function{color:#f08d49}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd}.token.string,.token.char,.token.attr-value,.token.regex,.token.variable{color:#7ec699}.token.operator,.token.entity,.token.url{color:#67cdcc}.token.important,.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}.code-block-wrapper[data-v-5a9f6db9]{margin:1.5em 0;background:#262733;border-radius:8px;overflow:hidden;box-shadow:0 2px 6px #6e7d8c12;border:1px solid #222229;position:relative}.code-block-tooltip[data-v-5a9f6db9]{position:fixed;transform:translate(-50%,-100%);margin-top:-8px;background:#000000e6;color:#fff;padding:6px 12px;border-radius:4px;font-size:13px;white-space:nowrap;pointer-events:none;z-index:10000;animation:tooltipFadeIn-5a9f6db9 .2s ease;box-shadow:0 2px 8px #0000004d}.code-block-tooltip[data-v-5a9f6db9]:after{content:"";position:absolute;top:100%;left:50%;transform:translate(-50%);border:5px solid transparent;border-top-color:#000000e6}@keyframes tooltipFadeIn-5a9f6db9{0%{opacity:0;transform:translate(-50%,calc(-100% - 5px))}to{opacity:1;transform:translate(-50%,-100%)}}.code-block-header[data-v-5a9f6db9]{display:flex;justify-content:space-between;align-items:center;background:#2d2f3a;padding:.35rem .85rem;border-bottom:1px solid #23242d}.lang-label[data-v-5a9f6db9]{color:#a3b0d0;font-size:.89em;font-weight:600;letter-spacing:1px;-webkit-user-select:none;user-select:none}.op-btn-group[data-v-5a9f6db9]{display:flex;gap:.4rem}.op-btn-group button[data-v-5a9f6db9]{border:none;outline:none;background:transparent;color:#8fa3c1;cursor:pointer;border-radius:3px;padding:.21em;transition:color .18s,background .22s;line-height:1;display:flex;align-items:center;font-size:1.02rem;opacity:.85}.op-btn-group button[data-v-5a9f6db9]:hover:not(:disabled){background:#ecf6ff1a}.op-btn-group button[data-v-5a9f6db9]:disabled{cursor:not-allowed;opacity:.5}.copy-code-button[data-v-5a9f6db9]{color:#8fa3c1}.publish-html-button[data-v-5a9f6db9]{color:#10b981}.preview-html-button[data-v-5a9f6db9]{color:#3486ff}.close-preview-button[data-v-5a9f6db9]{color:#ff5d5b;background:#fae8e410}.fullscreen-button[data-v-5a9f6db9]{color:#8b5cf6}.exit-fullscreen-button[data-v-5a9f6db9]{color:#f59e0b}pre[data-v-5a9f6db9],code[data-v-5a9f6db9]{background:transparent;border-radius:0;font-family:JetBrains Mono,Fira Code,Consolas,Menlo,Monaco,monospace;font-size:.92em;line-height:1.6;color:#ddd}pre[data-v-5a9f6db9]{margin:0;padding:1rem;overflow-x:auto;background:#1e1e2e}pre[data-v-5a9f6db9]::-webkit-scrollbar{height:8px;background:transparent}pre[data-v-5a9f6db9]::-webkit-scrollbar-thumb{background-color:#8889;border-radius:4px}pre[data-v-5a9f6db9]::-webkit-scrollbar-thumb:hover{background-color:#888888e6}.iframe-wrapper[data-v-5a9f6db9]{width:100%;overflow:hidden;display:block;position:relative;border:1px solid #ddd;background:#fff;transition:height .1s ease-out}.html-preview-iframe[data-v-5a9f6db9]{width:100%;height:100%;border:none;background:#fff;display:block}.code-block-wrapper.fullscreen[data-v-5a9f6db9]{position:fixed;inset:0;width:100vw;height:100vh;z-index:9999;margin:0;border-radius:0;display:flex;flex-direction:column}.code-block-wrapper.fullscreen .code-block-header[data-v-5a9f6db9]{flex-shrink:0}.code-block-wrapper.fullscreen pre[data-v-5a9f6db9]{flex:1;overflow:auto;max-height:none}.code-block-wrapper.fullscreen .iframe-wrapper[data-v-5a9f6db9]{flex:1;height:auto!important;border:none}.code-block-wrapper.fullscreen .html-preview-iframe[data-v-5a9f6db9]{height:100%}.code-block-wrapper.code-block-wrapper pre{background-color:#1e1e2e!important;background:#1e1e2e!important}.code-block-wrapper.code-block-wrapper code{background:transparent!important}.code-block-wrapper pre[class*=language-]{background-color:#1e1e2e!important;background:#1e1e2e!important}.code-block-wrapper code[class*=language-]{background:transparent!important;background-color:transparent!important}.code-block-wrapper pre,.code-block-wrapper pre code,.code-block-wrapper pre[class*=language-],.code-block-wrapper code[class*=language-],.markdown-body .code-block-wrapper pre,.markdown-body .code-block-wrapper pre[class*=language-]{background-color:#1e1e2e!important;background:#1e1e2e!important}.mermaid-diagram-wrapper[data-v-d873f163]{margin:1em 0;background:var(--mermaid-bg, #f9f9f9);border-radius:8px;border:1px solid var(--mermaid-border, #e0e0e0);position:relative}.mermaid-controls[data-v-d873f163]{position:absolute;top:8px;right:8px;z-index:10;display:flex;gap:8px;align-items:center;background:#ffffffe6;padding:4px 8px;border-radius:6px;box-shadow:0 2px 8px #0000001a}.control-btn[data-v-d873f163]{background:transparent;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;color:#555;transition:color .2s,background .2s;border-radius:4px}.control-btn[data-v-d873f163]:hover{background:#0000000d;color:#000}.zoom-indicator[data-v-d873f163]{font-size:12px;color:#666;min-width:45px;text-align:center;font-weight:500}.mermaid-diagram[data-v-d873f163]{position:relative;overflow:hidden;padding:1em;cursor:grab;-webkit-user-select:none;user-select:none}.mermaid-diagram.is-dragging[data-v-d873f163]{cursor:grabbing}.mermaid-diagram.is-fullscreen[data-v-d873f163]{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:9999;background:var(--mermaid-bg, #f9f9f9);margin:0;border-radius:0;display:flex;align-items:center;justify-content:center}.mermaid-diagram-content[data-v-d873f163]{display:flex;justify-content:center;align-items:flex-start;width:100%}.mermaid-diagram.is-fullscreen .mermaid-diagram-content[data-v-d873f163]{align-items:center;height:100%}.mermaid-diagram-content[data-v-d873f163] svg{display:block;height:auto;max-width:100%}.mermaid-error[data-v-d873f163]{color:#c00;background:#fff0f0;padding:1em;border-radius:4px;margin:1em}.mermaid-error pre[data-v-d873f163]{margin:.5em 0 0;font-size:.9em;white-space:pre-wrap}.theme-dark{background:#1e1e1ef2}.theme-dark{color:#bbb}.theme-dark{background:#ffffff1a;color:#fff}.theme-dark{color:#999}@media (prefers-color-scheme: dark){.mermaid-controls[data-v-d873f163],.mermaid-diagram.is-fullscreen[data-v-d873f163]{background:#1e1e1ef2}.control-btn[data-v-d873f163]{color:#bbb}.control-btn[data-v-d873f163]:hover{background:#ffffff1a;color:#fff}.zoom-indicator[data-v-d873f163]{color:#999}}.blinking-cursor[data-v-0d4d5123]{display:inline-block;animation:blink-0d4d5123 1.2s ease-in-out infinite;vertical-align:text-bottom;margin-left:2px}.cursor-square[data-v-0d4d5123]{width:8px;height:1.2em}.cursor-line[data-v-0d4d5123]{width:2px;height:1.2em}.cursor-circle[data-v-0d4d5123]{width:8px;height:8px;border-radius:50%;vertical-align:baseline}@keyframes blink-0d4d5123{0%,to{opacity:1}50%{opacity:.2}}.mio-image-viewer[data-v-ed2e02d9]{display:inline-block;max-width:100%;height:auto}/*!
1
+ code[class*=language-],pre[class*=language-]{color:#ccc;background:none;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#999}.token.punctuation{color:#ccc}.token.tag,.token.attr-name,.token.namespace,.token.deleted{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.number,.token.function{color:#f08d49}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd}.token.string,.token.char,.token.attr-value,.token.regex,.token.variable{color:#7ec699}.token.operator,.token.entity,.token.url{color:#67cdcc}.token.important,.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}.code-block-wrapper[data-v-5a9f6db9]{margin:1.5em 0;background:#262733;border-radius:8px;overflow:hidden;box-shadow:0 2px 6px #6e7d8c12;border:1px solid #222229;position:relative}.code-block-tooltip[data-v-5a9f6db9]{position:fixed;transform:translate(-50%,-100%);margin-top:-8px;background:#000000e6;color:#fff;padding:6px 12px;border-radius:4px;font-size:13px;white-space:nowrap;pointer-events:none;z-index:10000;animation:tooltipFadeIn-5a9f6db9 .2s ease;box-shadow:0 2px 8px #0000004d}.code-block-tooltip[data-v-5a9f6db9]:after{content:"";position:absolute;top:100%;left:50%;transform:translate(-50%);border:5px solid transparent;border-top-color:#000000e6}@keyframes tooltipFadeIn-5a9f6db9{0%{opacity:0;transform:translate(-50%,calc(-100% - 5px))}to{opacity:1;transform:translate(-50%,-100%)}}.code-block-header[data-v-5a9f6db9]{display:flex;justify-content:space-between;align-items:center;background:#2d2f3a;padding:.35rem .85rem;border-bottom:1px solid #23242d}.lang-label[data-v-5a9f6db9]{color:#a3b0d0;font-size:.89em;font-weight:600;letter-spacing:1px;-webkit-user-select:none;user-select:none}.op-btn-group[data-v-5a9f6db9]{display:flex;gap:.4rem}.op-btn-group button[data-v-5a9f6db9]{border:none;outline:none;background:transparent;color:#8fa3c1;cursor:pointer;border-radius:3px;padding:.21em;transition:color .18s,background .22s;line-height:1;display:flex;align-items:center;font-size:1.02rem;opacity:.85}.op-btn-group button[data-v-5a9f6db9]:hover:not(:disabled){background:#ecf6ff1a}.op-btn-group button[data-v-5a9f6db9]:disabled{cursor:not-allowed;opacity:.5}.copy-code-button[data-v-5a9f6db9]{color:#8fa3c1}.publish-html-button[data-v-5a9f6db9]{color:#10b981}.preview-html-button[data-v-5a9f6db9]{color:#3486ff}.close-preview-button[data-v-5a9f6db9]{color:#ff5d5b;background:#fae8e410}.fullscreen-button[data-v-5a9f6db9]{color:#8b5cf6}.exit-fullscreen-button[data-v-5a9f6db9]{color:#f59e0b}pre[data-v-5a9f6db9],code[data-v-5a9f6db9]{background:transparent;border-radius:0;font-family:JetBrains Mono,Fira Code,Consolas,Menlo,Monaco,monospace;font-size:.92em;line-height:1.6;color:#ddd}pre[data-v-5a9f6db9]{margin:0;padding:1rem;overflow-x:auto;background:#1e1e2e}pre[data-v-5a9f6db9]::-webkit-scrollbar{height:8px;background:transparent}pre[data-v-5a9f6db9]::-webkit-scrollbar-thumb{background-color:#8889;border-radius:4px}pre[data-v-5a9f6db9]::-webkit-scrollbar-thumb:hover{background-color:#888888e6}.iframe-wrapper[data-v-5a9f6db9]{width:100%;overflow:hidden;display:block;position:relative;border:1px solid #ddd;background:#fff;transition:height .1s ease-out}.html-preview-iframe[data-v-5a9f6db9]{width:100%;height:100%;border:none;background:#fff;display:block}.code-block-wrapper.fullscreen[data-v-5a9f6db9]{position:fixed;inset:0;width:100vw;height:100vh;z-index:9999;margin:0;border-radius:0;display:flex;flex-direction:column}.code-block-wrapper.fullscreen .code-block-header[data-v-5a9f6db9]{flex-shrink:0}.code-block-wrapper.fullscreen pre[data-v-5a9f6db9]{flex:1;overflow:auto;max-height:none}.code-block-wrapper.fullscreen .iframe-wrapper[data-v-5a9f6db9]{flex:1;height:auto!important;border:none}.code-block-wrapper.fullscreen .html-preview-iframe[data-v-5a9f6db9]{height:100%}.code-block-wrapper.code-block-wrapper pre{background-color:#1e1e2e!important;background:#1e1e2e!important}.code-block-wrapper.code-block-wrapper code{background:transparent!important}.code-block-wrapper pre[class*=language-]{background-color:#1e1e2e!important;background:#1e1e2e!important}.code-block-wrapper code[class*=language-]{background:transparent!important;background-color:transparent!important}.code-block-wrapper pre,.code-block-wrapper pre code,.code-block-wrapper pre[class*=language-],.code-block-wrapper code[class*=language-],.markdown-body .code-block-wrapper pre,.markdown-body .code-block-wrapper pre[class*=language-]{background-color:#1e1e2e!important;background:#1e1e2e!important}.mermaid-diagram-wrapper[data-v-d873f163]{margin:1em 0;background:var(--mermaid-bg, #f9f9f9);border-radius:8px;border:1px solid var(--mermaid-border, #e0e0e0);position:relative}.mermaid-controls[data-v-d873f163]{position:absolute;top:8px;right:8px;z-index:10;display:flex;gap:8px;align-items:center;background:#ffffffe6;padding:4px 8px;border-radius:6px;box-shadow:0 2px 8px #0000001a}.control-btn[data-v-d873f163]{background:transparent;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;color:#555;transition:color .2s,background .2s;border-radius:4px}.control-btn[data-v-d873f163]:hover{background:#0000000d;color:#000}.zoom-indicator[data-v-d873f163]{font-size:12px;color:#666;min-width:45px;text-align:center;font-weight:500}.mermaid-diagram[data-v-d873f163]{position:relative;overflow:hidden;padding:1em;cursor:grab;-webkit-user-select:none;user-select:none}.mermaid-diagram.is-dragging[data-v-d873f163]{cursor:grabbing}.mermaid-diagram.is-fullscreen[data-v-d873f163]{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:9999;background:var(--mermaid-bg, #f9f9f9);margin:0;border-radius:0;display:flex;align-items:center;justify-content:center}.mermaid-diagram-content[data-v-d873f163]{display:flex;justify-content:center;align-items:flex-start;width:100%}.mermaid-diagram.is-fullscreen .mermaid-diagram-content[data-v-d873f163]{align-items:center;height:100%}.mermaid-diagram-content[data-v-d873f163] svg{display:block;height:auto;max-width:100%}.mermaid-error[data-v-d873f163]{color:#c00;background:#fff0f0;padding:1em;border-radius:4px;margin:1em}.mermaid-error pre[data-v-d873f163]{margin:.5em 0 0;font-size:.9em;white-space:pre-wrap}.theme-dark{background:#1e1e1ef2}.theme-dark{color:#bbb}.theme-dark{background:#ffffff1a;color:#fff}.theme-dark{color:#999}@media (prefers-color-scheme: dark){.mermaid-controls[data-v-d873f163],.mermaid-diagram.is-fullscreen[data-v-d873f163]{background:#1e1e1ef2}.control-btn[data-v-d873f163]{color:#bbb}.control-btn[data-v-d873f163]:hover{background:#ffffff1a;color:#fff}.zoom-indicator[data-v-d873f163]{color:#999}}.blinking-cursor[data-v-0d4d5123]{display:inline-block;animation:blink-0d4d5123 1.2s ease-in-out infinite;vertical-align:text-bottom;margin-left:2px}.cursor-square[data-v-0d4d5123]{width:8px;height:1.2em}.cursor-line[data-v-0d4d5123]{width:2px;height:1.2em}.cursor-circle[data-v-0d4d5123]{width:8px;height:8px;border-radius:50%;vertical-align:baseline}@keyframes blink-0d4d5123{0%,to{opacity:1}50%{opacity:.2}}.mio-image-viewer[data-v-768757af]{display:inline-block;max-width:100%;height:auto}/*!
2
2
  * Viewer.js v1.11.7
3
3
  * https://fengyuanchen.github.io/viewerjs
4
4
  *
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("../viewer-Canph5Zx.cjs"),t=require("vue"),m=["src","alt","title","data-original","data-index"],f=t.defineComponent({__name:"ImageViewer",props:{src:{},alt:{default:""},title:{default:""},index:{default:0},context:{}},setup(i){const l=i,n=t.ref(null),e=t.inject("imageViewerManager",null),o={cursor:"pointer",maxWidth:"100%",height:"auto"},g=t.computed(()=>{const r=l.context?.images||[];if(l.src&&r.length>0){const a=r.findIndex(u=>u.src===l.src);return a!==-1?a:0}return 0});t.onMounted(()=>{e&&n.value&&e.registerImage(n.value)}),t.onUnmounted(()=>{e&&n.value&&e.unregisterImage(n.value)});function s(){e&&e.show(g.value)}return(r,a)=>(t.openBlock(),t.createElementBlock("img",{ref_key:"imgRef",ref:n,src:i.src,alt:i.alt,title:i.title,"data-original":i.src,"data-index":i.index,class:"mio-image-viewer",style:o,onClick:s},null,8,m))}}),x=c._export_sfc(f,[["__scopeId","data-v-ed2e02d9"]]);function h(i){const{priority:l=50,viewerOptions:n={}}=i||{};return{name:"imageViewer",priority:l,test:e=>e.type==="tag"&&e.name==="img",render:(e,o,g,s)=>{const r=s?.images||[];let a=0;if(e.attribs?.src&&r.length>0){const u=r.findIndex(d=>d.src===e.attribs?.src);u!==-1&&(a=u)}return t.h(x,{src:e.attribs?.src||"",alt:e.attribs?.alt||"",title:e.attribs?.title||"",index:a,viewerOptions:n,context:s})}}}exports.codeBlockPlugin=c.codeBlockPlugin;exports.cursorPlugin=c.cursorPlugin;exports.emojiPlugin=c.emojiPlugin;exports.mermaidPlugin=c.mermaidPlugin;exports.imageViewerPlugin=h;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("../viewer-Canph5Zx.cjs"),t=require("vue"),f=["src","alt","title","data-original","data-index","crossorigin"],h=t.defineComponent({__name:"ImageViewer",props:{src:{},alt:{default:""},title:{default:""},index:{default:0},crossOrigin:{type:Boolean,default:!1},context:{}},setup(i){const r=i,a=t.ref(null),n=t.inject("imageViewerManager",null),e={cursor:"pointer",maxWidth:"100%",height:"auto"},g=t.computed(()=>{const s=r.context?.images||[];if(r.src&&s.length>0){const c=s.findIndex(u=>u.src===r.src);return c!==-1?c:0}return 0}),d=t.computed(()=>r.crossOrigin?r.src.startsWith("http")&&!r.src.includes(window.location.host):!1);t.onMounted(()=>{n&&a.value&&n.registerImage(a.value)}),t.onUnmounted(()=>{n&&a.value&&n.unregisterImage(a.value)});function o(){n&&n.show(g.value)}return(s,c)=>(t.openBlock(),t.createElementBlock("img",{ref_key:"imgRef",ref:a,src:i.src,alt:i.alt,title:i.title,"data-original":i.src,"data-index":i.index,class:"mio-image-viewer",crossorigin:d.value?"anonymous":void 0,style:e,onClick:o},null,8,f))}}),x=l._export_sfc(h,[["__scopeId","data-v-768757af"]]);function v(i){const{priority:r=50,viewerOptions:a={},crossOrigin:n=!1}=i||{};return{name:"imageViewer",priority:r,test:e=>e.type==="tag"&&e.name==="img",render:(e,g,d,o)=>{const s=o?.images||[];let c=0;if(e.attribs?.src&&s.length>0){const u=s.findIndex(m=>m.src===e.attribs?.src);u!==-1&&(c=u)}return t.h(x,{src:e.attribs?.src||"",alt:e.attribs?.alt||"",title:e.attribs?.title||"",index:c,viewerOptions:a,crossOrigin:n,context:o})}}}exports.codeBlockPlugin=l.codeBlockPlugin;exports.cursorPlugin=l.cursorPlugin;exports.emojiPlugin=l.emojiPlugin;exports.mermaidPlugin=l.mermaidPlugin;exports.imageViewerPlugin=v;
@@ -1,81 +1,85 @@
1
- import { aF as u } from "../viewer-CZUSHxhf.js";
2
- import { aG as M, aJ as j, aH as R, aI as E } from "../viewer-CZUSHxhf.js";
3
- import { defineComponent as g, ref as d, inject as f, computed as x, onMounted as h, onUnmounted as I, createElementBlock as v, openBlock as w, h as p } from "vue";
4
- const k = ["src", "alt", "title", "data-original", "data-index"], b = /* @__PURE__ */ g({
1
+ import { aF as d } from "../viewer-CZUSHxhf.js";
2
+ import { aG as M, aJ as R, aH as j, aI as S } from "../viewer-CZUSHxhf.js";
3
+ import { defineComponent as f, ref as x, inject as h, computed as g, onMounted as v, onUnmounted as w, createElementBlock as p, openBlock as I, h as y } from "vue";
4
+ const k = ["src", "alt", "title", "data-original", "data-index", "crossorigin"], b = /* @__PURE__ */ f({
5
5
  __name: "ImageViewer",
6
6
  props: {
7
7
  src: {},
8
8
  alt: { default: "" },
9
9
  title: { default: "" },
10
10
  index: { default: 0 },
11
+ crossOrigin: { type: Boolean, default: !1 },
11
12
  context: {}
12
13
  },
13
14
  setup(t) {
14
- const n = t, i = d(null), e = f("imageViewerManager", null), l = {
15
+ const i = t, n = x(null), r = h("imageViewerManager", null), e = {
15
16
  cursor: "pointer",
16
17
  maxWidth: "100%",
17
18
  height: "auto"
18
- }, o = x(() => {
19
- const a = n.context?.images || [];
20
- if (n.src && a.length > 0) {
21
- const r = a.findIndex((c) => c.src === n.src);
22
- return r !== -1 ? r : 0;
19
+ }, l = g(() => {
20
+ const a = i.context?.images || [];
21
+ if (i.src && a.length > 0) {
22
+ const s = a.findIndex((c) => c.src === i.src);
23
+ return s !== -1 ? s : 0;
23
24
  }
24
25
  return 0;
26
+ }), u = g(() => i.crossOrigin ? i.src.startsWith("http") && !i.src.includes(window.location.host) : !1);
27
+ v(() => {
28
+ r && n.value && r.registerImage(n.value);
29
+ }), w(() => {
30
+ r && n.value && r.unregisterImage(n.value);
25
31
  });
26
- h(() => {
27
- e && i.value && e.registerImage(i.value);
28
- }), I(() => {
29
- e && i.value && e.unregisterImage(i.value);
30
- });
31
- function s() {
32
- e && e.show(o.value);
32
+ function o() {
33
+ r && r.show(l.value);
33
34
  }
34
- return (a, r) => (w(), v("img", {
35
+ return (a, s) => (I(), p("img", {
35
36
  ref_key: "imgRef",
36
- ref: i,
37
+ ref: n,
37
38
  src: t.src,
38
39
  alt: t.alt,
39
40
  title: t.title,
40
41
  "data-original": t.src,
41
42
  "data-index": t.index,
42
43
  class: "mio-image-viewer",
43
- style: l,
44
- onClick: s
44
+ crossorigin: u.value ? "anonymous" : void 0,
45
+ style: e,
46
+ onClick: o
45
47
  }, null, 8, k));
46
48
  }
47
- }), y = /* @__PURE__ */ u(b, [["__scopeId", "data-v-ed2e02d9"]]);
48
- function _(t) {
49
+ }), C = /* @__PURE__ */ d(b, [["__scopeId", "data-v-768757af"]]);
50
+ function V(t) {
49
51
  const {
50
- priority: n = 50,
51
- viewerOptions: i = {}
52
+ priority: i = 50,
53
+ viewerOptions: n = {},
54
+ crossOrigin: r = !1
52
55
  } = t || {};
53
56
  return {
54
57
  name: "imageViewer",
55
- priority: n,
58
+ priority: i,
56
59
  test: (e) => e.type === "tag" && e.name === "img",
57
- render: (e, l, o, s) => {
58
- const a = s?.images || [];
59
- let r = 0;
60
+ render: (e, l, u, o) => {
61
+ const a = o?.images || [];
62
+ let s = 0;
60
63
  if (e.attribs?.src && a.length > 0) {
61
64
  const c = a.findIndex((m) => m.src === e.attribs?.src);
62
- c !== -1 && (r = c);
65
+ c !== -1 && (s = c);
63
66
  }
64
- return p(y, {
67
+ return y(C, {
65
68
  src: e.attribs?.src || "",
66
69
  alt: e.attribs?.alt || "",
67
70
  title: e.attribs?.title || "",
68
- index: r,
69
- viewerOptions: i,
70
- context: s
71
+ index: s,
72
+ viewerOptions: n,
73
+ crossOrigin: r,
74
+ context: o
71
75
  });
72
76
  }
73
77
  };
74
78
  }
75
79
  export {
76
80
  M as codeBlockPlugin,
77
- j as cursorPlugin,
78
- R as emojiPlugin,
79
- _ as imageViewerPlugin,
80
- E as mermaidPlugin
81
+ R as cursorPlugin,
82
+ j as emojiPlugin,
83
+ V as imageViewerPlugin,
84
+ S as mermaidPlugin
81
85
  };
@@ -13,6 +13,11 @@ export interface ImageViewerPluginOptions {
13
13
  * @see https://github.com/fengyuanchen/viewerjs#options
14
14
  */
15
15
  viewerOptions?: any;
16
+ /**
17
+ * 是否开启跨域支持(默认 false)
18
+ * 如果开启,外部图片将添加 crossorigin="anonymous" 属性
19
+ */
20
+ crossOrigin?: boolean;
16
21
  }
17
22
  /**
18
23
  * imageViewerPlugin - 图片预览插件
@@ -31,6 +36,7 @@ export interface ImageViewerPluginOptions {
31
36
  * @param options - 插件配置选项
32
37
  * @param options.priority - 优先级(默认 50)
33
38
  * @param options.viewerOptions - Viewer.js 配置选项
39
+ * @param options.crossOrigin - 是否开启跨域支持(默认 false)
34
40
  * @returns CustomPlugin
35
41
  *
36
42
  * @example
@@ -48,7 +54,8 @@ export interface ImageViewerPluginOptions {
48
54
  * title: true,
49
55
  * button: true,
50
56
  * loop: false
51
- * }
57
+ * },
58
+ * crossOrigin: true
52
59
  * }
53
60
  * }
54
61
  * ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mio-previewer",
3
- "version": "0.2.71",
3
+ "version": "0.2.73",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {