@eternalheart/vue-file-preview 1.4.0 → 1.5.0
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 +490 -48
- package/README.zh-CN.md +477 -35
- package/lib/FilePreviewContent.vue.d.ts +4 -1
- package/lib/FilePreviewContent.vue.d.ts.map +1 -1
- package/lib/FilePreviewEmbed.vue.d.ts +4 -1
- package/lib/FilePreviewEmbed.vue.d.ts.map +1 -1
- package/lib/FilePreviewModal.vue.d.ts +3 -0
- package/lib/FilePreviewModal.vue.d.ts.map +1 -1
- package/lib/chunks/base.types-DaftHyg7.mjs +26 -0
- package/lib/chunks/base.types-DaftHyg7.mjs.map +1 -0
- package/lib/chunks/index-B4Qrpue-.mjs +363 -0
- package/lib/chunks/index-B4Qrpue-.mjs.map +1 -0
- package/lib/chunks/{index-OOoaYpkl.mjs → index-B4_GrkcL.mjs} +1015 -1013
- package/lib/chunks/{index-OOoaYpkl.mjs.map → index-B4_GrkcL.mjs.map} +1 -1
- package/lib/chunks/{index-CoITLoYL.mjs → index-BBTQY-NC.mjs} +20 -18
- package/lib/chunks/index-BBTQY-NC.mjs.map +1 -0
- package/lib/chunks/index-BLv4-Akb.mjs +88 -0
- package/lib/chunks/index-BLv4-Akb.mjs.map +1 -0
- package/lib/chunks/{index-By6vGdnK.mjs → index-ByL3tKKJ.mjs} +39 -37
- package/lib/chunks/index-ByL3tKKJ.mjs.map +1 -0
- package/lib/chunks/{index-BcKI75RW.mjs → index-C8YVDqOn.mjs} +102 -99
- package/lib/chunks/index-C8YVDqOn.mjs.map +1 -0
- package/lib/chunks/index-CcwQsOlb.mjs +301 -0
- package/lib/chunks/index-CcwQsOlb.mjs.map +1 -0
- package/lib/chunks/index-Cl0RCSLM.mjs +2325 -0
- package/lib/chunks/index-Cl0RCSLM.mjs.map +1 -0
- package/lib/chunks/{index-B-LW-GJO.mjs → index-CmQF0hIS.mjs} +37 -35
- package/lib/chunks/index-CmQF0hIS.mjs.map +1 -0
- package/lib/chunks/{index-DAdO2lkI.mjs → index-CuoBDJt9.mjs} +158 -156
- package/lib/chunks/index-CuoBDJt9.mjs.map +1 -0
- package/lib/chunks/{index-nFIiu1lA.mjs → index-CygGEOtD.mjs} +634 -632
- package/lib/chunks/{index-nFIiu1lA.mjs.map → index-CygGEOtD.mjs.map} +1 -1
- package/lib/chunks/index-D8PZWag-.mjs +388 -0
- package/lib/chunks/index-D8PZWag-.mjs.map +1 -0
- package/lib/chunks/{index-jcgLzyDR.mjs → index-DKmx5DoZ.mjs} +135 -133
- package/lib/chunks/{index-jcgLzyDR.mjs.map → index-DKmx5DoZ.mjs.map} +1 -1
- package/lib/chunks/{index-DGqDJMot.mjs → index-DfJJqrP7.mjs} +28 -26
- package/lib/chunks/index-DfJJqrP7.mjs.map +1 -0
- package/lib/chunks/{index-BT9hXA6r.mjs → index-Dnke7icG.mjs} +43 -41
- package/lib/chunks/index-Dnke7icG.mjs.map +1 -0
- package/lib/chunks/index-DpZ3hWAS.mjs +116 -0
- package/lib/chunks/index-DpZ3hWAS.mjs.map +1 -0
- package/lib/chunks/index-FzAEFyPz.mjs +153 -0
- package/lib/chunks/index-FzAEFyPz.mjs.map +1 -0
- package/lib/chunks/index-XbqznrKc.mjs +320 -0
- package/lib/chunks/index-XbqznrKc.mjs.map +1 -0
- package/lib/chunks/index-rpZSCYgw.mjs +111 -0
- package/lib/chunks/index-rpZSCYgw.mjs.map +1 -0
- package/lib/chunks/useShikiHighlight-ByAb3sWs.mjs +41 -0
- package/lib/chunks/useShikiHighlight-ByAb3sWs.mjs.map +1 -0
- package/lib/composables/useShikiHighlight.d.ts +18 -0
- package/lib/composables/useShikiHighlight.d.ts.map +1 -0
- package/lib/index.cjs +35 -33
- package/lib/index.cjs.map +1 -1
- package/lib/index.css +1 -1
- package/lib/index.mjs +1 -1
- package/lib/renderers/Audio/index.vue.d.ts +5 -1
- package/lib/renderers/Audio/index.vue.d.ts.map +1 -1
- package/lib/renderers/Csv/index.vue.d.ts +5 -1
- package/lib/renderers/Csv/index.vue.d.ts.map +1 -1
- package/lib/renderers/Docx/index.vue.d.ts +5 -1
- package/lib/renderers/Docx/index.vue.d.ts.map +1 -1
- package/lib/renderers/Epub/index.vue.d.ts +4 -11
- package/lib/renderers/Epub/index.vue.d.ts.map +1 -1
- package/lib/renderers/Font/index.vue.d.ts +5 -1
- package/lib/renderers/Font/index.vue.d.ts.map +1 -1
- package/lib/renderers/Image/index.vue.d.ts +5 -12
- package/lib/renderers/Image/index.vue.d.ts.map +1 -1
- package/lib/renderers/Json/index.vue.d.ts +5 -1
- package/lib/renderers/Json/index.vue.d.ts.map +1 -1
- package/lib/renderers/Markdown/index.vue.d.ts +5 -2
- package/lib/renderers/Markdown/index.vue.d.ts.map +1 -1
- package/lib/renderers/Mobi/index.vue.d.ts +5 -12
- package/lib/renderers/Mobi/index.vue.d.ts.map +1 -1
- package/lib/renderers/Msg/index.vue.d.ts +5 -1
- package/lib/renderers/Msg/index.vue.d.ts.map +1 -1
- package/lib/renderers/Pdf/index.vue.d.ts +5 -14
- package/lib/renderers/Pdf/index.vue.d.ts.map +1 -1
- package/lib/renderers/Pptx/index.vue.d.ts +5 -1
- package/lib/renderers/Pptx/index.vue.d.ts.map +1 -1
- package/lib/renderers/Subtitle/index.vue.d.ts +5 -1
- package/lib/renderers/Subtitle/index.vue.d.ts.map +1 -1
- package/lib/renderers/Text/index.vue.d.ts +5 -6
- package/lib/renderers/Text/index.vue.d.ts.map +1 -1
- package/lib/renderers/Unsupported/index.vue.d.ts +5 -1
- package/lib/renderers/Unsupported/index.vue.d.ts.map +1 -1
- package/lib/renderers/Video/index.vue.d.ts +5 -1
- package/lib/renderers/Video/index.vue.d.ts.map +1 -1
- package/lib/renderers/Xlsx/index.vue.d.ts +5 -1
- package/lib/renderers/Xlsx/index.vue.d.ts.map +1 -1
- package/lib/renderers/Xml/index.vue.d.ts +5 -1
- package/lib/renderers/Xml/index.vue.d.ts.map +1 -1
- package/lib/renderers/Zip/index.vue.d.ts +10 -6
- package/lib/renderers/Zip/index.vue.d.ts.map +1 -1
- package/lib/renderers/base.types.d.ts +59 -0
- package/lib/renderers/base.types.d.ts.map +1 -0
- package/lib/renderers/registry.d.ts +36 -0
- package/lib/renderers/registry.d.ts.map +1 -0
- package/lib/renderers/toolbar.types.d.ts +1 -0
- package/lib/renderers/toolbar.types.d.ts.map +1 -1
- package/package.json +3 -3
- package/lib/chunks/index-8ETACZ6T.mjs +0 -267
- package/lib/chunks/index-8ETACZ6T.mjs.map +0 -1
- package/lib/chunks/index-B-LW-GJO.mjs.map +0 -1
- package/lib/chunks/index-BT9hXA6r.mjs.map +0 -1
- package/lib/chunks/index-BcKI75RW.mjs.map +0 -1
- package/lib/chunks/index-By6vGdnK.mjs.map +0 -1
- package/lib/chunks/index-C-Mu0fvH.mjs +0 -202
- package/lib/chunks/index-C-Mu0fvH.mjs.map +0 -1
- package/lib/chunks/index-CGSq7-Q-.mjs +0 -279
- package/lib/chunks/index-CGSq7-Q-.mjs.map +0 -1
- package/lib/chunks/index-CWpU8prZ.mjs +0 -74
- package/lib/chunks/index-CWpU8prZ.mjs.map +0 -1
- package/lib/chunks/index-CYa_XneH.mjs +0 -303
- package/lib/chunks/index-CYa_XneH.mjs.map +0 -1
- package/lib/chunks/index-CbJkkdEB.mjs +0 -114
- package/lib/chunks/index-CbJkkdEB.mjs.map +0 -1
- package/lib/chunks/index-CoITLoYL.mjs.map +0 -1
- package/lib/chunks/index-D9QQL7U6.mjs +0 -2506
- package/lib/chunks/index-D9QQL7U6.mjs.map +0 -1
- package/lib/chunks/index-DAdO2lkI.mjs.map +0 -1
- package/lib/chunks/index-DGqDJMot.mjs.map +0 -1
- package/lib/chunks/index-DTyG1Wzp.mjs +0 -81
- package/lib/chunks/index-DTyG1Wzp.mjs.map +0 -1
- package/lib/chunks/index-DglMeKJM.mjs +0 -130
- package/lib/chunks/index-DglMeKJM.mjs.map +0 -1
- package/lib/renderers/Epub/toolbar.d.ts +0 -16
- package/lib/renderers/Epub/toolbar.d.ts.map +0 -1
- package/lib/renderers/Image/toolbar.d.ts +0 -15
- package/lib/renderers/Image/toolbar.d.ts.map +0 -1
- package/lib/renderers/Markdown/toolbar.d.ts +0 -9
- package/lib/renderers/Markdown/toolbar.d.ts.map +0 -1
- package/lib/renderers/Mobi/toolbar.d.ts +0 -16
- package/lib/renderers/Mobi/toolbar.d.ts.map +0 -1
- package/lib/renderers/Pdf/toolbar.d.ts +0 -16
- package/lib/renderers/Pdf/toolbar.d.ts.map +0 -1
- package/lib/renderers/Text/toolbar.d.ts +0 -12
- package/lib/renderers/Text/toolbar.d.ts.map +0 -1
- package/lib/renderers/Zip/toolbar.d.ts +0 -13
- package/lib/renderers/Zip/toolbar.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -4,23 +4,22 @@ English | [简体中文](./README.zh-CN.md)
|
|
|
4
4
|
|
|
5
5
|
A modern, feature-rich file preview component for Vue 3 with support for images, videos, audio, PDFs, Office documents (Word, Excel, PowerPoint), Markdown, and code files.
|
|
6
6
|
|
|
7
|
-
## ✨ Features
|
|
8
|
-
|
|
9
|
-
- 🎨 **Modern UI** -
|
|
10
|
-
- 📁 **Multi-format Support** - Supports 20+ file formats
|
|
11
|
-
- 🪟 **Two Display Modes** - Full-screen modal **or** inline embedded preview
|
|
12
|
-
- 🖼️ **Powerful Image Viewer** - Zoom, rotate, drag, mouse wheel zoom
|
|
13
|
-
- 🎬 **Custom Video Player** - Built on Video.js, supports multiple video formats
|
|
14
|
-
- 🎵 **Custom Audio Player** - Beautiful audio control interface
|
|
15
|
-
- 📄 **PDF Viewer** - Pagination support
|
|
16
|
-
- 📊 **Office Documents Support** - Word, Excel, PowerPoint file preview
|
|
17
|
-
- 📝 **Markdown Rendering** - GitHub Flavored Markdown support
|
|
18
|
-
- 💻 **Code Highlighting** - Supports 40+ programming languages
|
|
19
|
-
- 📱 **Responsive Design** - Adapts to all screen sizes
|
|
20
|
-
- ⌨️ **Keyboard Navigation** - Arrow keys and ESC support
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## 📦 Installation
|
|
7
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2728.svg" width="20" height="20" alt="✨" /> Features
|
|
8
|
+
|
|
9
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3a8.svg" width="16" height="16" alt="🎨" style="vertical-align: middle;" /> **Modern UI** - Clean and modern interface with smooth animations
|
|
10
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c1.svg" width="16" height="16" alt="📁" style="vertical-align: middle;" /> **Multi-format Support** - Supports 20+ file formats
|
|
11
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1fa9f.svg" width="16" height="16" alt="🪟" style="vertical-align: middle;" /> **Two Display Modes** - Full-screen modal **or** inline embedded preview
|
|
12
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f5bc.svg" width="16" height="16" alt="🖼️" style="vertical-align: middle;" /> **Powerful Image Viewer** - Zoom, rotate, drag, mouse wheel zoom
|
|
13
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3ac.svg" width="16" height="16" alt="🎬" style="vertical-align: middle;" /> **Custom Video Player** - Built on Video.js, supports multiple video formats
|
|
14
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3b5.svg" width="16" height="16" alt="🎵" style="vertical-align: middle;" /> **Custom Audio Player** - Beautiful audio control interface
|
|
15
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c4.svg" width="16" height="16" alt="📄" style="vertical-align: middle;" /> **PDF Viewer** - Pagination support
|
|
16
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4ca.svg" width="16" height="16" alt="📊" style="vertical-align: middle;" /> **Office Documents Support** - Word, Excel, PowerPoint file preview
|
|
17
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4dd.svg" width="16" height="16" alt="📝" style="vertical-align: middle;" /> **Markdown Rendering** - GitHub Flavored Markdown support
|
|
18
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4bb.svg" width="16" height="16" alt="💻" style="vertical-align: middle;" /> **Code Highlighting** - Supports 40+ programming languages
|
|
19
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4f1.svg" width="16" height="16" alt="📱" style="vertical-align: middle;" /> **Responsive Design** - Adapts to all screen sizes
|
|
20
|
+
- <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2328.svg" width="16" height="16" alt="⌨️" style="vertical-align: middle;" /> **Keyboard Navigation** - Arrow keys and ESC support
|
|
21
|
+
|
|
22
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4e6.svg" width="20" height="20" alt="📦" /> Installation
|
|
24
23
|
|
|
25
24
|
```bash
|
|
26
25
|
# Using npm
|
|
@@ -122,7 +121,7 @@ export default defineConfig({
|
|
|
122
121
|
|
|
123
122
|
> Note: `@jsquash/avif` is only used as a fallback when the browser does not natively support AVIF (Chrome 85+, Firefox 93+, Safari 16+ all support it natively). If your target browsers cover the native list, you can also remove `@jsquash/avif` from your dependencies entirely.
|
|
124
123
|
|
|
125
|
-
## 🚀 Quick Start
|
|
124
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f680.svg" width="20" height="20" alt="🚀" /> Quick Start
|
|
126
125
|
|
|
127
126
|
### Basic Usage
|
|
128
127
|
|
|
@@ -169,14 +168,18 @@ import { ref } from 'vue';
|
|
|
169
168
|
import { FilePreviewModal, type PreviewFileInput } from '@eternalheart/vue-file-preview';
|
|
170
169
|
import '@eternalheart/vue-file-preview/style.css';
|
|
171
170
|
|
|
171
|
+
// Assume file1 comes from a File API source: <input type="file">,
|
|
172
|
+
// drag & drop, clipboard paste, or fetch().then(r => r.blob())
|
|
173
|
+
const file1 = new File(['content'], 'example.txt', { type: 'text/plain' });
|
|
174
|
+
|
|
172
175
|
const files: PreviewFileInput[] = [
|
|
173
|
-
// 1. Native File object
|
|
176
|
+
// 1. Native File object (auto revoked when unmounted)
|
|
174
177
|
file1,
|
|
175
178
|
|
|
176
|
-
// 2. HTTP URL string
|
|
179
|
+
// 2. HTTP URL string (loaded on demand)
|
|
177
180
|
'https://example.com/image.jpg',
|
|
178
181
|
|
|
179
|
-
// 3. File object with metadata
|
|
182
|
+
// 3. File object with metadata (recommended for remote resources)
|
|
180
183
|
{
|
|
181
184
|
name: 'document.pdf',
|
|
182
185
|
type: 'application/pdf',
|
|
@@ -185,6 +188,9 @@ const files: PreviewFileInput[] = [
|
|
|
185
188
|
},
|
|
186
189
|
];
|
|
187
190
|
|
|
191
|
+
// Memory note: If you generate URLs via URL.createObjectURL(),
|
|
192
|
+
// call URL.revokeObjectURL() when files are removed to avoid memory leaks.
|
|
193
|
+
|
|
188
194
|
const isOpen = ref(true);
|
|
189
195
|
</script>
|
|
190
196
|
|
|
@@ -239,7 +245,7 @@ Differences from `FilePreviewModal`:
|
|
|
239
245
|
<FilePreviewEmbed :files="files" :width="800" :height="500" />
|
|
240
246
|
```
|
|
241
247
|
|
|
242
|
-
## 📖 Supported File Formats
|
|
248
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4d6.svg" width="20" height="20" alt="📖" /> Supported File Formats
|
|
243
249
|
|
|
244
250
|
### Images
|
|
245
251
|
- **Formats**: JPG, PNG, GIF, WebP, SVG, BMP, ICO
|
|
@@ -288,7 +294,56 @@ Differences from `FilePreviewModal`:
|
|
|
288
294
|
### E-books
|
|
289
295
|
- **EPUB**: Chapter navigation, pagination
|
|
290
296
|
|
|
291
|
-
##
|
|
297
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/26a0.svg" width="20" height="20" alt="⚠️" /> Limitations & Performance
|
|
298
|
+
|
|
299
|
+
### Support Levels
|
|
300
|
+
|
|
301
|
+
**✅ Full Support (Production Ready)**
|
|
302
|
+
- Images (JPG, PNG, GIF, WebP, SVG, BMP, ICO)
|
|
303
|
+
- Videos (MP4, WebM, OGG)
|
|
304
|
+
- Audio (MP3, WAV, OGG)
|
|
305
|
+
- PDF
|
|
306
|
+
- Markdown
|
|
307
|
+
- Code files (40+ languages via Shiki, lazy-loaded)
|
|
308
|
+
- JSON, CSV, XML
|
|
309
|
+
|
|
310
|
+
**⚠️ Partial Support (Preview Only)**
|
|
311
|
+
- **Office (DOCX, XLSX, PPTX)**: Basic layout and text rendering. Complex formatting (charts, macros, embedded objects) may not render accurately.
|
|
312
|
+
- **ZIP**: Directory tree browsing + inline preview for text/code/images. Large archives (>100MB) may cause performance issues.
|
|
313
|
+
- **Fonts (TTF, OTF, WOFF)**: Metadata + character preview. Full font feature testing not supported.
|
|
314
|
+
|
|
315
|
+
**🧪 Experimental**
|
|
316
|
+
- **MSG (Outlook Email)**: Headers and plain text body. Complex HTML body may not render correctly.
|
|
317
|
+
- **EPUB**: Basic chapter navigation. CSS styling may differ from native readers. DRM-protected files not supported.
|
|
318
|
+
- **Subtitle formats (SRT, ASS, TTML, LRC)**: Text display only. No video sync or advanced styling.
|
|
319
|
+
|
|
320
|
+
### Performance Boundaries
|
|
321
|
+
|
|
322
|
+
| File Size | Status | Notes |
|
|
323
|
+
|-----------|--------|-------|
|
|
324
|
+
| < 50MB | ✅ Recommended | Smooth preview experience |
|
|
325
|
+
| 50-100MB | ⚠️ May lag | UI may become unresponsive during load |
|
|
326
|
+
| > 100MB | ❌ Not recommended | Browser memory limits may be exceeded |
|
|
327
|
+
|
|
328
|
+
**Special Cases:**
|
|
329
|
+
- **ZIP archives**: Performance depends on file count, not just size
|
|
330
|
+
- **Office documents**: Complex files (>200 pages, heavy images) may timeout
|
|
331
|
+
- **Code highlighting**: Files >5MB may take 3-5s to highlight
|
|
332
|
+
|
|
333
|
+
### Browser Compatibility
|
|
334
|
+
|
|
335
|
+
**Minimum Requirements:**
|
|
336
|
+
- Chrome 90+ / Edge 90+
|
|
337
|
+
- Firefox 88+
|
|
338
|
+
- Safari 14+
|
|
339
|
+
|
|
340
|
+
**Known Limitations:**
|
|
341
|
+
- **Safari iOS**: Video autoplay requires user interaction
|
|
342
|
+
- **Firefox**: AVIF support requires Firefox 93+ (fallback decoder included)
|
|
343
|
+
- **Office formats**: Rendering quality varies across browsers
|
|
344
|
+
- **EPUB**: Some CSS features unsupported in older browsers
|
|
345
|
+
|
|
346
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3ae.svg" width="20" height="20" alt="🎮" /> API Reference
|
|
292
347
|
|
|
293
348
|
### FilePreviewModal Props
|
|
294
349
|
|
|
@@ -302,6 +357,7 @@ Differences from `FilePreviewModal`:
|
|
|
302
357
|
| `messages` | `Partial<Record<Locale, Partial<Messages>>>` | ❌ | Custom translation overrides |
|
|
303
358
|
| `headless` | `boolean` | ❌ | Headless mode — hides toolbar and navigation arrows |
|
|
304
359
|
| `theme` | `Theme` | ❌ | Theme mode: `'auto' \| 'dark' \| 'light'` (default `'dark'`) |
|
|
360
|
+
| `showDownload` | `boolean` | ❌ | Whether to show the download button (default `true`) |
|
|
305
361
|
|
|
306
362
|
### FilePreviewModal Events
|
|
307
363
|
|
|
@@ -323,6 +379,7 @@ Differences from `FilePreviewModal`:
|
|
|
323
379
|
| `messages` | `Partial<Record<Locale, Partial<Messages>>>` | ❌ | - | Custom translation overrides |
|
|
324
380
|
| `headless` | `boolean` | ❌ | `false` | Headless mode — hides toolbar and navigation arrows |
|
|
325
381
|
| `theme` | `Theme` | ❌ | `'dark'` | Theme mode: `'auto' \| 'dark' \| 'light'` |
|
|
382
|
+
| `showDownload` | `boolean` | ❌ | `true` | Whether to show the download button |
|
|
326
383
|
|
|
327
384
|
### FilePreviewEmbed Events
|
|
328
385
|
|
|
@@ -365,39 +422,424 @@ interface PreviewFileLink {
|
|
|
365
422
|
const url: string = 'https://example.com/file.pdf';
|
|
366
423
|
```
|
|
367
424
|
|
|
368
|
-
##
|
|
425
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f9e9.svg" width="20" height="20" alt="🧩" /> Custom Renderers
|
|
369
426
|
|
|
370
|
-
|
|
371
|
-
- `←` - Previous file
|
|
372
|
-
- `→` - Next file
|
|
373
|
-
- `Mouse Wheel` - Zoom image (image preview only)
|
|
427
|
+
The library supports custom renderers for handling file types not built-in. Custom renderers can optionally provide toolbar configurations and integrate with the library's architecture.
|
|
374
428
|
|
|
375
|
-
|
|
429
|
+
### Event-Driven Toolbar Updates
|
|
376
430
|
|
|
377
|
-
-
|
|
378
|
-
- [Vue Demo](https://wh131462.github.io/file-preview/vue/)
|
|
379
|
-
- [React Demo](https://wh131462.github.io/file-preview/)
|
|
431
|
+
Custom renderers can implement real-time toolbar updates using Vue 3's reactivity system:
|
|
380
432
|
|
|
381
|
-
|
|
433
|
+
**Benefits:**
|
|
434
|
+
- **Real-time updates**: Toolbar reflects state changes immediately via Vue reactivity
|
|
435
|
+
- **Better performance**: No polling overhead, leverages Vue's efficient change detection
|
|
436
|
+
- **Type-safe**: Full TypeScript support with proper interfaces
|
|
382
437
|
|
|
383
|
-
|
|
438
|
+
**Implementation:**
|
|
384
439
|
|
|
385
|
-
|
|
440
|
+
```vue
|
|
441
|
+
<script setup lang="ts">
|
|
442
|
+
import { ref, computed, watch } from 'vue';
|
|
443
|
+
import { ChevronLeft, ChevronRight } from 'lucide-vue-next';
|
|
444
|
+
import type { ToolbarGroup } from '@eternalheart/vue-file-preview';
|
|
386
445
|
|
|
387
|
-
|
|
446
|
+
interface Props {
|
|
447
|
+
url: string;
|
|
448
|
+
}
|
|
388
449
|
|
|
389
|
-
|
|
390
|
-
{
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
450
|
+
const props = defineProps<Props>();
|
|
451
|
+
const emit = defineEmits<{
|
|
452
|
+
pageChange: [current: number, total: number];
|
|
453
|
+
}>();
|
|
454
|
+
|
|
455
|
+
const currentPage = ref(1);
|
|
456
|
+
const totalPages = ref(10);
|
|
457
|
+
|
|
458
|
+
// Emit page changes
|
|
459
|
+
watch([currentPage, totalPages], () => {
|
|
460
|
+
emit('pageChange', currentPage.value, totalPages.value);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const getToolbarGroups = (): ToolbarGroup[] => [
|
|
464
|
+
{
|
|
465
|
+
items: [
|
|
466
|
+
{
|
|
467
|
+
type: 'button',
|
|
468
|
+
icon: ChevronLeft,
|
|
469
|
+
tooltip: 'Previous Page',
|
|
470
|
+
action: () => currentPage.value = Math.max(1, currentPage.value - 1),
|
|
471
|
+
disabled: currentPage.value <= 1
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
type: 'text',
|
|
475
|
+
content: `${currentPage.value} / ${totalPages.value}`,
|
|
476
|
+
minWidth: '4rem'
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
type: 'button',
|
|
480
|
+
icon: ChevronRight,
|
|
481
|
+
tooltip: 'Next Page',
|
|
482
|
+
action: () => currentPage.value = Math.min(totalPages.value, currentPage.value + 1),
|
|
483
|
+
disabled: currentPage.value >= totalPages.value
|
|
484
|
+
}
|
|
485
|
+
]
|
|
486
|
+
}
|
|
487
|
+
];
|
|
488
|
+
|
|
489
|
+
// Expose for parent component
|
|
490
|
+
defineExpose({
|
|
491
|
+
getToolbarGroups
|
|
492
|
+
});
|
|
493
|
+
</script>
|
|
494
|
+
|
|
495
|
+
<template>
|
|
496
|
+
<div>Your custom renderer UI</div>
|
|
497
|
+
</template>
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Main component usage:**
|
|
501
|
+
|
|
502
|
+
```vue
|
|
503
|
+
<script setup>
|
|
504
|
+
import { CustomRenderer } from './CustomRenderer.vue';
|
|
505
|
+
|
|
506
|
+
const files = [
|
|
507
|
+
{ name: 'custom.xyz', type: 'application/custom', url: '/path/to/file' }
|
|
508
|
+
];
|
|
509
|
+
</script>
|
|
510
|
+
|
|
511
|
+
<template>
|
|
512
|
+
<FilePreviewModal
|
|
513
|
+
:files="files"
|
|
514
|
+
:custom-renderers="[
|
|
515
|
+
{
|
|
516
|
+
test: (file) => file.type === 'application/custom',
|
|
517
|
+
component: CustomRenderer
|
|
518
|
+
}
|
|
519
|
+
]"
|
|
520
|
+
/>
|
|
521
|
+
</template>
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
The main component automatically tracks reactive changes in `getToolbarGroups()` via Vue's reactivity system. No manual subscription needed.
|
|
525
|
+
|
|
526
|
+
### Renderer Lazy Loading
|
|
527
|
+
|
|
528
|
+
All built-in renderers use code-splitting via `defineAsyncComponent` to minimize the main bundle size and improve initial load performance.
|
|
529
|
+
|
|
530
|
+
**Architecture:**
|
|
531
|
+
|
|
532
|
+
- **Registration**: Renderers register in `src/renderers/lazy.ts` using `defineAsyncComponent` wrappers
|
|
533
|
+
- **Loading**: Each renderer is a separate chunk, loaded on-demand when needed
|
|
534
|
+
- **Fallback**: `RendererLoading` component handles the loading state
|
|
535
|
+
|
|
536
|
+
**Bundle Size Impact:**
|
|
537
|
+
|
|
538
|
+
- Main entry point: gzip ≤ 60 KB (strictly enforced by CI)
|
|
539
|
+
- Each renderer: separate async chunk
|
|
540
|
+
- Total library: gzip ≤ 500 KB (all renderers combined)
|
|
541
|
+
|
|
542
|
+
**Implementation Example:**
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
// src/renderers/lazy.ts
|
|
546
|
+
import { defineAsyncComponent } from 'vue';
|
|
547
|
+
|
|
548
|
+
const wrap = (loader: () => Promise<any>) =>
|
|
549
|
+
defineAsyncComponent({
|
|
550
|
+
loader,
|
|
551
|
+
loadingComponent: RendererLoading,
|
|
552
|
+
delay: 0
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
export const CustomRenderer = wrap(() => import('./Custom/index.vue'));
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
```vue
|
|
559
|
+
<!-- src/FilePreviewContent.vue -->
|
|
560
|
+
<script setup>
|
|
561
|
+
import { CustomRenderer } from './renderers/lazy'; // ✅ Lazy import
|
|
562
|
+
// NOT: import CustomRenderer from './renderers/Custom/index.vue'; // ❌ Direct import breaks code-splitting
|
|
563
|
+
</script>
|
|
564
|
+
|
|
565
|
+
<template>
|
|
566
|
+
<CustomRenderer
|
|
567
|
+
v-if="fileType === 'custom'"
|
|
568
|
+
ref="rendererRef"
|
|
569
|
+
:url="currentFile.url"
|
|
570
|
+
/>
|
|
571
|
+
</template>
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**For Custom Renderers:**
|
|
575
|
+
|
|
576
|
+
If you want your custom renderer to benefit from code-splitting, use the same pattern:
|
|
577
|
+
|
|
578
|
+
```vue
|
|
579
|
+
<script setup>
|
|
580
|
+
import { defineAsyncComponent } from 'vue';
|
|
581
|
+
|
|
582
|
+
const MyCustomRenderer = defineAsyncComponent(() => import('./MyCustomRenderer.vue'));
|
|
583
|
+
|
|
584
|
+
const files = [...];
|
|
585
|
+
</script>
|
|
586
|
+
|
|
587
|
+
<template>
|
|
588
|
+
<FilePreviewModal
|
|
589
|
+
:files="files"
|
|
590
|
+
:custom-renderers="[
|
|
591
|
+
{
|
|
592
|
+
test: (file) => file.type === 'application/custom',
|
|
593
|
+
component: MyCustomRenderer
|
|
594
|
+
}
|
|
595
|
+
]"
|
|
596
|
+
/>
|
|
597
|
+
</template>
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### i18n Integration
|
|
601
|
+
|
|
602
|
+
Custom renderers can access the library's i18n system via the `useTranslator()` composable for consistent multilingual support.
|
|
603
|
+
|
|
604
|
+
**Architecture:**
|
|
605
|
+
|
|
606
|
+
- **Dictionary Source**: `file-preview-core/src/i18n/messages/` (zh-CN.ts, en-US.ts)
|
|
607
|
+
- **No Hardcoding**: All user-visible text must use translation keys
|
|
608
|
+
- **Automatic Locale Switching**: Follows the `locale` prop passed to `FilePreviewModal`
|
|
609
|
+
|
|
610
|
+
**Usage in Custom Renderers:**
|
|
611
|
+
|
|
612
|
+
```vue
|
|
613
|
+
<script setup lang="ts">
|
|
614
|
+
import { ref } from 'vue';
|
|
615
|
+
import { useTranslator } from '@eternalheart/vue-file-preview';
|
|
616
|
+
|
|
617
|
+
interface Props {
|
|
618
|
+
url: string;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const props = defineProps<Props>();
|
|
622
|
+
const { t } = useTranslator();
|
|
623
|
+
const error = ref<string | null>(null);
|
|
624
|
+
</script>
|
|
625
|
+
|
|
626
|
+
<template>
|
|
627
|
+
<div v-if="error" class="vfp-text-fg-primary">
|
|
628
|
+
{{ t('custom.load_failed') }}: {{ error }}
|
|
629
|
+
</div>
|
|
630
|
+
<div v-else>
|
|
631
|
+
<button>{{ t('common.download') }}</button>
|
|
632
|
+
<span>{{ t('custom.loading') }}</span>
|
|
633
|
+
</div>
|
|
634
|
+
</template>
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
**Note on Usage:**
|
|
638
|
+
|
|
639
|
+
- In `<template>`: Use `t('key')` directly (automatically unwrapped)
|
|
640
|
+
- In `<script>`: Use `t.value('key')` for imperative calls
|
|
641
|
+
|
|
642
|
+
**Adding Custom Translation Keys:**
|
|
643
|
+
|
|
644
|
+
For custom renderers, extend translations via the `messages` prop (do NOT modify source files in `node_modules`):
|
|
645
|
+
|
|
646
|
+
```vue
|
|
647
|
+
<template>
|
|
648
|
+
<FilePreviewModal
|
|
649
|
+
:files="files"
|
|
650
|
+
locale="en-US"
|
|
651
|
+
:messages="{
|
|
652
|
+
'en-US': {
|
|
653
|
+
'custom.load_failed': 'Failed to load custom file',
|
|
654
|
+
'custom.file_size': 'File size: {size} KB'
|
|
655
|
+
},
|
|
656
|
+
'zh-CN': {
|
|
657
|
+
'custom.load_failed': '自定义文件加载失败',
|
|
658
|
+
'custom.file_size': '文件大小: {size} KB'
|
|
659
|
+
}
|
|
660
|
+
}"
|
|
661
|
+
:custom-renderers="[...]"
|
|
662
|
+
/>
|
|
663
|
+
</template>
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
**Guidelines:**
|
|
667
|
+
- Use `<scope>.<snake_name>` format (e.g., `custom.load_failed`, `custom.parse_error`)
|
|
668
|
+
- Provide translations for all enabled locales (`zh-CN` and `en-US`)
|
|
669
|
+
- Common keys already available: `common.loading`, `common.download`, `common.close`, `toolbar.*`
|
|
670
|
+
|
|
671
|
+
**Parameterized Translations:**
|
|
672
|
+
|
|
673
|
+
```vue
|
|
674
|
+
<template>
|
|
675
|
+
<!-- Dictionary: 'custom.file_size': 'File size: {size} KB' -->
|
|
676
|
+
<span>{{ t('custom.file_size', { size: 1024 }) }}</span>
|
|
677
|
+
<!-- → "File size: 1024 KB" -->
|
|
678
|
+
</template>
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
**Toolbar Integration:**
|
|
682
|
+
|
|
683
|
+
Toolbar items should also use translated strings:
|
|
684
|
+
|
|
685
|
+
```ts
|
|
686
|
+
const getToolbarGroups = (): ToolbarGroup[] => [
|
|
687
|
+
{
|
|
688
|
+
items: [
|
|
689
|
+
{
|
|
690
|
+
type: 'button',
|
|
691
|
+
icon: Download,
|
|
692
|
+
tooltip: t.value('common.download'), // ✅ Translated (use .value in script)
|
|
693
|
+
action: handleDownload
|
|
694
|
+
}
|
|
695
|
+
]
|
|
696
|
+
}
|
|
697
|
+
];
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### Theme Adaptation
|
|
701
|
+
|
|
702
|
+
Custom renderers must use semantic color tokens to support the library's `'auto' | 'dark' | 'light'` theme system.
|
|
703
|
+
|
|
704
|
+
**Semantic Token System:**
|
|
705
|
+
|
|
706
|
+
All colors are defined as CSS variables (`--fp-*`) and exposed via Tailwind classes with the `vfp-` prefix:
|
|
707
|
+
|
|
708
|
+
| Usage | Class | Description |
|
|
709
|
+
|-------|-------|-------------|
|
|
710
|
+
| **Text (fg)** | | |
|
|
711
|
+
| Primary text | `vfp-text-fg-primary` | Highest contrast |
|
|
712
|
+
| Body text | `vfp-text-fg-secondary` | Default text |
|
|
713
|
+
| Secondary text | `vfp-text-fg-tertiary` | Captions, counters |
|
|
714
|
+
| Muted text | `vfp-text-fg-muted` | Placeholders |
|
|
715
|
+
| Disabled text | `vfp-text-fg-disabled` | Disabled buttons |
|
|
716
|
+
| **Background (surface)** | | |
|
|
717
|
+
| Surface 1 | `vfp-bg-surface-1` | Cards, weakest |
|
|
718
|
+
| Surface 2 | `vfp-bg-surface-2` | Hover states |
|
|
719
|
+
| Surface 3 | `vfp-bg-surface-3` | Emphasis |
|
|
720
|
+
| Toolbar | `vfp-bg-surface-toolbar` | Top toolbar |
|
|
721
|
+
| **Borders** | | |
|
|
722
|
+
| Weak border | `vfp-border-line-weak` | Subtle lines |
|
|
723
|
+
| Standard border | `vfp-border-line` | Default borders |
|
|
724
|
+
| Strong border | `vfp-border-line-strong` | Emphasis |
|
|
725
|
+
| **Code** | | |
|
|
726
|
+
| Code background | `vfp-bg-code-bg` | Dark: #1e1e1e / Light: #f6f8fa |
|
|
727
|
+
| Code text | `vfp-text-code-fg` | Follows theme |
|
|
728
|
+
| **Accent** | | |
|
|
729
|
+
| Accent background | `vfp-bg-accent` | Primary buttons |
|
|
730
|
+
| Accent hover | `vfp-bg-accent-hover` | Hover state |
|
|
731
|
+
|
|
732
|
+
**✅ Correct Usage:**
|
|
733
|
+
|
|
734
|
+
```vue
|
|
735
|
+
<template>
|
|
736
|
+
<div class="vfp-bg-surface-1 vfp-border vfp-border-line-weak vfp-rounded">
|
|
737
|
+
<h2 class="vfp-text-fg-primary vfp-text-lg">Title</h2>
|
|
738
|
+
<p class="vfp-text-fg-secondary">Body text</p>
|
|
739
|
+
<button class="vfp-bg-surface-2 hover:vfp-bg-surface-3 vfp-text-fg-primary">
|
|
740
|
+
Click me
|
|
741
|
+
</button>
|
|
742
|
+
<pre class="vfp-bg-code-bg vfp-text-code-fg">{{ code }}</pre>
|
|
743
|
+
</div>
|
|
744
|
+
</template>
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
**For `<style scoped>` blocks**, use CSS variables:
|
|
748
|
+
|
|
749
|
+
```vue
|
|
750
|
+
<style scoped>
|
|
751
|
+
.my-block {
|
|
752
|
+
color: var(--fp-fg-primary);
|
|
753
|
+
background: var(--fp-surface-2);
|
|
754
|
+
border: 1px solid var(--fp-line);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
.my-code {
|
|
758
|
+
background: var(--fp-code-bg);
|
|
759
|
+
color: var(--fp-code-fg);
|
|
397
760
|
}
|
|
761
|
+
</style>
|
|
398
762
|
```
|
|
399
763
|
|
|
400
|
-
|
|
764
|
+
**❌ Incorrect Usage (DO NOT USE):**
|
|
765
|
+
|
|
766
|
+
```vue
|
|
767
|
+
<!-- ❌ Literal color classes — breaks theme switching -->
|
|
768
|
+
<div class="vfp-text-white/90 vfp-bg-white/10 vfp-border-white/15">
|
|
769
|
+
<div class="vfp-text-gray-700 vfp-bg-gray-100">
|
|
770
|
+
|
|
771
|
+
<!-- ❌ Inline literal colors -->
|
|
772
|
+
<div :style="{ color: '#ffffff', background: '#1f2937' }">
|
|
773
|
+
|
|
774
|
+
<!-- ❌ Hardcoded dark-only colors in scoped style -->
|
|
775
|
+
<style scoped>
|
|
776
|
+
.foo { color: rgba(255, 255, 255, 0.75); } /* Use var(--fp-fg-secondary) */
|
|
777
|
+
.foo { background: #1e1e1e; } /* Use var(--fp-code-bg) */
|
|
778
|
+
</style>
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
**Theme-Aware Third-Party Libraries:**
|
|
782
|
+
|
|
783
|
+
For libraries with theme props (e.g., `shiki`), use `useResolvedTheme()`:
|
|
784
|
+
|
|
785
|
+
```vue
|
|
786
|
+
<script setup lang="ts">
|
|
787
|
+
import { ref, watch } from 'vue';
|
|
788
|
+
import { codeToHtml } from 'shiki';
|
|
789
|
+
import { useResolvedTheme } from '@eternalheart/vue-file-preview';
|
|
790
|
+
|
|
791
|
+
const props = defineProps<{ code: string; lang: string }>();
|
|
792
|
+
const resolvedTheme = useResolvedTheme(); // Ref<'dark' | 'light'>
|
|
793
|
+
const highlighted = ref('');
|
|
794
|
+
|
|
795
|
+
const highlightCode = async () => {
|
|
796
|
+
highlighted.value = await codeToHtml(props.code, {
|
|
797
|
+
lang: props.lang,
|
|
798
|
+
theme: resolvedTheme.value === 'light' ? 'github-light' : 'dark-plus'
|
|
799
|
+
});
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
// Re-highlight when theme changes
|
|
803
|
+
watch(resolvedTheme, highlightCode, { immediate: true });
|
|
804
|
+
</script>
|
|
805
|
+
|
|
806
|
+
<template>
|
|
807
|
+
<div v-html="highlighted"></div>
|
|
808
|
+
</template>
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
**Testing:**
|
|
812
|
+
|
|
813
|
+
Always test your custom renderer in both Light and Dark themes:
|
|
814
|
+
|
|
815
|
+
```vue
|
|
816
|
+
<FilePreviewModal
|
|
817
|
+
:files="files"
|
|
818
|
+
theme="light" // Switch between 'light', 'dark', 'auto'
|
|
819
|
+
:custom-renderers="[...]"
|
|
820
|
+
/>
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
Verify:
|
|
824
|
+
- Text is readable in both themes (no white-on-white or black-on-black)
|
|
825
|
+
- Borders and dividers are visible
|
|
826
|
+
- Hover states have sufficient contrast
|
|
827
|
+
- Code blocks follow theme (not always dark)
|
|
828
|
+
|
|
829
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2328.svg" width="20" height="20" alt="⌨️" /> Keyboard Shortcuts
|
|
830
|
+
|
|
831
|
+
- `ESC` - Close preview
|
|
832
|
+
- `←` - Previous file
|
|
833
|
+
- `→` - Next file
|
|
834
|
+
- `Mouse Wheel` - Zoom image (image preview only)
|
|
835
|
+
|
|
836
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4da.svg" width="20" height="20" alt="📚" /> Documentation
|
|
837
|
+
|
|
838
|
+
- [Full Documentation](https://wh131462.github.io/file-preview/docs/)
|
|
839
|
+
- [Vue Demo](https://wh131462.github.io/file-preview/vue/)
|
|
840
|
+
- [React Demo](https://wh131462.github.io/file-preview/)
|
|
841
|
+
|
|
842
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f6e0.svg" width="20" height="20" alt="🛠️" /> Development
|
|
401
843
|
|
|
402
844
|
```bash
|
|
403
845
|
# Clone repository
|
|
@@ -413,15 +855,15 @@ pnpm dev:vue-example
|
|
|
413
855
|
pnpm build:vue
|
|
414
856
|
```
|
|
415
857
|
|
|
416
|
-
## 📄 License
|
|
858
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c4.svg" width="20" height="20" alt="📄" /> License
|
|
417
859
|
|
|
418
860
|
[MIT](./LICENSE) © [EternalHeart](https://github.com/wh131462)
|
|
419
861
|
|
|
420
|
-
## 🤝 Contributing
|
|
862
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f91d.svg" width="20" height="20" alt="🤝" /> Contributing
|
|
421
863
|
|
|
422
864
|
Issues and Pull Requests are welcome!
|
|
423
865
|
|
|
424
|
-
## 🔗 Links
|
|
866
|
+
## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f517.svg" width="20" height="20" alt="🔗" /> Links
|
|
425
867
|
|
|
426
868
|
- [GitHub](https://github.com/wh131462/file-preview)
|
|
427
869
|
- [npm](https://www.npmjs.com/package/@eternalheart/vue-file-preview)
|