@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
@@ -34,6 +34,7 @@ import { Badge } from './Badge.js';
34
34
  import { Worker } from './Worker.js';
35
35
  import { Scroll } from './Scroll.js';
36
36
  import { windowGetH, windowGetW } from './windowGetDimensions.js';
37
+ import { SearchBox } from './SearchBox.js';
37
38
 
38
39
  const logger = loggerFactory(import.meta, { trace: true });
39
40
 
@@ -73,7 +74,7 @@ const Modal = {
73
74
  const minWidth = width;
74
75
  const heightDefaultTopBar = 50;
75
76
  const heightDefaultBottomBar = 0;
76
- const idModal = options && 'id' in options ? options.id : getId(this.Data, 'modal-');
77
+ const idModal = options.id ? options.id : getId(this.Data, 'modal-');
77
78
  this.Data[idModal] = {
78
79
  options,
79
80
  onCloseListener: {},
@@ -549,6 +550,9 @@ const Modal = {
549
550
  const inputInfoNode = s(`.input-info-${inputSearchBoxId}`).cloneNode(true);
550
551
  s(`.input-info-${inputSearchBoxId}`).remove();
551
552
  {
553
+ // Inject SearchBox base styles
554
+ SearchBox.injectStyles();
555
+
552
556
  const id = 'search-box-history';
553
557
  const searchBoxHistoryId = id;
554
558
  const formDataInfoNode = [
@@ -568,7 +572,6 @@ const Modal = {
568
572
  });
569
573
  let currentKeyBoardSearchBoxIndex = 0;
570
574
  let results = [];
571
- let historySearchBox = [];
572
575
 
573
576
  const checkHistoryBoxTitleStatus = () => {
574
577
  if (s(`.search-box-result-title`) && s(`.search-box-result-title`).classList) {
@@ -589,8 +592,28 @@ const Modal = {
589
592
  } else s(`.key-shortcut-container-info`).classList.add('hide');
590
593
  };
591
594
 
592
- const renderSearchResult = async (results) => {
595
+ const renderSearchResult = async (results, isRecentHistory = false) => {
596
+ // Check if the search history modal still exists before rendering
597
+ if (!s(`.html-${searchBoxHistoryId}`)) return;
598
+
593
599
  htmls(`.html-${searchBoxHistoryId}`, '');
600
+
601
+ // Show/hide clear-all button based on whether showing recent history
602
+ // Use setTimeout to ensure the button is in the DOM after modal renders
603
+ const updateClearAllBtn = () => {
604
+ const clearAllBtn = s(`.btn-search-history-clear-all`);
605
+ if (clearAllBtn) {
606
+ if (isRecentHistory && results.length > 0) {
607
+ clearAllBtn.style.display = 'flex';
608
+ } else {
609
+ clearAllBtn.style.display = 'none';
610
+ }
611
+ }
612
+ };
613
+ // Try immediately and also with a delay to handle timing
614
+ updateClearAllBtn();
615
+ setTimeout(updateClearAllBtn, 50);
616
+
594
617
  if (results.length === 0) {
595
618
  append(
596
619
  `.html-${searchBoxHistoryId}`,
@@ -609,108 +632,63 @@ const Modal = {
609
632
  }),
610
633
  }),
611
634
  );
635
+ return;
612
636
  }
613
- let indexResult = -1;
614
- for (const result of results) {
615
- indexResult++;
616
- const indexRender = indexResult;
617
- append(
618
- `.html-${searchBoxHistoryId}`,
619
- await BtnIcon.Render({
620
- label: `${
621
- result.fontAwesomeIcon
622
- ? html`<i class="${result.fontAwesomeIcon.classList.toString()}"></i> `
623
- : result.imgElement
624
- ? html`<img
625
- class="inl"
626
- src="${result.imgElement.src}"
627
- style="${renderCssAttr({ style: { width: '25px', height: '25px' } })}"
628
- />`
629
- : ''
630
- } ${Translate.Render(result.routerId)}`,
631
- class: `wfa search-result-btn-${result.routerId} ${
632
- indexResult === currentKeyBoardSearchBoxIndex ? 'main-btn-menu-active' : ''
633
- } search-result-btn-${indexResult}`,
634
- style: renderCssAttr({
635
- style: { padding: '3px', margin: '2px', 'text-align': 'left' },
636
- }),
637
- }),
638
- );
639
- s(`.search-result-btn-${result.routerId}`).onclick = () => {
640
- if (!s(`.html-${searchBoxHistoryId}`) || !s(`.html-${searchBoxHistoryId}`).hasChildNodes()) return;
641
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.remove(
642
- `main-btn-menu-active`,
643
- );
644
- currentKeyBoardSearchBoxIndex = indexRender;
645
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.add(
646
- `main-btn-menu-active`,
647
- );
648
- setSearchValue(`.search-result-btn-${result.routerId}`);
649
- };
650
- }
637
+
638
+ // Use SearchBox component for rendering results
639
+ const searchContext = {
640
+ RouterInstance: Worker.RouterInstance,
641
+ options: options,
642
+ isRecentHistory: isRecentHistory, // Flag for delete button visibility
643
+ onResultClick: () => {
644
+ // Dismiss search box on result click
645
+ if (s(`.${searchBoxHistoryId}`)) {
646
+ Modal.removeModal(searchBoxHistoryId);
647
+ }
648
+ },
649
+ };
650
+
651
+ SearchBox.renderResults(results, `html-${searchBoxHistoryId}`, searchContext);
651
652
  };
652
653
 
653
- const getResultSearchBox = (validatorData) => {
654
- if (!s(`.html-${searchBoxHistoryId}`) || !s(`.html-${searchBoxHistoryId}`).hasChildNodes()) return;
654
+ const getResultSearchBox = async (validatorData) => {
655
+ if (!s(`.html-${searchBoxHistoryId}`)) return;
655
656
  const { model, id } = validatorData;
657
+
656
658
  switch (model) {
657
659
  case 'search-box':
658
660
  {
659
- if (
660
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex] &&
661
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList
662
- )
663
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.remove(
664
- `main-btn-menu-active`,
665
- );
666
661
  currentKeyBoardSearchBoxIndex = 0;
667
- if (
668
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex] &&
669
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList
670
- )
671
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.add(
672
- `main-btn-menu-active`,
673
- );
674
662
  results = [];
675
- const routerInstance = Worker.RouterInstance.Routes();
676
- for (const _routerId of Object.keys(routerInstance)) {
677
- const routerId = _routerId.slice(1);
678
- if (routerId) {
679
- if (
680
- s(`.main-btn-${routerId}`) &&
681
- (routerId.toLowerCase().match(s(`.${id}`).value.toLowerCase()) ||
682
- (Translate.Data[routerId] &&
683
- Object.keys(Translate.Data[routerId]).filter((keyLang) =>
684
- Translate.Data[routerId][keyLang]
685
- .toLowerCase()
686
- .match(s(`.${id}`).value.toLowerCase()),
687
- ).length > 0))
688
- ) {
689
- const fontAwesomeIcon = getAllChildNodes(s(`.main-btn-${routerId}`)).find((e) => {
690
- return (
691
- e.classList &&
692
- Array.from(e.classList).find((e) => e.match('fa-') && !e.match('fa-grip-vertical'))
693
- );
694
- });
695
- const imgElement = getAllChildNodes(s(`.main-btn-${routerId}`)).find((e) => {
696
- return (
697
- e.classList &&
698
- Array.from(e.classList).find((e) =>
699
- options.searchCustomImgClass
700
- ? e.match(options.searchCustomImgClass)
701
- : e.match('img-btn-square-menu'),
702
- )
703
- );
704
- });
705
- if (imgElement || fontAwesomeIcon) {
706
- results.push({
707
- routerId,
708
- fontAwesomeIcon: fontAwesomeIcon,
709
- imgElement,
710
- });
711
- }
663
+
664
+ const query = s(`.${id}`) ? s(`.${id}`).value : '';
665
+ const trimmedQuery = query.trim();
666
+
667
+ // Use SearchBox component for extensible search
668
+ const searchContext = {
669
+ RouterInstance: Worker.RouterInstance,
670
+ options: options,
671
+ minQueryLength: options?.minSearchQueryLength || 1, // Allow single character search by default
672
+ onResultClick: () => {
673
+ // Dismiss search box on result click
674
+ if (s(`.${searchBoxHistoryId}`)) {
675
+ Modal.removeModal(searchBoxHistoryId);
712
676
  }
713
- }
677
+ },
678
+ };
679
+
680
+ // Check minimum query length (default: 1 character)
681
+ const minLength = searchContext.minQueryLength;
682
+ if (trimmedQuery.length >= minLength) {
683
+ results = await SearchBox.search(trimmedQuery, searchContext);
684
+ renderSearchResult(results, false); // Search results - no delete buttons
685
+ } else if (trimmedQuery.length === 0) {
686
+ // Show recent results from persistent history when query is empty
687
+ const recentResults = SearchBox.RecentResults.getAll();
688
+ renderSearchResult(recentResults, true); // Recent history - show delete buttons
689
+ } else {
690
+ // Query is too short - show nothing or a hint
691
+ renderSearchResult([], false);
714
692
  }
715
693
  }
716
694
  break;
@@ -718,8 +696,6 @@ const Modal = {
718
696
  default:
719
697
  break;
720
698
  }
721
- if (s(`.${inputSearchBoxId}`).value.trim()) renderSearchResult(results);
722
- else renderSearchResult(historySearchBox);
723
699
  };
724
700
 
725
701
  const searchBoxCallBack = async (validatorData) => {
@@ -742,28 +718,20 @@ const Modal = {
742
718
  });
743
719
  };
744
720
 
745
- const getDefaultSearchBoxSelector = () => `.search-result-btn-${currentKeyBoardSearchBoxIndex}`;
746
-
747
721
  const updateSearchBoxValue = (selector) => {
748
- if (!selector) selector = getDefaultSearchBoxSelector();
749
- // check exist childNodes
750
- if (!s(selector) || !s(selector).hasChildNodes()) return;
751
-
752
- if (s(selector).childNodes) {
753
- if (
754
- s(selector).childNodes[s(selector).childNodes.length - 1] &&
755
- s(selector).childNodes[s(selector).childNodes.length - 1].data &&
756
- s(selector).childNodes[s(selector).childNodes.length - 1].data.trim()
757
- ) {
758
- s(`.${inputSearchBoxId}`).value =
759
- s(selector).childNodes[s(selector).childNodes.length - 1].data.trim();
760
- } else if (
761
- s(selector).childNodes[s(selector).childNodes.length - 2] &&
762
- s(selector).childNodes[s(selector).childNodes.length - 2].outerText &&
763
- s(selector).childNodes[s(selector).childNodes.length - 2].outerText.trim()
764
- ) {
765
- s(`.${inputSearchBoxId}`).value =
766
- s(selector).childNodes[s(selector).childNodes.length - 2].outerText.trim();
722
+ if (!selector) {
723
+ // Get the currently active search result item
724
+ const activeItem = s(`.html-${searchBoxHistoryId} .search-result-item.active-search-result`);
725
+ if (activeItem) {
726
+ const titleEl = activeItem.querySelector('.search-result-title');
727
+ if (titleEl && titleEl.textContent) {
728
+ s(`.${inputSearchBoxId}`).value = titleEl.textContent.trim();
729
+ }
730
+ }
731
+ } else if (s(selector)) {
732
+ const titleEl = s(selector).querySelector('.search-result-title');
733
+ if (titleEl && titleEl.textContent) {
734
+ s(`.${inputSearchBoxId}`).value = titleEl.textContent.trim();
767
735
  }
768
736
  }
769
737
  checkHistoryBoxTitleStatus();
@@ -771,17 +739,32 @@ const Modal = {
771
739
  };
772
740
 
773
741
  const setSearchValue = (selector) => {
774
- if (!selector) selector = getDefaultSearchBoxSelector();
775
-
776
- // check exist childNodes
777
- if (!s(selector) || !s(selector).hasChildNodes()) return;
778
-
779
- historySearchBox = historySearchBox.filter(
780
- (h) => h.routerId !== results[currentKeyBoardSearchBoxIndex].routerId,
781
- );
782
- historySearchBox.unshift(results[currentKeyBoardSearchBoxIndex]);
783
- updateSearchBoxValue(selector);
784
- s(`.main-btn-${results[currentKeyBoardSearchBoxIndex].routerId}`).click();
742
+ // Get all search result items
743
+ const allItems = sa(`.html-${searchBoxHistoryId} .search-result-item`);
744
+ if (!allItems || allItems.length === 0) return;
745
+
746
+ const activeItem = allItems[currentKeyBoardSearchBoxIndex];
747
+ if (!activeItem) return;
748
+
749
+ const resultId = activeItem.getAttribute('data-result-id');
750
+ const resultType = activeItem.getAttribute('data-result-type');
751
+
752
+ if (resultType === 'route' && results[currentKeyBoardSearchBoxIndex]) {
753
+ const result = results[currentKeyBoardSearchBoxIndex];
754
+ // Track in persistent history
755
+ SearchBox.RecentResults.add(result);
756
+ updateSearchBoxValue();
757
+ if (s(`.main-btn-${resultId}`)) {
758
+ s(`.main-btn-${resultId}`).click();
759
+ }
760
+ } else {
761
+ // Track custom provider result in persistent history
762
+ if (results[currentKeyBoardSearchBoxIndex]) {
763
+ SearchBox.RecentResults.add(results[currentKeyBoardSearchBoxIndex]);
764
+ }
765
+ // Trigger click on custom result
766
+ activeItem.click();
767
+ }
785
768
  Modal.removeModal(searchBoxHistoryId);
786
769
  };
787
770
  let boxHistoryDelayRender = 0;
@@ -799,21 +782,28 @@ const Modal = {
799
782
  barConfig.buttons.restore.disabled = true;
800
783
  barConfig.buttons.menu.disabled = true;
801
784
  barConfig.buttons.close.disabled = false;
785
+
802
786
  await Modal.Render({
803
787
  id: searchBoxHistoryId,
804
788
  barConfig,
805
- title: html`<div class="search-box-recent-title">
806
- ${renderViewTitle({
807
- icon: html`<i class="fas fa-history mini-title"></i>`,
808
- text: Translate.Render('recent'),
809
- })}
789
+ title: html`<div
790
+ style="display: flex; align-items: center; justify-content: space-between; width: 100%;"
791
+ >
792
+ <div style="display: flex; align-items: center; gap: 8px;">
793
+ <div class="search-box-recent-title">
794
+ ${renderViewTitle({
795
+ icon: html`<i class="fas fa-history mini-title"></i>`,
796
+ text: Translate.Render('recent'),
797
+ })}
798
+ </div>
799
+ <div class="search-box-result-title hide">
800
+ ${renderViewTitle({
801
+ icon: html`<i class="far fa-list-alt mini-title"></i>`,
802
+ text: Translate.Render('results'),
803
+ })}
804
+ </div>
810
805
  </div>
811
- <div class="search-box-result-title hide">
812
- ${renderViewTitle({
813
- icon: html`<i class="far fa-list-alt mini-title"></i>`,
814
- text: Translate.Render('results'),
815
- })}
816
- </div>`,
806
+ </div>`,
817
807
  html: () => html``,
818
808
  titleClass: 'mini-title',
819
809
  style: {
@@ -825,6 +815,7 @@ const Modal = {
825
815
  : '300px !important',
826
816
  'z-index': 7,
827
817
  },
818
+ class: 'search-history-modal',
828
819
  dragDisabled: true,
829
820
  maximize: true,
830
821
  barMode: options.barMode,
@@ -842,6 +833,63 @@ const Modal = {
842
833
 
843
834
  Modal.MoveTitleToBar(id);
844
835
 
836
+ // Add styles for inline button layout in the search history modal bar
837
+ const styleId = 'search-history-modal-bar-styles';
838
+ if (!s(`#${styleId}`)) {
839
+ const styleTag = document.createElement('style');
840
+ styleTag.id = styleId;
841
+ styleTag.textContent = `
842
+ .search-history-modal .btn-bar-modal-container .bar-default-modal {
843
+ display: flex;
844
+ flex-direction: row-reverse;
845
+ align-items: center;
846
+ }
847
+ .search-history-modal .btn-bar-modal-container .btn-modal-default {
848
+ display: inline-flex;
849
+ align-items: center;
850
+ justify-content: center;
851
+ float: none;
852
+ }
853
+ .search-history-modal .html-${searchBoxHistoryId} {
854
+ overflow-x: hidden;
855
+ box-sizing: border-box;
856
+ }
857
+ .search-history-modal .html-${searchBoxHistoryId} .search-result-item,
858
+ .search-history-modal .html-${searchBoxHistoryId} .search-result-history-item {
859
+ box-sizing: border-box;
860
+ max-width: 100%;
861
+ }
862
+ `;
863
+ document.head.appendChild(styleTag);
864
+ }
865
+
866
+ // Add clear all button to the bar area, before the close button
867
+ const clearAllBtnHtml = await BtnIcon.Render({
868
+ class: `btn-search-history-clear-all btn-modal-default btn-modal-default-${searchBoxHistoryId}`,
869
+ label: html`<i class="fas fa-trash-alt"></i>`,
870
+ attrs: `title="Clear all recent items"`,
871
+ style: 'padding: 4px 8px; font-size: 12px; display: none;',
872
+ });
873
+
874
+ // Insert before close button in the bar (with flex row-reverse, inserting after close button places our button to its left visually)
875
+ const closeBtn = s(`.btn-close-${searchBoxHistoryId}`);
876
+ if (closeBtn) {
877
+ closeBtn.insertAdjacentHTML('afterend', clearAllBtnHtml);
878
+ }
879
+
880
+ // Add click handler for clear all history button
881
+ const clearAllBtn = s(`.btn-search-history-clear-all`);
882
+ if (clearAllBtn) {
883
+ clearAllBtn.onclick = (e) => {
884
+ e.preventDefault();
885
+ e.stopPropagation();
886
+ // Clear all history from persistent storage
887
+ SearchBox.RecentResults.clear();
888
+ // Re-render to show empty history (with isRecentHistory true to hide button)
889
+ renderSearchResult([], true);
890
+ };
891
+ }
892
+
845
893
  prepend(`.btn-bar-modal-container-${id}`, html`<div class="hide">${inputInfoNode.outerHTML}</div>`);
846
894
  }
847
895
  };
@@ -879,27 +927,29 @@ const Modal = {
879
927
  keys: ['ArrowUp'],
880
928
  eventCallBack: () => {
881
929
  if (s(`.${id}`)) {
882
- if (
883
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex] &&
884
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex - 1]
885
- ) {
886
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.remove(
887
- `main-btn-menu-active`,
888
- );
930
+ const allItems = sa(`.html-${searchBoxHistoryId} .search-result-item`);
931
+ if (!allItems || allItems.length === 0) return;
932
+
933
+ // Remove active class from current
934
+ if (allItems[currentKeyBoardSearchBoxIndex]) {
935
+ allItems[currentKeyBoardSearchBoxIndex].classList.remove('active-search-result');
936
+ }
937
+
938
+ // Navigate up
939
+ if (currentKeyBoardSearchBoxIndex > 0) {
889
940
  currentKeyBoardSearchBoxIndex--;
890
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.add(
891
- `main-btn-menu-active`,
892
- );
893
941
  } else {
894
- if (s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex])
895
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.remove(
896
- `main-btn-menu-active`,
897
- );
898
- currentKeyBoardSearchBoxIndex = s(`.html-${searchBoxHistoryId}`).childNodes.length - 1;
899
- if (s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex])
900
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.add(
901
- `main-btn-menu-active`,
902
- );
942
+ currentKeyBoardSearchBoxIndex = allItems.length - 1;
943
+ }
944
+
945
+ // Add active class to new and ensure visibility
946
+ if (allItems[currentKeyBoardSearchBoxIndex]) {
947
+ allItems[currentKeyBoardSearchBoxIndex].classList.add('active-search-result');
948
+ // Use optimized scroll method to ensure item is always visible
949
+ const container = s(`.html-${searchBoxHistoryId}`);
950
+ if (container) {
951
+ SearchBox.scrollIntoViewIfNeeded(allItems[currentKeyBoardSearchBoxIndex], container);
952
+ }
903
953
  }
904
954
  updateSearchBoxValue();
905
955
  }
@@ -912,27 +962,29 @@ const Modal = {
912
962
  keys: ['ArrowDown'],
913
963
  eventCallBack: () => {
914
964
  if (s(`.${id}`)) {
915
- if (
916
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex] &&
917
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex + 1]
918
- ) {
919
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.remove(
920
- `main-btn-menu-active`,
921
- );
965
+ const allItems = sa(`.html-${searchBoxHistoryId} .search-result-item`);
966
+ if (!allItems || allItems.length === 0) return;
967
+
968
+ // Remove active class from current
969
+ if (allItems[currentKeyBoardSearchBoxIndex]) {
970
+ allItems[currentKeyBoardSearchBoxIndex].classList.remove('active-search-result');
971
+ }
972
+
973
+ // Navigate down
974
+ if (currentKeyBoardSearchBoxIndex < allItems.length - 1) {
922
975
  currentKeyBoardSearchBoxIndex++;
923
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.add(
924
- `main-btn-menu-active`,
925
- );
926
976
  } else {
927
- if (s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex])
928
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.remove(
929
- `main-btn-menu-active`,
930
- );
931
977
  currentKeyBoardSearchBoxIndex = 0;
932
- if (s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex])
933
- s(`.html-${searchBoxHistoryId}`).childNodes[currentKeyBoardSearchBoxIndex].classList.add(
934
- `main-btn-menu-active`,
935
- );
978
+ }
979
+
980
+ // Add active class to new and ensure visibility
981
+ if (allItems[currentKeyBoardSearchBoxIndex]) {
982
+ allItems[currentKeyBoardSearchBoxIndex].classList.add('active-search-result');
983
+ // Use optimized scroll method to ensure item is always visible
984
+ const container = s(`.html-${searchBoxHistoryId}`);
985
+ if (container) {
986
+ SearchBox.scrollIntoViewIfNeeded(allItems[currentKeyBoardSearchBoxIndex], container);
987
+ }
936
988
  }
937
989
  updateSearchBoxValue();
938
990
  }
@@ -2054,6 +2106,34 @@ const Modal = {
2054
2106
  },
2055
2107
  mobileModal: () => windowGetW() < 600 || windowGetH() < 600,
2056
2108
  writeHTML: ({ idModal, html }) => htmls(`.html-${idModal}`, html),
2109
+ updateModal: async function ({ idModal, html, title }) {
2110
+ if (!this.Data[idModal] || !s(`.${idModal}`)) {
2111
+ console.warn(`Modal ${idModal} not found for update`);
2112
+ return false;
2113
+ }
2114
+
2115
+ // Update modal content
2116
+ if (html) {
2117
+ this.writeHTML({ idModal, html });
2118
+ }
2119
+
2120
+ // Update modal title if provided
2121
+ if (title) {
2122
+ const titleElement = s(`.${idModal} .modal-title`);
2123
+ if (titleElement) {
2124
+ titleElement.innerHTML = title;
2125
+ }
2126
+ }
2127
+
2128
+ // Trigger reload listeners
2129
+ if (this.Data[idModal].onReloadModalListener) {
2130
+ for (const event of Object.keys(this.Data[idModal].onReloadModalListener)) {
2131
+ await this.Data[idModal].onReloadModalListener[event]();
2132
+ }
2133
+ }
2134
+
2135
+ return true;
2136
+ },
2057
2137
  viewModalOpen: function () {
2058
2138
  return Object.keys(this.Data).find((idModal) => s(`.${idModal}`) && this.Data[idModal].options.mode === 'view');
2059
2139
  },