vgapp 1.1.4 → 1.1.5

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +32 -13
  2. package/README.md +48 -48
  3. package/agents.md +2 -1
  4. package/app/langs/en/buttons.json +17 -17
  5. package/app/langs/en/messages.json +36 -36
  6. package/app/langs/ru/buttons.json +17 -17
  7. package/app/langs/ru/messages.json +36 -36
  8. package/app/modules/vgfilepreview/js/i18n.js +56 -56
  9. package/app/modules/vgfilepreview/js/renderers/image-modal.js +145 -145
  10. package/app/modules/vgfilepreview/js/renderers/image.js +92 -92
  11. package/app/modules/vgfilepreview/js/renderers/index.js +19 -19
  12. package/app/modules/vgfilepreview/js/renderers/office-modal.js +168 -168
  13. package/app/modules/vgfilepreview/js/renderers/office.js +79 -79
  14. package/app/modules/vgfilepreview/js/renderers/pdf-modal.js +260 -260
  15. package/app/modules/vgfilepreview/js/renderers/pdf.js +76 -76
  16. package/app/modules/vgfilepreview/js/renderers/playlist.js +71 -71
  17. package/app/modules/vgfilepreview/js/renderers/text-modal.js +343 -343
  18. package/app/modules/vgfilepreview/js/renderers/text.js +83 -83
  19. package/app/modules/vgfilepreview/js/renderers/video-modal.js +272 -272
  20. package/app/modules/vgfilepreview/js/renderers/video.js +80 -80
  21. package/app/modules/vgfilepreview/js/renderers/zip-modal.js +522 -522
  22. package/app/modules/vgfilepreview/js/renderers/zip.js +89 -89
  23. package/app/modules/vgfilepreview/js/vgfilepreview.js +965 -592
  24. package/app/modules/vgfilepreview/readme.md +68 -68
  25. package/app/modules/vgfilepreview/scss/_variables.scss +113 -113
  26. package/app/modules/vgfilepreview/scss/vgfilepreview.scss +464 -460
  27. package/app/modules/vgfiles/js/base.js +463 -463
  28. package/app/modules/vgfiles/js/droppable.js +260 -260
  29. package/app/modules/vgfiles/js/render.js +153 -153
  30. package/app/modules/vgfiles/js/vgfiles.js +41 -41
  31. package/app/modules/vgfiles/readme.md +123 -123
  32. package/app/modules/vgfiles/scss/_variables.scss +18 -18
  33. package/app/modules/vgfiles/scss/vgfiles.scss +148 -148
  34. package/app/modules/vgformsender/js/vgformsender.js +13 -13
  35. package/app/modules/vgmodal/js/vgmodal.drag.js +332 -0
  36. package/app/modules/vgmodal/js/vgmodal.js +116 -14
  37. package/app/modules/vgmodal/js/vgmodal.resize.js +435 -0
  38. package/app/modules/vgnav/js/vgnav.js +135 -135
  39. package/app/modules/vgnav/readme.md +67 -67
  40. package/app/modules/vgnestable/README.md +307 -307
  41. package/app/modules/vgnestable/scss/_variables.scss +60 -60
  42. package/app/modules/vgnestable/scss/vgnestable.scss +163 -163
  43. package/app/modules/vgselect/js/vgselect.js +39 -39
  44. package/app/modules/vgselect/scss/vgselect.scss +22 -22
  45. package/app/modules/vgspy/readme.md +28 -28
  46. package/app/utils/js/components/audio-metadata.js +240 -240
  47. package/app/utils/js/components/file-icon.js +109 -109
  48. package/app/utils/js/components/file-preview.js +304 -304
  49. package/app/utils/js/components/sanitize.js +150 -150
  50. package/app/utils/js/components/video-metadata.js +140 -0
  51. package/build/vgapp.css +1 -1
  52. package/build/vgapp.css.map +1 -1
  53. package/build/vgapp.js.map +1 -1
  54. package/index.scss +9 -9
  55. package/package.json +1 -1
@@ -1,594 +1,967 @@
1
- import BaseModule from "../../base-module";
2
- import {mergeDeepObject} from "../../../utils/js/functions";
3
- import FilePreviewHelper from "../../../utils/js/components/file-preview";
4
- import {getSVG} from "../../module-fn";
5
- import createFilePreviewRenderers from "./renderers";
6
- import {createFilePreviewI18n, resolveFilePreviewLang} from "./i18n";
7
-
8
- const NAME = 'filepreview';
9
- const NAME_KEY = 'vg.filepreview';
10
-
11
- class VGFilePreview extends BaseModule {
12
- constructor(el, params = {}) {
13
- super(el, params);
14
-
15
- this._params = this._getParams(el, mergeDeepObject({
16
- validate: true,
17
- lang: 'ru',
18
- ui: {
19
- nameOnly: false,
20
- preview: false
21
- }
22
- }, params));
23
-
24
- this._filePath = '';
25
- this._fileUrl = null;
26
- this._isValid = false;
27
- this._fileMeta = {};
28
- this._fields = [];
29
- this._editableFields = {};
30
- this._helper = new FilePreviewHelper(this._element);
31
- this._renderers = createFilePreviewRenderers();
32
- this._lang = resolveFilePreviewLang(this._params.lang, this._element);
33
- this._i18n = createFilePreviewI18n(this._lang);
34
- this._inlineAudio = null;
35
- this._inlineAudioSrc = '';
36
- this._inlineAudioButton = null;
37
- this._inlineAudioIcon = null;
38
- this._inlineAudioContainer = null;
39
-
40
- this.init();
41
- }
42
-
43
- static get NAME() {
44
- return NAME;
45
- }
46
-
47
- static get NAME_KEY() {
48
- return NAME_KEY;
49
- }
50
-
51
- get filePath() {
52
- return this._filePath;
53
- }
54
-
55
- get fileUrl() {
56
- return this._fileUrl;
57
- }
58
-
59
- get isValid() {
60
- return this._isValid;
61
- }
62
-
63
- get fileMeta() {
64
- return this._fileMeta;
65
- }
66
-
67
- get fields() {
68
- return this._fields;
69
- }
70
-
71
- get editableFields() {
72
- return this._editableFields;
73
- }
74
-
75
- init() {
76
- const filePath = this._helper.getFilePath();
77
- this._filePath = filePath;
78
-
79
- if (this._params.validate) {
80
- const validation = this._helper.validateFilePath(filePath);
81
- this._isValid = validation.isValid;
82
- this._fileUrl = validation.fileUrl;
83
- } else {
84
- this._isValid = true;
85
- this._fileUrl = null;
86
- }
87
-
88
- this._helper.syncState(this._isValid);
89
-
90
- if (!this._isValid) {
1
+ import BaseModule from "../../base-module";
2
+ import {mergeDeepObject} from "../../../utils/js/functions";
3
+ import FilePreviewHelper from "../../../utils/js/components/file-preview";
4
+ import {extractAudioMetadata} from "../../../utils/js/components/audio-metadata";
5
+ import {extractVideoMetadata} from "../../../utils/js/components/video-metadata";
6
+ import {getSVG} from "../../module-fn";
7
+ import createFilePreviewRenderers from "./renderers";
8
+ import ImageModal from "./renderers/image-modal";
9
+ import {createFilePreviewI18n, resolveFilePreviewLang} from "./i18n";
10
+
11
+ const NAME = 'filepreview';
12
+ const NAME_KEY = 'vg.filepreview';
13
+
14
+ class VGFilePreview extends BaseModule {
15
+ constructor(el, params = {}) {
16
+ super(el, params);
17
+
18
+ this._params = this._getParams(el, mergeDeepObject({
19
+ validate: true,
20
+ lang: 'ru',
21
+ ui: {
22
+ nameOnly: false
23
+ },
24
+ preview: {
25
+ audio: {enable: true},
26
+ video: {enable: true},
27
+ image: {enable: true},
28
+ archive: {enable: true},
29
+ text: {enable: true},
30
+ office: {enable: true},
31
+ pdf: {enable: true}
32
+ }
33
+ }, params));
34
+
35
+ this._filePath = '';
36
+ this._fileUrl = null;
37
+ this._isValid = false;
38
+ this._fileMeta = {};
39
+ this._fields = [];
40
+ this._editableFields = {};
41
+ this._helper = new FilePreviewHelper(this._element);
42
+ this._renderers = createFilePreviewRenderers();
43
+ this._lang = resolveFilePreviewLang(this._params.lang, this._element);
44
+ this._i18n = createFilePreviewI18n(this._lang);
45
+ this._inlineAudio = null;
46
+ this._inlineAudioSrc = '';
47
+ this._inlineAudioButton = null;
48
+ this._inlineAudioIcon = null;
49
+ this._inlineAudioContainer = null;
50
+ this._audioMetaPromise = null;
51
+ this._audioMetaApplied = false;
52
+ this._audioCoverObjectUrl = '';
53
+ this._videoMetaPromise = null;
54
+ this._videoMetaApplied = false;
55
+ this._videoCoverObjectUrl = '';
56
+ this._imageModal = null;
57
+
58
+ this.init();
59
+ }
60
+
61
+ static get NAME() {
62
+ return NAME;
63
+ }
64
+
65
+ static get NAME_KEY() {
66
+ return NAME_KEY;
67
+ }
68
+
69
+ get filePath() {
70
+ return this._filePath;
71
+ }
72
+
73
+ get fileUrl() {
74
+ return this._fileUrl;
75
+ }
76
+
77
+ get isValid() {
78
+ return this._isValid;
79
+ }
80
+
81
+ get fileMeta() {
82
+ return this._fileMeta;
83
+ }
84
+
85
+ get fields() {
86
+ return this._fields;
87
+ }
88
+
89
+ get editableFields() {
90
+ return this._editableFields;
91
+ }
92
+
93
+ init() {
94
+ const filePath = this._helper.getFilePath();
95
+ this._filePath = filePath;
96
+
97
+ if (this._params.validate) {
98
+ const validation = this._helper.validateFilePath(filePath);
99
+ this._isValid = validation.isValid;
100
+ this._fileUrl = validation.fileUrl;
101
+ } else {
102
+ this._isValid = true;
103
+ this._fileUrl = null;
104
+ }
105
+
106
+ this._helper.syncState(this._isValid);
107
+
108
+ if (!this._isValid) {
109
+ return false;
110
+ }
111
+
112
+ this._fileMeta = this._helper.getFileMeta(this._filePath);
113
+ return this.preview();
114
+ }
115
+
116
+ preview() {
117
+ this._setState('loading');
118
+ this._fields = this._helper.getFields();
119
+ this._editableFields = this._helper.resolveEditableFields(this._fields);
120
+ this._helper.syncEditableFields(this._editableFields);
121
+ this._renderIcon();
122
+ this._renderTextFields();
123
+ this._renderDownloadField();
124
+ this._enrichVideoMetadata();
125
+ this._renderPreview();
126
+
127
+ return this._editableFields;
128
+ }
129
+
130
+ _renderIcon() {
131
+ const iconField = this._editableFields.icon;
132
+ if (!iconField) {
133
+ return;
134
+ }
135
+
136
+ const imageSrc = this._getImageIconSrc();
137
+ if (imageSrc) {
138
+ iconField.innerHTML = '';
139
+ const image = document.createElement('img');
140
+ image.src = imageSrc;
141
+ image.alt = this._fileMeta?.originalName || this._fileMeta?.name || '';
142
+ image.className = 'vg-filepreview-icon-image';
143
+ image.loading = 'lazy';
144
+ image.addEventListener('error', () => {
145
+ this._renderDefaultIcon(iconField);
146
+ });
147
+ if (this._isPreviewGroupEnabled('image')) {
148
+ this._bindIconImageToModal(image, imageSrc);
149
+ }
150
+ iconField.appendChild(image);
151
+ return;
152
+ }
153
+
154
+ this._renderDefaultIcon(iconField);
155
+ }
156
+
157
+ _renderTextFields() {
158
+ const displayName = this._getDisplayName();
159
+ const fileName = this._getFileName();
160
+ const hasDataTitle = this._hasDataDrivenDisplayName();
161
+
162
+ const nameField = this._editableFields.name;
163
+ if (nameField) {
164
+ if (this._isAudioFile() && this._isPreviewGroupEnabled('audio')) {
165
+ this._renderAudioNameField(nameField);
166
+ this._element.setAttribute('data-vg-filepreview-renderer', 'audio');
167
+ } else if (displayName) {
168
+ nameField.classList.remove('vg-filepreview-audio-inline');
169
+ nameField.textContent = displayName;
170
+ this._applyNameClampStyles(nameField);
171
+ }
172
+ }
173
+
174
+ const extField = this._editableFields.ext;
175
+ if (extField && this._fileMeta.ext) {
176
+ extField.textContent = this._fileMeta.ext;
177
+ }
178
+
179
+ const sizeField = this._editableFields.size;
180
+ if (sizeField && this._fileMeta.sizeText) {
181
+ sizeField.textContent = this._fileMeta.sizeText;
182
+ }
183
+
184
+ const originalNameField = this._editableFields.original_name;
185
+ if (!originalNameField) {
186
+ return;
187
+ }
188
+
189
+ if (hasDataTitle && fileName) {
190
+ originalNameField.textContent = fileName;
191
+ this._applyNameClampStyles(originalNameField);
192
+ return;
193
+ }
194
+
195
+ if (this._fileMeta.originalName) {
196
+ originalNameField.textContent = this._fileMeta.originalName;
197
+ this._applyNameClampStyles(originalNameField);
198
+ return;
199
+ }
200
+
201
+ if (this._fileMeta.isMedia) {
202
+ originalNameField.textContent = '';
203
+ }
204
+ }
205
+
206
+ _renderAudioNameField(nameField) {
207
+ const fileName = this._getDisplayName();
208
+ if (!fileName) {
209
+ return;
210
+ }
211
+
212
+ nameField.innerHTML = '';
213
+ nameField.classList.add('vg-filepreview-audio-inline');
214
+
215
+ const button = document.createElement('button');
216
+ button.type = 'button';
217
+ button.className = 'vg-filepreview-audio-inline__toggle';
218
+ button.setAttribute('aria-label', 'Play/Pause');
219
+
220
+ const icon = document.createElement('span');
221
+ icon.className = 'vg-filepreview-audio-inline__icon';
222
+ button.appendChild(icon);
223
+
224
+ const text = document.createElement('span');
225
+ text.className = 'vg-filepreview-audio-inline__name';
226
+ text.textContent = fileName;
227
+ this._applyNameClampStyles(text);
228
+ text.addEventListener('click', (event) => {
229
+ event.preventDefault();
230
+ event.stopPropagation();
231
+ this._toggleInlineAudio();
232
+ });
233
+
234
+ button.addEventListener('click', (event) => {
235
+ event.preventDefault();
236
+ event.stopPropagation();
237
+ this._toggleInlineAudio();
238
+ });
239
+
240
+ nameField.appendChild(button);
241
+ nameField.appendChild(text);
242
+
243
+ this._inlineAudioButton = button;
244
+ this._inlineAudioIcon = icon;
245
+ const rootFile = this._element?.classList?.contains('file')
246
+ ? this._element
247
+ : this._element?.closest?.('.file');
248
+ this._inlineAudioContainer = rootFile || this._element || nameField;
249
+ this._setInlineAudioProgress(0);
250
+ const isCurrentAudioPlaying = VGFilePreview._activeAudioOwner === this && this._inlineAudio && !this._inlineAudio.paused;
251
+ this._syncInlineAudioIcon(isCurrentAudioPlaying);
252
+ this._enrichAudioMetadata(text);
253
+ }
254
+
255
+ _toggleInlineAudio() {
256
+ if (!this._isPreviewGroupEnabled('audio')) {
257
+ return;
258
+ }
259
+
260
+ const src = this._fileUrl?.href || this._filePath || '';
261
+ if (!src || !this._inlineAudioButton) {
262
+ return;
263
+ }
264
+
265
+ if (VGFilePreview._activeAudioOwner && VGFilePreview._activeAudioOwner !== this) {
266
+ VGFilePreview._activeAudioOwner._stopInlineAudio();
267
+ }
268
+
269
+ if (!this._inlineAudio || this._inlineAudioSrc !== src) {
270
+ this._stopInlineAudio();
271
+ this._inlineAudio = new Audio(src);
272
+ this._inlineAudioSrc = src;
273
+ this._inlineAudio.addEventListener('ended', () => {
274
+ this._syncInlineAudioIcon(false);
275
+ this._setInlineAudioProgress(0);
276
+ });
277
+ this._inlineAudio.addEventListener('timeupdate', () => this._syncInlineAudioProgress());
278
+ this._inlineAudio.addEventListener('loadedmetadata', () => this._syncInlineAudioProgress());
279
+ }
280
+
281
+ if (this._inlineAudio.paused) {
282
+ this._inlineAudio.play().then(() => {
283
+ VGFilePreview._activeAudioOwner = this;
284
+ this._syncInlineAudioIcon(true);
285
+ }).catch(() => {
286
+ this._syncInlineAudioIcon(false);
287
+ });
288
+ return;
289
+ }
290
+
291
+ this._inlineAudio.pause();
292
+ this._syncInlineAudioIcon(false);
293
+ if (VGFilePreview._activeAudioOwner === this) {
294
+ VGFilePreview._activeAudioOwner = null;
295
+ }
296
+ }
297
+
298
+ _stopInlineAudio() {
299
+ if (!this._inlineAudio) {
300
+ this._syncInlineAudioIcon(false);
301
+ this._setInlineAudioProgress(0);
302
+ return;
303
+ }
304
+
305
+ this._inlineAudio.pause();
306
+ this._inlineAudio.currentTime = 0;
307
+ this._syncInlineAudioIcon(false);
308
+ this._setInlineAudioProgress(0);
309
+ if (VGFilePreview._activeAudioOwner === this) {
310
+ VGFilePreview._activeAudioOwner = null;
311
+ }
312
+ }
313
+
314
+ _syncInlineAudioIcon(isPlaying) {
315
+ if (!this._inlineAudioIcon) {
316
+ return;
317
+ }
318
+
319
+ this._inlineAudioIcon.innerHTML = isPlaying ? (getSVG('pause') || '') : (getSVG('play') || '');
320
+ }
321
+
322
+ _syncInlineAudioProgress() {
323
+ if (!this._inlineAudio) {
324
+ this._setInlineAudioProgress(0);
325
+ return;
326
+ }
327
+
328
+ const duration = Number(this._inlineAudio.duration || 0);
329
+ const currentTime = Number(this._inlineAudio.currentTime || 0);
330
+ if (!duration || !Number.isFinite(duration) || duration <= 0) {
331
+ this._setInlineAudioProgress(0);
332
+ return;
333
+ }
334
+
335
+ const progress = Math.max(0, Math.min(100, (currentTime / duration) * 100));
336
+ this._setInlineAudioProgress(progress);
337
+ }
338
+
339
+ _setInlineAudioProgress(percent) {
340
+ if (!this._inlineAudioContainer) {
341
+ return;
342
+ }
343
+
344
+ const normalized = Math.max(0, Math.min(100, Number(percent) || 0));
345
+ this._inlineAudioContainer.style.setProperty('--vg-filepreview-audio-inline-progress', `${normalized}%`);
346
+ }
347
+
348
+ _renderDownloadField() {
349
+ const downloadField = this._editableFields.download;
350
+ if (!downloadField) {
351
+ return;
352
+ }
353
+
354
+ const downloadLabel = this._i18n?.button('download') || '';
355
+ const downloadIcon = getSVG('download') || '';
356
+ const fieldTag = String(downloadField.tagName || '').toUpperCase();
357
+ let control = null;
358
+
359
+ if (fieldTag === 'A' || fieldTag === 'BUTTON') {
360
+ control = downloadField;
361
+ } else {
362
+ control = downloadField.querySelector('[data-vg-filepreview-download-control]');
363
+ if (!control) {
364
+ control = document.createElement('button');
365
+ control.type = 'button';
366
+ downloadField.innerHTML = '';
367
+ downloadField.appendChild(control);
368
+ }
369
+ }
370
+
371
+ control.classList.add('vg-filepreview-download-trigger');
372
+ control.setAttribute('data-vg-filepreview-download-control', 'true');
373
+
374
+ if (fieldTag === 'A' && control === downloadField) {
375
+ control.setAttribute('href', this._fileUrl?.href || this._filePath || '#');
376
+ if (this._fileMeta?.name) {
377
+ control.setAttribute('download', this._fileMeta.name);
378
+ }
379
+ } else if (String(control.tagName || '').toUpperCase() === 'BUTTON') {
380
+ control.setAttribute('type', 'button');
381
+ }
382
+
383
+ if (!control.hasAttribute('data-vg-filepreview-download-content-init')) {
384
+ control.setAttribute('data-vg-filepreview-download-content-init', 'true');
385
+ control.innerHTML = '';
386
+
387
+ if (downloadIcon) {
388
+ const icon = document.createElement('span');
389
+ icon.className = 'vg-filepreview-download-trigger__icon';
390
+ icon.innerHTML = downloadIcon;
391
+ control.appendChild(icon);
392
+ }
393
+
394
+ if (downloadLabel) {
395
+ const text = document.createElement('span');
396
+ text.className = 'vg-filepreview-download-trigger__text';
397
+ text.textContent = downloadLabel;
398
+ control.appendChild(text);
399
+ }
400
+ }
401
+
402
+ if (!control.hasAttribute('data-vg-filepreview-download-bind')) {
403
+ control.setAttribute('data-vg-filepreview-download-bind', 'true');
404
+ control.addEventListener('click', (event) => {
405
+ event.preventDefault();
406
+ this._downloadFile();
407
+ });
408
+ }
409
+ }
410
+
411
+ _downloadFile() {
412
+ const src = this._fileUrl?.href || this._filePath || '';
413
+ if (!src) {
414
+ return;
415
+ }
416
+
417
+ const fileName = this._fileMeta?.originalName || this._fileMeta?.name || 'file';
418
+
419
+ fetch(src, {
420
+ method: 'GET',
421
+ credentials: 'same-origin'
422
+ })
423
+ .then((response) => {
424
+ if (!response.ok) {
425
+ throw new Error(`HTTP ${response.status}`);
426
+ }
427
+ return response.blob();
428
+ })
429
+ .then((blob) => {
430
+ const objectUrl = URL.createObjectURL(blob);
431
+ this._downloadByLink(objectUrl, fileName);
432
+ setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
433
+ })
434
+ .catch(() => {
435
+ this._downloadByLink(src, fileName);
436
+ });
437
+ }
438
+
439
+ _downloadByLink(href, fileName) {
440
+ const link = document.createElement('a');
441
+ link.href = href;
442
+ link.style.display = 'none';
443
+ link.rel = 'noopener noreferrer';
444
+ if (fileName) {
445
+ link.setAttribute('download', fileName);
446
+ }
447
+
448
+ document.body.appendChild(link);
449
+ link.click();
450
+ document.body.removeChild(link);
451
+ }
452
+
453
+ _enrichAudioMetadata(nameField = null) {
454
+ if (this._audioMetaApplied || this._audioMetaPromise || !this._isAudioFile()) {
455
+ return;
456
+ }
457
+
458
+ const src = this._fileUrl?.href || this._filePath || '';
459
+ if (!src) {
460
+ return;
461
+ }
462
+
463
+ this._audioMetaPromise = this._createAudioFileFromSource(src)
464
+ .then((file) => {
465
+ if (!file) {
466
+ return null;
467
+ }
468
+ return extractAudioMetadata(file);
469
+ })
470
+ .then((meta) => {
471
+ if (!meta) {
472
+ return false;
473
+ }
474
+
475
+ let changed = false;
476
+ const title = String(meta.title || '').trim();
477
+ if (title) {
478
+ this._element.setAttribute('data-vg-filepreview-display-name', title);
479
+
480
+ if (nameField) {
481
+ nameField.textContent = title;
482
+ this._applyNameClampStyles(nameField);
483
+ }
484
+
485
+ const originalNameField = this._editableFields?.original_name;
486
+ if (originalNameField) {
487
+ originalNameField.textContent = this._getFileName();
488
+ this._applyNameClampStyles(originalNameField);
489
+ }
490
+
491
+ changed = true;
492
+ }
493
+
494
+ if (meta.pictureBlob instanceof Blob && this._editableFields?.icon) {
495
+ if (this._audioCoverObjectUrl) {
496
+ URL.revokeObjectURL(this._audioCoverObjectUrl);
497
+ }
498
+ this._audioCoverObjectUrl = URL.createObjectURL(meta.pictureBlob);
499
+
500
+ const iconField = this._editableFields.icon;
501
+ iconField.innerHTML = '';
502
+
503
+ const image = document.createElement('img');
504
+ image.src = this._audioCoverObjectUrl;
505
+ image.alt = title || this._fileMeta?.originalName || this._fileMeta?.name || '';
506
+ image.className = 'vg-filepreview-icon-image';
507
+ image.loading = 'lazy';
508
+ image.addEventListener('error', () => {
509
+ this._renderDefaultIcon(iconField);
510
+ });
511
+ this._bindIconImageToModal(image, this._audioCoverObjectUrl, title);
512
+ iconField.appendChild(image);
513
+
514
+ changed = true;
515
+ }
516
+
517
+ this._audioMetaApplied = true;
518
+ return changed;
519
+ })
520
+ .catch(() => false)
521
+ .finally(() => {
522
+ this._audioMetaPromise = null;
523
+ });
524
+ }
525
+
526
+ _enrichVideoMetadata() {
527
+ if (!this._isPreviewGroupEnabled('video')) {
528
+ return;
529
+ }
530
+
531
+ if (this._videoMetaApplied || this._videoMetaPromise || !this._isVideoFile()) {
532
+ return;
533
+ }
534
+
535
+ const src = this._fileUrl?.href || this._filePath || '';
536
+ if (!src) {
537
+ return;
538
+ }
539
+
540
+ this._videoMetaPromise = this._createMediaFileFromSource(src, this._fileMeta?.originalName || this._fileMeta?.name || 'video.mp4', 'video/mp4')
541
+ .then((file) => {
542
+ if (!file) {
543
+ return null;
544
+ }
545
+ return extractVideoMetadata(file);
546
+ })
547
+ .then((meta) => {
548
+ if (!meta || !(meta.posterBlob instanceof Blob) || !this._editableFields?.icon) {
549
+ return false;
550
+ }
551
+
552
+ if (this._videoCoverObjectUrl) {
553
+ URL.revokeObjectURL(this._videoCoverObjectUrl);
554
+ }
555
+ this._videoCoverObjectUrl = URL.createObjectURL(meta.posterBlob);
556
+
557
+ const iconField = this._editableFields.icon;
558
+ iconField.innerHTML = '';
559
+
560
+ const image = document.createElement('img');
561
+ image.src = this._videoCoverObjectUrl;
562
+ image.alt = this._fileMeta?.originalName || this._fileMeta?.name || '';
563
+ image.className = 'vg-filepreview-icon-image';
564
+ image.loading = 'lazy';
565
+ image.addEventListener('error', () => {
566
+ this._renderDefaultIcon(iconField);
567
+ });
568
+ if (this._isPreviewGroupEnabled('video')) {
569
+ this._bindIconImageToModal(image, this._videoCoverObjectUrl, image.alt);
570
+ }
571
+ iconField.appendChild(image);
572
+
573
+ this._videoMetaApplied = true;
574
+ return true;
575
+ })
576
+ .catch(() => false)
577
+ .finally(() => {
578
+ this._videoMetaPromise = null;
579
+ });
580
+ }
581
+
582
+ async _createAudioFileFromSource(src = '') {
583
+ const name = this._fileMeta?.originalName || this._fileMeta?.name || 'audio.mp3';
584
+ return this._createMediaFileFromSource(src, name, 'audio/mpeg');
585
+ }
586
+
587
+ async _createMediaFileFromSource(src = '', name = 'file', defaultType = 'application/octet-stream') {
588
+ try {
589
+ const response = await fetch(src, {
590
+ method: 'GET',
591
+ credentials: 'same-origin'
592
+ });
593
+ if (!response.ok) {
594
+ return null;
595
+ }
596
+
597
+ const blob = await response.blob();
598
+ if (!blob || !blob.size) {
599
+ return null;
600
+ }
601
+
602
+ return new File([blob], name, {
603
+ type: blob.type || defaultType,
604
+ lastModified: Date.now()
605
+ });
606
+ } catch {
607
+ return null;
608
+ }
609
+ }
610
+
611
+ _bindIconImageToModal(imageNode, src = '', title = '') {
612
+ if (!imageNode) {
613
+ return;
614
+ }
615
+
616
+ const modalSrc = String(src || '').trim();
617
+ if (!modalSrc) {
618
+ return;
619
+ }
620
+
621
+ const modalTitle = String(title || this._fileMeta?.originalName || this._fileMeta?.name || '').trim();
622
+ imageNode.setAttribute('data-vg-filepreview-image-modal-src', modalSrc);
623
+ imageNode.setAttribute('data-vg-filepreview-image-modal-title', modalTitle);
624
+ imageNode.classList.add('is-preview-action');
625
+
626
+ if (imageNode.hasAttribute('data-vg-filepreview-image-modal-bind')) {
627
+ return;
628
+ }
629
+
630
+ imageNode.setAttribute('data-vg-filepreview-image-modal-bind', 'true');
631
+ imageNode.addEventListener('click', (event) => {
632
+ event.preventDefault();
633
+ event.stopPropagation();
634
+
635
+ const nodeSrc = String(imageNode.getAttribute('data-vg-filepreview-image-modal-src') || '').trim();
636
+ if (!nodeSrc) {
637
+ return;
638
+ }
639
+
640
+ const nodeTitle = String(imageNode.getAttribute('data-vg-filepreview-image-modal-title') || '').trim();
641
+ if (!this._imageModal) {
642
+ this._imageModal = ImageModal.getInstance();
643
+ }
644
+
645
+ this._imageModal.open({
646
+ src: nodeSrc,
647
+ title: nodeTitle,
648
+ defaultTitle: this._i18n?.message('image_title') || ''
649
+ });
650
+ });
651
+ }
652
+
653
+ _renderPreview() {
654
+ const isNameOnly = Boolean(this._params?.ui?.nameOnly) || !this._shouldRenderPreviewForCurrentFile();
655
+ const previewContainer = this._resolvePreviewContainer({
656
+ autoCreate: !isNameOnly
657
+ });
658
+ if (!previewContainer && !isNameOnly) {
659
+ this._setState('error');
660
+ return;
661
+ }
662
+
663
+ if (previewContainer) {
664
+ previewContainer.innerHTML = '';
665
+ }
666
+
667
+ const context = {
668
+ element: this._element,
669
+ filePath: this._filePath,
670
+ fileUrl: this._fileUrl,
671
+ fileMeta: this._fileMeta,
672
+ previewContainer,
673
+ lang: this._lang,
674
+ i18n: this._i18n,
675
+ ui: this._params?.ui || {}
676
+ };
677
+
678
+ let rendered = false;
679
+ this._renderers.forEach((renderer) => {
680
+ if (rendered || typeof renderer?.canRender !== 'function' || typeof renderer?.render !== 'function') {
681
+ return;
682
+ }
683
+ if (!this._isRendererEnabled(renderer?.name)) {
684
+ return;
685
+ }
686
+
687
+ if (!renderer.canRender(context)) {
688
+ return;
689
+ }
690
+
691
+ try {
692
+ rendered = renderer.render(context) === true;
693
+ } catch (error) {
694
+ rendered = false;
695
+ }
696
+ if (rendered) {
697
+ this._element.setAttribute('data-vg-filepreview-renderer', renderer.name || 'custom');
698
+ }
699
+ });
700
+
701
+ if (!rendered && !isNameOnly) {
702
+ this._element.removeAttribute('data-vg-filepreview-renderer');
703
+ this._setState('empty');
704
+ return;
705
+ }
706
+ this._setState('ready');
707
+ }
708
+
709
+ _resolvePreviewContainer(params = {}) {
710
+ const autoCreate = !Object.prototype.hasOwnProperty.call(params, 'autoCreate') || Boolean(params.autoCreate);
711
+ if (!autoCreate) {
712
+ const disabledPreview = this._editableFields.preview || this._element.querySelector('[data-vg-filepreview-slot="preview"]');
713
+ if (disabledPreview) {
714
+ disabledPreview.innerHTML = '';
715
+ disabledPreview.classList.remove('preview');
716
+ disabledPreview.setAttribute('hidden', 'hidden');
717
+ disabledPreview.setAttribute('aria-hidden', 'true');
718
+ }
719
+ return null;
720
+ }
721
+
722
+ const editablePreview = this._editableFields.preview;
723
+ if (editablePreview) {
724
+ editablePreview.classList.add('preview');
725
+ editablePreview.removeAttribute('hidden');
726
+ editablePreview.removeAttribute('aria-hidden');
727
+ editablePreview.setAttribute('data-vg-filepreview-slot', 'preview');
728
+ return editablePreview;
729
+ }
730
+
731
+ const existedPreview = this._element.querySelector('[data-vg-filepreview-slot="preview"]');
732
+ if (existedPreview) {
733
+ existedPreview.classList.add('preview');
734
+ existedPreview.removeAttribute('hidden');
735
+ existedPreview.removeAttribute('aria-hidden');
736
+ this._editableFields.preview = existedPreview;
737
+ return existedPreview;
738
+ }
739
+
740
+ const container = document.createElement('div');
741
+ container.className = 'preview';
742
+ container.setAttribute('data-vg-filepreview-slot', 'preview');
743
+ this._element.appendChild(container);
744
+ this._editableFields.preview = container;
745
+
746
+ return container;
747
+ }
748
+
749
+ _shouldRenderPreviewForCurrentFile() {
750
+ if (this._isAudioFile()) {
751
+ return this._isPreviewGroupEnabled('audio');
752
+ }
753
+
754
+ const ext = String(this._fileMeta?.ext || '').toLowerCase();
755
+ if (ext === '.pdf') {
756
+ return this._isPreviewGroupEnabled('pdf');
757
+ }
758
+
759
+ if (this._isVideoFile()) {
760
+ return this._isPreviewGroupEnabled('video');
761
+ }
762
+
763
+ if (this._isImageFile()) {
91
764
  return false;
92
765
  }
93
-
94
- this._fileMeta = this._helper.getFileMeta(this._filePath);
95
- return this.preview();
96
- }
97
-
98
- preview() {
99
- this._setState('loading');
100
- this._fields = this._helper.getFields();
101
- this._editableFields = this._helper.resolveEditableFields(this._fields);
102
- this._helper.syncEditableFields(this._editableFields);
103
- this._renderIcon();
104
- this._renderTextFields();
105
- this._renderDownloadField();
106
-
107
- if (this._shouldRenderPreview()) {
108
- this._renderPreview();
109
- return this._editableFields;
110
- }
111
-
112
- const previewField = this._editableFields.preview;
113
- if (previewField) {
114
- previewField.innerHTML = '';
115
- }
116
-
117
- this._element.removeAttribute('data-vg-filepreview-renderer');
118
- this._setState('ready');
119
-
120
- return this._editableFields;
121
- }
122
-
123
- _renderIcon() {
124
- const iconField = this._editableFields.icon;
125
- if (!iconField) {
126
- return;
127
- }
128
-
129
- const imageSrc = this._getImageIconSrc();
130
- if (imageSrc) {
131
- iconField.innerHTML = '';
132
- const image = document.createElement('img');
133
- image.src = imageSrc;
134
- image.alt = this._fileMeta?.originalName || this._fileMeta?.name || '';
135
- image.className = 'vg-filepreview-icon-image';
136
- image.loading = 'lazy';
137
- image.addEventListener('error', () => {
138
- this._renderDefaultIcon(iconField);
139
- });
140
- iconField.appendChild(image);
141
- return;
142
- }
143
-
144
- this._renderDefaultIcon(iconField);
145
- }
146
-
147
- _renderTextFields() {
148
- const nameField = this._editableFields.name;
149
- if (nameField) {
150
- if (this._isAudioFile()) {
151
- this._renderAudioNameField(nameField);
152
- this._element.setAttribute('data-vg-filepreview-renderer', 'audio');
153
- } else if (this._fileMeta.name) {
154
- nameField.classList.remove('vg-filepreview-audio-inline');
155
- nameField.textContent = this._fileMeta.name;
156
- this._applyNameClampStyles(nameField);
157
- }
158
- }
159
-
160
- const extField = this._editableFields.ext;
161
- if (extField && this._fileMeta.ext) {
162
- extField.textContent = this._fileMeta.ext;
163
- }
164
-
165
- const sizeField = this._editableFields.size;
166
- if (sizeField && this._fileMeta.sizeText) {
167
- sizeField.textContent = this._fileMeta.sizeText;
168
- }
169
-
170
- const originalNameField = this._editableFields.original_name;
171
- if (!originalNameField) {
172
- return;
173
- }
174
-
175
- if (this._fileMeta.originalName) {
176
- originalNameField.textContent = this._fileMeta.originalName;
177
- this._applyNameClampStyles(originalNameField);
178
- return;
179
- }
180
-
181
- if (this._fileMeta.isMedia) {
182
- originalNameField.textContent = '';
183
- }
184
- }
185
-
186
- _renderAudioNameField(nameField) {
187
- const displayName = String(
188
- this._element?.getAttribute('data-vg-filepreview-display-name')
189
- || this._fileMeta?.originalName
190
- || this._fileMeta?.name
191
- || ''
192
- ).trim();
193
-
194
- const fileName = displayName;
195
- if (!fileName) {
196
- return;
197
- }
198
-
199
- nameField.innerHTML = '';
200
- nameField.classList.add('vg-filepreview-audio-inline');
201
-
202
- const button = document.createElement('button');
203
- button.type = 'button';
204
- button.className = 'vg-filepreview-audio-inline__toggle';
205
- button.setAttribute('aria-label', 'Play/Pause');
206
-
207
- const icon = document.createElement('span');
208
- icon.className = 'vg-filepreview-audio-inline__icon';
209
- button.appendChild(icon);
210
-
211
- const text = document.createElement('span');
212
- text.className = 'vg-filepreview-audio-inline__name';
213
- text.textContent = fileName;
214
- this._applyNameClampStyles(text);
215
-
216
- button.addEventListener('click', (event) => {
217
- event.preventDefault();
218
- event.stopPropagation();
219
- this._toggleInlineAudio();
220
- });
221
-
222
- nameField.appendChild(button);
223
- nameField.appendChild(text);
224
-
225
- this._inlineAudioButton = button;
226
- this._inlineAudioIcon = icon;
227
- const rootFile = this._element?.classList?.contains('file')
228
- ? this._element
229
- : this._element?.closest?.('.file');
230
- this._inlineAudioContainer = rootFile || this._element || nameField;
231
- this._setInlineAudioProgress(0);
232
- const isCurrentAudioPlaying = VGFilePreview._activeAudioOwner === this && this._inlineAudio && !this._inlineAudio.paused;
233
- this._syncInlineAudioIcon(isCurrentAudioPlaying);
234
- }
235
-
236
- _toggleInlineAudio() {
237
- const src = this._fileUrl?.href || this._filePath || '';
238
- if (!src || !this._inlineAudioButton) {
239
- return;
240
- }
241
-
242
- if (VGFilePreview._activeAudioOwner && VGFilePreview._activeAudioOwner !== this) {
243
- VGFilePreview._activeAudioOwner._stopInlineAudio();
244
- }
245
-
246
- if (!this._inlineAudio || this._inlineAudioSrc !== src) {
247
- this._stopInlineAudio();
248
- this._inlineAudio = new Audio(src);
249
- this._inlineAudioSrc = src;
250
- this._inlineAudio.addEventListener('ended', () => {
251
- this._syncInlineAudioIcon(false);
252
- this._setInlineAudioProgress(0);
253
- });
254
- this._inlineAudio.addEventListener('timeupdate', () => this._syncInlineAudioProgress());
255
- this._inlineAudio.addEventListener('loadedmetadata', () => this._syncInlineAudioProgress());
256
- }
257
-
258
- if (this._inlineAudio.paused) {
259
- this._inlineAudio.play().then(() => {
260
- VGFilePreview._activeAudioOwner = this;
261
- this._syncInlineAudioIcon(true);
262
- }).catch(() => {
263
- this._syncInlineAudioIcon(false);
264
- });
265
- return;
266
- }
267
-
268
- this._inlineAudio.pause();
269
- this._syncInlineAudioIcon(false);
270
- if (VGFilePreview._activeAudioOwner === this) {
271
- VGFilePreview._activeAudioOwner = null;
272
- }
273
- }
274
-
275
- _stopInlineAudio() {
276
- if (!this._inlineAudio) {
277
- this._syncInlineAudioIcon(false);
278
- this._setInlineAudioProgress(0);
279
- return;
280
- }
281
-
282
- this._inlineAudio.pause();
283
- this._inlineAudio.currentTime = 0;
284
- this._syncInlineAudioIcon(false);
285
- this._setInlineAudioProgress(0);
286
- if (VGFilePreview._activeAudioOwner === this) {
287
- VGFilePreview._activeAudioOwner = null;
288
- }
289
- }
290
-
291
- _syncInlineAudioIcon(isPlaying) {
292
- if (!this._inlineAudioIcon) {
293
- return;
294
- }
295
-
296
- this._inlineAudioIcon.innerHTML = isPlaying ? (getSVG('pause') || '') : (getSVG('play') || '');
297
- }
298
-
299
- _syncInlineAudioProgress() {
300
- if (!this._inlineAudio) {
301
- this._setInlineAudioProgress(0);
302
- return;
303
- }
304
-
305
- const duration = Number(this._inlineAudio.duration || 0);
306
- const currentTime = Number(this._inlineAudio.currentTime || 0);
307
- if (!duration || !Number.isFinite(duration) || duration <= 0) {
308
- this._setInlineAudioProgress(0);
309
- return;
310
- }
311
-
312
- const progress = Math.max(0, Math.min(100, (currentTime / duration) * 100));
313
- this._setInlineAudioProgress(progress);
314
- }
315
-
316
- _setInlineAudioProgress(percent) {
317
- if (!this._inlineAudioContainer) {
318
- return;
319
- }
320
-
321
- const normalized = Math.max(0, Math.min(100, Number(percent) || 0));
322
- this._inlineAudioContainer.style.setProperty('--vg-filepreview-audio-inline-progress', `${normalized}%`);
323
- }
324
-
325
- _renderDownloadField() {
326
- const downloadField = this._editableFields.download;
327
- if (!downloadField) {
328
- return;
329
- }
330
-
331
- const downloadLabel = this._i18n?.button('download') || '';
332
- const downloadIcon = getSVG('download') || '';
333
- const fieldTag = String(downloadField.tagName || '').toUpperCase();
334
- let control = null;
335
-
336
- if (fieldTag === 'A' || fieldTag === 'BUTTON') {
337
- control = downloadField;
338
- } else {
339
- control = downloadField.querySelector('[data-vg-filepreview-download-control]');
340
- if (!control) {
341
- control = document.createElement('button');
342
- control.type = 'button';
343
- downloadField.innerHTML = '';
344
- downloadField.appendChild(control);
345
- }
346
- }
347
-
348
- control.classList.add('vg-filepreview-download-trigger');
349
- control.setAttribute('data-vg-filepreview-download-control', 'true');
350
-
351
- if (fieldTag === 'A' && control === downloadField) {
352
- control.setAttribute('href', this._fileUrl?.href || this._filePath || '#');
353
- if (this._fileMeta?.name) {
354
- control.setAttribute('download', this._fileMeta.name);
355
- }
356
- } else if (String(control.tagName || '').toUpperCase() === 'BUTTON') {
357
- control.setAttribute('type', 'button');
358
- }
359
-
360
- if (!control.hasAttribute('data-vg-filepreview-download-content-init')) {
361
- control.setAttribute('data-vg-filepreview-download-content-init', 'true');
362
- control.innerHTML = '';
363
-
364
- if (downloadIcon) {
365
- const icon = document.createElement('span');
366
- icon.className = 'vg-filepreview-download-trigger__icon';
367
- icon.innerHTML = downloadIcon;
368
- control.appendChild(icon);
369
- }
370
-
371
- if (downloadLabel) {
372
- const text = document.createElement('span');
373
- text.className = 'vg-filepreview-download-trigger__text';
374
- text.textContent = downloadLabel;
375
- control.appendChild(text);
376
- }
377
- }
378
-
379
- if (!control.hasAttribute('data-vg-filepreview-download-bind')) {
380
- control.setAttribute('data-vg-filepreview-download-bind', 'true');
381
- control.addEventListener('click', (event) => {
382
- event.preventDefault();
383
- this._downloadFile();
384
- });
385
- }
386
- }
387
-
388
- _downloadFile() {
389
- const src = this._fileUrl?.href || this._filePath || '';
390
- if (!src) {
391
- return;
392
- }
393
-
394
- const fileName = this._fileMeta?.originalName || this._fileMeta?.name || 'file';
395
-
396
- fetch(src, {
397
- method: 'GET',
398
- credentials: 'same-origin'
399
- })
400
- .then((response) => {
401
- if (!response.ok) {
402
- throw new Error(`HTTP ${response.status}`);
403
- }
404
- return response.blob();
405
- })
406
- .then((blob) => {
407
- const objectUrl = URL.createObjectURL(blob);
408
- this._downloadByLink(objectUrl, fileName);
409
- setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
410
- })
411
- .catch(() => {
412
- this._downloadByLink(src, fileName);
413
- });
414
- }
415
-
416
- _downloadByLink(href, fileName) {
417
- const link = document.createElement('a');
418
- link.href = href;
419
- link.style.display = 'none';
420
- link.rel = 'noopener noreferrer';
421
- if (fileName) {
422
- link.setAttribute('download', fileName);
423
- }
424
-
425
- document.body.appendChild(link);
426
- link.click();
427
- document.body.removeChild(link);
428
- }
429
-
430
- _renderPreview() {
431
- const isNameOnly = Boolean(this._params?.ui?.nameOnly);
432
- const previewContainer = this._resolvePreviewContainer({
433
- autoCreate: !isNameOnly
434
- });
435
- if (!previewContainer && !isNameOnly) {
436
- this._setState('error');
437
- return;
438
- }
439
-
440
- if (previewContainer) {
441
- previewContainer.innerHTML = '';
442
- }
443
-
444
- const context = {
445
- element: this._element,
446
- filePath: this._filePath,
447
- fileUrl: this._fileUrl,
448
- fileMeta: this._fileMeta,
449
- previewContainer,
450
- lang: this._lang,
451
- i18n: this._i18n,
452
- ui: this._params?.ui || {}
453
- };
454
-
455
- let rendered = false;
456
- this._renderers.forEach((renderer) => {
457
- if (rendered || typeof renderer?.canRender !== 'function' || typeof renderer?.render !== 'function') {
458
- return;
459
- }
460
-
461
- if (!renderer.canRender(context)) {
462
- return;
463
- }
464
-
465
- try {
466
- rendered = renderer.render(context) === true;
467
- } catch (error) {
468
- rendered = false;
469
- }
470
- if (rendered) {
471
- this._element.setAttribute('data-vg-filepreview-renderer', renderer.name || 'custom');
472
- }
473
- });
474
-
475
- if (!rendered && !isNameOnly) {
476
- this._element.removeAttribute('data-vg-filepreview-renderer');
477
- this._setState('empty');
478
- return;
479
- }
480
- this._setState('ready');
481
- }
482
-
483
- _resolvePreviewContainer(params = {}) {
484
- const autoCreate = !Object.prototype.hasOwnProperty.call(params, 'autoCreate') || Boolean(params.autoCreate);
485
- const editablePreview = this._editableFields.preview;
486
- if (editablePreview) {
487
- editablePreview.setAttribute('data-vg-filepreview-slot', 'preview');
488
- return editablePreview;
489
- }
490
-
491
- const existedPreview = this._element.querySelector('[data-vg-filepreview-slot="preview"]');
492
- if (existedPreview) {
493
- this._editableFields.preview = existedPreview;
494
- return existedPreview;
495
- }
496
-
497
- if (!autoCreate) {
498
- return null;
499
- }
500
-
501
- const container = document.createElement('div');
502
- container.className = 'preview';
503
- container.setAttribute('data-vg-filepreview-slot', 'preview');
504
- this._element.appendChild(container);
505
- this._editableFields.preview = container;
506
-
507
- return container;
508
- }
509
-
510
- _shouldRenderPreview() {
511
- return Boolean(this._params?.ui?.preview);
512
- }
513
-
514
- _getImageIconSrc() {
515
- const ext = String(this._fileMeta?.ext || '').toLowerCase();
516
- const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg', '.avif', '.ico'];
517
- if (!imageExts.includes(ext)) {
518
- return '';
519
- }
520
-
521
- return this._fileUrl?.href || this._filePath || '';
522
- }
523
-
524
- _renderDefaultIcon(iconField) {
525
- const icon = getSVG(this._filePath);
526
- if (!icon) {
527
- iconField.innerHTML = '';
528
- return;
529
- }
530
-
531
- iconField.innerHTML = icon;
532
- }
533
-
534
- _applyNameClampStyles(field) {
535
- if (!field || !field.style) {
536
- return;
537
- }
538
-
539
- field.style.minWidth = '60px';
540
- field.style.maxWidth = '100%';
541
- field.style.overflow = 'hidden';
542
- field.style.textOverflow = 'ellipsis';
543
- field.style.whiteSpace = 'nowrap';
544
- }
545
-
546
- static init(element, params = {}) {
547
- return VGFilePreview.getOrCreateInstance(element, params);
548
- }
549
-
550
- static stopActiveInlineAudio() {
551
- const owner = VGFilePreview._activeAudioOwner;
552
- if (owner && typeof owner._stopInlineAudio === 'function') {
553
- owner._stopInlineAudio();
554
- }
555
-
556
- VGFilePreview._activeAudioOwner = null;
557
- }
558
-
559
- static stopActiveInlineAudioIfDetached(nodes = []) {
560
- const owner = VGFilePreview._activeAudioOwner;
561
- if (!owner || !owner._element || !Array.isArray(nodes) || !nodes.length) {
562
- return;
563
- }
564
-
565
- const shouldStop = nodes.some((node) => {
566
- if (!node || typeof node.contains !== 'function') {
567
- return false;
568
- }
569
-
570
- return node === owner._element || node.contains(owner._element);
571
- });
572
-
573
- if (shouldStop) {
574
- owner._stopInlineAudio();
575
- VGFilePreview._activeAudioOwner = null;
576
- }
577
- }
578
-
579
- _setState(state = '') {
580
- const value = String(state || '').trim();
581
- if (!value) {
582
- this._element.removeAttribute('data-vg-filepreview-state');
583
- return;
584
- }
585
- this._element.setAttribute('data-vg-filepreview-state', value);
586
- }
587
-
588
- _isAudioFile() {
589
- const ext = String(this._fileMeta?.ext || '').toLowerCase();
590
- return ['.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a', '.opus', '.wma'].includes(ext);
591
- }
592
- }
593
-
594
- export default VGFilePreview;
766
+
767
+ if (this._isOfficeFile()) {
768
+ return this._isPreviewGroupEnabled('office');
769
+ }
770
+
771
+ if (this._isArchiveFile()) {
772
+ return this._isPreviewGroupEnabled('archive');
773
+ }
774
+
775
+ if (this._isTextFile()) {
776
+ return this._isPreviewGroupEnabled('text');
777
+ }
778
+
779
+ return false;
780
+ }
781
+
782
+ _isRendererEnabled(rendererName = '') {
783
+ const name = String(rendererName || '').trim();
784
+ const map = {
785
+ image: 'image',
786
+ video: 'video',
787
+ pdf: 'pdf',
788
+ office: 'office',
789
+ zip: 'archive',
790
+ text: 'text'
791
+ };
792
+ const group = map[name];
793
+ if (!group) {
794
+ return true;
795
+ }
796
+
797
+ return this._isPreviewGroupEnabled(group);
798
+ }
799
+
800
+ _isPreviewGroupEnabled(groupName = '') {
801
+ const group = String(groupName || '').trim();
802
+ if (!group) {
803
+ return false;
804
+ }
805
+
806
+ const preview = this._params?.preview;
807
+ if (!preview || typeof preview !== 'object' || Array.isArray(preview)) {
808
+ return false;
809
+ }
810
+
811
+ if (!Object.prototype.hasOwnProperty.call(preview, group)) {
812
+ return false;
813
+ }
814
+
815
+ const config = preview[group];
816
+ if (typeof config === 'boolean') {
817
+ return config;
818
+ }
819
+
820
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
821
+ return false;
822
+ }
823
+
824
+ if (!Object.prototype.hasOwnProperty.call(config, 'enable')) {
825
+ return false;
826
+ }
827
+
828
+ return Boolean(config.enable);
829
+ }
830
+
831
+ _getImageIconSrc() {
832
+ const ext = String(this._fileMeta?.ext || '').toLowerCase();
833
+ const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg', '.avif', '.ico'];
834
+ if (!imageExts.includes(ext)) {
835
+ return '';
836
+ }
837
+
838
+ return this._fileUrl?.href || this._filePath || '';
839
+ }
840
+
841
+ _isImageFile() {
842
+ const ext = String(this._fileMeta?.ext || '').toLowerCase();
843
+ return ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg', '.avif', '.ico', '.tif', '.tiff', '.heic', '.heif'].includes(ext);
844
+ }
845
+
846
+ _isOfficeFile() {
847
+ const ext = String(this._fileMeta?.ext || '').toLowerCase();
848
+ return ['.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.odt', '.ods', '.odp'].includes(ext);
849
+ }
850
+
851
+ _isArchiveFile() {
852
+ const ext = String(this._fileMeta?.ext || '').toLowerCase();
853
+ return ['.zip'].includes(ext);
854
+ }
855
+
856
+ _isTextFile() {
857
+ const ext = String(this._fileMeta?.ext || '').toLowerCase();
858
+ return ['.txt', '.md', '.csv', '.json', '.xml', '.yml', '.yaml', '.log', '.ini', '.conf', '.env'].includes(ext);
859
+ }
860
+
861
+ _renderDefaultIcon(iconField) {
862
+ const icon = getSVG(this._filePath);
863
+ if (!icon) {
864
+ iconField.innerHTML = '';
865
+ return;
866
+ }
867
+
868
+ iconField.innerHTML = icon;
869
+ }
870
+
871
+ _applyNameClampStyles(field) {
872
+ if (!field || !field.style) {
873
+ return;
874
+ }
875
+
876
+ field.style.minWidth = '60px';
877
+ field.style.maxWidth = '100%';
878
+ field.style.overflow = 'hidden';
879
+ field.style.textOverflow = 'ellipsis';
880
+ field.style.whiteSpace = 'nowrap';
881
+ }
882
+
883
+ static init(element, params = {}) {
884
+ return VGFilePreview.getOrCreateInstance(element, params);
885
+ }
886
+
887
+ static stopActiveInlineAudio() {
888
+ const owner = VGFilePreview._activeAudioOwner;
889
+ if (owner && typeof owner._stopInlineAudio === 'function') {
890
+ owner._stopInlineAudio();
891
+ }
892
+
893
+ VGFilePreview._activeAudioOwner = null;
894
+ }
895
+
896
+ static stopActiveInlineAudioIfDetached(nodes = []) {
897
+ const owner = VGFilePreview._activeAudioOwner;
898
+ if (!owner || !owner._element || !Array.isArray(nodes) || !nodes.length) {
899
+ return;
900
+ }
901
+
902
+ const shouldStop = nodes.some((node) => {
903
+ if (!node || typeof node.contains !== 'function') {
904
+ return false;
905
+ }
906
+
907
+ return node === owner._element || node.contains(owner._element);
908
+ });
909
+
910
+ if (shouldStop) {
911
+ owner._stopInlineAudio();
912
+ VGFilePreview._activeAudioOwner = null;
913
+ }
914
+ }
915
+
916
+ _setState(state = '') {
917
+ const value = String(state || '').trim();
918
+ if (!value) {
919
+ this._element.removeAttribute('data-vg-filepreview-state');
920
+ return;
921
+ }
922
+ this._element.setAttribute('data-vg-filepreview-state', value);
923
+ }
924
+
925
+ _isAudioFile() {
926
+ const ext = String(this._fileMeta?.ext || '').toLowerCase();
927
+ return ['.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a', '.opus', '.wma'].includes(ext);
928
+ }
929
+
930
+ _isVideoFile() {
931
+ const ext = String(this._fileMeta?.ext || '').toLowerCase();
932
+ return ['.mp4', '.webm', '.mov', '.mkv', '.avi', '.m4v', '.ogv'].includes(ext);
933
+ }
934
+
935
+ _getFileName() {
936
+ return String(this._fileMeta?.originalName || this._fileMeta?.name || '').trim();
937
+ }
938
+
939
+ _getDataDisplayName() {
940
+ return String(this._element?.getAttribute('data-vg-filepreview-display-name') || '').trim();
941
+ }
942
+
943
+ _hasDataDrivenDisplayName() {
944
+ const dataName = this._getDataDisplayName();
945
+ if (!dataName) {
946
+ return false;
947
+ }
948
+
949
+ const fileName = this._getFileName();
950
+ if (!fileName) {
951
+ return true;
952
+ }
953
+
954
+ return dataName !== fileName;
955
+ }
956
+
957
+ _getDisplayName() {
958
+ const dataName = this._getDataDisplayName();
959
+ if (dataName) {
960
+ return dataName;
961
+ }
962
+
963
+ return this._getFileName();
964
+ }
965
+ }
966
+
967
+ export default VGFilePreview;