@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.
Files changed (78) hide show
  1. package/README.md +2 -2
  2. package/baremetal/commission-workflows.json +33 -3
  3. package/bin/deploy.js +1 -1
  4. package/cli.md +7 -2
  5. package/conf.js +3 -0
  6. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  7. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  8. package/package.json +1 -1
  9. package/packer/scripts/fuse-tar-root +3 -3
  10. package/scripts/disk-clean.sh +23 -23
  11. package/scripts/gpu-diag.sh +2 -2
  12. package/scripts/ip-info.sh +11 -11
  13. package/scripts/maas-upload-boot-resource.sh +1 -1
  14. package/scripts/nvim.sh +1 -1
  15. package/scripts/packer-setup.sh +13 -13
  16. package/scripts/rocky-setup.sh +2 -2
  17. package/scripts/rpmfusion-ffmpeg-setup.sh +4 -4
  18. package/scripts/ssl.sh +7 -7
  19. package/src/api/core/core.service.js +0 -5
  20. package/src/api/default/default.service.js +7 -5
  21. package/src/api/document/document.model.js +30 -1
  22. package/src/api/document/document.router.js +6 -0
  23. package/src/api/document/document.service.js +423 -51
  24. package/src/api/file/file.model.js +112 -4
  25. package/src/api/file/file.ref.json +42 -0
  26. package/src/api/file/file.service.js +380 -32
  27. package/src/api/user/user.model.js +38 -1
  28. package/src/api/user/user.router.js +96 -63
  29. package/src/api/user/user.service.js +81 -48
  30. package/src/cli/baremetal.js +689 -329
  31. package/src/cli/cluster.js +50 -52
  32. package/src/cli/db.js +424 -166
  33. package/src/cli/deploy.js +1 -1
  34. package/src/cli/index.js +12 -1
  35. package/src/cli/lxd.js +3 -3
  36. package/src/cli/repository.js +1 -1
  37. package/src/cli/run.js +2 -1
  38. package/src/cli/ssh.js +10 -10
  39. package/src/client/components/core/Account.js +327 -36
  40. package/src/client/components/core/AgGrid.js +3 -0
  41. package/src/client/components/core/Auth.js +9 -3
  42. package/src/client/components/core/Chat.js +2 -2
  43. package/src/client/components/core/Content.js +159 -78
  44. package/src/client/components/core/Css.js +16 -2
  45. package/src/client/components/core/CssCore.js +16 -12
  46. package/src/client/components/core/FileExplorer.js +115 -8
  47. package/src/client/components/core/Input.js +204 -11
  48. package/src/client/components/core/LogIn.js +42 -20
  49. package/src/client/components/core/Modal.js +257 -177
  50. package/src/client/components/core/Panel.js +324 -27
  51. package/src/client/components/core/PanelForm.js +280 -73
  52. package/src/client/components/core/PublicProfile.js +888 -0
  53. package/src/client/components/core/Router.js +117 -15
  54. package/src/client/components/core/SearchBox.js +1117 -0
  55. package/src/client/components/core/SignUp.js +26 -7
  56. package/src/client/components/core/SocketIo.js +6 -3
  57. package/src/client/components/core/Translate.js +98 -0
  58. package/src/client/components/core/Validator.js +15 -0
  59. package/src/client/components/core/windowGetDimensions.js +6 -6
  60. package/src/client/components/default/MenuDefault.js +59 -12
  61. package/src/client/components/default/RoutesDefault.js +1 -0
  62. package/src/client/services/core/core.service.js +163 -1
  63. package/src/client/services/default/default.management.js +451 -64
  64. package/src/client/services/default/default.service.js +13 -6
  65. package/src/client/services/document/document.service.js +23 -0
  66. package/src/client/services/file/file.service.js +43 -16
  67. package/src/client/services/user/user.service.js +13 -9
  68. package/src/db/DataBaseProvider.js +1 -1
  69. package/src/db/mongo/MongooseDB.js +1 -1
  70. package/src/index.js +1 -1
  71. package/src/mailer/MailerProvider.js +4 -4
  72. package/src/runtime/express/Express.js +2 -1
  73. package/src/runtime/lampp/Lampp.js +2 -2
  74. package/src/server/auth.js +3 -6
  75. package/src/server/data-query.js +449 -0
  76. package/src/server/dns.js +4 -4
  77. package/src/server/object-layer.js +0 -3
  78. package/src/ws/IoInterface.js +2 -2
@@ -6,16 +6,79 @@ import { NotificationManager } from './NotificationManager.js';
6
6
  import { DocumentService } from '../../services/document/document.service.js';
7
7
  import { FileService } from '../../services/file/file.service.js';
8
8
  import { getSrcFromFileData } from './Input.js';
9
- import { imageShimmer, renderCssAttr } from './Css.js';
9
+ import { imageShimmer, renderCssAttr, darkTheme, ThemeEvents, subThemeManager, lightenHex, darkenHex } from './Css.js';
10
10
  import { Translate } from './Translate.js';
11
11
  import { Modal } from './Modal.js';
12
12
  import { closeModalRouteChangeEvents, listenQueryPathInstance, setQueryPath, getQueryParams } from './Router.js';
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 (
@@ -30,11 +93,14 @@ const PanelForm = {
30
93
  share: {
31
94
  copyLink: false,
32
95
  },
96
+ showCreatorProfile: false,
33
97
  },
34
98
  ) {
35
99
  const { idPanel, defaultUrlImage, Elements } = options;
36
100
 
37
- let prefixTags = [idPanel, 'public'];
101
+ // Authenticated users don't need 'public' tag - they see all their own posts
102
+ // Only include 'public' for unauthenticated users (handled by backend)
103
+ let prefixTags = [idPanel];
38
104
  this.Data[idPanel] = {
39
105
  originData: [],
40
106
  data: [],
@@ -112,6 +178,7 @@ const PanelForm = {
112
178
  route: options.route,
113
179
  formContainerClass: 'session-in-log-in',
114
180
  share: options.share,
181
+ showCreatorProfile: options.showCreatorProfile,
115
182
  onClick: async function ({ payload }) {
116
183
  if (options.route) {
117
184
  setQueryPath({ path: options.route, queryPath: payload._id });
@@ -125,29 +192,40 @@ const PanelForm = {
125
192
  render: imageShimmer(),
126
193
  });
127
194
  }
128
- if (!options.data.fileId)
129
- return await options.htmlRender({
130
- render: html`
131
- <img
132
- class="abs center"
133
- style="${renderCssAttr({
134
- style: {
135
- width: '100px',
136
- height: '100px',
137
- opacity: 0.2,
138
- },
139
- })}"
140
- src="${defaultUrlImage}"
141
- />
142
- `,
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
+ },
143
210
  });
144
- return await options.fileRender({
145
- file: PanelForm.Data[idPanel].filesData.find((f) => f._id === options.data._id)?.fileId?.fileBlob,
146
- style: {
147
- overflow: 'auto',
148
- width: '100%',
149
- height: 'auto',
150
- },
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
+ `,
151
229
  });
152
230
  },
153
231
  on: {
@@ -322,11 +400,37 @@ const PanelForm = {
322
400
  return { data: [], status: 'error', message: 'Must provide either content or attach a file' };
323
401
  }
324
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
+
325
407
  let mdFileId;
326
- const mdFileName = `${getCapVariableName(data.title)}.md`;
408
+ const mdFileName = `${getCapVariableName(sanitizedTitle)}.md`;
327
409
  const location = `${prefixTags.join('/')}`;
328
- const blob = new Blob([data.mdFileId], { type: 'text/markdown' });
329
- const md = new File([blob], mdFileName, { type: 'text/markdown' });
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
+
431
+ // Parse and normalize tags
432
+ // Note: 'public' tag is automatically extracted by the backend and converted to isPublic field
433
+ // It will be filtered from the tags array to keep visibility control separate from content tags
330
434
  const tags = data.tags
331
435
  ? uniqueArray(
332
436
  data.tags
@@ -348,18 +452,25 @@ const PanelForm = {
348
452
  }
349
453
  }
350
454
 
351
- const mdBlob = {
352
- data: {
353
- data: await getDataFromInputFile(md),
354
- },
355
- mimetype: md.type,
356
- name: md.name,
357
- };
358
- const mdPlain = await getRawContentFile(getBlobFromUint8ArrayFile(mdBlob.data.data, mdBlob.mimetype));
359
455
  const baseNewDoc = newInstance(data);
360
456
  baseNewDoc.tags = tags.filter((t) => !prefixTags.includes(t));
361
- baseNewDoc.mdFileId = marked.parse(data.mdFileId);
362
- baseNewDoc.userId = Elements.Data.user.main.model.user._id;
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
+ };
363
474
  baseNewDoc.tools = true;
364
475
 
365
476
  const documents = [];
@@ -371,32 +482,49 @@ const PanelForm = {
371
482
 
372
483
  for (const file of inputFiles) {
373
484
  indexFormDoc++;
374
- let fileId;
485
+ let fileId = undefined; // Reset for each iteration - only set if user uploaded a file
375
486
 
376
487
  await (async () => {
377
488
  const body = new FormData();
378
- body.append('md', md);
489
+ // Only append md file if it was created (has content)
490
+ if (md) body.append('md', md);
379
491
  if (file) body.append('file', file);
380
- const { status, data } = await FileService.post({ body });
492
+ const { status, data: uploadedFiles } = await FileService.post({ body });
381
493
  // await timer(3000);
382
494
  NotificationManager.Push({
383
495
  html: Translate.Render(`${status}-upload-file`),
384
496
  status,
385
497
  });
386
- if (status === 'success') {
387
- // Identify files by comparing filename instead of just mimetype
388
- // This handles the case where an .md file is uploaded as the optional file
389
- // - mdFileId: matches the generated mdFileName from the title
390
- // - fileId: any other file (including other .md files)
391
- for (const uploadedFile of data) {
392
- if (uploadedFile.name === mdFileName) {
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
393
510
  mdFileId = uploadedFile._id;
394
- } else {
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)
395
514
  fileId = uploadedFile._id;
515
+ logger.info(`Assigned user-uploaded file to fileId: ${uploadedFile.name}`);
396
516
  }
397
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
+ }
398
525
  }
399
526
  })();
527
+ // Backend will automatically extract 'public' from tags and set isPublic field
400
528
  const body = {
401
529
  location,
402
530
  tags,
@@ -420,6 +548,15 @@ const PanelForm = {
420
548
  _id: documentData._id,
421
549
  id: documentData._id,
422
550
  createdAt: documentData.createdAt,
551
+ // Use server response data - backend has already processed tags and isPublic
552
+ isPublic: documentData.isPublic || false,
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),
423
560
  };
424
561
 
425
562
  if (documentStatus === 'error') status = 'error';
@@ -428,7 +565,7 @@ const PanelForm = {
428
565
  const filesData = {
429
566
  id: documentData._id,
430
567
  _id: documentData._id,
431
- mdFileId: { mdBlob, mdPlain },
568
+ mdFileId: mdBlob && mdPlain ? { mdBlob, mdPlain } : null,
432
569
  fileId: {
433
570
  fileBlob: file
434
571
  ? {
@@ -526,29 +663,50 @@ const PanelForm = {
526
663
  let mdFileId, fileId;
527
664
  let mdBlob, fileBlob;
528
665
  let mdPlain, filePlain;
666
+ let parsedMarkdown = '';
529
667
 
530
668
  try {
531
- {
532
- const {
533
- data: [file],
534
- } = await FileService.get({ id: documentObject.mdFileId._id });
535
-
536
- // const ext = file.name.split('.')[file.name.split('.').length - 1];
537
- mdBlob = file;
538
- mdPlain = await getRawContentFile(getBlobFromUint8ArrayFile(file.data.data, file.mimetype));
539
- mdFileId = newInstance(mdPlain);
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
+ }
540
692
  }
693
+
694
+ // Handle optional fileId
541
695
  if (documentObject.fileId) {
542
- const {
543
- data: [file],
544
- } = await FileService.get({ id: documentObject.fileId._id });
545
-
546
- // const ext = file.name.split('.')[file.name.split('.').length - 1];
547
- fileBlob = file;
548
- filePlain = undefined;
549
- fileId = getSrcFromFileData(file);
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
+ }
550
707
  }
551
708
 
709
+ // Store file metadata and references
552
710
  panelData.filesData.push({
553
711
  id: documentObject._id,
554
712
  _id: documentObject._id,
@@ -556,22 +714,67 @@ const PanelForm = {
556
714
  fileId: { fileBlob, filePlain },
557
715
  });
558
716
 
717
+ // Add to data array for display - use pre-parsed markdown
559
718
  panelData.data.push({
560
719
  id: documentObject._id,
561
720
  title: documentObject.title,
562
721
  createdAt: documentObject.createdAt,
722
+ // Backend filters 'public' tag automatically - it's converted to isPublic field
563
723
  tags: documentObject.tags.filter((t) => !prefixTags.includes(t)),
564
- mdFileId: marked.parse(mdFileId),
565
- userId: documentObject.userId._id,
724
+ mdFileId: parsedMarkdown,
725
+ userId:
726
+ documentObject.userId && typeof documentObject.userId === 'object'
727
+ ? documentObject.userId._id
728
+ : documentObject.userId,
729
+ userInfo:
730
+ documentObject.userId && typeof documentObject.userId === 'object'
731
+ ? userInfoFactory(documentObject)
732
+ : null,
566
733
  fileId,
567
- tools: Elements.Data.user.main.model.user._id === documentObject.userId._id,
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,
568
739
  _id: documentObject._id,
569
740
  totalCopyShareLinkCount: documentObject.totalCopyShareLinkCount || 0,
741
+ isPublic: documentObject.isPublic || false,
570
742
  });
571
743
  } catch (fileError) {
572
- logger.error('Error fetching files for document:', documentObject._id, fileError);
744
+ logger.error('Error processing files for document:', documentObject._id, fileError);
573
745
  // Still add the document to originData even if file fetching fails
574
- // but skip adding to data and filesData arrays
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
+ });
575
778
  }
576
779
  }
577
780
 
@@ -672,7 +875,9 @@ const PanelForm = {
672
875
  cid,
673
876
  forceUpdate,
674
877
  },
675
- JSON.stringify(Elements.Data.user.main.model.user, null, 4),
878
+ Elements.Data.user?.main?.model?.user
879
+ ? JSON.stringify(Elements.Data.user.main.model.user, null, 4)
880
+ : 'No user data',
676
881
  );
677
882
 
678
883
  // Normalize empty values for comparison (undefined, null, '' should all be treated as empty)
@@ -681,7 +886,9 @@ const PanelForm = {
681
886
 
682
887
  if (loadingGetData || (normalizedLastCid === normalizedCid && !forceUpdate)) return;
683
888
  loadingGetData = true;
684
- lastUserId = newInstance(Elements.Data.user.main.model.user._id);
889
+ lastUserId = Elements.Data.user?.main?.model?.user?._id
890
+ ? newInstance(Elements.Data.user.main.model.user._id)
891
+ : null;
685
892
  lastCid = cid;
686
893
 
687
894
  logger.warn('Init render panel data');