underpost 2.8.87 → 2.8.88

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 (99) hide show
  1. package/.env.development +35 -3
  2. package/.env.production +39 -4
  3. package/.env.test +35 -3
  4. package/.github/workflows/ghpkg.ci.yml +1 -1
  5. package/.github/workflows/npmpkg.ci.yml +1 -1
  6. package/.github/workflows/pwa-microservices-template-page.cd.yml +6 -5
  7. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  8. package/.github/workflows/release.cd.yml +3 -3
  9. package/README.md +56 -2
  10. package/bin/build.js +4 -0
  11. package/bin/deploy.js +62 -8
  12. package/bin/file.js +3 -2
  13. package/cli.md +8 -2
  14. package/conf.js +30 -4
  15. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  16. package/manifests/deployment/dd-test-development/deployment.yaml +174 -0
  17. package/manifests/deployment/dd-test-development/proxy.yaml +51 -0
  18. package/package.json +6 -5
  19. package/src/api/core/core.router.js +2 -1
  20. package/src/api/default/default.controller.js +6 -1
  21. package/src/api/default/default.router.js +6 -2
  22. package/src/api/default/default.service.js +10 -1
  23. package/src/api/document/document.controller.js +66 -0
  24. package/src/api/document/document.model.js +51 -0
  25. package/src/api/document/document.router.js +24 -0
  26. package/src/api/document/document.service.js +125 -0
  27. package/src/api/file/file.controller.js +15 -1
  28. package/src/api/file/file.router.js +2 -1
  29. package/src/api/test/test.router.js +1 -1
  30. package/src/api/user/postman_collection.json +216 -0
  31. package/src/api/user/user.controller.js +25 -60
  32. package/src/api/user/user.model.js +29 -7
  33. package/src/api/user/user.router.js +9 -3
  34. package/src/api/user/user.service.js +84 -32
  35. package/src/cli/baremetal.js +33 -3
  36. package/src/cli/cloud-init.js +11 -0
  37. package/src/cli/cron.js +0 -1
  38. package/src/cli/deploy.js +46 -23
  39. package/src/cli/index.js +5 -0
  40. package/src/cli/lxd.js +7 -0
  41. package/src/cli/repository.js +42 -6
  42. package/src/cli/run.js +45 -13
  43. package/src/cli/ssh.js +20 -6
  44. package/src/client/Default.index.js +42 -1
  45. package/src/client/components/core/Account.js +10 -2
  46. package/src/client/components/core/AgGrid.js +30 -8
  47. package/src/client/components/core/Auth.js +99 -56
  48. package/src/client/components/core/BtnIcon.js +3 -2
  49. package/src/client/components/core/CalendarCore.js +2 -3
  50. package/src/client/components/core/CommonJs.js +1 -2
  51. package/src/client/components/core/Content.js +15 -12
  52. package/src/client/components/core/Css.js +2 -1
  53. package/src/client/components/core/CssCore.js +6 -1
  54. package/src/client/components/core/Docs.js +5 -5
  55. package/src/client/components/core/FileExplorer.js +3 -3
  56. package/src/client/components/core/Input.js +22 -17
  57. package/src/client/components/core/JoyStick.js +2 -2
  58. package/src/client/components/core/LoadingAnimation.js +2 -2
  59. package/src/client/components/core/LogIn.js +16 -23
  60. package/src/client/components/core/LogOut.js +5 -1
  61. package/src/client/components/core/Logger.js +4 -1
  62. package/src/client/components/core/Modal.js +82 -53
  63. package/src/client/components/core/ObjectLayerEngineModal.js +2 -1
  64. package/src/client/components/core/Pagination.js +207 -0
  65. package/src/client/components/core/Panel.js +10 -10
  66. package/src/client/components/core/PanelForm.js +130 -33
  67. package/src/client/components/core/Recover.js +2 -2
  68. package/src/client/components/core/Router.js +210 -34
  69. package/src/client/components/core/SignUp.js +1 -2
  70. package/src/client/components/core/Stream.js +1 -1
  71. package/src/client/components/core/VanillaJs.js +3 -84
  72. package/src/client/components/core/Worker.js +2 -2
  73. package/src/client/components/default/LogInDefault.js +0 -6
  74. package/src/client/components/default/LogOutDefault.js +0 -16
  75. package/src/client/components/default/MenuDefault.js +97 -44
  76. package/src/client/components/default/RoutesDefault.js +5 -2
  77. package/src/client/services/core/core.service.js +8 -20
  78. package/src/client/services/default/default.management.js +115 -18
  79. package/src/client/services/default/default.service.js +13 -4
  80. package/src/client/services/document/document.service.js +97 -0
  81. package/src/client/services/file/file.service.js +2 -0
  82. package/src/client/services/test/test.service.js +3 -0
  83. package/src/client/services/user/user.management.js +6 -0
  84. package/src/client/services/user/user.service.js +15 -4
  85. package/src/client/ssr/Render.js +1 -1
  86. package/src/client/ssr/head/DefaultScripts.js +3 -0
  87. package/src/client/ssr/head/Seo.js +1 -0
  88. package/src/index.js +24 -2
  89. package/src/runtime/lampp/Lampp.js +89 -2
  90. package/src/runtime/xampp/Xampp.js +48 -1
  91. package/src/server/auth.js +519 -155
  92. package/src/server/backup.js +2 -2
  93. package/src/server/conf.js +66 -8
  94. package/src/server/process.js +2 -1
  95. package/src/server/runtime.js +135 -286
  96. package/src/server/ssl.js +1 -2
  97. package/src/server/ssr.js +85 -0
  98. package/src/server/start.js +2 -2
  99. package/src/server/valkey.js +2 -1
@@ -0,0 +1,207 @@
1
+ import { getQueryParams, setQueryParams } from './Router.js';
2
+
3
+ class AgPagination extends HTMLElement {
4
+ constructor() {
5
+ super();
6
+ this.attachShadow({ mode: 'open' });
7
+ this._gridId = null;
8
+ const queryParams = getQueryParams();
9
+ this._currentPage = parseInt(queryParams.page, 10) || 1;
10
+ this._limit = parseInt(queryParams.limit, 10) || 10;
11
+ this._totalPages = 1;
12
+ this._totalItems = 0;
13
+ this.handlePageChange = this.handlePageChange.bind(this);
14
+ this.handleLimitChange = this.handleLimitChange.bind(this);
15
+ }
16
+
17
+ static get observedAttributes() {
18
+ return ['grid-id', 'current-page', 'total-pages', 'total-items', 'limit'];
19
+ }
20
+
21
+ attributeChangedCallback(name, oldValue, newValue) {
22
+ switch (name) {
23
+ case 'grid-id':
24
+ this._gridId = newValue;
25
+ break;
26
+ case 'current-page':
27
+ this._currentPage = parseInt(newValue, 10) || this._currentPage;
28
+ break;
29
+ case 'total-pages':
30
+ this._totalPages = parseInt(newValue, 10) || 1;
31
+ break;
32
+ case 'total-items':
33
+ this._totalItems = parseInt(newValue, 10) || 0;
34
+ break;
35
+ case 'limit':
36
+ this._limit = parseInt(newValue, 10) || this._limit;
37
+ break;
38
+ }
39
+ this.update();
40
+ }
41
+
42
+ connectedCallback() {
43
+ this.render();
44
+ this.addEventListeners();
45
+ this.update();
46
+ }
47
+
48
+ disconnectedCallback() {
49
+ // Event listeners on shadow DOM are garbage collected with the component
50
+ }
51
+
52
+ handlePageChange(newPage) {
53
+ if (newPage < 1 || newPage > this._totalPages || newPage === this._currentPage) {
54
+ return;
55
+ }
56
+ this._currentPage = newPage;
57
+ setQueryParams({ page: newPage, limit: this._limit });
58
+ this.dispatchEvent(new CustomEvent('page-change', { detail: { page: newPage } }));
59
+ }
60
+
61
+ handleLimitChange(event) {
62
+ const newLimit = parseInt(event.target.value, 10);
63
+ if (newLimit === this._limit) {
64
+ return;
65
+ }
66
+ this._limit = newLimit;
67
+ this._currentPage = 1; // Reset to first page on limit change
68
+ setQueryParams({ page: 1, limit: newLimit });
69
+ this.dispatchEvent(new CustomEvent('limit-change', { detail: { limit: newLimit } }));
70
+ }
71
+
72
+ update() {
73
+ if (!this.shadowRoot.querySelector('#page-info')) return;
74
+
75
+ const isFirstPage = this._currentPage === 1;
76
+ const isLastPage = this._currentPage === this._totalPages;
77
+
78
+ this.shadowRoot.querySelector('#first-page').disabled = isFirstPage;
79
+ this.shadowRoot.querySelector('#prev-page').disabled = isFirstPage;
80
+ this.shadowRoot.querySelector('#next-page').disabled = isLastPage;
81
+ this.shadowRoot.querySelector('#last-page').disabled = isLastPage;
82
+
83
+ const startItem = this._totalItems > 0 ? (this._currentPage - 1) * this._limit + 1 : 0;
84
+ const endItem = Math.min(this._currentPage * this._limit, this._totalItems);
85
+
86
+ this.shadowRoot.querySelector('#summary-info').textContent = `${startItem} - ${endItem} of ${this._totalItems}`;
87
+ this.shadowRoot.querySelector('#page-info').textContent = `Page ${this._currentPage} of ${this._totalPages}`;
88
+
89
+ const limitSelector = this.shadowRoot.querySelector('#limit-selector');
90
+ if (limitSelector.value != this._limit) {
91
+ limitSelector.value = this._limit;
92
+ }
93
+
94
+ this.renderPageButtons();
95
+ }
96
+
97
+ renderPageButtons() {
98
+ const pageButtonsContainer = this.shadowRoot.querySelector('#page-buttons');
99
+ pageButtonsContainer.innerHTML = '';
100
+
101
+ const maxButtons = 5;
102
+ let startPage = Math.max(1, this._currentPage - Math.floor(maxButtons / 2));
103
+ let endPage = Math.min(this._totalPages, startPage + maxButtons - 1);
104
+
105
+ if (endPage - startPage + 1 < maxButtons) {
106
+ startPage = Math.max(1, endPage - maxButtons + 1);
107
+ }
108
+
109
+ for (let i = startPage; i <= endPage; i++) {
110
+ const button = document.createElement('button');
111
+ button.textContent = i;
112
+ button.disabled = i === this._currentPage;
113
+ if (i === this._currentPage) {
114
+ button.classList.add('active');
115
+ }
116
+ button.addEventListener('click', () => this.handlePageChange(i));
117
+ pageButtonsContainer.appendChild(button);
118
+ }
119
+ }
120
+
121
+ addEventListeners() {
122
+ this.shadowRoot.querySelector('#first-page').addEventListener('click', () => this.handlePageChange(1));
123
+ this.shadowRoot
124
+ .querySelector('#prev-page')
125
+ .addEventListener('click', () => this.handlePageChange(this._currentPage - 1));
126
+ this.shadowRoot
127
+ .querySelector('#next-page')
128
+ .addEventListener('click', () => this.handlePageChange(this._currentPage + 1));
129
+ this.shadowRoot
130
+ .querySelector('#last-page')
131
+ .addEventListener('click', () => this.handlePageChange(this._totalPages));
132
+ this.shadowRoot.querySelector('#limit-selector').addEventListener('change', this.handleLimitChange);
133
+ }
134
+
135
+ render() {
136
+ this.shadowRoot.innerHTML = html`
137
+ <style>
138
+ :host {
139
+ display: flex;
140
+ align-items: center;
141
+ justify-content: center;
142
+ padding: 8px;
143
+ font-family: sans-serif;
144
+ font-size: 14px;
145
+ gap: 8px;
146
+ }
147
+ button {
148
+ border: 1px solid #ccc;
149
+ background-color: #f0f0f0;
150
+ padding: 6px 12px;
151
+ cursor: pointer;
152
+ border-radius: 4px;
153
+ }
154
+ button:disabled {
155
+ cursor: not-allowed;
156
+ opacity: 0.5;
157
+ }
158
+ button.active {
159
+ border-color: #007bff;
160
+ background-color: #007bff;
161
+ color: white;
162
+ }
163
+ #page-info {
164
+ min-width: 80px;
165
+ text-align: center;
166
+ }
167
+ #page-buttons {
168
+ display: flex;
169
+ gap: 4px;
170
+ }
171
+ .summary-panel,
172
+ .page-summary-panel {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 8px;
176
+ }
177
+ #limit-selector {
178
+ border: 1px solid #ccc;
179
+ background-color: #f0f0f0;
180
+ padding: 6px 12px;
181
+ border-radius: 4px;
182
+ }
183
+ </style>
184
+ <div class="summary-panel">
185
+ <span id="summary-info"></span>
186
+ </div>
187
+ <button id="first-page">First</button>
188
+ <button id="prev-page">Previous</button>
189
+ <div class="page-summary-panel">
190
+ <div id="page-buttons"></div>
191
+ <span id="page-info"></span>
192
+ </div>
193
+ <button id="next-page">Next</button>
194
+ <button id="last-page">Last</button>
195
+ <select id="limit-selector">
196
+ <option value="10">10</option>
197
+ <option value="20">20</option>
198
+ <option value="50">50</option>
199
+ <option value="100">100</option>
200
+ </select>
201
+ `;
202
+ }
203
+ }
204
+
205
+ customElements.define('ag-pagination', AgPagination);
206
+
207
+ export { AgPagination };
@@ -2,17 +2,14 @@ import { getId, isValidDate, newInstance } from './CommonJs.js';
2
2
  import { LoadingAnimation } from '../core/LoadingAnimation.js';
3
3
  import { Validator } from '../core/Validator.js';
4
4
  import { Input } from '../core/Input.js';
5
- import { Responsive } from '../core/Responsive.js';
6
5
  import { darkTheme, ThemeEvents } from './Css.js';
7
- import { append, getDataFromInputFile, getQueryParams, htmls, prepend, s } from './VanillaJs.js';
6
+ import { append, getDataFromInputFile, htmls, s } from './VanillaJs.js';
8
7
  import { BtnIcon } from './BtnIcon.js';
9
8
  import { Translate } from './Translate.js';
10
9
  import { DropDown } from './DropDown.js';
11
- import { dynamicCol, renderCssAttr } from './Css.js';
10
+ import { dynamicCol } from './Css.js';
12
11
  import { EventsUI } from './EventsUI.js';
13
12
  import { ToggleSwitch } from './ToggleSwitch.js';
14
- import { Modal } from './Modal.js';
15
- import { RouterEvents } from './Router.js';
16
13
  import { RichText } from './RichText.js';
17
14
  import { loggerFactory } from './Logger.js';
18
15
  import { Badge } from './Badge.js';
@@ -117,6 +114,7 @@ const Panel = {
117
114
  options.originData().find((d) => d._id === obj._id || d.id === obj.id),
118
115
  options.filesData().find((d) => d._id === obj._id || d.id === obj.id),
119
116
  );
117
+ if (options.on.initEdit) await options.on.initEdit({ data: obj });
120
118
  });
121
119
  s(`.a-${payload._id}`).onclick = async (e) => {
122
120
  e.preventDefault();
@@ -470,11 +468,9 @@ const Panel = {
470
468
  s(`.${btnSelector}`).classList.remove('hide');
471
469
  }
472
470
  }
473
- setTimeout(() => {
474
- s(`.${idPanel}-form-body`).classList.add('hide');
475
- });
471
+ s(`.${idPanel}-form-body`).classList.add('hide');
476
472
  };
477
- s(`.btn-${idPanel}-add`).onclick = (e) => {
473
+ s(`.btn-${idPanel}-add`).onclick = async (e) => {
478
474
  e.preventDefault();
479
475
  // s(`.btn-${idPanel}-clean`).click();
480
476
  Panel.Tokens[idPanel].editId = undefined;
@@ -483,16 +479,20 @@ const Panel = {
483
479
  s(`.${scrollClassContainer}`).scrollTop = 0;
484
480
 
485
481
  openPanelForm();
482
+ if (options.on.initAdd) await options.on.initAdd();
486
483
  };
487
484
  if (s(`.${scrollClassContainer}`)) s(`.${scrollClassContainer}`).style.overflow = 'auto';
488
485
  });
489
486
 
490
487
  if (data.length > 0) for (const obj of data) render += await renderPanel(obj);
491
- else
488
+ else {
492
489
  render += html`<div class="in" style="min-height: 200px">
493
490
  <div class="abs center"><i class="fas fa-exclamation-circle"></i> ${Translate.Render(`no-result-found`)}</div>
494
491
  </div>`;
495
492
 
493
+ if (options.on.noResultFound) setTimeout(options.on.noResultFound);
494
+ }
495
+
496
496
  this.Tokens[idPanel] = { idPanel, scrollClassContainer, formData, data, titleKey, subTitleKey, renderPanel };
497
497
 
498
498
  let customButtonsRender = '';
@@ -1,12 +1,6 @@
1
- import { getCapVariableName, newInstance, random, range, uniqueArray } from './CommonJs.js';
1
+ import { getCapVariableName, newInstance, random, range, timer, uniqueArray } from './CommonJs.js';
2
2
  import { marked } from 'marked';
3
- import {
4
- getBlobFromUint8ArrayFile,
5
- getDataFromInputFile,
6
- getQueryParams,
7
- getRawContentFile,
8
- htmls,
9
- } from './VanillaJs.js';
3
+ import { append, getBlobFromUint8ArrayFile, getDataFromInputFile, getRawContentFile, htmls, s } from './VanillaJs.js';
10
4
  import { Panel } from './Panel.js';
11
5
  import { NotificationManager } from './NotificationManager.js';
12
6
  import { DocumentService } from '../../services/document/document.service.js';
@@ -15,7 +9,9 @@ import { getSrcFromFileData } from './Input.js';
15
9
  import { imageShimmer, renderCssAttr } from './Css.js';
16
10
  import { Translate } from './Translate.js';
17
11
  import { Modal } from './Modal.js';
18
- import { closeModalRouteChangeEvents, listenQueryPathInstance, setQueryPath } from './Router.js';
12
+ import { closeModalRouteChangeEvents, listenQueryPathInstance, setQueryPath, getQueryParams } from './Router.js';
13
+ import { Scroll } from './Scroll.js';
14
+ import { LoadingAnimation } from './LoadingAnimation.js';
19
15
 
20
16
  const PanelForm = {
21
17
  Data: {},
@@ -39,6 +35,10 @@ const PanelForm = {
39
35
  originData: [],
40
36
  data: [],
41
37
  filesData: [],
38
+ skip: 0,
39
+ limit: 3, // Load 5 items per page
40
+ hasMore: true,
41
+ loading: false,
42
42
  };
43
43
 
44
44
  const formData = [
@@ -138,7 +138,7 @@ const PanelForm = {
138
138
  `,
139
139
  });
140
140
  return await options.fileRender({
141
- file: PanelForm.Data[idPanel].filesData.find((f) => f._id === options.data._id).fileId.fileBlob,
141
+ file: PanelForm.Data[idPanel].filesData.find((f) => f._id === options.data._id)?.fileId?.fileBlob,
142
142
  style: {
143
143
  overflow: 'auto',
144
144
  width: '100%',
@@ -178,6 +178,15 @@ const PanelForm = {
178
178
  }
179
179
  return { status: 'error' };
180
180
  },
181
+ initAdd: async function () {
182
+ s(`.modal-${options.route}`).scrollTo({ top: 0, behavior: 'smooth' });
183
+ },
184
+ initEdit: async function ({ data }) {
185
+ s(`.modal-${options.route}`).scrollTo({ top: 0, behavior: 'smooth' });
186
+ },
187
+ noResultFound: async function () {
188
+ LoadingAnimation.spinner.stop(`.panel-placeholder-bottom-${idPanel}`);
189
+ },
181
190
  add: async function ({ data, editId }) {
182
191
  let mdFileId;
183
192
  const mdFileName = `${getCapVariableName(data.title)}.md`;
@@ -318,19 +327,36 @@ const PanelForm = {
318
327
  },
319
328
  });
320
329
 
321
- const getPanelData = async () => {
330
+ const getPanelData = async (isLoadMore = false) => {
331
+ const panelData = PanelForm.Data[idPanel];
332
+ if (panelData.loading || !panelData.hasMore) return;
333
+ panelData.loading = true;
334
+
335
+ if (!isLoadMore) {
336
+ // Reset for a fresh load
337
+ panelData.skip = 0;
338
+ panelData.hasMore = true;
339
+ }
340
+
322
341
  const result = await DocumentService.get({
323
- id: `public/?tags=${prefixTags.join(',')}${getQueryParams().cid ? `&cid=${getQueryParams().cid}` : ''}`,
342
+ params: {
343
+ tags: prefixTags.join(','),
344
+ ...(getQueryParams().cid && { cid: getQueryParams().cid }),
345
+ skip: panelData.skip,
346
+ limit: panelData.limit,
347
+ },
348
+ id: 'public/',
324
349
  });
325
350
 
326
- NotificationManager.Push({
327
- html: result.status === 'success' ? Translate.Render('success-get-posts') : result.message,
328
- status: result.status,
329
- });
330
351
  if (result.status === 'success') {
331
- PanelForm.Data[idPanel].originData = newInstance(result.data);
332
- PanelForm.Data[idPanel].filesData = [];
333
- PanelForm.Data[idPanel].data = [];
352
+ if (!isLoadMore) {
353
+ panelData.originData = [];
354
+ panelData.filesData = [];
355
+ panelData.data = [];
356
+ }
357
+
358
+ panelData.originData.push(...newInstance(result.data));
359
+
334
360
  for (const documentObject of result.data) {
335
361
  let mdFileId, fileId;
336
362
  let mdBlob, fileBlob;
@@ -339,7 +365,6 @@ const PanelForm = {
339
365
  {
340
366
  const {
341
367
  data: [file],
342
- status,
343
368
  } = await FileService.get({ id: documentObject.mdFileId });
344
369
 
345
370
  // const ext = file.name.split('.')[file.name.split('.').length - 1];
@@ -350,7 +375,6 @@ const PanelForm = {
350
375
  if (documentObject.fileId) {
351
376
  const {
352
377
  data: [file],
353
- status,
354
378
  } = await FileService.get({ id: documentObject.fileId._id });
355
379
 
356
380
  // const ext = file.name.split('.')[file.name.split('.').length - 1];
@@ -359,14 +383,14 @@ const PanelForm = {
359
383
  fileId = getSrcFromFileData(file);
360
384
  }
361
385
 
362
- PanelForm.Data[idPanel].filesData.push({
386
+ panelData.filesData.push({
363
387
  id: documentObject._id,
364
388
  _id: documentObject._id,
365
389
  mdFileId: { mdBlob, mdPlain },
366
390
  fileId: { fileBlob, filePlain },
367
391
  });
368
392
 
369
- PanelForm.Data[idPanel].data.push({
393
+ panelData.data.push({
370
394
  id: documentObject._id,
371
395
  title: documentObject.title,
372
396
  createdAt: documentObject.createdAt,
@@ -378,7 +402,23 @@ const PanelForm = {
378
402
  _id: documentObject._id,
379
403
  });
380
404
  }
405
+
406
+ panelData.skip += result.data.length;
407
+ panelData.hasMore = result.data.length === panelData.limit;
408
+ if (result.data.length < panelData.limit) {
409
+ LoadingAnimation.spinner.stop(`.panel-placeholder-bottom-${idPanel}`);
410
+ }
411
+ } else {
412
+ NotificationManager.Push({
413
+ html: result.message,
414
+ status: result.status,
415
+ });
416
+ panelData.hasMore = false;
381
417
  }
418
+
419
+ await timer(250);
420
+
421
+ panelData.loading = false;
382
422
  };
383
423
  const renderSrrPanelData = async () =>
384
424
  await panelRender({
@@ -426,10 +466,10 @@ const PanelForm = {
426
466
  let firsUpdateEvent = false;
427
467
  let lastCid;
428
468
  let lastUserId;
429
- closeModalRouteChangeEvents[idPanel] = (newPath) => {
430
- if (newPath.split('?')[0] === '/' && PanelForm.Data[idPanel].data && PanelForm.Data[idPanel].data.length === 0) {
469
+ closeModalRouteChangeEvents[idPanel] = () => {
470
+ setTimeout(() => {
431
471
  this.Data[idPanel].updatePanel();
432
- }
472
+ });
433
473
  };
434
474
  this.Data[idPanel].updatePanel = async () => {
435
475
  const cid = getQueryParams().cid ? getQueryParams().cid : '';
@@ -437,13 +477,60 @@ const PanelForm = {
437
477
  if (lastCid === cid && !forceUpdate) return;
438
478
  lastUserId = newInstance(Elements.Data.user.main.model.user._id);
439
479
  lastCid = cid;
440
- if (options.route === 'home') Modal.homeCid = newInstance(cid);
441
- htmls(`.${options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body'}`, await renderSrrPanelData());
480
+
481
+ if (cid) {
482
+ this.Data[idPanel] = {
483
+ ...this.Data[idPanel],
484
+ originData: [],
485
+ data: [],
486
+ filesData: [],
487
+ skip: 0,
488
+ limit: 3, // Load 5 items per page
489
+ hasMore: true,
490
+ loading: false,
491
+ };
492
+ }
493
+
494
+ const containerSelector = `.${options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body'}`;
495
+ htmls(containerSelector, await renderSrrPanelData());
496
+
442
497
  await getPanelData();
498
+
443
499
  htmls(
444
- `.${options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body'}`,
445
- await panelRender({ data: this.Data[idPanel].data }),
500
+ containerSelector,
501
+ html`
502
+ <div class="in">${await panelRender({ data: this.Data[idPanel].data })}</div>
503
+ <div class="in panel-placeholder-bottom panel-placeholder-bottom-${idPanel}"></div>
504
+ `,
446
505
  );
506
+
507
+ LoadingAnimation.spinner.play(`.panel-placeholder-bottom-${idPanel}`, 'dual-ring-mini');
508
+
509
+ const scrollContainerSelector = `.modal-${options.route}`;
510
+
511
+ if (cid) {
512
+ LoadingAnimation.spinner.stop(`.panel-placeholder-bottom-${idPanel}`);
513
+ return;
514
+ }
515
+ if (this.Data[idPanel].removeScrollEvent) {
516
+ this.Data[idPanel].removeScrollEvent();
517
+ }
518
+ const { removeEvent } = Scroll.setEvent(scrollContainerSelector, async (payload) => {
519
+ const panelData = PanelForm.Data[idPanel];
520
+ if (!panelData) return;
521
+
522
+ // Infinite scroll: load more items at bottom
523
+ if (payload.atBottom && panelData.hasMore && !panelData.loading) {
524
+ const oldDataCount = panelData.data.length;
525
+ await getPanelData(true); // isLoadMore = true
526
+ const newItems = panelData.data.slice(oldDataCount);
527
+ if (newItems.length > 0) {
528
+ for (const item of newItems) append(`.${idPanel}-render`, await Panel.Tokens[idPanel].renderPanel(item));
529
+ }
530
+ }
531
+ });
532
+ this.Data[idPanel].removeScrollEvent = removeEvent;
533
+
447
534
  if (!firsUpdateEvent && options.firsUpdateEvent) {
448
535
  firsUpdateEvent = true;
449
536
  await options.firsUpdateEvent();
@@ -454,15 +541,25 @@ const PanelForm = {
454
541
  id: options.parentIdModal ? 'html-' + options.parentIdModal : 'main-body',
455
542
  routeId: options.route,
456
543
  event: async (path) => {
457
- await this.Data[idPanel].updatePanel();
544
+ PanelForm.Data[idPanel] = {
545
+ ...PanelForm.Data[idPanel],
546
+ originData: [],
547
+ data: [],
548
+ filesData: [],
549
+ // skip: 0,
550
+ limit: 3, // Load 5 items per page
551
+ hasMore: true,
552
+ loading: false,
553
+ };
554
+ await PanelForm.Data[idPanel].updatePanel();
458
555
  },
459
556
  });
460
557
  if (!options.parentIdModal)
461
558
  Modal.Data['modal-menu'].onHome[idPanel] = async () => {
462
559
  lastCid = undefined;
463
560
  lastUserId = undefined;
464
- setQueryPath({ path: options.route, queryPath: '' });
465
- await this.Data[idPanel].updatePanel();
561
+ setQueryPath({ path: options.route, queryPath: options.route === 'home' ? '?' : '' });
562
+ await PanelForm.Data[idPanel].updatePanel();
466
563
  };
467
564
  }
468
565
 
@@ -3,11 +3,11 @@ import { Auth } from './Auth.js';
3
3
  import { BtnIcon } from './BtnIcon.js';
4
4
  import { EventsUI } from './EventsUI.js';
5
5
  import { Input } from './Input.js';
6
- import { LogIn } from './LogIn.js';
7
6
  import { NotificationManager } from './NotificationManager.js';
8
7
  import { Translate } from './Translate.js';
9
8
  import { Validator } from './Validator.js';
10
- import { getProxyPath, getQueryParams, s } from './VanillaJs.js';
9
+ import { s } from './VanillaJs.js';
10
+ import { getProxyPath, getQueryParams } from './Router.js';
11
11
 
12
12
  const Recover = {
13
13
  Event: {},