@underpostnet/underpost 2.97.0 → 2.97.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.
- package/README.md +2 -2
- package/baremetal/commission-workflows.json +33 -3
- package/bin/deploy.js +1 -1
- package/cli.md +7 -2
- package/conf.js +3 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/packer/scripts/fuse-tar-root +3 -3
- package/scripts/disk-clean.sh +23 -23
- package/scripts/gpu-diag.sh +2 -2
- package/scripts/ip-info.sh +11 -11
- package/scripts/maas-upload-boot-resource.sh +1 -1
- package/scripts/nvim.sh +1 -1
- package/scripts/packer-setup.sh +13 -13
- package/scripts/rocky-setup.sh +2 -2
- package/scripts/rpmfusion-ffmpeg-setup.sh +4 -4
- package/scripts/ssl.sh +7 -7
- package/src/api/core/core.service.js +0 -5
- package/src/api/default/default.service.js +7 -5
- package/src/api/document/document.model.js +30 -1
- package/src/api/document/document.router.js +6 -0
- package/src/api/document/document.service.js +423 -51
- package/src/api/file/file.model.js +112 -4
- package/src/api/file/file.ref.json +42 -0
- package/src/api/file/file.service.js +380 -32
- package/src/api/user/user.model.js +38 -1
- package/src/api/user/user.router.js +96 -63
- package/src/api/user/user.service.js +81 -48
- package/src/cli/baremetal.js +689 -329
- package/src/cli/cluster.js +50 -52
- package/src/cli/db.js +424 -166
- package/src/cli/deploy.js +1 -1
- package/src/cli/index.js +12 -1
- package/src/cli/lxd.js +3 -3
- package/src/cli/repository.js +1 -1
- package/src/cli/run.js +2 -1
- package/src/cli/ssh.js +10 -10
- package/src/client/components/core/Account.js +327 -36
- package/src/client/components/core/AgGrid.js +3 -0
- package/src/client/components/core/Auth.js +9 -3
- package/src/client/components/core/Chat.js +2 -2
- package/src/client/components/core/Content.js +159 -78
- package/src/client/components/core/Css.js +16 -2
- package/src/client/components/core/CssCore.js +16 -12
- package/src/client/components/core/FileExplorer.js +115 -8
- package/src/client/components/core/Input.js +204 -11
- package/src/client/components/core/LogIn.js +42 -20
- package/src/client/components/core/Modal.js +257 -177
- package/src/client/components/core/Panel.js +324 -27
- package/src/client/components/core/PanelForm.js +280 -73
- package/src/client/components/core/PublicProfile.js +888 -0
- package/src/client/components/core/Router.js +117 -15
- package/src/client/components/core/SearchBox.js +1117 -0
- package/src/client/components/core/SignUp.js +26 -7
- package/src/client/components/core/SocketIo.js +6 -3
- package/src/client/components/core/Translate.js +98 -0
- package/src/client/components/core/Validator.js +15 -0
- package/src/client/components/core/windowGetDimensions.js +6 -6
- package/src/client/components/default/MenuDefault.js +59 -12
- package/src/client/components/default/RoutesDefault.js +1 -0
- package/src/client/services/core/core.service.js +163 -1
- package/src/client/services/default/default.management.js +451 -64
- package/src/client/services/default/default.service.js +13 -6
- package/src/client/services/document/document.service.js +23 -0
- package/src/client/services/file/file.service.js +43 -16
- package/src/client/services/user/user.service.js +13 -9
- package/src/db/DataBaseProvider.js +1 -1
- package/src/db/mongo/MongooseDB.js +1 -1
- package/src/index.js +1 -1
- package/src/mailer/MailerProvider.js +4 -4
- package/src/runtime/express/Express.js +2 -1
- package/src/runtime/lampp/Lampp.js +2 -2
- package/src/server/auth.js +3 -6
- package/src/server/data-query.js +449 -0
- package/src/server/dns.js +4 -4
- package/src/server/object-layer.js +0 -3
- package/src/ws/IoInterface.js +2 -2
|
@@ -156,6 +156,15 @@ const FileExplorer = {
|
|
|
156
156
|
|
|
157
157
|
EventsUI.onClick(`.btn-input-file-explorer`, async (e) => {
|
|
158
158
|
e.preventDefault();
|
|
159
|
+
|
|
160
|
+
// Check authentication before upload
|
|
161
|
+
if (!Auth.getToken()) {
|
|
162
|
+
return NotificationManager.Push({
|
|
163
|
+
html: Translate.Render(`error-user-not-authenticated`),
|
|
164
|
+
status: 'error',
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
159
168
|
const { errorMessage } = await validators();
|
|
160
169
|
if (!formBodyFiles)
|
|
161
170
|
return NotificationManager.Push({
|
|
@@ -166,6 +175,12 @@ const FileExplorer = {
|
|
|
166
175
|
let fileData;
|
|
167
176
|
{
|
|
168
177
|
const { status, data } = await FileService.post({ body: formBodyFiles });
|
|
178
|
+
if (status === 'error' || !data) {
|
|
179
|
+
return NotificationManager.Push({
|
|
180
|
+
html: Translate.Render(`error-upload-file`),
|
|
181
|
+
status: 'error',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
169
184
|
fileData = data;
|
|
170
185
|
}
|
|
171
186
|
{
|
|
@@ -259,6 +274,8 @@ const FileExplorer = {
|
|
|
259
274
|
// params.data._id
|
|
260
275
|
|
|
261
276
|
this.eGui = document.createElement('div');
|
|
277
|
+
const isPublic = params.data.isPublic;
|
|
278
|
+
const toggleId = `toggle-public-${params.data._id}`;
|
|
262
279
|
this.eGui.innerHTML = html`
|
|
263
280
|
<div class="fl">
|
|
264
281
|
${await BtnIcon.Render({
|
|
@@ -281,6 +298,13 @@ const FileExplorer = {
|
|
|
281
298
|
label: html`<i class="fas fa-copy"></i>`,
|
|
282
299
|
type: 'button',
|
|
283
300
|
})}
|
|
301
|
+
${await BtnIcon.Render({
|
|
302
|
+
class: `in fll management-table-btn-mini ${toggleId}`,
|
|
303
|
+
label: isPublic
|
|
304
|
+
? html`<i class="fas fa-globe" style="color: #4caf50;"></i>`
|
|
305
|
+
: html`<i class="fas fa-lock" style="color: #9e9e9e;"></i>`,
|
|
306
|
+
type: 'button',
|
|
307
|
+
})}
|
|
284
308
|
</div>
|
|
285
309
|
`;
|
|
286
310
|
|
|
@@ -315,13 +339,21 @@ const FileExplorer = {
|
|
|
315
339
|
|
|
316
340
|
EventsUI.onClick(`.btn-file-download-${params.data._id}`, async (e) => {
|
|
317
341
|
e.preventDefault();
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
data:
|
|
321
|
-
status
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
342
|
+
try {
|
|
343
|
+
// Use FileService with blob/ prefix for centralized blob fetching
|
|
344
|
+
const { data: blobArray, status } = await FileService.get({ id: `blob/${params.data.fileId}` });
|
|
345
|
+
if (status === 'success' && blobArray && blobArray[0]) {
|
|
346
|
+
downloadFile(blobArray[0], params.data.name);
|
|
347
|
+
} else {
|
|
348
|
+
throw new Error('Failed to fetch file blob');
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
logger.error('Download failed:', error);
|
|
352
|
+
NotificationManager.Push({
|
|
353
|
+
html: 'Download failed',
|
|
354
|
+
status: 'error',
|
|
355
|
+
});
|
|
356
|
+
}
|
|
325
357
|
});
|
|
326
358
|
EventsUI.onClick(
|
|
327
359
|
`.btn-file-delete-${params.data._id}`,
|
|
@@ -371,6 +403,80 @@ const FileExplorer = {
|
|
|
371
403
|
},
|
|
372
404
|
{ context: 'modal' },
|
|
373
405
|
);
|
|
406
|
+
|
|
407
|
+
// Toggle public/private status
|
|
408
|
+
EventsUI.onClick(
|
|
409
|
+
`.${toggleId}`,
|
|
410
|
+
async (e) => {
|
|
411
|
+
e.preventDefault();
|
|
412
|
+
|
|
413
|
+
// If document is currently private, show confirmation before making public
|
|
414
|
+
if (!params.data.isPublic) {
|
|
415
|
+
const confirmResult = await Modal.RenderConfirm({
|
|
416
|
+
html: async () => {
|
|
417
|
+
return html`
|
|
418
|
+
<div class="in section-mp" style="text-align: center">
|
|
419
|
+
${Translate.Render('confirm-make-public')}
|
|
420
|
+
<br />
|
|
421
|
+
"${params.data.title}"
|
|
422
|
+
</div>
|
|
423
|
+
`;
|
|
424
|
+
},
|
|
425
|
+
id: `confirm-toggle-public-${params.data._id}`,
|
|
426
|
+
});
|
|
427
|
+
if (confirmResult.status !== 'confirm') return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
const { data, status } = await DocumentService.patch({
|
|
432
|
+
id: params.data._id,
|
|
433
|
+
action: 'toggle-public',
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
if (status === 'success') {
|
|
437
|
+
// Update local data
|
|
438
|
+
params.data.isPublic = data.isPublic;
|
|
439
|
+
|
|
440
|
+
// Update documentInstance
|
|
441
|
+
const docIndex = documentInstance.findIndex((d) => d._id === params.data._id);
|
|
442
|
+
if (docIndex !== -1) {
|
|
443
|
+
documentInstance[docIndex].isPublic = data.isPublic;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Update button icon
|
|
447
|
+
const btnElement = s(`.${toggleId}`);
|
|
448
|
+
if (btnElement) {
|
|
449
|
+
const iconElement = btnElement.querySelector('i');
|
|
450
|
+
if (iconElement) {
|
|
451
|
+
if (data.isPublic) {
|
|
452
|
+
iconElement.className = 'fas fa-globe';
|
|
453
|
+
iconElement.style.color = '#4caf50';
|
|
454
|
+
} else {
|
|
455
|
+
iconElement.className = 'fas fa-lock';
|
|
456
|
+
iconElement.style.color = '#9e9e9e';
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
NotificationManager.Push({
|
|
462
|
+
html: data.isPublic
|
|
463
|
+
? Translate.Render('document-now-public')
|
|
464
|
+
: Translate.Render('document-now-private'),
|
|
465
|
+
status: 'success',
|
|
466
|
+
});
|
|
467
|
+
} else {
|
|
468
|
+
throw new Error('Failed to toggle public status');
|
|
469
|
+
}
|
|
470
|
+
} catch (error) {
|
|
471
|
+
logger.error('Toggle public failed:', error);
|
|
472
|
+
NotificationManager.Push({
|
|
473
|
+
html: Translate.Render('error-toggle-public'),
|
|
474
|
+
status: 'error',
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
{ context: 'modal' },
|
|
479
|
+
);
|
|
374
480
|
});
|
|
375
481
|
}
|
|
376
482
|
|
|
@@ -618,7 +724,7 @@ const FileExplorer = {
|
|
|
618
724
|
columnDefs: [
|
|
619
725
|
{ field: 'name', flex: 2, headerName: 'Name', cellRenderer: LoadFileNameRenderer },
|
|
620
726
|
{ field: 'mimetype', flex: 1, headerName: 'Type' },
|
|
621
|
-
{ headerName: '', width:
|
|
727
|
+
{ headerName: '', width: 120, cellRenderer: LoadFileActionsRenderer },
|
|
622
728
|
],
|
|
623
729
|
},
|
|
624
730
|
})}
|
|
@@ -680,6 +786,7 @@ const FileExplorer = {
|
|
|
680
786
|
fileId: f.fileId._id,
|
|
681
787
|
_id: f._id,
|
|
682
788
|
title: f.title,
|
|
789
|
+
isPublic: f.isPublic || false,
|
|
683
790
|
};
|
|
684
791
|
});
|
|
685
792
|
let documentId = document._id;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input component module for form controls and file handling utilities.
|
|
3
|
+
* Provides input rendering, file data conversion, and blob endpoint integration.
|
|
4
|
+
*
|
|
5
|
+
* @module src/client/components/core/Input.js
|
|
6
|
+
* @namespace InputClient
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import { AgGrid } from './AgGrid.js';
|
|
2
10
|
import { BtnIcon } from './BtnIcon.js';
|
|
3
11
|
import { isValidDate } from './CommonJs.js';
|
|
@@ -8,8 +16,26 @@ import { RichText } from './RichText.js';
|
|
|
8
16
|
import { ToggleSwitch } from './ToggleSwitch.js';
|
|
9
17
|
import { Translate } from './Translate.js';
|
|
10
18
|
import { htmls, htmlStrSanitize, s } from './VanillaJs.js';
|
|
19
|
+
import { getApiBaseUrl } from '../../services/core/core.service.js';
|
|
20
|
+
import { FileService } from '../../services/file/file.service.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Logger instance for this module.
|
|
24
|
+
* @type {Function}
|
|
25
|
+
* @memberof InputClient
|
|
26
|
+
* @private
|
|
27
|
+
*/
|
|
11
28
|
const logger = loggerFactory(import.meta);
|
|
12
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Creates a FormData object from file input event.
|
|
32
|
+
* Filters files by extension if provided.
|
|
33
|
+
* @function fileFormDataFactory
|
|
34
|
+
* @memberof InputClient
|
|
35
|
+
* @param {Event} e - The input change event containing files.
|
|
36
|
+
* @param {string[]} [extensions] - Optional array of allowed MIME types.
|
|
37
|
+
* @returns {FormData} FormData object containing the valid files.
|
|
38
|
+
*/
|
|
13
39
|
const fileFormDataFactory = (e, extensions) => {
|
|
14
40
|
const form = new FormData();
|
|
15
41
|
for (const keyFile of Object.keys(e.target.files)) {
|
|
@@ -22,17 +48,156 @@ const fileFormDataFactory = (e, extensions) => {
|
|
|
22
48
|
return form;
|
|
23
49
|
};
|
|
24
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Convert file data to File object.
|
|
53
|
+
* Supports both legacy format (with buffer data) and new format (metadata only).
|
|
54
|
+
*
|
|
55
|
+
* Legacy format: `{ data: { data: [0, 1, 2, ...] }, mimetype: 'text/markdown', name: 'file.md' }`
|
|
56
|
+
* New format: `{ _id: '...', mimetype: 'text/markdown', name: 'file.md' }`
|
|
57
|
+
*
|
|
58
|
+
* @function getFileFromFileData
|
|
59
|
+
* @memberof InputClient
|
|
60
|
+
* @param {Object} fileData - File data object in legacy or new format.
|
|
61
|
+
* @param {Object} [fileData.data] - Legacy format data container.
|
|
62
|
+
* @param {Array<number>} [fileData.data.data] - Legacy format byte array.
|
|
63
|
+
* @param {string} [fileData._id] - New format file ID.
|
|
64
|
+
* @param {string} fileData.mimetype - MIME type of the file.
|
|
65
|
+
* @param {string} fileData.name - Name of the file.
|
|
66
|
+
* @returns {File|null} File object if legacy format, null if metadata-only or invalid.
|
|
67
|
+
*/
|
|
25
68
|
const getFileFromFileData = (fileData) => {
|
|
26
|
-
|
|
27
|
-
|
|
69
|
+
if (!fileData) {
|
|
70
|
+
logger.error('getFileFromFileData: fileData is undefined');
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if this is legacy format with buffer data
|
|
75
|
+
if (fileData.data?.data) {
|
|
76
|
+
try {
|
|
77
|
+
const blob = new Blob([new Uint8Array(fileData.data.data)], { type: fileData.mimetype });
|
|
78
|
+
return new File([blob], fileData.name, { type: fileData.mimetype });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logger.error('Error creating File from legacy buffer data:', error);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// New format - metadata only, cannot create File without content
|
|
86
|
+
// Return null and let caller fetch from blob endpoint if needed
|
|
87
|
+
if (fileData._id && !fileData.data?.data) {
|
|
88
|
+
logger.warn(
|
|
89
|
+
'getFileFromFileData: File is metadata-only, cannot create File object without content. File ID:',
|
|
90
|
+
fileData._id,
|
|
91
|
+
);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
logger.error('getFileFromFileData: Invalid file data structure', fileData);
|
|
96
|
+
return null;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Fetch file content from blob endpoint and create File object.
|
|
101
|
+
* Used for metadata-only format files during edit mode.
|
|
102
|
+
* Uses FileService with blob/ prefix for centralized blob fetching.
|
|
103
|
+
*
|
|
104
|
+
* @async
|
|
105
|
+
* @function getFileFromBlobEndpoint
|
|
106
|
+
* @memberof InputClient
|
|
107
|
+
* @param {Object} fileData - File metadata object with _id.
|
|
108
|
+
* @param {string} fileData._id - File ID for blob endpoint lookup.
|
|
109
|
+
* @param {string} [fileData.name] - Optional file name.
|
|
110
|
+
* @param {string} [fileData.mimetype] - Optional MIME type.
|
|
111
|
+
* @returns {Promise<File|null>} File object from blob endpoint, or null on error.
|
|
112
|
+
*/
|
|
113
|
+
const getFileFromBlobEndpoint = async (fileData) => {
|
|
114
|
+
if (!fileData || !fileData._id) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const { data: blobArray, status } = await FileService.get({ id: `blob/${fileData._id}` });
|
|
120
|
+
if (status !== 'success' || !blobArray || !blobArray[0]) {
|
|
121
|
+
logger.error('Failed to fetch file from blob endpoint');
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const blob = blobArray[0];
|
|
126
|
+
return new File([blob], fileData.name || 'file', { type: fileData.mimetype || blob.type });
|
|
127
|
+
} catch (error) {
|
|
128
|
+
logger.error('Error fetching file from blob endpoint:', error);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
28
131
|
};
|
|
29
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Get image/file source URL from file data.
|
|
135
|
+
* Supports both legacy format (with buffer) and new format (metadata only).
|
|
136
|
+
* For new format, returns blob endpoint URL.
|
|
137
|
+
*
|
|
138
|
+
* @function getSrcFromFileData
|
|
139
|
+
* @memberof InputClient
|
|
140
|
+
* @param {Object} fileData - File data object in legacy or new format.
|
|
141
|
+
* @param {Object} [fileData.data] - Legacy format data container.
|
|
142
|
+
* @param {Array<number>} [fileData.data.data] - Legacy format byte array.
|
|
143
|
+
* @param {string} [fileData._id] - New format file ID for blob endpoint.
|
|
144
|
+
* @param {string} fileData.mimetype - MIME type of the file.
|
|
145
|
+
* @returns {string|null} Object URL for legacy format, blob endpoint URL for new format, or null on error.
|
|
146
|
+
*/
|
|
30
147
|
const getSrcFromFileData = (fileData) => {
|
|
31
|
-
|
|
32
|
-
|
|
148
|
+
if (!fileData) {
|
|
149
|
+
logger.error('getSrcFromFileData: fileData is undefined');
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Legacy format with buffer data - create object URL
|
|
154
|
+
if (fileData.data?.data) {
|
|
155
|
+
try {
|
|
156
|
+
const file = getFileFromFileData(fileData);
|
|
157
|
+
if (file) {
|
|
158
|
+
return URL.createObjectURL(file);
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
logger.error('Error getting src from legacy buffer data:', error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// New format - use blob endpoint
|
|
166
|
+
if (fileData._id) {
|
|
167
|
+
try {
|
|
168
|
+
return getApiBaseUrl({ id: fileData._id, endpoint: 'file/blob' });
|
|
169
|
+
} catch (error) {
|
|
170
|
+
logger.error('Error generating blob URL:', error);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
logger.error('getSrcFromFileData: Cannot generate src, invalid file data:', fileData);
|
|
176
|
+
return null;
|
|
33
177
|
};
|
|
34
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Input component for rendering various form input types.
|
|
181
|
+
* Supports text, password, file, color, date, dropdown, toggle, rich text, and grid inputs.
|
|
182
|
+
* @namespace InputClient.Input
|
|
183
|
+
* @memberof InputClient
|
|
184
|
+
*/
|
|
35
185
|
const Input = {
|
|
186
|
+
/**
|
|
187
|
+
* Renders an input element based on the provided options.
|
|
188
|
+
* @async
|
|
189
|
+
* @function Render
|
|
190
|
+
* @memberof InputClient.Input
|
|
191
|
+
* @param {Object} options - Input configuration options.
|
|
192
|
+
* @param {string} options.id - Unique identifier for the input.
|
|
193
|
+
* @param {string} [options.type] - Input type (text, password, file, color, datetime-local, etc.).
|
|
194
|
+
* @param {string} [options.placeholder] - Placeholder text.
|
|
195
|
+
* @param {string} [options.label] - Label text for the input.
|
|
196
|
+
* @param {string} [options.containerClass] - CSS class for the container.
|
|
197
|
+
* @param {string} [options.inputClass] - CSS class for the input element.
|
|
198
|
+
* @param {boolean} [options.disabled] - Whether the input is disabled.
|
|
199
|
+
* @returns {Promise<string>} HTML string for the input component.
|
|
200
|
+
*/
|
|
36
201
|
Render: async function (options) {
|
|
37
202
|
const { id } = options;
|
|
38
203
|
options?.placeholder
|
|
@@ -184,8 +349,8 @@ const Input = {
|
|
|
184
349
|
}
|
|
185
350
|
return obj;
|
|
186
351
|
},
|
|
187
|
-
setValues: function (formData, obj, originObj, fileObj) {
|
|
188
|
-
setTimeout(() => {
|
|
352
|
+
setValues: async function (formData, obj, originObj, fileObj) {
|
|
353
|
+
setTimeout(async () => {
|
|
189
354
|
for (const inputData of formData) {
|
|
190
355
|
if (!s(`.${inputData.id}`)) continue;
|
|
191
356
|
|
|
@@ -194,11 +359,31 @@ const Input = {
|
|
|
194
359
|
if (fileObj && fileObj[inputData.model] && s(`.${inputData.id}`)) {
|
|
195
360
|
const dataTransfer = new DataTransfer();
|
|
196
361
|
|
|
197
|
-
if (fileObj[inputData.model].fileBlob)
|
|
198
|
-
|
|
362
|
+
if (fileObj[inputData.model].fileBlob) {
|
|
363
|
+
let fileBlobData = getFileFromFileData(fileObj[inputData.model].fileBlob);
|
|
364
|
+
|
|
365
|
+
// If fileBlob is metadata-only, try to fetch from blob endpoint
|
|
366
|
+
if (!fileBlobData && fileObj[inputData.model].fileBlob?._id) {
|
|
367
|
+
fileBlobData = await getFileFromBlobEndpoint(fileObj[inputData.model].fileBlob);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (fileBlobData) {
|
|
371
|
+
dataTransfer.items.add(fileBlobData);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
199
374
|
|
|
200
|
-
if (fileObj[inputData.model].mdBlob)
|
|
201
|
-
|
|
375
|
+
if (fileObj[inputData.model].mdBlob) {
|
|
376
|
+
let mdBlobData = getFileFromFileData(fileObj[inputData.model].mdBlob);
|
|
377
|
+
|
|
378
|
+
// If mdBlob is metadata-only, try to fetch from blob endpoint
|
|
379
|
+
if (!mdBlobData && fileObj[inputData.model].mdBlob?._id) {
|
|
380
|
+
mdBlobData = await getFileFromBlobEndpoint(fileObj[inputData.model].mdBlob);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (mdBlobData) {
|
|
384
|
+
dataTransfer.items.add(mdBlobData);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
202
387
|
|
|
203
388
|
if (dataTransfer.files.length) {
|
|
204
389
|
s(`.${inputData.id}`).files = dataTransfer.files;
|
|
@@ -384,4 +569,12 @@ function isTextInputFocused() {
|
|
|
384
569
|
return active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA');
|
|
385
570
|
}
|
|
386
571
|
|
|
387
|
-
export {
|
|
572
|
+
export {
|
|
573
|
+
Input,
|
|
574
|
+
InputFile,
|
|
575
|
+
fileFormDataFactory,
|
|
576
|
+
getSrcFromFileData,
|
|
577
|
+
getFileFromFileData,
|
|
578
|
+
getFileFromBlobEndpoint,
|
|
579
|
+
isTextInputFocused,
|
|
580
|
+
};
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import { CoreService } from '../../services/core/core.service.js';
|
|
1
|
+
import { CoreService, getApiBaseUrl } from '../../services/core/core.service.js';
|
|
2
2
|
import { FileService } from '../../services/file/file.service.js';
|
|
3
3
|
import { UserService } from '../../services/user/user.service.js';
|
|
4
4
|
import { Auth } from './Auth.js';
|
|
5
5
|
import { BtnIcon } from './BtnIcon.js';
|
|
6
6
|
import { EventsUI } from './EventsUI.js';
|
|
7
7
|
import { Input } from './Input.js';
|
|
8
|
+
import { loggerFactory } from './Logger.js';
|
|
8
9
|
import { NotificationManager } from './NotificationManager.js';
|
|
9
10
|
import { Translate } from './Translate.js';
|
|
10
11
|
import { Validator } from './Validator.js';
|
|
11
12
|
import { htmls, s } from './VanillaJs.js';
|
|
12
13
|
import { Webhook } from './Webhook.js';
|
|
13
14
|
|
|
15
|
+
const logger = loggerFactory(import.meta);
|
|
16
|
+
|
|
14
17
|
const LogIn = {
|
|
15
18
|
Scope: {
|
|
16
19
|
user: {
|
|
@@ -24,6 +27,8 @@ const LogIn = {
|
|
|
24
27
|
Event: {},
|
|
25
28
|
Trigger: async function (options) {
|
|
26
29
|
const { user } = options;
|
|
30
|
+
if (user) this.Scope.user.main.model.user = { ...this.Scope.user.main.model.user, ...user };
|
|
31
|
+
|
|
27
32
|
for (const eventKey of Object.keys(this.Event)) await this.Event[eventKey](options);
|
|
28
33
|
if (!user || user.role === 'guest') return;
|
|
29
34
|
await Webhook.register({ user });
|
|
@@ -52,26 +57,40 @@ const LogIn = {
|
|
|
52
57
|
</style>`,
|
|
53
58
|
);
|
|
54
59
|
if (!this.Scope.user.main.model.user.profileImage) {
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
// Try to load profile image only if profileImageId exists
|
|
61
|
+
if (!this.Scope.user.main.model.user.profileImage && user?.profileImageId) {
|
|
62
|
+
try {
|
|
63
|
+
const resultFile = await FileService.get({ id: user.profileImageId });
|
|
64
|
+
if (resultFile && resultFile.status === 'success' && resultFile.data[0]) {
|
|
65
|
+
const imageData = resultFile.data[0];
|
|
66
|
+
let imageSrc = null;
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
try {
|
|
69
|
+
// Handle new metadata-only format
|
|
70
|
+
if (!imageData.data?.data && imageData._id) {
|
|
71
|
+
imageSrc = getApiBaseUrl({ id: imageData._id, endpoint: 'file/blob' });
|
|
72
|
+
}
|
|
73
|
+
// Handle legacy format with buffer data
|
|
74
|
+
else if (imageData.data?.data) {
|
|
75
|
+
const imageBlob = new Blob([new Uint8Array(imageData.data.data)], { type: imageData.mimetype });
|
|
76
|
+
const imageFile = new File([imageBlob], imageData.name, { type: imageData.mimetype });
|
|
77
|
+
imageSrc = URL.createObjectURL(imageFile);
|
|
78
|
+
}
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
if (imageSrc) {
|
|
81
|
+
this.Scope.user.main.model.user.profileImage = {
|
|
82
|
+
resultFile,
|
|
83
|
+
imageData,
|
|
84
|
+
imageSrc,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.warn('Error processing profile image:', error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
logger.warn('Error fetching profile image:', error);
|
|
93
|
+
}
|
|
75
94
|
}
|
|
76
95
|
htmls(
|
|
77
96
|
`.action-btn-profile-log-in-render`,
|
|
@@ -80,7 +99,10 @@ const LogIn = {
|
|
|
80
99
|
class="abs center top-box-profile-img"
|
|
81
100
|
${this.Scope.user.main.model.user.profileImage
|
|
82
101
|
? `src="${this.Scope.user.main.model.user.profileImage.imageSrc}"`
|
|
83
|
-
:
|
|
102
|
+
: `src="${getApiBaseUrl({
|
|
103
|
+
id: 'assets/avatar',
|
|
104
|
+
endpoint: 'user',
|
|
105
|
+
})}"`}
|
|
84
106
|
/>
|
|
85
107
|
</div>`,
|
|
86
108
|
);
|