@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
|
@@ -17,6 +17,8 @@ import { Content } from './Content.js';
|
|
|
17
17
|
import { DocumentService } from '../../services/document/document.service.js';
|
|
18
18
|
import { NotificationManager } from './NotificationManager.js';
|
|
19
19
|
import { getApiBaseUrl } from '../../services/core/core.service.js';
|
|
20
|
+
import { getProxyPath, setQueryPath, navigateToProfile } from './Router.js';
|
|
21
|
+
import { PublicProfile } from './PublicProfile.js';
|
|
20
22
|
|
|
21
23
|
const logger = loggerFactory(import.meta);
|
|
22
24
|
|
|
@@ -197,7 +199,7 @@ const Panel = {
|
|
|
197
199
|
|
|
198
200
|
// Clear previous form values then populate with the current item's data
|
|
199
201
|
Input.cleanValues(formData);
|
|
200
|
-
Input.setValues(formData, obj, foundOrigin, foundFiles);
|
|
202
|
+
await Input.setValues(formData, obj, foundOrigin, foundFiles);
|
|
201
203
|
if (options.on.initEdit) await options.on.initEdit({ data: obj });
|
|
202
204
|
});
|
|
203
205
|
s(`.a-${payload._id}`).onclick = async (e) => {
|
|
@@ -240,6 +242,32 @@ const Panel = {
|
|
|
240
242
|
// Register theme change handler
|
|
241
243
|
const profileThemeHandlerId = `${id}-creator-profile-theme`;
|
|
242
244
|
ThemeEvents[profileThemeHandlerId] = updateCreatorProfileTheme;
|
|
245
|
+
|
|
246
|
+
// Add click handlers for public profile links
|
|
247
|
+
setTimeout(() => {
|
|
248
|
+
const links = sa(`.creator-profile-link-${id}`);
|
|
249
|
+
links.forEach((link) => {
|
|
250
|
+
link.onclick = async (e) => {
|
|
251
|
+
e.preventDefault();
|
|
252
|
+
const username = link.getAttribute('data-id');
|
|
253
|
+
// Check if public profile modal is already open
|
|
254
|
+
const currentModal = s('.modal-public-profile');
|
|
255
|
+
if (currentModal) {
|
|
256
|
+
// Modal is already open, update the profile content dynamically
|
|
257
|
+
// Navigate to clean URL without intermediate ?cid= in history
|
|
258
|
+
navigateToProfile(username, { replace: false });
|
|
259
|
+
await PublicProfile.Update({
|
|
260
|
+
idModal: 'modal-public-profile',
|
|
261
|
+
user: { username },
|
|
262
|
+
});
|
|
263
|
+
} else {
|
|
264
|
+
// Modal is not open, navigate to clean URL and open modal
|
|
265
|
+
navigateToProfile(username, { replace: false });
|
|
266
|
+
if (s('.main-btn-public-profile')) s('.main-btn-public-profile').click();
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
});
|
|
243
271
|
}
|
|
244
272
|
});
|
|
245
273
|
if (s(`.${idPanel}-${id}`)) s(`.${idPanel}-${id}`).remove();
|
|
@@ -287,37 +315,46 @@ const Panel = {
|
|
|
287
315
|
? 'rgba(255,255,255,0.02)'
|
|
288
316
|
: 'rgba(0,0,0,0.02)'}; border-radius: 4px 4px 0 0;"
|
|
289
317
|
>
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
318
|
+
<a
|
|
319
|
+
href="${getProxyPath()}u/${obj.userInfo.username}"
|
|
320
|
+
class="creator-profile-link-${id}"
|
|
321
|
+
data-id="${obj.userInfo.username}"
|
|
322
|
+
style="display: flex;"
|
|
323
|
+
>
|
|
324
|
+
${obj.userInfo.profileImageId && obj.userInfo.profileImageId._id
|
|
325
|
+
? html`<img
|
|
326
|
+
class="creator-avatar"
|
|
327
|
+
src="${getApiBaseUrl({ id: obj.userInfo.profileImageId._id, endpoint: 'file/blob' })}"
|
|
328
|
+
alt="${obj.userInfo.username}"
|
|
329
|
+
style="width: 36px; height: 36px; border-radius: 50%; object-fit: cover; border: 2px solid ${darkTheme
|
|
330
|
+
? 'rgba(102, 126, 234, 0.5)'
|
|
331
|
+
: 'rgba(102, 126, 234, 0.3)'}; flex-shrink: 0; box-shadow: 0 2px 8px rgba(0,0,0,0.15);"
|
|
332
|
+
title="${obj.userInfo.username}"
|
|
333
|
+
/>`
|
|
334
|
+
: html`<div
|
|
335
|
+
class="creator-avatar"
|
|
336
|
+
style="width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 16px; flex-shrink: 0; box-shadow: 0 2px 8px rgba(0,0,0,0.15);"
|
|
337
|
+
title="${obj.userInfo.username}"
|
|
338
|
+
>
|
|
339
|
+
${(obj.userInfo.username || 'U').charAt(0).toUpperCase()}
|
|
340
|
+
</div>`}
|
|
341
|
+
</a>
|
|
307
342
|
<div style="display: flex; flex-direction: column; min-width: 0; flex: 1;">
|
|
308
|
-
<
|
|
309
|
-
|
|
343
|
+
<a
|
|
344
|
+
href="${getProxyPath()}u/${obj.userInfo.username}"
|
|
345
|
+
class="creator-username creator-profile-link-${id}"
|
|
346
|
+
data-id="${obj.userInfo.username}"
|
|
310
347
|
style="font-size: 14px; font-weight: 600; color: ${darkTheme
|
|
311
348
|
? 'rgba(255,255,255,0.9)'
|
|
312
349
|
: 'rgba(0,0,0,0.85)'}; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
|
313
350
|
>
|
|
314
351
|
${obj.userInfo.username || obj.userInfo.email || 'Unknown'}
|
|
315
|
-
</
|
|
352
|
+
</a>
|
|
316
353
|
<span
|
|
317
354
|
style="font-size: 11px; color: ${darkTheme
|
|
318
355
|
? 'rgba(255,255,255,0.5)'
|
|
319
356
|
: 'rgba(0,0,0,0.45)'}; line-height: 1.3; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 500;"
|
|
320
|
-
|
|
357
|
+
>${obj.userInfo.briefDescription || 'Uploader'}</span
|
|
321
358
|
>
|
|
322
359
|
</div>
|
|
323
360
|
</div>`
|
|
@@ -718,15 +755,16 @@ const Panel = {
|
|
|
718
755
|
s(`.btn-${idPanel}-clean`).onclick = () => {
|
|
719
756
|
Input.cleanValues(formData);
|
|
720
757
|
};
|
|
721
|
-
s(`.btn-${idPanel}-clean-file`)
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
s(`.${fileFormData.id}`)
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
758
|
+
if (s(`.btn-${idPanel}-clean-file`))
|
|
759
|
+
s(`.btn-${idPanel}-clean-file`).onclick = () => {
|
|
760
|
+
// Clear file input specifically
|
|
761
|
+
const fileFormData = formData.find((f) => f.inputType === 'file');
|
|
762
|
+
if (fileFormData && s(`.${fileFormData.id}`)) {
|
|
763
|
+
s(`.${fileFormData.id}`).value = '';
|
|
764
|
+
s(`.${fileFormData.id}`).inputFiles = null;
|
|
765
|
+
htmls(`.file-name-render-${fileFormData.id}`, `${fileNameInputExtDefaultContent}`);
|
|
766
|
+
}
|
|
767
|
+
};
|
|
730
768
|
s(`.btn-${idPanel}-close`).onclick = (e) => {
|
|
731
769
|
e.preventDefault();
|
|
732
770
|
s(`.${idPanel}-form-body`).style.opacity = 0;
|
|
@@ -13,9 +13,72 @@ import { closeModalRouteChangeEvents, listenQueryPathInstance, setQueryPath, get
|
|
|
13
13
|
import { Scroll } from './Scroll.js';
|
|
14
14
|
import { LoadingAnimation } from './LoadingAnimation.js';
|
|
15
15
|
import { loggerFactory } from './Logger.js';
|
|
16
|
+
import { getApiBaseUrl } from '../../services/core/core.service.js';
|
|
16
17
|
|
|
17
18
|
const logger = loggerFactory(import.meta, { trace: true });
|
|
18
19
|
|
|
20
|
+
function sanitizeFilename(title, options = {}) {
|
|
21
|
+
const { replacement = '-', maxLength = 255, preserveExtension = true } = options;
|
|
22
|
+
|
|
23
|
+
if (typeof title !== 'string' || title.trim() === '') {
|
|
24
|
+
return 'untitled';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 1) Extract extension (optional)
|
|
28
|
+
let name = title;
|
|
29
|
+
let ext = '';
|
|
30
|
+
if (preserveExtension) {
|
|
31
|
+
const match = title.match(/(\.[^.\s]{1,10})$/u);
|
|
32
|
+
if (match) {
|
|
33
|
+
ext = match[1];
|
|
34
|
+
name = title.slice(0, -ext.length);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2) Normalize Unicode and remove diacritics
|
|
39
|
+
name = name.normalize('NFKD').replace(/[\u0300-\u036f]/g, '');
|
|
40
|
+
|
|
41
|
+
// 3) Remove control characters and null bytes
|
|
42
|
+
name = name.replace(/[\x00-\x1f\x7f]/g, '');
|
|
43
|
+
|
|
44
|
+
// 4) Remove forbidden filename characters (Windows / POSIX)
|
|
45
|
+
name = name.replace(/[<>:"/\\|?*\u0000]/g, '');
|
|
46
|
+
|
|
47
|
+
// 5) Collapse whitespace and replace with separator
|
|
48
|
+
name = name.replace(/\s+/g, replacement);
|
|
49
|
+
|
|
50
|
+
// 6) Collapse multiple separators
|
|
51
|
+
const escaped = replacement.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
52
|
+
name = name.replace(new RegExp(`${escaped}{2,}`, 'g'), replacement);
|
|
53
|
+
|
|
54
|
+
// 7) Trim dots and separators from edges
|
|
55
|
+
name = name.replace(new RegExp(`^[\\.${escaped}]+|[\\.${escaped}]+$`, 'g'), '');
|
|
56
|
+
|
|
57
|
+
// 8) Protect against Windows reserved names
|
|
58
|
+
if (/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(name)) {
|
|
59
|
+
name = '_' + name;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 9) Enforce max length
|
|
63
|
+
const maxNameLength = Math.max(1, maxLength - ext.length);
|
|
64
|
+
if (name.length > maxNameLength) {
|
|
65
|
+
name = name.slice(0, maxNameLength);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 10) Fallback
|
|
69
|
+
if (!name) name = 'untitled';
|
|
70
|
+
|
|
71
|
+
return name + ext;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const userInfoFactory = (userDoc) => ({
|
|
75
|
+
username: userDoc.userId.username,
|
|
76
|
+
email: userDoc.userId.email,
|
|
77
|
+
_id: userDoc.userId._id,
|
|
78
|
+
profileImageId: userDoc.userId.profileImageId,
|
|
79
|
+
briefDescription: userDoc.userId.briefDescription,
|
|
80
|
+
});
|
|
81
|
+
|
|
19
82
|
const PanelForm = {
|
|
20
83
|
Data: {},
|
|
21
84
|
instance: async function (
|
|
@@ -129,29 +192,40 @@ const PanelForm = {
|
|
|
129
192
|
render: imageShimmer(),
|
|
130
193
|
});
|
|
131
194
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
195
|
+
|
|
196
|
+
// Get the filesData for this item
|
|
197
|
+
const filesDataItem = PanelForm.Data[idPanel].filesData.find((f) => f._id === options.data._id);
|
|
198
|
+
|
|
199
|
+
// Priority 1: Check if there's an actual file (not markdown content)
|
|
200
|
+
// fileId array defaults to [null] for batch upload logic
|
|
201
|
+
const fileBlob = filesDataItem?.fileId?.fileBlob;
|
|
202
|
+
if (fileBlob) {
|
|
203
|
+
return await options.fileRender({
|
|
204
|
+
file: fileBlob,
|
|
205
|
+
style: {
|
|
206
|
+
overflow: 'auto',
|
|
207
|
+
width: '100%',
|
|
208
|
+
height: 'auto',
|
|
209
|
+
},
|
|
147
210
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Priority 2: If no actual file, show default image
|
|
214
|
+
// (Don't show markdown content in file area - mdFileId stays in content area)
|
|
215
|
+
return await options.htmlRender({
|
|
216
|
+
render: html`
|
|
217
|
+
<img
|
|
218
|
+
class="abs center"
|
|
219
|
+
style="${renderCssAttr({
|
|
220
|
+
style: {
|
|
221
|
+
width: '100px',
|
|
222
|
+
height: '100px',
|
|
223
|
+
opacity: 0.2,
|
|
224
|
+
},
|
|
225
|
+
})}"
|
|
226
|
+
src="${defaultUrlImage}"
|
|
227
|
+
/>
|
|
228
|
+
`,
|
|
155
229
|
});
|
|
156
230
|
},
|
|
157
231
|
on: {
|
|
@@ -326,11 +400,34 @@ const PanelForm = {
|
|
|
326
400
|
return { data: [], status: 'error', message: 'Must provide either content or attach a file' };
|
|
327
401
|
}
|
|
328
402
|
|
|
403
|
+
// Sanitize title for filename - normalize UTF-8 string
|
|
404
|
+
// In browser, strings are already UTF-16, just ensure valid characters
|
|
405
|
+
const sanitizedTitle = sanitizeFilename(data.title);
|
|
406
|
+
|
|
329
407
|
let mdFileId;
|
|
330
|
-
const mdFileName = `${getCapVariableName(
|
|
408
|
+
const mdFileName = `${getCapVariableName(sanitizedTitle)}.md`;
|
|
331
409
|
const location = `${prefixTags.join('/')}`;
|
|
332
|
-
|
|
333
|
-
|
|
410
|
+
|
|
411
|
+
// Only create markdown file if there's actual content
|
|
412
|
+
let md = null;
|
|
413
|
+
let mdBlob = null;
|
|
414
|
+
let mdPlain = null;
|
|
415
|
+
|
|
416
|
+
if (hasMdContent) {
|
|
417
|
+
// Markdown content is already UTF-16 in browser, use as-is
|
|
418
|
+
const blob = new Blob([data.mdFileId], { type: 'text/markdown' });
|
|
419
|
+
md = new File([blob], mdFileName, { type: 'text/markdown' });
|
|
420
|
+
|
|
421
|
+
mdBlob = {
|
|
422
|
+
data: {
|
|
423
|
+
data: await getDataFromInputFile(md),
|
|
424
|
+
},
|
|
425
|
+
mimetype: md.type,
|
|
426
|
+
name: md.name,
|
|
427
|
+
};
|
|
428
|
+
mdPlain = await getRawContentFile(getBlobFromUint8ArrayFile(mdBlob.data.data, mdBlob.mimetype));
|
|
429
|
+
}
|
|
430
|
+
|
|
334
431
|
// Parse and normalize tags
|
|
335
432
|
// Note: 'public' tag is automatically extracted by the backend and converted to isPublic field
|
|
336
433
|
// It will be filtered from the tags array to keep visibility control separate from content tags
|
|
@@ -355,18 +452,25 @@ const PanelForm = {
|
|
|
355
452
|
}
|
|
356
453
|
}
|
|
357
454
|
|
|
358
|
-
const mdBlob = {
|
|
359
|
-
data: {
|
|
360
|
-
data: await getDataFromInputFile(md),
|
|
361
|
-
},
|
|
362
|
-
mimetype: md.type,
|
|
363
|
-
name: md.name,
|
|
364
|
-
};
|
|
365
|
-
const mdPlain = await getRawContentFile(getBlobFromUint8ArrayFile(mdBlob.data.data, mdBlob.mimetype));
|
|
366
455
|
const baseNewDoc = newInstance(data);
|
|
367
456
|
baseNewDoc.tags = tags.filter((t) => !prefixTags.includes(t));
|
|
368
|
-
baseNewDoc.mdFileId = marked.parse(data.mdFileId);
|
|
369
|
-
baseNewDoc.userId = Elements.Data.user
|
|
457
|
+
baseNewDoc.mdFileId = hasMdContent ? marked.parse(data.mdFileId) : null;
|
|
458
|
+
baseNewDoc.userId = Elements.Data.user?.main?.model?.user?._id;
|
|
459
|
+
|
|
460
|
+
// Ensure profileImageId is properly formatted as object with _id property
|
|
461
|
+
const profileImageIdValue = Elements.Data.user?.main?.model?.user?.profileImageId;
|
|
462
|
+
const formattedProfileImageId = profileImageIdValue
|
|
463
|
+
? typeof profileImageIdValue === 'string'
|
|
464
|
+
? { _id: profileImageIdValue }
|
|
465
|
+
: profileImageIdValue
|
|
466
|
+
: null;
|
|
467
|
+
|
|
468
|
+
baseNewDoc.userInfo = {
|
|
469
|
+
username: Elements.Data.user?.main?.model?.user?.username,
|
|
470
|
+
email: Elements.Data.user?.main?.model?.user?.email,
|
|
471
|
+
_id: Elements.Data.user?.main?.model?.user?._id,
|
|
472
|
+
profileImageId: formattedProfileImageId,
|
|
473
|
+
};
|
|
370
474
|
baseNewDoc.tools = true;
|
|
371
475
|
|
|
372
476
|
const documents = [];
|
|
@@ -378,30 +482,46 @@ const PanelForm = {
|
|
|
378
482
|
|
|
379
483
|
for (const file of inputFiles) {
|
|
380
484
|
indexFormDoc++;
|
|
381
|
-
let fileId;
|
|
485
|
+
let fileId = undefined; // Reset for each iteration - only set if user uploaded a file
|
|
382
486
|
|
|
383
487
|
await (async () => {
|
|
384
488
|
const body = new FormData();
|
|
385
|
-
|
|
489
|
+
// Only append md file if it was created (has content)
|
|
490
|
+
if (md) body.append('md', md);
|
|
386
491
|
if (file) body.append('file', file);
|
|
387
|
-
const { status, data } = await FileService.post({ body });
|
|
492
|
+
const { status, data: uploadedFiles } = await FileService.post({ body });
|
|
388
493
|
// await timer(3000);
|
|
389
494
|
NotificationManager.Push({
|
|
390
495
|
html: Translate.Render(`${status}-upload-file`),
|
|
391
496
|
status,
|
|
392
497
|
});
|
|
393
|
-
if (status === 'success') {
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
// -
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
498
|
+
if (status === 'success' && uploadedFiles && Array.isArray(uploadedFiles)) {
|
|
499
|
+
// CRITICAL DIFFERENTIATION:
|
|
500
|
+
// - mdFileId: markdown file GENERATED FROM rich text editor content
|
|
501
|
+
// - fileId: file UPLOADED BY USER (could be .md, .pdf, image, etc.)
|
|
502
|
+
//
|
|
503
|
+
// Both can be markdown files, but we must distinguish:
|
|
504
|
+
// Rich text editor content → mdFileId
|
|
505
|
+
// User-uploaded file → fileId
|
|
506
|
+
|
|
507
|
+
for (const uploadedFile of uploadedFiles) {
|
|
508
|
+
if (hasMdContent && uploadedFile.name === mdFileName) {
|
|
509
|
+
// This is the markdown file created FROM rich text editor
|
|
400
510
|
mdFileId = uploadedFile._id;
|
|
401
|
-
|
|
511
|
+
logger.info(`Assigned rich text markdown to mdFileId: ${mdFileName}`);
|
|
512
|
+
} else if (!hasMdContent || uploadedFile.name !== mdFileName) {
|
|
513
|
+
// This is a file uploaded by user (even if it's an .md file)
|
|
402
514
|
fileId = uploadedFile._id;
|
|
515
|
+
logger.info(`Assigned user-uploaded file to fileId: ${uploadedFile.name}`);
|
|
403
516
|
}
|
|
404
517
|
}
|
|
518
|
+
|
|
519
|
+
// Validation: mdFileId should exist only if rich text content was provided
|
|
520
|
+
if (hasMdContent && !mdFileId) {
|
|
521
|
+
logger.error(
|
|
522
|
+
`ERROR: No markdown content file found. Expected: ${mdFileName}, Got: ${uploadedFiles.map((f) => f.name).join(', ')}`,
|
|
523
|
+
);
|
|
524
|
+
}
|
|
405
525
|
}
|
|
406
526
|
})();
|
|
407
527
|
// Backend will automatically extract 'public' from tags and set isPublic field
|
|
@@ -431,6 +551,12 @@ const PanelForm = {
|
|
|
431
551
|
// Use server response data - backend has already processed tags and isPublic
|
|
432
552
|
isPublic: documentData.isPublic || false,
|
|
433
553
|
tags: (documentData.tags || []).filter((t) => !prefixTags.includes(t)),
|
|
554
|
+
// Ensure userInfo is present for profile header rendering
|
|
555
|
+
userInfo:
|
|
556
|
+
baseNewDoc.userInfo ||
|
|
557
|
+
(documentData.userId && typeof documentData.userId === 'object'
|
|
558
|
+
? userInfoFactory(documentData)
|
|
559
|
+
: null),
|
|
434
560
|
};
|
|
435
561
|
|
|
436
562
|
if (documentStatus === 'error') status = 'error';
|
|
@@ -439,7 +565,7 @@ const PanelForm = {
|
|
|
439
565
|
const filesData = {
|
|
440
566
|
id: documentData._id,
|
|
441
567
|
_id: documentData._id,
|
|
442
|
-
mdFileId: { mdBlob, mdPlain },
|
|
568
|
+
mdFileId: mdBlob && mdPlain ? { mdBlob, mdPlain } : null,
|
|
443
569
|
fileId: {
|
|
444
570
|
fileBlob: file
|
|
445
571
|
? {
|
|
@@ -537,29 +663,50 @@ const PanelForm = {
|
|
|
537
663
|
let mdFileId, fileId;
|
|
538
664
|
let mdBlob, fileBlob;
|
|
539
665
|
let mdPlain, filePlain;
|
|
666
|
+
let parsedMarkdown = '';
|
|
540
667
|
|
|
541
668
|
try {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
669
|
+
// Fetch markdown content if mdFileId exists
|
|
670
|
+
if (documentObject.mdFileId) {
|
|
671
|
+
const mdFileIdValue = documentObject.mdFileId._id || documentObject.mdFileId;
|
|
672
|
+
try {
|
|
673
|
+
// Get markdown content from blob endpoint using FileService
|
|
674
|
+
const { data: blobArray, status } = await FileService.get({ id: `blob/${mdFileIdValue}` });
|
|
675
|
+
if (status === 'success' && blobArray && blobArray[0]) {
|
|
676
|
+
mdPlain = await blobArray[0].text();
|
|
677
|
+
// Parse markdown with proper error handling
|
|
678
|
+
try {
|
|
679
|
+
parsedMarkdown = mdPlain ? marked.parse(mdPlain) : '';
|
|
680
|
+
} catch (parseError) {
|
|
681
|
+
logger.error('Error parsing markdown for document:', documentObject._id, parseError);
|
|
682
|
+
parsedMarkdown = `<p><strong>Error rendering markdown:</strong> ${parseError.message}</p>`;
|
|
683
|
+
}
|
|
684
|
+
} else {
|
|
685
|
+
logger.warn('Failed to fetch markdown blob content');
|
|
686
|
+
parsedMarkdown = '';
|
|
687
|
+
}
|
|
688
|
+
} catch (fetchError) {
|
|
689
|
+
logger.error('Error fetching markdown content:', mdFileIdValue, fetchError);
|
|
690
|
+
parsedMarkdown = '';
|
|
691
|
+
}
|
|
551
692
|
}
|
|
693
|
+
|
|
694
|
+
// Handle optional fileId
|
|
552
695
|
if (documentObject.fileId) {
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
696
|
+
const fileIdValue = documentObject.fileId._id || documentObject.fileId;
|
|
697
|
+
try {
|
|
698
|
+
// Get file metadata for display
|
|
699
|
+
const { data: fileArray } = await FileService.get({ id: fileIdValue });
|
|
700
|
+
if (fileArray && fileArray[0]) {
|
|
701
|
+
fileBlob = fileArray[0];
|
|
702
|
+
fileId = getSrcFromFileData(fileArray[0]);
|
|
703
|
+
}
|
|
704
|
+
} catch (fetchError) {
|
|
705
|
+
logger.error('Error fetching file metadata:', fileIdValue, fetchError);
|
|
706
|
+
}
|
|
561
707
|
}
|
|
562
708
|
|
|
709
|
+
// Store file metadata and references
|
|
563
710
|
panelData.filesData.push({
|
|
564
711
|
id: documentObject._id,
|
|
565
712
|
_id: documentObject._id,
|
|
@@ -567,33 +714,67 @@ const PanelForm = {
|
|
|
567
714
|
fileId: { fileBlob, filePlain },
|
|
568
715
|
});
|
|
569
716
|
|
|
717
|
+
// Add to data array for display - use pre-parsed markdown
|
|
570
718
|
panelData.data.push({
|
|
571
719
|
id: documentObject._id,
|
|
572
720
|
title: documentObject.title,
|
|
573
721
|
createdAt: documentObject.createdAt,
|
|
574
722
|
// Backend filters 'public' tag automatically - it's converted to isPublic field
|
|
575
723
|
tags: documentObject.tags.filter((t) => !prefixTags.includes(t)),
|
|
576
|
-
mdFileId:
|
|
577
|
-
userId:
|
|
724
|
+
mdFileId: parsedMarkdown,
|
|
725
|
+
userId:
|
|
726
|
+
documentObject.userId && typeof documentObject.userId === 'object'
|
|
727
|
+
? documentObject.userId._id
|
|
728
|
+
: documentObject.userId,
|
|
578
729
|
userInfo:
|
|
579
730
|
documentObject.userId && typeof documentObject.userId === 'object'
|
|
580
|
-
?
|
|
581
|
-
username: documentObject.userId.username,
|
|
582
|
-
email: documentObject.userId.email,
|
|
583
|
-
_id: documentObject.userId._id,
|
|
584
|
-
profileImageId: documentObject.userId.profileImageId,
|
|
585
|
-
}
|
|
731
|
+
? userInfoFactory(documentObject)
|
|
586
732
|
: null,
|
|
587
733
|
fileId,
|
|
588
|
-
tools:
|
|
734
|
+
tools:
|
|
735
|
+
documentObject.userId &&
|
|
736
|
+
typeof documentObject.userId === 'object' &&
|
|
737
|
+
Elements.Data.user?.main?.model?.user?._id &&
|
|
738
|
+
documentObject.userId._id === Elements.Data.user.main.model.user._id,
|
|
589
739
|
_id: documentObject._id,
|
|
590
740
|
totalCopyShareLinkCount: documentObject.totalCopyShareLinkCount || 0,
|
|
591
741
|
isPublic: documentObject.isPublic || false,
|
|
592
742
|
});
|
|
593
743
|
} catch (fileError) {
|
|
594
|
-
logger.error('Error
|
|
744
|
+
logger.error('Error processing files for document:', documentObject._id, fileError);
|
|
595
745
|
// Still add the document to originData even if file fetching fails
|
|
596
|
-
//
|
|
746
|
+
// Add minimal data without file references
|
|
747
|
+
panelData.filesData.push({
|
|
748
|
+
id: documentObject._id,
|
|
749
|
+
_id: documentObject._id,
|
|
750
|
+
mdFileId: { mdBlob: null, mdPlain: '' },
|
|
751
|
+
fileId: { fileBlob: null, filePlain: undefined },
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
panelData.data.push({
|
|
755
|
+
id: documentObject._id,
|
|
756
|
+
title: documentObject.title,
|
|
757
|
+
createdAt: documentObject.createdAt,
|
|
758
|
+
tags: documentObject.tags.filter((t) => !prefixTags.includes(t)),
|
|
759
|
+
mdFileId: '',
|
|
760
|
+
userId:
|
|
761
|
+
documentObject.userId && typeof documentObject.userId === 'object'
|
|
762
|
+
? documentObject.userId._id
|
|
763
|
+
: documentObject.userId,
|
|
764
|
+
userInfo:
|
|
765
|
+
documentObject.userId && typeof documentObject.userId === 'object'
|
|
766
|
+
? userInfoFactory(documentObject)
|
|
767
|
+
: null,
|
|
768
|
+
fileId: null,
|
|
769
|
+
tools:
|
|
770
|
+
documentObject.userId &&
|
|
771
|
+
typeof documentObject.userId === 'object' &&
|
|
772
|
+
Elements.Data.user?.main?.model?.user?._id &&
|
|
773
|
+
documentObject.userId._id === Elements.Data.user.main.model.user._id,
|
|
774
|
+
_id: documentObject._id,
|
|
775
|
+
totalCopyShareLinkCount: documentObject.totalCopyShareLinkCount || 0,
|
|
776
|
+
isPublic: documentObject.isPublic || false,
|
|
777
|
+
});
|
|
597
778
|
}
|
|
598
779
|
}
|
|
599
780
|
|
|
@@ -694,7 +875,9 @@ const PanelForm = {
|
|
|
694
875
|
cid,
|
|
695
876
|
forceUpdate,
|
|
696
877
|
},
|
|
697
|
-
|
|
878
|
+
Elements.Data.user?.main?.model?.user
|
|
879
|
+
? JSON.stringify(Elements.Data.user.main.model.user, null, 4)
|
|
880
|
+
: 'No user data',
|
|
698
881
|
);
|
|
699
882
|
|
|
700
883
|
// Normalize empty values for comparison (undefined, null, '' should all be treated as empty)
|
|
@@ -703,7 +886,9 @@ const PanelForm = {
|
|
|
703
886
|
|
|
704
887
|
if (loadingGetData || (normalizedLastCid === normalizedCid && !forceUpdate)) return;
|
|
705
888
|
loadingGetData = true;
|
|
706
|
-
lastUserId =
|
|
889
|
+
lastUserId = Elements.Data.user?.main?.model?.user?._id
|
|
890
|
+
? newInstance(Elements.Data.user.main.model.user._id)
|
|
891
|
+
: null;
|
|
707
892
|
lastCid = cid;
|
|
708
893
|
|
|
709
894
|
logger.warn('Init render panel data');
|