underpost 3.2.4 → 3.2.8

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 (141) hide show
  1. package/.github/workflows/release.cd.yml +1 -2
  2. package/CHANGELOG.md +268 -1
  3. package/CLI-HELP.md +26 -13
  4. package/Dockerfile +0 -4
  5. package/README.md +3 -3
  6. package/bin/build.js +13 -3
  7. package/bin/deploy.js +570 -1
  8. package/bin/file.js +5 -0
  9. package/conf.js +11 -2
  10. package/jsconfig.json +1 -1
  11. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -3
  12. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -3
  13. package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
  14. package/manifests/deployment/dd-test-development/deployment.yaml +136 -66
  15. package/manifests/deployment/dd-test-development/proxy.yaml +41 -5
  16. package/package.json +20 -11
  17. package/src/api/core/core.controller.js +10 -10
  18. package/src/api/core/core.service.js +10 -10
  19. package/src/api/default/default.controller.js +10 -10
  20. package/src/api/default/default.service.js +10 -10
  21. package/src/api/document/document.controller.js +12 -12
  22. package/src/api/document/document.model.js +10 -16
  23. package/src/api/file/file.controller.js +8 -8
  24. package/src/api/file/file.model.js +10 -10
  25. package/src/api/file/file.service.js +36 -36
  26. package/src/api/test/test.controller.js +8 -8
  27. package/src/api/test/test.service.js +8 -8
  28. package/src/api/user/guest.service.js +99 -0
  29. package/src/api/user/user.controller.js +6 -6
  30. package/src/api/user/user.model.js +8 -13
  31. package/src/api/user/user.service.js +3 -20
  32. package/src/cli/deploy.js +33 -30
  33. package/src/cli/fs.js +62 -5
  34. package/src/cli/image.js +43 -1
  35. package/src/cli/index.js +5 -1
  36. package/src/cli/release.js +58 -2
  37. package/src/cli/repository.js +35 -3
  38. package/src/cli/run.js +304 -38
  39. package/src/cli/ssh.js +1 -1
  40. package/src/cli/static.js +43 -115
  41. package/src/client/Default.index.js +21 -33
  42. package/src/client/components/core/404.js +4 -4
  43. package/src/client/components/core/500.js +4 -4
  44. package/src/client/components/core/Account.js +73 -60
  45. package/src/client/components/core/AgGrid.js +23 -33
  46. package/src/client/components/core/Alert.js +12 -13
  47. package/src/client/components/core/AppStore.js +1 -1
  48. package/src/client/components/core/Auth.js +20 -32
  49. package/src/client/components/core/Badge.js +7 -13
  50. package/src/client/components/core/BtnIcon.js +15 -17
  51. package/src/client/components/core/CalendarCore.js +42 -63
  52. package/src/client/components/core/Chat.js +13 -15
  53. package/src/client/components/core/ClientEvents.js +87 -0
  54. package/src/client/components/core/ColorPaletteElement.js +309 -0
  55. package/src/client/components/core/Content.js +17 -14
  56. package/src/client/components/core/Css.js +15 -71
  57. package/src/client/components/core/CssCore.js +12 -16
  58. package/src/client/components/core/D3Chart.js +4 -4
  59. package/src/client/components/core/Docs.js +60 -59
  60. package/src/client/components/core/DropDown.js +69 -91
  61. package/src/client/components/core/EventBus.js +92 -0
  62. package/src/client/components/core/EventsUI.js +14 -17
  63. package/src/client/components/core/FileExplorer.js +102 -234
  64. package/src/client/components/core/FullScreen.js +47 -75
  65. package/src/client/components/core/Input.js +24 -69
  66. package/src/client/components/core/Keyboard.js +25 -18
  67. package/src/client/components/core/KeyboardAvoidance.js +145 -0
  68. package/src/client/components/core/LoadingAnimation.js +25 -31
  69. package/src/client/components/core/LogIn.js +41 -41
  70. package/src/client/components/core/LogOut.js +23 -14
  71. package/src/client/components/core/Modal.js +397 -176
  72. package/src/client/components/core/NotificationManager.js +14 -18
  73. package/src/client/components/core/Panel.js +54 -50
  74. package/src/client/components/core/PanelForm.js +25 -125
  75. package/src/client/components/core/Polyhedron.js +110 -214
  76. package/src/client/components/core/PublicProfile.js +39 -32
  77. package/src/client/components/core/Recover.js +52 -48
  78. package/src/client/components/core/Responsive.js +88 -32
  79. package/src/client/components/core/RichText.js +9 -18
  80. package/src/client/components/core/Router.js +24 -3
  81. package/src/client/components/core/SearchBox.js +37 -37
  82. package/src/client/components/core/SignUp.js +39 -30
  83. package/src/client/components/core/SocketIo.js +31 -2
  84. package/src/client/components/core/SocketIoHandler.js +6 -6
  85. package/src/client/components/core/ToggleSwitch.js +8 -20
  86. package/src/client/components/core/ToolTip.js +5 -17
  87. package/src/client/components/core/Translate.js +56 -59
  88. package/src/client/components/core/Validator.js +26 -16
  89. package/src/client/components/core/Wallet.js +15 -26
  90. package/src/client/components/core/Worker.js +140 -25
  91. package/src/client/components/core/windowGetDimensions.js +7 -7
  92. package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +87 -87
  93. package/src/client/components/default/CssDefault.js +12 -12
  94. package/src/client/components/default/LogInDefault.js +6 -4
  95. package/src/client/components/default/LogOutDefault.js +6 -4
  96. package/src/client/components/default/RouterDefault.js +47 -0
  97. package/src/client/components/default/SettingsDefault.js +4 -4
  98. package/src/client/components/default/SignUpDefault.js +6 -4
  99. package/src/client/components/default/TranslateDefault.js +3 -3
  100. package/src/client/services/core/core.service.js +17 -49
  101. package/src/client/services/default/default.management.js +139 -242
  102. package/src/client/services/default/default.service.js +10 -16
  103. package/src/client/services/document/document.service.js +14 -19
  104. package/src/client/services/file/file.service.js +8 -13
  105. package/src/client/services/test/test.service.js +8 -13
  106. package/src/client/services/user/guest.service.js +79 -0
  107. package/src/client/services/user/user.management.js +5 -5
  108. package/src/client/services/user/user.service.js +14 -20
  109. package/src/client/ssr/body/404.js +3 -3
  110. package/src/client/ssr/body/500.js +3 -3
  111. package/src/client/ssr/body/CacheControl.js +5 -2
  112. package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
  113. package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
  114. package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
  115. package/src/client/ssr/offline/Maintenance.js +12 -11
  116. package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
  117. package/src/client/ssr/pages/Test.js +2 -2
  118. package/src/client/sw/core.sw.js +212 -0
  119. package/src/index.js +1 -1
  120. package/src/runtime/express/Dockerfile +4 -4
  121. package/src/runtime/lampp/Dockerfile +8 -7
  122. package/src/runtime/wp/Dockerfile +11 -17
  123. package/src/server/backup.js +1 -2
  124. package/src/server/client-build-docs.js +45 -46
  125. package/src/server/client-build.js +334 -60
  126. package/src/server/client-formatted.js +47 -16
  127. package/src/server/conf.js +29 -13
  128. package/src/server/cron.js +6 -8
  129. package/src/server/dns.js +2 -1
  130. package/src/server/ipfs-client.js +232 -91
  131. package/src/server/process.js +13 -27
  132. package/src/server/start.js +6 -3
  133. package/src/server/valkey.js +134 -235
  134. package/tsconfig.docs.json +15 -0
  135. package/typedoc.json +20 -0
  136. package/jsdoc.json +0 -52
  137. package/src/client/components/core/ColorPalette.js +0 -5267
  138. package/src/client/components/core/JoyStick.js +0 -80
  139. package/src/client/components/default/RoutesDefault.js +0 -49
  140. package/src/client/sw/default.sw.js +0 -127
  141. package/src/client/sw/template.sw.js +0 -84
@@ -14,16 +14,12 @@ import { Scroll } from './Scroll.js';
14
14
  import { LoadingAnimation } from './LoadingAnimation.js';
15
15
  import { loggerFactory } from './Logger.js';
16
16
  import { getApiBaseUrl } from '../../services/core/core.service.js';
17
-
18
17
  const logger = loggerFactory(import.meta, { trace: true });
19
-
20
18
  function sanitizeFilename(title, options = {}) {
21
19
  const { replacement = '-', maxLength = 255, preserveExtension = true } = options;
22
-
23
20
  if (typeof title !== 'string' || title.trim() === '') {
24
21
  return 'untitled';
25
22
  }
26
-
27
23
  // 1) Extract extension (optional)
28
24
  let name = title;
29
25
  let ext = '';
@@ -34,43 +30,32 @@ function sanitizeFilename(title, options = {}) {
34
30
  name = title.slice(0, -ext.length);
35
31
  }
36
32
  }
37
-
38
33
  // 2) Normalize Unicode and remove diacritics
39
34
  name = name.normalize('NFKD').replace(/[\u0300-\u036f]/g, '');
40
-
41
35
  // 3) Remove control characters and null bytes
42
36
  name = name.replace(/[\x00-\x1f\x7f]/g, '');
43
-
44
37
  // 4) Remove forbidden filename characters (Windows / POSIX)
45
38
  name = name.replace(/[<>:"/\\|?*\u0000]/g, '');
46
-
47
39
  // 5) Collapse whitespace and replace with separator
48
40
  name = name.replace(/\s+/g, replacement);
49
-
50
41
  // 6) Collapse multiple separators
51
42
  const escaped = replacement.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
52
43
  name = name.replace(new RegExp(`${escaped}{2,}`, 'g'), replacement);
53
-
54
44
  // 7) Trim dots and separators from edges
55
45
  name = name.replace(new RegExp(`^[\\.${escaped}]+|[\\.${escaped}]+$`, 'g'), '');
56
-
57
46
  // 8) Protect against Windows reserved names
58
47
  if (/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(name)) {
59
48
  name = '_' + name;
60
49
  }
61
-
62
50
  // 9) Enforce max length
63
51
  const maxNameLength = Math.max(1, maxLength - ext.length);
64
52
  if (name.length > maxNameLength) {
65
53
  name = name.slice(0, maxNameLength);
66
54
  }
67
-
68
55
  // 10) Fallback
69
56
  if (!name) name = 'untitled';
70
-
71
57
  return name + ext;
72
58
  }
73
-
74
59
  const userInfoFactory = (userDoc) => ({
75
60
  username: userDoc.userId.username,
76
61
  email: userDoc.userId.email,
@@ -78,10 +63,9 @@ const userInfoFactory = (userDoc) => ({
78
63
  profileImageId: userDoc.userId.profileImageId,
79
64
  briefDescription: userDoc.userId.briefDescription,
80
65
  });
81
-
82
- const PanelForm = {
83
- Data: {},
84
- instance: async function (
66
+ class PanelForm {
67
+ static Data = {};
68
+ static async instance(
85
69
  options = {
86
70
  idPanel: '',
87
71
  defaultUrlImage: '',
@@ -98,11 +82,10 @@ const PanelForm = {
98
82
  },
99
83
  ) {
100
84
  const { idPanel, defaultUrlImage, appStore } = options;
101
-
102
85
  // Authenticated users don't need 'public' tag - they see all their own posts
103
86
  // Only include 'public' for unauthenticated users (handled by backend)
104
87
  let prefixTags = [idPanel];
105
- this.Data[idPanel] = {
88
+ PanelForm.Data[idPanel] = {
106
89
  originData: [],
107
90
  data: [],
108
91
  filesData: [],
@@ -112,7 +95,6 @@ const PanelForm = {
112
95
  loading: false,
113
96
  lasIdAvailable: null,
114
97
  };
115
-
116
98
  const formData = [
117
99
  {
118
100
  id: 'panel-title',
@@ -163,10 +145,9 @@ const PanelForm = {
163
145
  },
164
146
  },
165
147
  ];
166
-
167
148
  const titleIcon = html`<i class="fa-solid fa-quote-left title-icon-${idPanel}"></i>`;
168
149
  const panelRender = async ({ data }) =>
169
- await Panel.Render({
150
+ await Panel.instance({
170
151
  idPanel,
171
152
  formData,
172
153
  data,
@@ -193,10 +174,8 @@ const PanelForm = {
193
174
  render: imageShimmer(),
194
175
  });
195
176
  }
196
-
197
177
  // Get the filesData for this item
198
178
  const filesDataItem = PanelForm.Data[idPanel].filesData.find((f) => f._id === options.data._id);
199
-
200
179
  // Priority 1: Check if there's an actual file (not markdown content)
201
180
  // fileId array defaults to [null] for batch upload logic
202
181
  const fileBlob = filesDataItem?.fileId?.fileBlob;
@@ -210,7 +189,6 @@ const PanelForm = {
210
189
  },
211
190
  });
212
191
  }
213
-
214
192
  // Priority 2: If no actual file, show default image
215
193
  // (Don't show markdown content in file area - mdFileId stays in content area)
216
194
  return await options.htmlRender({
@@ -236,7 +214,7 @@ const PanelForm = {
236
214
  html: async () => {
237
215
  return html`
238
216
  <div class="in section-mp" style="text-align: center">
239
- ${Translate.Render('confirm-delete-item')}
217
+ ${Translate.instance('confirm-delete-item')}
240
218
  <br />
241
219
  "${data.title}"
242
220
  </div>
@@ -252,29 +230,23 @@ const PanelForm = {
252
230
  html: status,
253
231
  status,
254
232
  });
255
-
256
233
  // Handle cid query param update (supports comma-separated list)
257
234
  if (status === 'success') {
258
235
  const currentCid = getQueryParams().cid;
259
-
260
236
  if (currentCid) {
261
237
  // Parse cid as comma-separated list
262
238
  const cidList = currentCid
263
239
  .split(',')
264
240
  .map((id) => id.trim())
265
241
  .filter((id) => id);
266
-
267
242
  // Remove the deleted panel's id from the list
268
243
  const updatedCidList = cidList.filter((id) => id !== data.id);
269
-
270
244
  if (updatedCidList.length !== cidList.length) {
271
245
  // Wait for DOM cleanup before updating query
272
-
273
246
  if (updatedCidList.length === 0) {
274
247
  // No cids remain, clear query and reload panels with limit
275
248
  logger.warn('All cids removed, clearing query');
276
249
  setQueryPath({ path: options.route, queryPath: '' });
277
-
278
250
  if (options.parentIdModal) Modal.Data[options.parentIdModal].query = window.location.search;
279
251
  if (PanelForm.Data[idPanel].updatePanel) await PanelForm.Data[idPanel].updatePanel();
280
252
  } else {
@@ -285,41 +257,32 @@ const PanelForm = {
285
257
  if (options.parentIdModal) Modal.Data[options.parentIdModal].query = actualQuery;
286
258
  }
287
259
  }
288
-
289
260
  // Return early to skip smart deletion logic when cid is present
290
261
  return { status };
291
262
  }
292
263
  }
293
-
294
264
  // Smart deletion: remove from arrays and intelligently load more if needed
295
265
  if (status === 'success') {
296
266
  const panelData = PanelForm.Data[idPanel];
297
-
298
267
  // Remove the deleted item from all data arrays
299
268
  const indexInOrigin = panelData.originData.findIndex((d) => d._id === data._id);
300
269
  const indexInData = panelData.data.findIndex((d) => d._id === data._id);
301
270
  const indexInFiles = panelData.filesData.findIndex((d) => d._id === data._id);
302
-
303
271
  if (indexInOrigin > -1) panelData.originData.splice(indexInOrigin, 1);
304
272
  if (indexInData > -1) panelData.data.splice(indexInData, 1);
305
273
  if (indexInFiles > -1) panelData.filesData.splice(indexInFiles, 1);
306
-
307
274
  // Adjust skip count since we removed an item
308
275
  if (panelData.skip > 0) panelData.skip--;
309
-
310
276
  // If panels are below limit and there might be more, load them
311
277
  if (panelData.data.length < panelData.limit && panelData.hasMore && !panelData.loading) {
312
278
  const oldDataCount = panelData.data.length;
313
279
  const needed = panelData.limit - panelData.data.length; // Calculate exact number needed
314
280
  const originalLimit = panelData.limit;
315
-
316
281
  // Temporarily set limit to only fetch what's needed (1-to-1 replacement)
317
282
  panelData.limit = needed;
318
283
  await getPanelData(true); // Load only the needed items
319
284
  panelData.limit = originalLimit; // Restore original limit
320
-
321
285
  const newItems = panelData.data.slice(oldDataCount);
322
-
323
286
  if (oldDataCount === 0) {
324
287
  // List was empty, render all panels
325
288
  if (panelData.data.length > 0) {
@@ -331,7 +294,6 @@ const PanelForm = {
331
294
  <div class="in panel-placeholder-bottom panel-placeholder-bottom-${idPanel}"></div>
332
295
  `,
333
296
  );
334
-
335
297
  // Show spinner if there's potentially more data
336
298
  const lastOriginItem = panelData.originData[panelData.originData.length - 1];
337
299
  if (
@@ -360,7 +322,6 @@ const PanelForm = {
360
322
  }
361
323
  }
362
324
  }
363
-
364
325
  return { status };
365
326
  }
366
327
  return { status: 'error' };
@@ -392,33 +353,27 @@ const PanelForm = {
392
353
  // Validate that either mdFileId has content OR fileId has files
393
354
  const hasMdContent = data.mdFileId && data.mdFileId.trim().length > 0;
394
355
  const hasFiles = data.fileId && data.fileId.length > 0;
395
-
396
356
  if (!data.title || (!hasMdContent && !hasFiles)) {
397
357
  NotificationManager.Push({
398
- html: Translate.Render('require-title-and-content-or-file'),
358
+ html: Translate.instance('require-title-and-content-or-file'),
399
359
  status: 'error',
400
360
  });
401
361
  return { data: [], status: 'error', message: 'Must provide either content or attach a file' };
402
362
  }
403
-
404
363
  // Sanitize title for filename - normalize UTF-8 string
405
364
  // In browser, strings are already UTF-16, just ensure valid characters
406
365
  const sanitizedTitle = sanitizeFilename(data.title);
407
-
408
366
  let mdFileId;
409
367
  const mdFileName = `${getCapVariableName(sanitizedTitle)}.md`;
410
368
  const location = `${prefixTags.join('/')}`;
411
-
412
369
  // Only create markdown file if there's actual content
413
370
  let md = null;
414
371
  let mdBlob = null;
415
372
  let mdPlain = null;
416
-
417
373
  if (hasMdContent) {
418
374
  // Markdown content is already UTF-16 in browser, use as-is
419
375
  const blob = new Blob([data.mdFileId], { type: 'text/markdown' });
420
376
  md = new File([blob], mdFileName, { type: 'text/markdown' });
421
-
422
377
  mdBlob = {
423
378
  data: {
424
379
  data: await getDataFromInputFile(md),
@@ -428,7 +383,6 @@ const PanelForm = {
428
383
  };
429
384
  mdPlain = await getRawContentFile(getBlobFromUint8ArrayFile(mdBlob.data.data, mdBlob.mimetype));
430
385
  }
431
-
432
386
  // Parse and normalize tags
433
387
  // Note: 'public' tag is automatically extracted by the backend and converted to isPublic field
434
388
  // It will be filtered from the tags array to keep visibility control separate from content tags
@@ -452,14 +406,12 @@ const PanelForm = {
452
406
  originFileObj = PanelForm.Data[idPanel].filesData.find((d) => d._id === editId);
453
407
  }
454
408
  }
455
-
456
409
  const baseNewDoc = newInstance(data);
457
410
  baseNewDoc.tags = tags.filter((t) => !prefixTags.includes(t));
458
411
  baseNewDoc.mdFileId = hasMdContent
459
412
  ? `<div class="markdown-content">${marked.parse(data.mdFileId)}</div>`
460
413
  : null;
461
414
  baseNewDoc.userId = appStore.Data.user?.main?.model?.user?._id;
462
-
463
415
  // Ensure profileImageId is properly formatted as object with _id property
464
416
  const profileImageIdValue = appStore.Data.user?.main?.model?.user?.profileImageId;
465
417
  const formattedProfileImageId = profileImageIdValue
@@ -467,7 +419,6 @@ const PanelForm = {
467
419
  ? { _id: profileImageIdValue }
468
420
  : profileImageIdValue
469
421
  : null;
470
-
471
422
  baseNewDoc.userInfo = {
472
423
  username: appStore.Data.user?.main?.model?.user?.username,
473
424
  email: appStore.Data.user?.main?.model?.user?.email,
@@ -475,18 +426,14 @@ const PanelForm = {
475
426
  profileImageId: formattedProfileImageId,
476
427
  };
477
428
  baseNewDoc.tools = true;
478
-
479
429
  const documents = [];
480
430
  let message = '';
481
431
  let status = 'success';
482
432
  let indexFormDoc = -1;
483
-
484
433
  const inputFiles = data.fileId ? data.fileId : [null];
485
-
486
434
  for (const file of inputFiles) {
487
435
  indexFormDoc++;
488
436
  let fileId = undefined; // Reset for each iteration - only set if user uploaded a file
489
-
490
437
  await (async () => {
491
438
  const body = new FormData();
492
439
  // Only append md file if it was created (has content)
@@ -495,7 +442,7 @@ const PanelForm = {
495
442
  const { status, data: uploadedFiles } = await FileService.post({ body });
496
443
  // await timer(3000);
497
444
  NotificationManager.Push({
498
- html: Translate.Render(`${status}-upload-file`),
445
+ html: Translate.instance(`${status}-upload-file`),
499
446
  status,
500
447
  });
501
448
  if (status === 'success' && uploadedFiles && Array.isArray(uploadedFiles)) {
@@ -506,7 +453,6 @@ const PanelForm = {
506
453
  // Both can be markdown files, but we must distinguish:
507
454
  // Rich text editor content → mdFileId
508
455
  // User-uploaded file → fileId
509
-
510
456
  for (const uploadedFile of uploadedFiles) {
511
457
  if (hasMdContent && uploadedFile.name === mdFileName) {
512
458
  // This is the markdown file created FROM rich text editor
@@ -518,7 +464,6 @@ const PanelForm = {
518
464
  logger.info(`Assigned user-uploaded file to fileId: ${uploadedFile.name}`);
519
465
  }
520
466
  }
521
-
522
467
  // Validation: mdFileId should exist only if rich text content was provided
523
468
  if (hasMdContent && !mdFileId) {
524
469
  logger.error(
@@ -544,7 +489,6 @@ const PanelForm = {
544
489
  : await DocumentService.post({
545
490
  body,
546
491
  });
547
-
548
492
  const newDoc = {
549
493
  ...baseNewDoc,
550
494
  fileId: file ? URL.createObjectURL(file) : undefined,
@@ -561,10 +505,8 @@ const PanelForm = {
561
505
  ? userInfoFactory(documentData)
562
506
  : null),
563
507
  };
564
-
565
508
  if (documentStatus === 'error') status = 'error';
566
509
  if (message) message += `${indexFormDoc === 0 ? '' : ', '}${documentMessage}`;
567
-
568
510
  const filesData = {
569
511
  id: documentData._id,
570
512
  _id: documentData._id,
@@ -582,7 +524,6 @@ const PanelForm = {
582
524
  filePlain: undefined,
583
525
  },
584
526
  };
585
-
586
527
  if (originObj && indexFormDoc === 0) {
587
528
  PanelForm.Data[idPanel].originData[indexOriginObj] = documentData;
588
529
  PanelForm.Data[idPanel].data[indexOriginObj] = newDoc;
@@ -594,31 +535,26 @@ const PanelForm = {
594
535
  }
595
536
  documents.push(newDoc);
596
537
  }
597
-
598
538
  NotificationManager.Push({
599
539
  html:
600
540
  status === 'success'
601
541
  ? originObj
602
- ? Translate.Render('success-edit-post')
603
- : Translate.Render('success-add-post')
542
+ ? Translate.instance('success-edit-post')
543
+ : Translate.instance('success-add-post')
604
544
  : message,
605
545
  status: status,
606
546
  });
607
-
608
547
  setQueryPath({ path: options.route, queryPath: documents.map((d) => d._id).join(',') });
609
548
  if (options.parentIdModal) Modal.Data[options.parentIdModal].query = `${window.location.search}`;
610
-
611
549
  return { data: documents, status, message };
612
550
  },
613
551
  },
614
552
  });
615
-
616
553
  const getPanelData = async (isLoadMore = false) => {
617
554
  const panelData = PanelForm.Data[idPanel];
618
555
  logger.warn('getPanelData called, isLoadMore:', isLoadMore);
619
556
  try {
620
557
  const cidQuery = getQueryParams().cid;
621
-
622
558
  // When cid query exists, bypass pagination and loading checks
623
559
  if (!cidQuery) {
624
560
  if (panelData.loading || !panelData.hasMore) {
@@ -626,48 +562,39 @@ const PanelForm = {
626
562
  return;
627
563
  }
628
564
  }
629
-
630
565
  panelData.loading = true;
631
-
632
566
  if (!isLoadMore) {
633
567
  // Reset for a fresh load
634
568
  panelData.skip = 0;
635
569
  panelData.hasMore = true;
636
570
  }
637
-
638
571
  // When cid query exists, don't apply skip/limit pagination
639
572
  const params = {
640
573
  tags: prefixTags.join(','),
641
574
  ...(cidQuery && { cid: cidQuery }),
642
575
  };
643
-
644
576
  // Only apply pagination when there's no cid query
645
577
  if (!cidQuery) {
646
578
  params.skip = panelData.skip;
647
579
  params.limit = panelData.limit;
648
580
  }
649
-
650
581
  const result = await DocumentService.get({
651
582
  params,
652
583
  id: 'public/',
653
584
  });
654
-
655
585
  if (result.status === 'success') {
656
586
  if (!isLoadMore) {
657
587
  panelData.originData = [];
658
588
  panelData.filesData = [];
659
589
  panelData.data = [];
660
590
  }
661
-
662
591
  panelData.originData.push(...newInstance(result.data.data));
663
592
  panelData.lasIdAvailable = result.data.lastId;
664
-
665
593
  for (const documentObject of result.data.data) {
666
594
  let mdFileId, fileId;
667
595
  let mdBlob, fileBlob;
668
596
  let mdPlain, filePlain;
669
597
  let parsedMarkdown = '';
670
-
671
598
  try {
672
599
  // Fetch markdown content if mdFileId exists
673
600
  if (documentObject.mdFileId) {
@@ -693,7 +620,6 @@ const PanelForm = {
693
620
  parsedMarkdown = '';
694
621
  }
695
622
  }
696
-
697
623
  // Handle optional fileId
698
624
  if (documentObject.fileId) {
699
625
  const fileIdValue = documentObject.fileId._id || documentObject.fileId;
@@ -708,7 +634,6 @@ const PanelForm = {
708
634
  logger.error('Error fetching file metadata:', fileIdValue, fetchError);
709
635
  }
710
636
  }
711
-
712
637
  // Store file metadata and references
713
638
  panelData.filesData.push({
714
639
  id: documentObject._id,
@@ -716,7 +641,6 @@ const PanelForm = {
716
641
  mdFileId: { mdBlob, mdPlain },
717
642
  fileId: { fileBlob, filePlain },
718
643
  });
719
-
720
644
  // Add to data array for display - use pre-parsed markdown
721
645
  panelData.data.push({
722
646
  id: documentObject._id,
@@ -753,7 +677,6 @@ const PanelForm = {
753
677
  mdFileId: { mdBlob: null, mdPlain: '' },
754
678
  fileId: { fileBlob: null, filePlain: undefined },
755
679
  });
756
-
757
680
  panelData.data.push({
758
681
  id: documentObject._id,
759
682
  title: documentObject.title,
@@ -780,7 +703,6 @@ const PanelForm = {
780
703
  });
781
704
  }
782
705
  }
783
-
784
706
  // Only update pagination when not using cid query
785
707
  if (!cidQuery) {
786
708
  panelData.skip += result.data.data.length;
@@ -789,7 +711,6 @@ const PanelForm = {
789
711
  // When cid query is used, disable infinite scroll
790
712
  panelData.hasMore = false;
791
713
  }
792
-
793
714
  const lastItem = result.data.data[result.data.data.length - 1];
794
715
  if (result.data.data.length === 0 || (lastItem && lastItem._id === panelData.lasIdAvailable)) {
795
716
  LoadingAnimation.spinner.stop(`.panel-placeholder-bottom-${idPanel}`);
@@ -805,7 +726,6 @@ const PanelForm = {
805
726
  } catch (error) {
806
727
  logger.error(error);
807
728
  }
808
-
809
729
  await timer(250);
810
730
  panelData.loading = false;
811
731
  LoadingAnimation.spinner.stop(`.panel-placeholder-bottom-${idPanel}`);
@@ -859,10 +779,10 @@ const PanelForm = {
859
779
  let loadingGetData = false;
860
780
  closeModalRouteChangeEvents[idPanel] = () => {
861
781
  setTimeout(() => {
862
- this.Data[idPanel].updatePanel();
782
+ PanelForm.Data[idPanel].updatePanel();
863
783
  });
864
784
  };
865
- this.Data[idPanel].updatePanel = async (...args) => {
785
+ PanelForm.Data[idPanel].updatePanel = async (...args) => {
866
786
  const _updatePanel = async (...args) => {
867
787
  try {
868
788
  const cid = getQueryParams().cid ? getQueryParams().cid : '';
@@ -871,7 +791,6 @@ const PanelForm = {
871
791
  appStore.Data.user.main.model.user &&
872
792
  appStore.Data.user.main.model.user._id &&
873
793
  lastUserId !== appStore.Data.user.main.model.user._id;
874
-
875
794
  logger.warn(
876
795
  {
877
796
  idPanel,
@@ -882,22 +801,18 @@ const PanelForm = {
882
801
  ? JSON.stringify(appStore.Data.user.main.model.user, null, 4)
883
802
  : 'No user data',
884
803
  );
885
-
886
804
  // Normalize empty values for comparison (undefined, null, '' should all be treated as empty)
887
805
  const normalizedCid = cid || '';
888
806
  const normalizedLastCid = lastCid || '';
889
-
890
807
  if (loadingGetData || (normalizedLastCid === normalizedCid && !forceUpdate)) return;
891
808
  loadingGetData = true;
892
809
  lastUserId = appStore.Data.user?.main?.model?.user?._id
893
810
  ? newInstance(appStore.Data.user.main.model.user._id)
894
811
  : null;
895
812
  lastCid = cid;
896
-
897
- logger.warn('Init render panel data');
898
-
899
- this.Data[idPanel] = {
900
- ...this.Data[idPanel],
813
+ logger.warn('instance render panel data');
814
+ PanelForm.Data[idPanel] = {
815
+ ...PanelForm.Data[idPanel],
901
816
  originData: [],
902
817
  data: [],
903
818
  filesData: [],
@@ -905,47 +820,37 @@ const PanelForm = {
905
820
  hasMore: true,
906
821
  loading: false,
907
822
  };
908
-
909
823
  // Always reset skip to 0 when reloading (whether cid exists or not)
910
- this.Data[idPanel].skip = 0;
911
-
824
+ PanelForm.Data[idPanel].skip = 0;
912
825
  const containerSelector = `.${options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body'}`;
913
826
  htmls(containerSelector, await renderSrrPanelData());
914
-
915
827
  await getPanelData();
916
-
917
828
  htmls(
918
829
  containerSelector,
919
830
  html`
920
- <div class="in">${await panelRender({ data: this.Data[idPanel].data })}</div>
831
+ <div class="in">${await panelRender({ data: PanelForm.Data[idPanel].data })}</div>
921
832
  <div class="in panel-placeholder-bottom panel-placeholder-bottom-${idPanel}"></div>
922
833
  `,
923
834
  );
924
-
925
- const lastOriginItem = this.Data[idPanel].originData[this.Data[idPanel].originData.length - 1];
835
+ const lastOriginItem = PanelForm.Data[idPanel].originData[PanelForm.Data[idPanel].originData.length - 1];
926
836
  if (
927
- !this.Data[idPanel].lasIdAvailable ||
837
+ !PanelForm.Data[idPanel].lasIdAvailable ||
928
838
  !lastOriginItem ||
929
- this.Data[idPanel].lasIdAvailable !== lastOriginItem._id
839
+ PanelForm.Data[idPanel].lasIdAvailable !== lastOriginItem._id
930
840
  )
931
841
  LoadingAnimation.spinner.play(`.panel-placeholder-bottom-${idPanel}`, 'dual-ring-mini');
932
-
933
842
  const scrollContainerSelector = `.modal-${options.route}`;
934
-
935
843
  // Always remove old scroll event before setting new one
936
- if (this.Data[idPanel].removeScrollEvent) {
937
- this.Data[idPanel].removeScrollEvent();
844
+ if (PanelForm.Data[idPanel].removeScrollEvent) {
845
+ PanelForm.Data[idPanel].removeScrollEvent();
938
846
  }
939
-
940
847
  if (cid) {
941
848
  LoadingAnimation.spinner.stop(`.panel-placeholder-bottom-${idPanel}`);
942
849
  return;
943
850
  }
944
-
945
851
  const { removeEvent } = Scroll.setEvent(scrollContainerSelector, async (payload) => {
946
852
  const panelData = PanelForm.Data[idPanel];
947
853
  if (!panelData) return;
948
-
949
854
  // Infinite scroll: load more items at bottom
950
855
  if (payload.atBottom && panelData.hasMore && !panelData.loading) {
951
856
  const oldDataCount = panelData.data.length;
@@ -957,8 +862,7 @@ const PanelForm = {
957
862
  }
958
863
  }
959
864
  });
960
- this.Data[idPanel].removeScrollEvent = removeEvent;
961
-
865
+ PanelForm.Data[idPanel].removeScrollEvent = removeEvent;
962
866
  if (!firsUpdateEvent && options.firsUpdateEvent) {
963
867
  firsUpdateEvent = true;
964
868
  await options.firsUpdateEvent();
@@ -967,7 +871,6 @@ const PanelForm = {
967
871
  logger.error(error);
968
872
  }
969
873
  };
970
-
971
874
  await _updatePanel(...args);
972
875
  loadingGetData = false;
973
876
  };
@@ -998,14 +901,11 @@ const PanelForm = {
998
901
  await PanelForm.Data[idPanel].updatePanel();
999
902
  };
1000
903
  }
1001
-
1002
904
  if (options.parentIdModal) {
1003
905
  htmls(`.html-${options.parentIdModal}`, await renderSrrPanelData());
1004
906
  return '';
1005
907
  }
1006
-
1007
908
  return await renderSrrPanelData();
1008
- },
1009
- };
1010
-
909
+ }
910
+ }
1011
911
  export { PanelForm };