@underpostnet/underpost 2.97.1 → 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/cli.md +3 -1
- package/conf.js +2 -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/src/api/core/core.service.js +0 -5
- package/src/api/default/default.service.js +7 -5
- package/src/api/document/document.model.js +1 -1
- package/src/api/document/document.router.js +5 -0
- package/src/api/document/document.service.js +105 -47
- 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/db.js +424 -166
- package/src/cli/index.js +8 -0
- package/src/cli/repository.js +1 -1
- package/src/cli/run.js +1 -0
- 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/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 +138 -24
- package/src/client/components/core/Panel.js +69 -31
- package/src/client/components/core/PanelForm.js +262 -77
- 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 +329 -13
- 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/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/object-layer.js +0 -3
- package/src/ws/IoInterface.js +2 -2
|
@@ -13,8 +13,8 @@ const Chat = {
|
|
|
13
13
|
this.Data[idModal] = {};
|
|
14
14
|
setTimeout(() => {
|
|
15
15
|
Modal.Data[idModal].onObserverListener[`chat-${idModal}`] = (options) => {
|
|
16
|
-
const {
|
|
17
|
-
s(`.chat-box`).style.height = `${height
|
|
16
|
+
const { height } = options;
|
|
17
|
+
s(`.chat-box`).style.height = `${height - 250}px`;
|
|
18
18
|
};
|
|
19
19
|
s(`.btn-send-chat-${idModal}`).onclick = (e) => {
|
|
20
20
|
e.preventDefault();
|
|
@@ -5,7 +5,7 @@ import { s4 } from './CommonJs.js';
|
|
|
5
5
|
import { Translate } from './Translate.js';
|
|
6
6
|
import { Modal, renderViewTitle } from './Modal.js';
|
|
7
7
|
import { DocumentService } from '../../services/document/document.service.js';
|
|
8
|
-
import { CoreService, getApiBaseUrl } from '../../services/core/core.service.js';
|
|
8
|
+
import { CoreService, getApiBaseUrl, headersFactory } from '../../services/core/core.service.js';
|
|
9
9
|
import { loggerFactory } from './Logger.js';
|
|
10
10
|
import { imageShimmer, renderChessPattern, renderCssAttr, styleFactory } from './Css.js';
|
|
11
11
|
import { getQueryParams } from './Router.js';
|
|
@@ -39,20 +39,32 @@ const Content = {
|
|
|
39
39
|
}
|
|
40
40
|
documentObj = data[0];
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
// Get file metadata (does not include buffer data)
|
|
44
|
+
if (documentObj.fileId && documentObj.fileId._id) {
|
|
43
45
|
const { data, status, message } = await FileService.get({ id: documentObj.fileId._id });
|
|
44
46
|
if (status !== 'success' || !data || !data[0]) {
|
|
45
|
-
logger.
|
|
46
|
-
//
|
|
47
|
+
logger.warn('File metadata not found:', message);
|
|
48
|
+
// Continue without file - not fatal
|
|
49
|
+
} else {
|
|
50
|
+
file = data[0];
|
|
47
51
|
}
|
|
48
|
-
file = data[0];
|
|
49
52
|
}
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
|
|
54
|
+
// Get markdown file metadata (optional)
|
|
55
|
+
if (documentObj.mdFileId && documentObj.mdFileId._id) {
|
|
56
|
+
const { data, status, message } = await FileService.get({ id: documentObj.mdFileId._id });
|
|
52
57
|
if (status !== 'success' || !data || !data[0]) {
|
|
53
|
-
logger.
|
|
54
|
-
|
|
55
|
-
} else
|
|
58
|
+
logger.warn('Markdown file metadata not found:', message);
|
|
59
|
+
// Continue without markdown - try to render file instead
|
|
60
|
+
} else {
|
|
61
|
+
md = data[0];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check if we have at least one file to render
|
|
66
|
+
if (!md && !file) {
|
|
67
|
+
throw new Error(`no-preview-available`);
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
htmls(
|
|
@@ -63,6 +75,8 @@ const Content = {
|
|
|
63
75
|
})} `,
|
|
64
76
|
);
|
|
65
77
|
htmls(`.content-render-${idModal}`, ``);
|
|
78
|
+
|
|
79
|
+
// Pass file IDs to RenderFile - it will fetch blobs as needed
|
|
66
80
|
if (md) await this.RenderFile({ idModal, file: md, id: md._id });
|
|
67
81
|
if (file) await this.RenderFile({ idModal, file, id: file._id });
|
|
68
82
|
Modal.Data[idModal].onObserverListener[`main-content-observer`]();
|
|
@@ -89,17 +103,48 @@ const Content = {
|
|
|
89
103
|
</div>
|
|
90
104
|
<div class="abs center error-${idModal} hide"></div>`;
|
|
91
105
|
},
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Helper function to get file content
|
|
109
|
+
* Supports both legacy format (with buffer data) and new format (metadata only)
|
|
110
|
+
* For new format, fetches content from blob endpoint
|
|
111
|
+
*/
|
|
112
|
+
getFileContent: async function (file, options = {}) {
|
|
113
|
+
// If custom URL provided, use it
|
|
114
|
+
if (options.url) {
|
|
115
|
+
return await CoreService.getRaw({ url: options.url });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// If buffer data exists in file object (legacy format), use it
|
|
119
|
+
if (file.data?.data) {
|
|
120
|
+
return await getRawContentFile(getBlobFromUint8ArrayFile(file.data.data, file.mimetype));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Otherwise, fetch from blob endpoint (new metadata-only format)
|
|
124
|
+
if (file._id) {
|
|
125
|
+
try {
|
|
126
|
+
const { data: blobArray, status } = await FileService.get({ id: `blob/${file._id}` });
|
|
127
|
+
if (status === 'success' && blobArray && blobArray[0]) {
|
|
128
|
+
return await blobArray[0].text();
|
|
129
|
+
}
|
|
130
|
+
throw new Error('Failed to fetch file content');
|
|
131
|
+
} catch (error) {
|
|
132
|
+
logger.error('Error fetching file content from blob endpoint:', error);
|
|
133
|
+
throw new Error('Could not load file content');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
throw new Error('No file content available');
|
|
138
|
+
},
|
|
139
|
+
|
|
92
140
|
RenderFile: async function (
|
|
93
141
|
options = {
|
|
94
142
|
file: {
|
|
95
143
|
_id: '',
|
|
96
|
-
data: {
|
|
97
|
-
data: [0],
|
|
98
|
-
},
|
|
99
144
|
mimetype: '',
|
|
100
145
|
url: '',
|
|
101
146
|
name: '',
|
|
102
|
-
cid: '',
|
|
147
|
+
cid: '',
|
|
103
148
|
},
|
|
104
149
|
idModal: '',
|
|
105
150
|
style: {},
|
|
@@ -116,80 +161,116 @@ const Content = {
|
|
|
116
161
|
border: 'none',
|
|
117
162
|
};
|
|
118
163
|
if (!options.class) options.class = ``;
|
|
164
|
+
|
|
119
165
|
const { container, file } = options;
|
|
120
|
-
const ext = file.name
|
|
166
|
+
const ext = (file.name || '').split('.').pop()?.toLowerCase() || '';
|
|
121
167
|
let render = '';
|
|
122
168
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
169
|
+
try {
|
|
170
|
+
switch (ext) {
|
|
171
|
+
case 'md':
|
|
172
|
+
{
|
|
173
|
+
const content = await Content.getFileContent(file, options);
|
|
174
|
+
render += html`<div class="${options.class}" ${styleFactory(options.style)}>${marked.parse(content)}</div>`;
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
|
|
178
|
+
case 'jpg':
|
|
179
|
+
case 'jpeg':
|
|
180
|
+
case 'webp':
|
|
181
|
+
case 'svg':
|
|
182
|
+
case 'gif':
|
|
183
|
+
case 'png': {
|
|
184
|
+
const url = await Content.urlFactory(options);
|
|
185
|
+
if (url) {
|
|
186
|
+
const imgRender = html`<img
|
|
187
|
+
alt="${file.name ? file.name : `file ${s4()}`}"
|
|
188
|
+
class="in ${options.class}"
|
|
189
|
+
${styleFactory(options.style, `${renderChessPattern(50)}`)}
|
|
190
|
+
src="${url}"
|
|
191
|
+
/>`;
|
|
192
|
+
render += imgRender;
|
|
193
|
+
} else {
|
|
194
|
+
render = html`<div class="in ${options.class}" ${styleFactory(options.style)}>
|
|
195
|
+
<p style="color: red;">Error loading image</p>
|
|
196
|
+
</div>`;
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
130
199
|
}
|
|
131
200
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
></iframe>`;
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
201
|
+
case 'pdf': {
|
|
202
|
+
const url = await Content.urlFactory(options);
|
|
203
|
+
if (url) {
|
|
204
|
+
render += html`<iframe
|
|
205
|
+
class="in ${options.class} iframe-${options.idModal}"
|
|
206
|
+
${styleFactory(options.style)}
|
|
207
|
+
src="${url}"
|
|
208
|
+
></iframe>`;
|
|
209
|
+
} else {
|
|
210
|
+
render = html`<div class="in ${options.class}" ${styleFactory(options.style)}>
|
|
211
|
+
<p style="color: red;">Error loading PDF</p>
|
|
212
|
+
</div>`;
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
case 'json':
|
|
218
|
+
{
|
|
219
|
+
const content = await Content.getFileContent(file, options);
|
|
220
|
+
render += html`<pre class="in ${options.class}" ${styleFactory(options.style)}>
|
|
221
|
+
${JSON.stringify(JSON.parse(content), null, 4)}</pre
|
|
222
|
+
>`;
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
159
225
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
break;
|
|
173
|
-
|
|
174
|
-
default:
|
|
175
|
-
render += html`<div class="in ${options.class}" ${styleFactory(options.style)}>
|
|
176
|
-
${options.url
|
|
177
|
-
? await CoreService.getRaw({ url: options.url })
|
|
178
|
-
: await getRawContentFile(getBlobFromUint8ArrayFile(file.data.data, file.mimetype))}
|
|
179
|
-
</div>`;
|
|
180
|
-
break;
|
|
226
|
+
default:
|
|
227
|
+
{
|
|
228
|
+
const content = await Content.getFileContent(file, options);
|
|
229
|
+
render += html`<div class="in ${options.class}" ${styleFactory(options.style)}>${content}</div>`;
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
logger.error('Error rendering file:', error);
|
|
235
|
+
render = html`<div class="in ${options.class}" ${styleFactory(options.style)}>
|
|
236
|
+
<p style="color: red;">Error loading file: ${error.message}</p>
|
|
237
|
+
</div>`;
|
|
181
238
|
}
|
|
239
|
+
|
|
182
240
|
if (options.raw) return render;
|
|
183
241
|
append(container, render);
|
|
184
242
|
},
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Generate appropriate URL for file display
|
|
246
|
+
* Prefers blob endpoint for new metadata-only format
|
|
247
|
+
*/
|
|
248
|
+
urlFactory: async function (options) {
|
|
249
|
+
// If custom URL provided, use it
|
|
250
|
+
if (options.url) {
|
|
251
|
+
return options.url;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// If buffer data exists (legacy), create object URL
|
|
255
|
+
if (options.file?.data?.data) {
|
|
256
|
+
return URL.createObjectURL(getBlobFromUint8ArrayFile(options.file.data.data, options.file.mimetype));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Use blob endpoint for metadata-only format with proper authentication
|
|
260
|
+
if (options.file?._id) {
|
|
261
|
+
try {
|
|
262
|
+
const { data: blobArray, status } = await FileService.get({ id: `blob/${options.file._id}` });
|
|
263
|
+
if (status === 'success' && blobArray && blobArray[0]) {
|
|
264
|
+
return URL.createObjectURL(blobArray[0]);
|
|
265
|
+
}
|
|
266
|
+
throw new Error('Failed to fetch file blob');
|
|
267
|
+
} catch (error) {
|
|
268
|
+
logger.error('Error fetching file blob:', error);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return null;
|
|
193
274
|
},
|
|
194
275
|
};
|
|
195
276
|
|
|
@@ -232,6 +232,9 @@ const CssCommonCore = async () => {
|
|
|
232
232
|
width: 40px;
|
|
233
233
|
text-align: center;
|
|
234
234
|
}
|
|
235
|
+
.input-container {
|
|
236
|
+
width: 275px;
|
|
237
|
+
}
|
|
235
238
|
</style>
|
|
236
239
|
${boxShadow({ selector: '.account-profile-image' })}
|
|
237
240
|
<div class="ag-grid-style"></div>`;
|
|
@@ -404,9 +407,7 @@ const CssCoreDark = {
|
|
|
404
407
|
color: #fff;
|
|
405
408
|
background: #313131;
|
|
406
409
|
}
|
|
407
|
-
|
|
408
|
-
width: 256px;
|
|
409
|
-
}
|
|
410
|
+
|
|
410
411
|
.btn-eye-password {
|
|
411
412
|
text-align: center;
|
|
412
413
|
background: #1a1a1a;
|
|
@@ -480,7 +481,7 @@ const CssCoreDark = {
|
|
|
480
481
|
cursor: pointer;
|
|
481
482
|
}
|
|
482
483
|
.btn-custom {
|
|
483
|
-
width:
|
|
484
|
+
width: 278px;
|
|
484
485
|
font-size: 20px;
|
|
485
486
|
padding: 10px;
|
|
486
487
|
min-height: 45px;
|
|
@@ -491,7 +492,7 @@ const CssCoreDark = {
|
|
|
491
492
|
}
|
|
492
493
|
.toggle-form-container,
|
|
493
494
|
.dropdown-option {
|
|
494
|
-
width:
|
|
495
|
+
width: 255px;
|
|
495
496
|
font-size: 20px;
|
|
496
497
|
padding: 10px;
|
|
497
498
|
}
|
|
@@ -503,7 +504,7 @@ const CssCoreDark = {
|
|
|
503
504
|
background: #232323;
|
|
504
505
|
}
|
|
505
506
|
.form-button {
|
|
506
|
-
width:
|
|
507
|
+
width: 278px;
|
|
507
508
|
font-size: 20px;
|
|
508
509
|
padding: 10px;
|
|
509
510
|
text-align: center;
|
|
@@ -599,10 +600,14 @@ const CssCoreLight = {
|
|
|
599
600
|
}
|
|
600
601
|
|
|
601
602
|
.box-shadow {
|
|
602
|
-
box-shadow:
|
|
603
|
+
box-shadow:
|
|
604
|
+
0 4px 8px 0 rgba(0, 0, 0, 0.2),
|
|
605
|
+
0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
|
603
606
|
}
|
|
604
607
|
.box-shadow:hover {
|
|
605
|
-
box-shadow:
|
|
608
|
+
box-shadow:
|
|
609
|
+
0 8px 16px 0 rgba(0, 0, 0, 0.2),
|
|
610
|
+
0 10px 30px 0 rgba(0, 0, 0, 0.3);
|
|
606
611
|
}
|
|
607
612
|
.box-content-border {
|
|
608
613
|
border: 2px solid #bbb;
|
|
@@ -715,7 +720,6 @@ const CssCoreLight = {
|
|
|
715
720
|
border-radius: 5px;
|
|
716
721
|
border: 2px solid #bbb;
|
|
717
722
|
transition: 0.3s;
|
|
718
|
-
width: 256px;
|
|
719
723
|
}
|
|
720
724
|
.input-container:hover {
|
|
721
725
|
color: #1a1a1a;
|
|
@@ -793,7 +797,7 @@ const CssCoreLight = {
|
|
|
793
797
|
cursor: pointer;
|
|
794
798
|
}
|
|
795
799
|
.btn-custom {
|
|
796
|
-
width:
|
|
800
|
+
width: 278px;
|
|
797
801
|
font-size: 20px;
|
|
798
802
|
padding: 10px;
|
|
799
803
|
min-height: 45px;
|
|
@@ -805,7 +809,7 @@ const CssCoreLight = {
|
|
|
805
809
|
}
|
|
806
810
|
.toggle-form-container,
|
|
807
811
|
.dropdown-option {
|
|
808
|
-
width:
|
|
812
|
+
width: 255px;
|
|
809
813
|
font-size: 20px;
|
|
810
814
|
padding: 10px;
|
|
811
815
|
}
|
|
@@ -817,7 +821,7 @@ const CssCoreLight = {
|
|
|
817
821
|
background: #e4e4e4;
|
|
818
822
|
}
|
|
819
823
|
.form-button {
|
|
820
|
-
width:
|
|
824
|
+
width: 278px;
|
|
821
825
|
font-size: 20px;
|
|
822
826
|
padding: 10px;
|
|
823
827
|
text-align: center;
|
|
@@ -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;
|