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 +122 -157
- package/README.zh-CN.md +85 -110
- package/dist/mio-previewer.css +1 -1
- package/dist/plugins/custom.cjs.js +1 -1
- package/dist/plugins/custom.es.js +42 -38
- package/dist/types/plugins/custom/imageViewerPlugin.d.ts +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
# mio-previewer
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
✨ **Core Value Proposition:**
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
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
|
|
37
|
-
import { MdRenderer } from
|
|
38
|
-
import
|
|
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(
|
|
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
|
|
58
|
-
import { MdRenderer } from
|
|
59
|
-
import
|
|
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 =
|
|
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
|
|
88
|
-
import { katexPlugin } from
|
|
89
|
-
import
|
|
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
|
|
120
|
-
import { AlertPlugin } from
|
|
121
|
-
import
|
|
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
|
|
159
|
-
import { CodeBlockPlugin } from
|
|
160
|
-
import
|
|
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
|
|
191
|
-
import { mermaidPlugin } from
|
|
192
|
-
import
|
|
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
|
|
228
|
-
import
|
|
229
|
-
import
|
|
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
|
|
240
|
-
import { EmojiPlugin } from
|
|
241
|
-
import
|
|
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 =
|
|
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
|
|
261
|
-
import { MdRenderer } from
|
|
262
|
-
import { AlertPlugin, katexPlugin } from
|
|
263
|
-
import {
|
|
264
|
-
|
|
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:
|
|
289
|
+
name: "highlight",
|
|
320
290
|
priority: 50,
|
|
321
291
|
test: (node) => {
|
|
322
|
-
return node.type ===
|
|
323
|
-
node.name === 'mark'
|
|
292
|
+
return node.type === "tag" && node.name === "mark";
|
|
324
293
|
},
|
|
325
294
|
render: (node, renderChildren, h) => {
|
|
326
|
-
return h(
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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(
|
|
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
|
|
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
|
|
365
|
-
|
|
366
|
-
| `md`
|
|
367
|
-
| `isStreaming`
|
|
368
|
-
| `useWorker`
|
|
369
|
-
| `customPlugins`
|
|
370
|
-
| `markdownItPlugins` | `MarkdownItPluginConfig[]` | `[]`
|
|
371
|
-
| `markdownItOptions` | `object`
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
411
|
-
|
|
412
|
-
| `mermaidPlugin`
|
|
413
|
-
| `CodeBlockPlugin`
|
|
414
|
-
| `EmojiPlugin`
|
|
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,
|
|
425
|
-
linkify: true,
|
|
395
|
+
html: true, // Enable HTML tags
|
|
396
|
+
linkify: true, // Auto-convert URLs
|
|
426
397
|
typographer: true, // Smart quotes, dashes
|
|
427
|
-
breaks: false
|
|
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
|
-
|
|
1
|
+
### mio-previewer
|
|
2
2
|
|
|
3
3
|
[English](./README.md) | 中文文档
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
一个专门为 **AI 流式对话 (Streaming Responses)** 设计的 Vue 3 Markdown 渲染引擎。它将标准的 Markdown 文本转换为可响应的 Vue VNode 树,利用 Vue 的 Diff 算法实现丝滑的增量 DOM 更新,彻底规避 `v-html` 带来的性能抖动和状态丢失。
|
|
6
6
|
|
|
7
|
-
✨
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
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
|
|
83
|
-
import { MdRenderer } from
|
|
84
|
-
import
|
|
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 =
|
|
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
|
|
244
|
-
import { EmojiPlugin } from
|
|
245
|
-
import
|
|
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 =
|
|
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:
|
|
303
|
+
name: "highlight",
|
|
324
304
|
priority: 50,
|
|
325
305
|
test: (node) => {
|
|
326
|
-
return node.type ===
|
|
327
|
-
node.name === 'mark'
|
|
306
|
+
return node.type === "tag" && node.name === "mark";
|
|
328
307
|
},
|
|
329
308
|
render: (node, renderChildren, h) => {
|
|
330
|
-
return h(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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(
|
|
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
|
|
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\`
|
|
371
|
-
| \`isStreaming\`
|
|
372
|
-
| \`useWorker\`
|
|
373
|
-
| \`customPlugins\`
|
|
374
|
-
| \`markdownItPlugins\` | \`MarkdownItPluginConfig[]\` | \`[]\`
|
|
375
|
-
| \`markdownItOptions\` | \`object\`
|
|
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(
|
|
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\`
|
|
417
|
-
| \`CodeBlockPlugin\` | \`mio-previewer/plugins/custom\` | Prism 语法高亮
|
|
418
|
-
| \`EmojiPlugin\`
|
|
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,
|
|
428
|
-
linkify: true,
|
|
408
|
+
html: true, // 启用 HTML 标签
|
|
409
|
+
linkify: true, // 自动转换 URL
|
|
429
410
|
typographer: true, // 智能引号、破折号
|
|
430
|
-
breaks: false
|
|
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
|
|
package/dist/mio-previewer.css
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
|
2
|
-
import { aG as M, aJ as
|
|
3
|
-
import { defineComponent as
|
|
4
|
-
const k = ["src", "alt", "title", "data-original", "data-index"], b = /* @__PURE__ */
|
|
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
|
|
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
|
-
},
|
|
19
|
-
const a =
|
|
20
|
-
if (
|
|
21
|
-
const
|
|
22
|
-
return
|
|
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
|
-
|
|
27
|
-
|
|
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,
|
|
35
|
+
return (a, s) => (I(), p("img", {
|
|
35
36
|
ref_key: "imgRef",
|
|
36
|
-
ref:
|
|
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
|
-
|
|
44
|
-
|
|
44
|
+
crossorigin: u.value ? "anonymous" : void 0,
|
|
45
|
+
style: e,
|
|
46
|
+
onClick: o
|
|
45
47
|
}, null, 8, k));
|
|
46
48
|
}
|
|
47
|
-
}),
|
|
48
|
-
function
|
|
49
|
+
}), C = /* @__PURE__ */ d(b, [["__scopeId", "data-v-768757af"]]);
|
|
50
|
+
function V(t) {
|
|
49
51
|
const {
|
|
50
|
-
priority:
|
|
51
|
-
viewerOptions:
|
|
52
|
+
priority: i = 50,
|
|
53
|
+
viewerOptions: n = {},
|
|
54
|
+
crossOrigin: r = !1
|
|
52
55
|
} = t || {};
|
|
53
56
|
return {
|
|
54
57
|
name: "imageViewer",
|
|
55
|
-
priority:
|
|
58
|
+
priority: i,
|
|
56
59
|
test: (e) => e.type === "tag" && e.name === "img",
|
|
57
|
-
render: (e, l,
|
|
58
|
-
const a =
|
|
59
|
-
let
|
|
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 && (
|
|
65
|
+
c !== -1 && (s = c);
|
|
63
66
|
}
|
|
64
|
-
return
|
|
67
|
+
return y(C, {
|
|
65
68
|
src: e.attribs?.src || "",
|
|
66
69
|
alt: e.attribs?.alt || "",
|
|
67
70
|
title: e.attribs?.title || "",
|
|
68
|
-
index:
|
|
69
|
-
viewerOptions:
|
|
70
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
* ```
|