vgapp 1.1.3 → 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 (54) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +48 -48
  3. package/agents.md +7 -0
  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 -530
  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/index.scss +9 -9
  54. package/package.json +1 -1
@@ -1,532 +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
- }
21
- }, params));
22
-
23
- this._filePath = '';
24
- this._fileUrl = null;
25
- this._isValid = false;
26
- this._fileMeta = {};
27
- this._fields = [];
28
- this._editableFields = {};
29
- this._helper = new FilePreviewHelper(this._element);
30
- this._renderers = createFilePreviewRenderers();
31
- this._lang = resolveFilePreviewLang(this._params.lang, this._element);
32
- this._i18n = createFilePreviewI18n(this._lang);
33
- this._inlineAudio = null;
34
- this._inlineAudioSrc = '';
35
- this._inlineAudioButton = null;
36
- this._inlineAudioIcon = null;
37
- this._inlineAudioContainer = null;
38
-
39
- this.init();
40
- }
41
-
42
- static get NAME() {
43
- return NAME;
44
- }
45
-
46
- static get NAME_KEY() {
47
- return NAME_KEY;
48
- }
49
-
50
- get filePath() {
51
- return this._filePath;
52
- }
53
-
54
- get fileUrl() {
55
- return this._fileUrl;
56
- }
57
-
58
- get isValid() {
59
- return this._isValid;
60
- }
61
-
62
- get fileMeta() {
63
- return this._fileMeta;
64
- }
65
-
66
- get fields() {
67
- return this._fields;
68
- }
69
-
70
- get editableFields() {
71
- return this._editableFields;
72
- }
73
-
74
- init() {
75
- const filePath = this._helper.getFilePath();
76
- this._filePath = filePath;
77
-
78
- if (this._params.validate) {
79
- const validation = this._helper.validateFilePath(filePath);
80
- this._isValid = validation.isValid;
81
- this._fileUrl = validation.fileUrl;
82
- } else {
83
- this._isValid = true;
84
- this._fileUrl = null;
85
- }
86
-
87
- this._helper.syncState(this._isValid);
88
-
89
- 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()) {
90
764
  return false;
91
765
  }
92
-
93
- this._fileMeta = this._helper.getFileMeta(this._filePath);
94
- return this.preview();
95
- }
96
-
97
- preview() {
98
- this._setState('loading');
99
- this._fields = this._helper.getFields();
100
- this._editableFields = this._helper.resolveEditableFields(this._fields);
101
- this._helper.syncEditableFields(this._editableFields);
102
- this._renderIcon();
103
- this._renderTextFields();
104
- this._renderDownloadField();
105
- this._renderPreview();
106
-
107
- return this._editableFields;
108
- }
109
-
110
- _renderIcon() {
111
- const iconField = this._editableFields.icon;
112
- if (!iconField) {
113
- return;
114
- }
115
-
116
- const icon = getSVG(this._filePath);
117
- if (!icon) {
118
- return;
119
- }
120
-
121
- iconField.innerHTML = icon;
122
- }
123
-
124
- _renderTextFields() {
125
- const nameField = this._editableFields.name;
126
- if (nameField) {
127
- if (this._isAudioFile()) {
128
- this._renderAudioNameField(nameField);
129
- this._element.setAttribute('data-vg-filepreview-renderer', 'audio');
130
- } else if (this._fileMeta.name) {
131
- nameField.classList.remove('vg-filepreview-audio-inline');
132
- nameField.textContent = this._fileMeta.name;
133
- }
134
- }
135
-
136
- const extField = this._editableFields.ext;
137
- if (extField && this._fileMeta.ext) {
138
- extField.textContent = this._fileMeta.ext;
139
- }
140
-
141
- const sizeField = this._editableFields.size;
142
- if (sizeField && this._fileMeta.sizeText) {
143
- sizeField.textContent = this._fileMeta.sizeText;
144
- }
145
-
146
- const originalNameField = this._editableFields.original_name;
147
- if (!originalNameField) {
148
- return;
149
- }
150
-
151
- if (this._fileMeta.originalName) {
152
- originalNameField.textContent = this._fileMeta.originalName;
153
- return;
154
- }
155
-
156
- if (this._fileMeta.isMedia) {
157
- originalNameField.textContent = '';
158
- }
159
- }
160
-
161
- _renderAudioNameField(nameField) {
162
- const displayName = String(
163
- this._element?.getAttribute('data-vg-filepreview-display-name')
164
- || this._fileMeta?.originalName
165
- || this._fileMeta?.name
166
- || ''
167
- ).trim();
168
-
169
- const fileName = displayName;
170
- if (!fileName) {
171
- return;
172
- }
173
-
174
- nameField.innerHTML = '';
175
- nameField.classList.add('vg-filepreview-audio-inline');
176
-
177
- const button = document.createElement('button');
178
- button.type = 'button';
179
- button.className = 'vg-filepreview-audio-inline__toggle';
180
- button.setAttribute('aria-label', 'Play/Pause');
181
-
182
- const icon = document.createElement('span');
183
- icon.className = 'vg-filepreview-audio-inline__icon';
184
- button.appendChild(icon);
185
-
186
- const text = document.createElement('span');
187
- text.className = 'vg-filepreview-audio-inline__name';
188
- text.textContent = fileName;
189
-
190
- button.addEventListener('click', (event) => {
191
- event.preventDefault();
192
- event.stopPropagation();
193
- this._toggleInlineAudio();
194
- });
195
-
196
- nameField.appendChild(button);
197
- nameField.appendChild(text);
198
-
199
- this._inlineAudioButton = button;
200
- this._inlineAudioIcon = icon;
201
- const rootFile = this._element?.classList?.contains('file')
202
- ? this._element
203
- : this._element?.closest?.('.file');
204
- this._inlineAudioContainer = rootFile || this._element || nameField;
205
- this._setInlineAudioProgress(0);
206
- const isCurrentAudioPlaying = VGFilePreview._activeAudioOwner === this && this._inlineAudio && !this._inlineAudio.paused;
207
- this._syncInlineAudioIcon(isCurrentAudioPlaying);
208
- }
209
-
210
- _toggleInlineAudio() {
211
- const src = this._fileUrl?.href || this._filePath || '';
212
- if (!src || !this._inlineAudioButton) {
213
- return;
214
- }
215
-
216
- if (VGFilePreview._activeAudioOwner && VGFilePreview._activeAudioOwner !== this) {
217
- VGFilePreview._activeAudioOwner._stopInlineAudio();
218
- }
219
-
220
- if (!this._inlineAudio || this._inlineAudioSrc !== src) {
221
- this._stopInlineAudio();
222
- this._inlineAudio = new Audio(src);
223
- this._inlineAudioSrc = src;
224
- this._inlineAudio.addEventListener('ended', () => {
225
- this._syncInlineAudioIcon(false);
226
- this._setInlineAudioProgress(0);
227
- });
228
- this._inlineAudio.addEventListener('timeupdate', () => this._syncInlineAudioProgress());
229
- this._inlineAudio.addEventListener('loadedmetadata', () => this._syncInlineAudioProgress());
230
- }
231
-
232
- if (this._inlineAudio.paused) {
233
- this._inlineAudio.play().then(() => {
234
- VGFilePreview._activeAudioOwner = this;
235
- this._syncInlineAudioIcon(true);
236
- }).catch(() => {
237
- this._syncInlineAudioIcon(false);
238
- });
239
- return;
240
- }
241
-
242
- this._inlineAudio.pause();
243
- this._syncInlineAudioIcon(false);
244
- if (VGFilePreview._activeAudioOwner === this) {
245
- VGFilePreview._activeAudioOwner = null;
246
- }
247
- }
248
-
249
- _stopInlineAudio() {
250
- if (!this._inlineAudio) {
251
- this._syncInlineAudioIcon(false);
252
- this._setInlineAudioProgress(0);
253
- return;
254
- }
255
-
256
- this._inlineAudio.pause();
257
- this._inlineAudio.currentTime = 0;
258
- this._syncInlineAudioIcon(false);
259
- this._setInlineAudioProgress(0);
260
- if (VGFilePreview._activeAudioOwner === this) {
261
- VGFilePreview._activeAudioOwner = null;
262
- }
263
- }
264
-
265
- _syncInlineAudioIcon(isPlaying) {
266
- if (!this._inlineAudioIcon) {
267
- return;
268
- }
269
-
270
- this._inlineAudioIcon.innerHTML = isPlaying ? (getSVG('pause') || '') : (getSVG('play') || '');
271
- }
272
-
273
- _syncInlineAudioProgress() {
274
- if (!this._inlineAudio) {
275
- this._setInlineAudioProgress(0);
276
- return;
277
- }
278
-
279
- const duration = Number(this._inlineAudio.duration || 0);
280
- const currentTime = Number(this._inlineAudio.currentTime || 0);
281
- if (!duration || !Number.isFinite(duration) || duration <= 0) {
282
- this._setInlineAudioProgress(0);
283
- return;
284
- }
285
-
286
- const progress = Math.max(0, Math.min(100, (currentTime / duration) * 100));
287
- this._setInlineAudioProgress(progress);
288
- }
289
-
290
- _setInlineAudioProgress(percent) {
291
- if (!this._inlineAudioContainer) {
292
- return;
293
- }
294
-
295
- const normalized = Math.max(0, Math.min(100, Number(percent) || 0));
296
- this._inlineAudioContainer.style.setProperty('--vg-filepreview-audio-inline-progress', `${normalized}%`);
297
- }
298
-
299
- _renderDownloadField() {
300
- const downloadField = this._editableFields.download;
301
- if (!downloadField) {
302
- return;
303
- }
304
-
305
- const downloadLabel = this._i18n?.button('download') || '';
306
- const downloadIcon = getSVG('download') || '';
307
- const fieldTag = String(downloadField.tagName || '').toUpperCase();
308
- let control = null;
309
-
310
- if (fieldTag === 'A' || fieldTag === 'BUTTON') {
311
- control = downloadField;
312
- } else {
313
- control = downloadField.querySelector('[data-vg-filepreview-download-control]');
314
- if (!control) {
315
- control = document.createElement('button');
316
- control.type = 'button';
317
- downloadField.innerHTML = '';
318
- downloadField.appendChild(control);
319
- }
320
- }
321
-
322
- control.classList.add('vg-filepreview-download-trigger');
323
- control.setAttribute('data-vg-filepreview-download-control', 'true');
324
-
325
- if (fieldTag === 'A' && control === downloadField) {
326
- control.setAttribute('href', this._fileUrl?.href || this._filePath || '#');
327
- if (this._fileMeta?.name) {
328
- control.setAttribute('download', this._fileMeta.name);
329
- }
330
- } else if (String(control.tagName || '').toUpperCase() === 'BUTTON') {
331
- control.setAttribute('type', 'button');
332
- }
333
-
334
- if (!control.hasAttribute('data-vg-filepreview-download-content-init')) {
335
- control.setAttribute('data-vg-filepreview-download-content-init', 'true');
336
- control.innerHTML = '';
337
-
338
- if (downloadIcon) {
339
- const icon = document.createElement('span');
340
- icon.className = 'vg-filepreview-download-trigger__icon';
341
- icon.innerHTML = downloadIcon;
342
- control.appendChild(icon);
343
- }
344
-
345
- if (downloadLabel) {
346
- const text = document.createElement('span');
347
- text.className = 'vg-filepreview-download-trigger__text';
348
- text.textContent = downloadLabel;
349
- control.appendChild(text);
350
- }
351
- }
352
-
353
- if (!control.hasAttribute('data-vg-filepreview-download-bind')) {
354
- control.setAttribute('data-vg-filepreview-download-bind', 'true');
355
- control.addEventListener('click', (event) => {
356
- event.preventDefault();
357
- this._downloadFile();
358
- });
359
- }
360
- }
361
-
362
- _downloadFile() {
363
- const src = this._fileUrl?.href || this._filePath || '';
364
- if (!src) {
365
- return;
366
- }
367
-
368
- const fileName = this._fileMeta?.originalName || this._fileMeta?.name || 'file';
369
-
370
- fetch(src, {
371
- method: 'GET',
372
- credentials: 'same-origin'
373
- })
374
- .then((response) => {
375
- if (!response.ok) {
376
- throw new Error(`HTTP ${response.status}`);
377
- }
378
- return response.blob();
379
- })
380
- .then((blob) => {
381
- const objectUrl = URL.createObjectURL(blob);
382
- this._downloadByLink(objectUrl, fileName);
383
- setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
384
- })
385
- .catch(() => {
386
- this._downloadByLink(src, fileName);
387
- });
388
- }
389
-
390
- _downloadByLink(href, fileName) {
391
- const link = document.createElement('a');
392
- link.href = href;
393
- link.style.display = 'none';
394
- link.rel = 'noopener noreferrer';
395
- if (fileName) {
396
- link.setAttribute('download', fileName);
397
- }
398
-
399
- document.body.appendChild(link);
400
- link.click();
401
- document.body.removeChild(link);
402
- }
403
-
404
- _renderPreview() {
405
- const isNameOnly = Boolean(this._params?.ui?.nameOnly);
406
- const previewContainer = this._resolvePreviewContainer({
407
- autoCreate: !isNameOnly
408
- });
409
- if (!previewContainer && !isNameOnly) {
410
- this._setState('error');
411
- return;
412
- }
413
-
414
- if (previewContainer) {
415
- previewContainer.innerHTML = '';
416
- }
417
-
418
- const context = {
419
- element: this._element,
420
- filePath: this._filePath,
421
- fileUrl: this._fileUrl,
422
- fileMeta: this._fileMeta,
423
- previewContainer,
424
- lang: this._lang,
425
- i18n: this._i18n,
426
- ui: this._params?.ui || {}
427
- };
428
-
429
- let rendered = false;
430
- this._renderers.forEach((renderer) => {
431
- if (rendered || typeof renderer?.canRender !== 'function' || typeof renderer?.render !== 'function') {
432
- return;
433
- }
434
-
435
- if (!renderer.canRender(context)) {
436
- return;
437
- }
438
-
439
- try {
440
- rendered = renderer.render(context) === true;
441
- } catch (error) {
442
- rendered = false;
443
- }
444
- if (rendered) {
445
- this._element.setAttribute('data-vg-filepreview-renderer', renderer.name || 'custom');
446
- }
447
- });
448
-
449
- if (!rendered && !isNameOnly) {
450
- this._element.removeAttribute('data-vg-filepreview-renderer');
451
- this._setState('empty');
452
- return;
453
- }
454
- this._setState('ready');
455
- }
456
-
457
- _resolvePreviewContainer(params = {}) {
458
- const autoCreate = !Object.prototype.hasOwnProperty.call(params, 'autoCreate') || Boolean(params.autoCreate);
459
- const editablePreview = this._editableFields.preview;
460
- if (editablePreview) {
461
- editablePreview.setAttribute('data-vg-filepreview-slot', 'preview');
462
- return editablePreview;
463
- }
464
-
465
- const existedPreview = this._element.querySelector('[data-vg-filepreview-slot="preview"]');
466
- if (existedPreview) {
467
- this._editableFields.preview = existedPreview;
468
- return existedPreview;
469
- }
470
-
471
- if (!autoCreate) {
472
- return null;
473
- }
474
-
475
- const container = document.createElement('div');
476
- container.className = 'preview';
477
- container.setAttribute('data-vg-filepreview-slot', 'preview');
478
- this._element.appendChild(container);
479
- this._editableFields.preview = container;
480
-
481
- return container;
482
- }
483
-
484
- static init(element, params = {}) {
485
- return VGFilePreview.getOrCreateInstance(element, params);
486
- }
487
-
488
- static stopActiveInlineAudio() {
489
- const owner = VGFilePreview._activeAudioOwner;
490
- if (owner && typeof owner._stopInlineAudio === 'function') {
491
- owner._stopInlineAudio();
492
- }
493
-
494
- VGFilePreview._activeAudioOwner = null;
495
- }
496
-
497
- static stopActiveInlineAudioIfDetached(nodes = []) {
498
- const owner = VGFilePreview._activeAudioOwner;
499
- if (!owner || !owner._element || !Array.isArray(nodes) || !nodes.length) {
500
- return;
501
- }
502
-
503
- const shouldStop = nodes.some((node) => {
504
- if (!node || typeof node.contains !== 'function') {
505
- return false;
506
- }
507
-
508
- return node === owner._element || node.contains(owner._element);
509
- });
510
-
511
- if (shouldStop) {
512
- owner._stopInlineAudio();
513
- VGFilePreview._activeAudioOwner = null;
514
- }
515
- }
516
-
517
- _setState(state = '') {
518
- const value = String(state || '').trim();
519
- if (!value) {
520
- this._element.removeAttribute('data-vg-filepreview-state');
521
- return;
522
- }
523
- this._element.setAttribute('data-vg-filepreview-state', value);
524
- }
525
-
526
- _isAudioFile() {
527
- const ext = String(this._fileMeta?.ext || '').toLowerCase();
528
- return ['.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a', '.opus', '.wma'].includes(ext);
529
- }
530
- }
531
-
532
- 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;