vgapp 1.1.2 → 1.1.4

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 +23 -5
  2. package/README.md +19 -16
  3. package/agents.md +6 -0
  4. package/app/langs/en/buttons.json +17 -2
  5. package/app/langs/en/messages.json +36 -1
  6. package/app/langs/ru/buttons.json +17 -2
  7. package/app/langs/ru/messages.json +69 -34
  8. package/app/modules/module-fn.js +15 -9
  9. package/app/modules/vgfilepreview/index.js +3 -0
  10. package/app/modules/vgfilepreview/js/i18n.js +56 -0
  11. package/app/modules/vgfilepreview/js/renderers/image-modal.js +145 -0
  12. package/app/modules/vgfilepreview/js/renderers/image.js +92 -0
  13. package/app/modules/vgfilepreview/js/renderers/index.js +19 -0
  14. package/app/modules/vgfilepreview/js/renderers/office-modal.js +168 -0
  15. package/app/modules/vgfilepreview/js/renderers/office.js +79 -0
  16. package/app/modules/vgfilepreview/js/renderers/pdf-modal.js +260 -0
  17. package/app/modules/vgfilepreview/js/renderers/pdf.js +76 -0
  18. package/app/modules/vgfilepreview/js/renderers/playlist.js +71 -0
  19. package/app/modules/vgfilepreview/js/renderers/text-modal.js +343 -0
  20. package/app/modules/vgfilepreview/js/renderers/text.js +83 -0
  21. package/app/modules/vgfilepreview/js/renderers/video-modal.js +272 -0
  22. package/app/modules/vgfilepreview/js/renderers/video.js +80 -0
  23. package/app/modules/vgfilepreview/js/renderers/zip-modal.js +522 -0
  24. package/app/modules/vgfilepreview/js/renderers/zip.js +89 -0
  25. package/app/modules/vgfilepreview/js/vgfilepreview.js +594 -0
  26. package/app/modules/vgfilepreview/readme.md +68 -0
  27. package/app/modules/vgfilepreview/scss/_variables.scss +113 -0
  28. package/app/modules/vgfilepreview/scss/vgfilepreview.scss +460 -0
  29. package/app/modules/vgfiles/js/base.js +463 -175
  30. package/app/modules/vgfiles/js/droppable.js +260 -260
  31. package/app/modules/vgfiles/js/render.js +153 -153
  32. package/app/modules/vgfiles/js/vgfiles.js +41 -29
  33. package/app/modules/vgfiles/readme.md +116 -217
  34. package/app/modules/vgfiles/scss/_variables.scss +18 -10
  35. package/app/modules/vgfiles/scss/vgfiles.scss +153 -59
  36. package/app/modules/vgformsender/js/vgformsender.js +13 -13
  37. package/app/modules/vgmodal/js/vgmodal.js +12 -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 -0
  47. package/app/utils/js/components/file-icon.js +109 -0
  48. package/app/utils/js/components/file-preview.js +304 -0
  49. package/app/utils/js/components/sanitize.js +150 -150
  50. package/build/vgapp.css +1 -1
  51. package/build/vgapp.css.map +1 -1
  52. package/build/vgapp.js.map +1 -1
  53. package/index.js +1 -0
  54. package/index.scss +9 -6
  55. package/package.json +1 -1
@@ -0,0 +1,260 @@
1
+ import VGModal from "../../../vgmodal";
2
+
3
+ class PdfModal {
4
+ constructor() {
5
+ this._modalId = 'vg-filepreview-pdf-modal';
6
+ this._labels = {};
7
+ this._src = '';
8
+ this._downloadName = '';
9
+ this._page = 1;
10
+ this._zoom = 100;
11
+ }
12
+
13
+ static getInstance() {
14
+ if (!PdfModal._instance) {
15
+ PdfModal._instance = new PdfModal();
16
+ }
17
+
18
+ return PdfModal._instance;
19
+ }
20
+
21
+ open(payload = {}) {
22
+ const src = String(payload.src || '').trim();
23
+ if (!src) {
24
+ return;
25
+ }
26
+
27
+ this._ensureModal();
28
+ if (!this._modal || !this._frame) {
29
+ return;
30
+ }
31
+
32
+ this._labels = payload?.labels && typeof payload.labels === 'object' ? payload.labels : {};
33
+ this._src = src;
34
+ this._downloadName = String(payload.downloadName || '').trim();
35
+ this._page = 1;
36
+ this._zoom = 100;
37
+
38
+ const defaultTitle = String(payload.defaultTitle || '').trim();
39
+ const title = String(payload.title || '').trim();
40
+ this._title.textContent = title || defaultTitle;
41
+
42
+ this._syncControls();
43
+ this._updateFrameSrc();
44
+ this._modal.show();
45
+ }
46
+
47
+ _ensureModal() {
48
+ if (this._modal && this._root) {
49
+ return;
50
+ }
51
+ this._initModal();
52
+ }
53
+
54
+ _initModal() {
55
+ const params = {
56
+ centered: true,
57
+ dismiss: true,
58
+ backdrop: true,
59
+ keyboard: true,
60
+ sizes: {
61
+ width: 'fit-content',
62
+ },
63
+ animation: {
64
+ enable: false
65
+ }
66
+ };
67
+
68
+ const existed = document.getElementById(this._modalId);
69
+ if (existed) {
70
+ this._root = existed;
71
+ this._modal = VGModal.getOrCreateInstance(existed, params);
72
+ this._bindElements(existed);
73
+ this._bindLifecycle(existed);
74
+ return;
75
+ }
76
+
77
+ this._modal = VGModal.build(this._modalId, params, (modalInstance) => {
78
+ const element = modalInstance._element;
79
+ this._root = element;
80
+ element.classList.add('vg-filepreview-pdf-modal');
81
+
82
+ const body = element.querySelector('.vg-modal-body');
83
+ const content = element.querySelector('.vg-modal-content');
84
+ if (!body || !content) {
85
+ return;
86
+ }
87
+
88
+ body.classList.add('vg-filepreview-pdf-modal__body');
89
+ body.innerHTML = '';
90
+
91
+ let header = element.querySelector('.vg-modal-header');
92
+ if (!header) {
93
+ header = document.createElement('div');
94
+ header.className = 'vg-modal-header';
95
+ content.prepend(header);
96
+ }
97
+
98
+ this._title = document.createElement('div');
99
+ this._title.className = 'vg-modal-title';
100
+
101
+ const toolbar = document.createElement('div');
102
+ toolbar.className = 'vg-filepreview-pdf-modal__toolbar';
103
+
104
+ this._pagePrev = document.createElement('button');
105
+ this._pagePrev.type = 'button';
106
+ this._pagePrev.className = 'vg-filepreview-pdf-modal__btn';
107
+ this._pagePrev.setAttribute('data-role', 'page-prev');
108
+ this._pagePrev.addEventListener('click', () => {
109
+ this._page = Math.max(1, this._page - 1);
110
+ this._syncControls();
111
+ this._updateFrameSrc();
112
+ });
113
+
114
+ this._pageInfo = document.createElement('span');
115
+ this._pageInfo.className = 'vg-filepreview-pdf-modal__meta';
116
+ this._pageInfo.setAttribute('data-role', 'page');
117
+
118
+ this._pageNext = document.createElement('button');
119
+ this._pageNext.type = 'button';
120
+ this._pageNext.className = 'vg-filepreview-pdf-modal__btn';
121
+ this._pageNext.setAttribute('data-role', 'page-next');
122
+ this._pageNext.addEventListener('click', () => {
123
+ this._page += 1;
124
+ this._syncControls();
125
+ this._updateFrameSrc();
126
+ });
127
+
128
+ this._zoomOut = document.createElement('button');
129
+ this._zoomOut.type = 'button';
130
+ this._zoomOut.className = 'vg-filepreview-pdf-modal__btn';
131
+ this._zoomOut.setAttribute('data-role', 'zoom-out');
132
+ this._zoomOut.addEventListener('click', () => {
133
+ this._zoom = Math.max(50, this._zoom - 10);
134
+ this._syncControls();
135
+ this._updateFrameSrc();
136
+ });
137
+
138
+ this._zoomIn = document.createElement('button');
139
+ this._zoomIn.type = 'button';
140
+ this._zoomIn.className = 'vg-filepreview-pdf-modal__btn';
141
+ this._zoomIn.setAttribute('data-role', 'zoom-in');
142
+ this._zoomIn.addEventListener('click', () => {
143
+ this._zoom = Math.min(300, this._zoom + 10);
144
+ this._syncControls();
145
+ this._updateFrameSrc();
146
+ });
147
+
148
+ this._zoomInfo = document.createElement('span');
149
+ this._zoomInfo.className = 'vg-filepreview-pdf-modal__meta';
150
+ this._zoomInfo.setAttribute('data-role', 'zoom');
151
+
152
+ this._download = document.createElement('button');
153
+ this._download.type = 'button';
154
+ this._download.className = 'vg-filepreview-pdf-modal__btn primary';
155
+ this._download.setAttribute('data-role', 'download');
156
+ this._download.addEventListener('click', () => this._downloadFile());
157
+
158
+ toolbar.appendChild(this._pagePrev);
159
+ toolbar.appendChild(this._pageInfo);
160
+ toolbar.appendChild(this._pageNext);
161
+ toolbar.appendChild(this._zoomOut);
162
+ toolbar.appendChild(this._zoomInfo);
163
+ toolbar.appendChild(this._zoomIn);
164
+ toolbar.appendChild(this._download);
165
+
166
+ this._frame = document.createElement('iframe');
167
+ this._frame.className = 'vg-filepreview-pdf-modal__frame';
168
+ this._frame.setAttribute('title', 'PDF preview');
169
+
170
+ header.appendChild(this._title);
171
+ //header.appendChild(toolbar);
172
+ body.appendChild(this._frame);
173
+
174
+ this._bindLifecycle(element);
175
+ });
176
+ }
177
+
178
+ _bindElements(root) {
179
+ this._title = root.querySelector('.vg-filepreview-pdf-modal__title');
180
+ this._frame = root.querySelector('.vg-filepreview-pdf-modal__frame');
181
+ this._pagePrev = root.querySelector('.vg-filepreview-pdf-modal__btn[data-role="page-prev"]');
182
+ this._pageNext = root.querySelector('.vg-filepreview-pdf-modal__btn[data-role="page-next"]');
183
+ this._zoomOut = root.querySelector('.vg-filepreview-pdf-modal__btn[data-role="zoom-out"]');
184
+ this._zoomIn = root.querySelector('.vg-filepreview-pdf-modal__btn[data-role="zoom-in"]');
185
+ this._download = root.querySelector('.vg-filepreview-pdf-modal__btn[data-role="download"]');
186
+ this._pageInfo = root.querySelector('.vg-filepreview-pdf-modal__meta[data-role="page"]');
187
+ this._zoomInfo = root.querySelector('.vg-filepreview-pdf-modal__meta[data-role="zoom"]');
188
+ }
189
+
190
+ _bindLifecycle(root) {
191
+ if (!root || root.hasAttribute('data-vg-filepreview-pdf-lifecycle-bind')) {
192
+ return;
193
+ }
194
+
195
+ root.setAttribute('data-vg-filepreview-pdf-lifecycle-bind', 'true');
196
+ root.addEventListener('vg.modal.hidden', () => this._destroyModal());
197
+ }
198
+
199
+ _syncControls() {
200
+ if (!this._pageInfo || !this._zoomInfo) {
201
+ return;
202
+ }
203
+
204
+ this._pagePrev.textContent = this._label('prevPage');
205
+ this._pageNext.textContent = this._label('nextPage');
206
+ this._zoomOut.textContent = this._label('zoomOut');
207
+ this._zoomIn.textContent = this._label('zoomIn');
208
+ this._download.textContent = this._label('download');
209
+ this._pageInfo.textContent = `${this._label('page')}: ${this._page}`;
210
+ this._zoomInfo.textContent = `${this._label('zoom')}: ${this._zoom}%`;
211
+ }
212
+
213
+ _updateFrameSrc() {
214
+ if (!this._frame || !this._src) {
215
+ return;
216
+ }
217
+
218
+ const hash = `#page=${this._page}&zoom=${this._zoom}`;
219
+ this._frame.src = `${this._src}${hash}`;
220
+ }
221
+
222
+ _downloadFile() {
223
+ if (!this._src) {
224
+ return;
225
+ }
226
+
227
+ const link = document.createElement('a');
228
+ link.href = this._src;
229
+ if (this._downloadName) {
230
+ link.setAttribute('download', this._downloadName);
231
+ }
232
+ link.style.display = 'none';
233
+ document.body.appendChild(link);
234
+ link.click();
235
+ document.body.removeChild(link);
236
+ }
237
+
238
+ _label(key) {
239
+ const value = this._labels?.[key];
240
+ return String(value || '').trim();
241
+ }
242
+
243
+ _destroyModal() {
244
+ if (this._modal && typeof this._modal.dispose === 'function') {
245
+ this._modal.dispose();
246
+ }
247
+ if (this._root && this._root.parentNode) {
248
+ this._root.parentNode.removeChild(this._root);
249
+ }
250
+
251
+ this._root = null;
252
+ this._modal = null;
253
+ this._title = null;
254
+ this._frame = null;
255
+ this._labels = null;
256
+ this._src = '';
257
+ }
258
+ }
259
+
260
+ export default PdfModal;
@@ -0,0 +1,76 @@
1
+ import PdfModal from "./pdf-modal";
2
+
3
+ const PDF_EXTENSIONS = new Set([
4
+ '.pdf'
5
+ ]);
6
+
7
+ class PdfFilePreviewRenderer {
8
+ constructor() {
9
+ this.name = 'pdf';
10
+ this._modal = PdfModal.getInstance();
11
+ }
12
+
13
+ canRender(context = {}) {
14
+ const ext = String(context?.fileMeta?.ext || '').toLowerCase();
15
+ return PDF_EXTENSIONS.has(ext);
16
+ }
17
+
18
+ render(context = {}) {
19
+ const container = context?.previewContainer;
20
+ const nameOnly = Boolean(context?.ui?.nameOnly);
21
+ const i18n = context?.i18n;
22
+
23
+ const src = context?.fileUrl?.href || context?.filePath || '';
24
+ if (!src) {
25
+ return false;
26
+ }
27
+
28
+ const openPdf = (event) => {
29
+ if (event) {
30
+ event.preventDefault();
31
+ }
32
+
33
+ this._modal.open({
34
+ src,
35
+ title: context?.fileMeta?.name || i18n?.message('pdf_title') || '',
36
+ defaultTitle: i18n?.message('pdf_title') || '',
37
+ labels: {
38
+ page: i18n?.message('pdf_page') || '',
39
+ zoom: i18n?.message('pdf_zoom') || '',
40
+ zoomIn: i18n?.button('zoom_in') || '',
41
+ zoomOut: i18n?.button('zoom_out') || '',
42
+ prevPage: i18n?.button('prev') || '',
43
+ nextPage: i18n?.button('next') || '',
44
+ download: i18n?.button('download') || ''
45
+ },
46
+ downloadName: context?.fileMeta?.originalName || context?.fileMeta?.name || 'file.pdf'
47
+ });
48
+ };
49
+
50
+ const titleLink = context?.element?.querySelector('.name');
51
+ if (titleLink && !titleLink.hasAttribute('data-vg-filepreview-pdf-bind')) {
52
+ titleLink.setAttribute('data-vg-filepreview-pdf-bind', 'true');
53
+ titleLink.classList.add('is-preview-action');
54
+ titleLink.addEventListener('click', openPdf);
55
+ }
56
+
57
+ if (nameOnly) {
58
+ return Boolean(titleLink);
59
+ }
60
+
61
+ if (!container) {
62
+ return false;
63
+ }
64
+
65
+ const trigger = document.createElement('button');
66
+ trigger.type = 'button';
67
+ trigger.className = 'vg-filepreview-pdf-trigger';
68
+ trigger.textContent = i18n?.button('open_pdf') || '';
69
+ trigger.addEventListener('click', openPdf);
70
+ container.appendChild(trigger);
71
+
72
+ return true;
73
+ }
74
+ }
75
+
76
+ export default PdfFilePreviewRenderer;
@@ -0,0 +1,71 @@
1
+ const getPathFromElement = (element) => {
2
+ const value = element?.getAttribute('data-vg-filepreview') || '';
3
+ return String(value).trim();
4
+ };
5
+
6
+ const parseExt = (path) => {
7
+ if (!path) {
8
+ return '';
9
+ }
10
+
11
+ const clean = String(path).split('#')[0].split('?')[0];
12
+ const name = clean.split('/').pop() || '';
13
+ if (!name.includes('.')) {
14
+ return '';
15
+ }
16
+
17
+ return `.${String(name.split('.').pop() || '').toLowerCase()}`;
18
+ };
19
+
20
+ const toAbsoluteUrl = (path) => {
21
+ try {
22
+ return new URL(path, window.location.origin).href;
23
+ } catch {
24
+ return '';
25
+ }
26
+ };
27
+
28
+ const resolveTitle = (element, fallbackPath = '') => {
29
+ const node = element?.querySelector('.name');
30
+ const title = String(node?.textContent || '').trim();
31
+ if (title) {
32
+ return title;
33
+ }
34
+
35
+ const name = String(fallbackPath).split('/').pop() || '';
36
+ return decodeURIComponent(name);
37
+ };
38
+
39
+ const resolveSubtitle = (element) => {
40
+ const node = element?.querySelector('.original_name');
41
+ return String(node?.textContent || '').trim();
42
+ };
43
+
44
+ const buildMediaPlaylist = (currentElement, acceptExt = () => true) => {
45
+ const nodes = Array.from(document.querySelectorAll('[data-vg-filepreview][data-vg-filepreview-valid="true"]'));
46
+ const tracks = nodes
47
+ .map((element) => {
48
+ const path = getPathFromElement(element);
49
+ const src = toAbsoluteUrl(path);
50
+ const ext = parseExt(path);
51
+ return {
52
+ element,
53
+ path,
54
+ src,
55
+ ext,
56
+ title: resolveTitle(element, path),
57
+ subtitle: resolveSubtitle(element)
58
+ };
59
+ })
60
+ .filter((item) => item.src && acceptExt(item.ext));
61
+
62
+ const currentSrc = toAbsoluteUrl(getPathFromElement(currentElement));
63
+ const currentIndex = tracks.findIndex((item) => item.src === currentSrc);
64
+
65
+ return {
66
+ tracks,
67
+ currentIndex: currentIndex >= 0 ? currentIndex : 0
68
+ };
69
+ };
70
+
71
+ export { buildMediaPlaylist };