web-mojo 2.4.6 → 2.4.9

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 (118) hide show
  1. package/dist/admin-models.cjs.js +1 -1
  2. package/dist/admin-models.cjs.js.map +1 -1
  3. package/dist/admin-models.es.js +1 -1
  4. package/dist/admin-models.es.js.map +1 -1
  5. package/dist/admin.cjs.js +1 -1
  6. package/dist/admin.cjs.js.map +1 -1
  7. package/dist/admin.es.js +1 -1
  8. package/dist/admin.es.js.map +1 -1
  9. package/dist/auth.cjs.js +1 -1
  10. package/dist/auth.es.js +1 -1
  11. package/dist/charts.cjs.js +1 -1
  12. package/dist/charts.es.js +1 -1
  13. package/dist/chunks/{ChatView-D2WOSxPu.js → ChatView-C_2dRLAY.js} +2 -2
  14. package/dist/chunks/{ChatView-D2WOSxPu.js.map → ChatView-C_2dRLAY.js.map} +1 -1
  15. package/dist/chunks/{ChatView-kWguc444.js → ChatView-DVb2Ufq_.js} +2 -2
  16. package/dist/chunks/{ChatView-kWguc444.js.map → ChatView-DVb2Ufq_.js.map} +1 -1
  17. package/dist/chunks/{Collection-DNmr743A.js → Collection-Bq_EMAqK.js} +2 -2
  18. package/dist/chunks/{Collection-DNmr743A.js.map → Collection-Bq_EMAqK.js.map} +1 -1
  19. package/dist/chunks/{Collection-C0pHSKDH.js → Collection-tgvDhyz_.js} +2 -2
  20. package/dist/chunks/{Collection-C0pHSKDH.js.map → Collection-tgvDhyz_.js.map} +1 -1
  21. package/dist/chunks/{ContextMenu-T3yDdsIe.js → ContextMenu-BL_sdgIw.js} +2 -2
  22. package/dist/chunks/{ContextMenu-T3yDdsIe.js.map → ContextMenu-BL_sdgIw.js.map} +1 -1
  23. package/dist/chunks/{ContextMenu-BeveGkJr.js → ContextMenu-fIy3upEy.js} +2 -2
  24. package/dist/chunks/{ContextMenu-BeveGkJr.js.map → ContextMenu-fIy3upEy.js.map} +1 -1
  25. package/dist/chunks/{DataView-VZIXSsZa.js → DataView-DRQ2yFRz.js} +2 -2
  26. package/dist/chunks/{DataView-VZIXSsZa.js.map → DataView-DRQ2yFRz.js.map} +1 -1
  27. package/dist/chunks/{DataView-z2rxXk4L.js → DataView-DW_ufpQo.js} +2 -2
  28. package/dist/chunks/{DataView-z2rxXk4L.js.map → DataView-DW_ufpQo.js.map} +1 -1
  29. package/dist/chunks/{FormView-COIPtbrd.js → FormView-eWUWw8oL.js} +2 -2
  30. package/dist/chunks/{FormView-COIPtbrd.js.map → FormView-eWUWw8oL.js.map} +1 -1
  31. package/dist/chunks/{FormView-DUXQruUZ.js → FormView-xl_w506t.js} +2 -2
  32. package/dist/chunks/{FormView-DUXQruUZ.js.map → FormView-xl_w506t.js.map} +1 -1
  33. package/dist/chunks/{ListView-BjsNHuZ1.js → ListView-Bj4ONQFG.js} +2 -2
  34. package/dist/chunks/{ListView-BjsNHuZ1.js.map → ListView-Bj4ONQFG.js.map} +1 -1
  35. package/dist/chunks/{ListView-BxcxIwC3.js → ListView-jyIVhCN3.js} +2 -2
  36. package/dist/chunks/{ListView-BxcxIwC3.js.map → ListView-jyIVhCN3.js.map} +1 -1
  37. package/dist/chunks/MetricsCountryMapView-D_666qlW.js +2 -0
  38. package/dist/chunks/MetricsCountryMapView-D_666qlW.js.map +1 -0
  39. package/dist/chunks/MetricsCountryMapView-U3xr0QXD.js +2 -0
  40. package/dist/chunks/MetricsCountryMapView-U3xr0QXD.js.map +1 -0
  41. package/dist/chunks/Modal-CCVJefEX.js +3 -0
  42. package/dist/chunks/{Modal-Bm1OQ8Ou.js.map → Modal-CCVJefEX.js.map} +1 -1
  43. package/dist/chunks/Modal-Uwt-GBgJ.js +3 -0
  44. package/dist/chunks/{Modal-KnJhNZ1E.js.map → Modal-Uwt-GBgJ.js.map} +1 -1
  45. package/dist/chunks/Passkeys-DScGUF6p.js +2 -0
  46. package/dist/chunks/{Passkeys-BlHx11-5.js.map → Passkeys-DScGUF6p.js.map} +1 -1
  47. package/dist/chunks/{Passkeys-B1-Z4-16.js → Passkeys-c4amXO03.js} +2 -2
  48. package/dist/chunks/{Passkeys-B1-Z4-16.js.map → Passkeys-c4amXO03.js.map} +1 -1
  49. package/dist/chunks/{TokenManager-CiNtJclo.js → TokenManager-BRx2U5w4.js} +2 -2
  50. package/dist/chunks/{TokenManager-CiNtJclo.js.map → TokenManager-BRx2U5w4.js.map} +1 -1
  51. package/dist/chunks/{TokenManager-6atX9uKB.js → TokenManager-DZP2Lcnz.js} +2 -2
  52. package/dist/chunks/{TokenManager-6atX9uKB.js.map → TokenManager-DZP2Lcnz.js.map} +1 -1
  53. package/dist/chunks/{User-9qvKV7G6.js → User-DR-2VJcv.js} +2 -2
  54. package/dist/chunks/{User-9qvKV7G6.js.map → User-DR-2VJcv.js.map} +1 -1
  55. package/dist/chunks/{User-BM76Ughk.js → User-e9aOjdHv.js} +2 -2
  56. package/dist/chunks/{User-BM76Ughk.js.map → User-e9aOjdHv.js.map} +1 -1
  57. package/dist/chunks/{UserProfileView-DDflzpTa.js → UserProfileView-CCNcNT-M.js} +2 -2
  58. package/dist/chunks/{UserProfileView-DDflzpTa.js.map → UserProfileView-CCNcNT-M.js.map} +1 -1
  59. package/dist/chunks/{UserProfileView-cUF8ED9n.js → UserProfileView-PmerV2tC.js} +2 -2
  60. package/dist/chunks/{UserProfileView-cUF8ED9n.js.map → UserProfileView-PmerV2tC.js.map} +1 -1
  61. package/dist/chunks/{View-CPWwS19u.js → View-CLWMWXbO.js} +2 -2
  62. package/dist/chunks/{View-CPWwS19u.js.map → View-CLWMWXbO.js.map} +1 -1
  63. package/dist/chunks/{View-BxlKR1IW.js → View-DADtcBcb.js} +2 -2
  64. package/dist/chunks/{View-BxlKR1IW.js.map → View-DADtcBcb.js.map} +1 -1
  65. package/dist/chunks/{WebApp-BdxhRnnT.js → WebApp-BBrvjnnH.js} +2 -2
  66. package/dist/chunks/{WebApp-BdxhRnnT.js.map → WebApp-BBrvjnnH.js.map} +1 -1
  67. package/dist/chunks/{WebApp-Bo_egO0c.js → WebApp-wMWf0qkm.js} +2 -2
  68. package/dist/chunks/{WebApp-Bo_egO0c.js.map → WebApp-wMWf0qkm.js.map} +1 -1
  69. package/dist/chunks/exportChart-BszC9qir.js +2 -0
  70. package/dist/chunks/{exportChart-BTrEOM9j.js.map → exportChart-BszC9qir.js.map} +1 -1
  71. package/dist/chunks/exportChart-CnAGJjyS.js +2 -0
  72. package/dist/chunks/{exportChart-BfzZUb1j.js.map → exportChart-CnAGJjyS.js.map} +1 -1
  73. package/dist/chunks/{index-DsID1QpB.js → index-B312g4f-.js} +2 -2
  74. package/dist/chunks/{index-DsID1QpB.js.map → index-B312g4f-.js.map} +1 -1
  75. package/dist/chunks/{index-Cxffar1o.js → index-D7ITRVln.js} +2 -2
  76. package/dist/chunks/{index-Cxffar1o.js.map → index-D7ITRVln.js.map} +1 -1
  77. package/dist/chunks/{version-BxLEknar.js → version-Cz_nlYsB.js} +2 -2
  78. package/dist/chunks/{version-BxLEknar.js.map → version-Cz_nlYsB.js.map} +1 -1
  79. package/dist/chunks/{version-BYy2UAT0.js → version-D2vRYNuT.js} +2 -2
  80. package/dist/chunks/{version-BYy2UAT0.js.map → version-D2vRYNuT.js.map} +1 -1
  81. package/dist/docit.cjs.js +1 -1
  82. package/dist/docit.es.js +1 -1
  83. package/dist/index.cjs.js +1 -1
  84. package/dist/index.es.js +1 -1
  85. package/dist/lightbox.cjs.js +1 -1
  86. package/dist/lightbox.es.js +1 -1
  87. package/dist/map.cjs.js +1 -1
  88. package/dist/map.es.js +1 -1
  89. package/dist/timeline.cjs.js +1 -1
  90. package/dist/timeline.es.js +1 -1
  91. package/dist/user-profile.cjs.js +1 -1
  92. package/dist/user-profile.es.js +1 -1
  93. package/package.json +1 -1
  94. package/dist/chunks/AssistantPanelView-CkQEcaFk.js +0 -2
  95. package/dist/chunks/AssistantPanelView-CkQEcaFk.js.map +0 -1
  96. package/dist/chunks/AssistantPanelView-D1wEbgtM.js +0 -2
  97. package/dist/chunks/AssistantPanelView-D1wEbgtM.js.map +0 -1
  98. package/dist/chunks/MetricsCountryMapView-Bp3qoVHp.js +0 -2
  99. package/dist/chunks/MetricsCountryMapView-Bp3qoVHp.js.map +0 -1
  100. package/dist/chunks/MetricsCountryMapView-CWjIEBJB.js +0 -2
  101. package/dist/chunks/MetricsCountryMapView-CWjIEBJB.js.map +0 -1
  102. package/dist/chunks/Modal-Bm1OQ8Ou.js +0 -3
  103. package/dist/chunks/Modal-KnJhNZ1E.js +0 -3
  104. package/dist/chunks/Passkeys-BlHx11-5.js +0 -2
  105. package/dist/chunks/TicketPanelView-DVePzWyJ.js +0 -2
  106. package/dist/chunks/TicketPanelView-DVePzWyJ.js.map +0 -1
  107. package/dist/chunks/TicketPanelView-TYh5iZiR.js +0 -2
  108. package/dist/chunks/TicketPanelView-TYh5iZiR.js.map +0 -1
  109. package/dist/chunks/admin-CHPo4iDn.js +0 -2
  110. package/dist/chunks/admin-CHPo4iDn.js.map +0 -1
  111. package/dist/chunks/admin-CucHFXfD.js +0 -2
  112. package/dist/chunks/admin-CucHFXfD.js.map +0 -1
  113. package/dist/chunks/admin-models-CkHjtMHf.js +0 -2
  114. package/dist/chunks/admin-models-CkHjtMHf.js.map +0 -1
  115. package/dist/chunks/admin-models-DLtFboxy.js +0 -2
  116. package/dist/chunks/admin-models-DLtFboxy.js.map +0 -1
  117. package/dist/chunks/exportChart-BTrEOM9j.js +0 -2
  118. package/dist/chunks/exportChart-BfzZUb1j.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"TokenManager-CiNtJclo.js","sources":["../../src/core/views/navigation/SimpleSearchView.js","../../src/core/views/navigation/GroupSelectorButton.js","../../src/core/views/navigation/TopNav.js","../../src/core/services/TokenManager.js"],"sourcesContent":["/**\n * SimpleSearchView - Generic searchable list component\n * Displays a searchable, scrollable list of items from any Collection\n * Emits item:selected event when user selects an item\n */\n\nimport { View } from '@core/View.js';\n\n/**\n * ResultsView - Internal child view for rendering search results\n * This is only used within SimpleSearchView and handles the scrollable results area\n */\nclass ResultsView extends View {\n constructor(options = {}) {\n super({\n className: 'search-results-view flex-grow-1 overflow-auto d-flex flex-column',\n template: `\n <div id=\"results-container\" class=\"flex-grow-1 overflow-auto\">\n {{#data.loading}}\n <div class=\"text-center p-4\">\n <div class=\"spinner-border spinner-border-sm text-muted\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <div class=\"mt-2 small text-muted\">{{data.loadingText}}</div>\n </div>\n {{/data.loading}}\n\n {{^data.loading}}\n {{#data.items}}\n <div class=\"simple-search-item position-relative\"\n data-action=\"select-item\"\n data-item-index=\"{{index}}\">\n {{{itemContent}}}\n\n </div>\n {{/data.items}}\n\n {{#data.showNoResults}}\n <div class=\"text-center p-4\">\n <i class=\"bi bi-search text-muted mb-2\" style=\"font-size: 1.5rem;\"></i>\n <div class=\"text-muted small\">{{data.noResultsText}}</div>\n <button type=\"button\"\n class=\"btn btn-link btn-sm mt-2 p-0\"\n data-action=\"clear-search\">\n Clear search\n </button>\n </div>\n {{/data.showNoResults}}\n\n {{#data.showEmpty}}\n <div class=\"text-center p-4\">\n <i class=\"{{data.emptyIcon}} text-muted mb-2\" style=\"font-size: 2rem;\"></i>\n <div class=\"text-muted small mb-2\">{{data.emptyText}}</div>\n {{#data.emptySubtext}}\n <div class=\"text-muted\" style=\"font-size: 0.75rem;\">\n {{data.emptySubtext}}\n </div>\n {{/data.emptySubtext}}\n </div>\n {{/data.showEmpty}}\n {{/data.loading}}\n </div>\n\n {{#data.showResultsCount}}\n <div class=\"border-top bg-body-tertiary p-2 text-center\">\n <small class=\"text-muted\">\n {{data.filteredCount}} of {{data.totalCount}}\n </small>\n </div>\n {{/data.showResultsCount}}\n `,\n ...options\n });\n\n this.parentView = options.parentView;\n }\n\n async handleActionSelectItem(event, element) {\n event.preventDefault();\n const itemIndex = parseInt(element.getAttribute('data-item-index'));\n if (this.parentView) {\n this.parentView.handleItemSelection(itemIndex);\n }\n }\n\n async handleActionClearSearch(event, _element) {\n event.preventDefault();\n\n if (this.parentView) {\n this.parentView.clearSearch();\n }\n }\n\n async onAfterRender() {\n if (this.parentView && this.parentView.maxHeight) {\n const container = this.element.querySelector('#results-container');\n if (container) {\n container.style.maxHeight = `${this.parentView.maxHeight}px`;\n }\n }\n }\n}\n\nclass SimpleSearchView extends View {\n constructor(options = {}) {\n super({\n className: 'simple-search-view d-flex flex-column',\n template: `\n <div class=\"p-3 border-bottom bg-body-tertiary\">\n {{#data.headerText}}\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <h6 class=\"text-muted fw-semibold mb-0\">\n {{#data.headerIcon}}<i class=\"{{data.headerIcon}} me-2\"></i>{{/data.headerIcon}}\n {{{data.headerText}}}\n </h6>\n {{#data.showExitButton}}\n <button class=\"btn btn-link p-0 text-muted simple-search-exit-btn\"\n type=\"button\"\n data-action=\"exit-view\"\n title=\"Exit\"\n aria-label=\"Exit view\">\n <i class=\"bi bi-x-lg\" aria-hidden=\"true\"></i>\n </button>\n {{/data.showExitButton}}\n </div>\n {{/data.headerText}}\n <div class=\"position-relative\">\n <input type=\"text\"\n class=\"form-control form-control-sm pe-5\"\n placeholder=\"{{data.searchPlaceholder}}\"\n value=\"{{data.searchValue}}\"\n data-filter=\"live-search\"\n data-filter-debounce=\"{{data.debounceMs}}\"\n data-change-action=\"search-items\">\n <button class=\"btn btn-link p-0 position-absolute top-50 end-0 translate-middle-y me-2 text-muted simple-search-clear-btn\"\n type=\"button\"\n data-action=\"clear-search\"\n title=\"Clear search\"\n aria-label=\"Clear search\">\n <i class=\"bi bi-x-circle-fill\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n <div data-container=\"results\"></div>\n\n {{#data.showFooter}}\n <div class=\"p-3 border-top bg-body-tertiary\">\n <small class=\"text-muted\">\n <i class=\"{{data.footerIcon}} me-1\"></i>\n {{{data.footerContent}}}\n </small>\n </div>\n {{/data.showFooter}}\n `,\n ...options\n });\n\n // Configuration options\n this.Collection = options.Collection;\n this.collection = options.collection;\n this.itemTemplate = options.itemTemplate || this.getDefaultItemTemplate();\n this.searchFields = options.searchFields || ['name'];\n this.collectionParams = { size: 25, ...options.collectionParams };\n\n // UI text configuration\n if (options.headerText === undefined) this.headerText = 'Select Item';\n this.headerText = options.headerText;\n this.headerIcon = options.headerIcon || 'bi bi-list';\n this.searchPlaceholder = options.searchPlaceholder || 'Search...';\n this.loadingText = options.loadingText || 'Loading items...';\n this.noResultsText = options.noResultsText || 'No items match your search';\n this.emptyText = options.emptyText || 'No items available';\n this.emptySubtext = options.emptySubtext || null;\n this.emptyIcon = options.emptyIcon || 'bi bi-inbox';\n this.footerContent = options.footerContent || null;\n this.footerIcon = options.footerIcon || 'bi bi-info-circle';\n this.showExitButton = options.showExitButton || false;\n\n // State\n this.searchValue = '';\n this.filteredItems = [];\n this.loading = false;\n this.hasSearched = false;\n this.searchTimer = null;\n this.debounceMs = options.debounceMs || 300;\n if (options.maxHeight) {\n this.maxHeight = options.maxHeight;\n } else {\n this.addClass('h-100');\n }\n\n // Create results child view\n this.resultsView = new ResultsView({\n parentView: this\n });\n\n if (!this.collection && this.Collection) {\n this.collection = new this.Collection();\n }\n\n // Add as child view\n this.addChild(this.resultsView);\n\n }\n\n onInit() {\n // Initialize collection if provided\n if (this.collection) {\n this.setupCollection();\n }\n\n // Load items on init if collection is available\n if (this.collection && this.options.autoLoad !== false) {\n this.loadItems();\n }\n }\n\n setupCollection() {\n // Set collection parameters\n Object.assign(this.collection.params, this.collectionParams);\n\n // Listen for collection updates\n this.collection.on('fetch:success', () => {\n this.loading = false;\n this.updateFilteredItems();\n });\n\n this.collection.on('fetch:error', () => {\n this.loading = false;\n });\n }\n\n async loadItems() {\n if (!this.collection) {\n console.warn('SimpleSearchView: No collection provided');\n return;\n }\n\n this.loading = true;\n this.updateResultsView();\n\n try {\n await this.collection.fetch();\n this.updateFilteredItems();\n } catch (error) {\n console.error('Error loading items:', error);\n const app = this.getApp();\n app?.showError?.('Failed to load items. Please try again.');\n } finally {\n this.loading = false;\n this.updateFilteredItems();\n }\n }\n\n updateFilteredItems() {\n if (!this.collection) {\n this.filteredItems = [];\n return;\n }\n\n // Server-side filtering is handled in performSearch()\n // Just use the collection's items directly - they're already filtered by the server\n this.filteredItems = this.collection.toJSON();\n\n this.updateResultsView();\n }\n\n getNestedValue(obj, path) {\n return path.split('.').reduce((current, key) => current?.[key], obj);\n }\n\n async getViewData() {\n return {\n searchValue: this.searchValue,\n showFooter: !!this.footerContent,\n showExitButton: this.showExitButton,\n debounceMs: this.debounceMs,\n\n // UI text\n headerText: this.headerText,\n headerIcon: this.headerIcon,\n searchPlaceholder: this.searchPlaceholder,\n footerContent: this.footerContent,\n footerIcon: this.footerIcon\n };\n }\n\n updateResultsView() {\n if (!this.resultsView) return;\n\n const hasItems = this.collection && this.collection.length() > 0;\n const hasFilteredItems = this.filteredItems.length > 0;\n const hasSearchValue = this.searchValue.length > 0;\n\n // Process items with template\n const processedItems = this.filteredItems.map((item, index) => {\n return {\n ...item,\n index,\n itemContent: this.processItemTemplate(item)\n };\n });\n\n this.resultsView.data = {\n loading: this.loading,\n items: processedItems,\n showEmpty: !this.loading && !hasItems,\n showNoResults: !this.loading && hasItems && !hasFilteredItems && hasSearchValue,\n showResultsCount: !this.loading && hasItems,\n filteredCount: this.filteredItems.length,\n totalCount: this.collection?.restEnabled\n ? (this.collection?.meta?.count || 0)\n : (this.collection?.length() || 0),\n\n // UI text\n loadingText: this.loadingText,\n noResultsText: this.noResultsText,\n emptyText: this.emptyText,\n emptySubtext: this.emptySubtext,\n emptyIcon: this.emptyIcon\n };\n\n this.resultsView.render();\n }\n\n processItemTemplate(item) {\n let template = this.itemTemplate;\n\n // Simple template replacement for item properties\n template = template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, prop) => {\n return this.getNestedValue(item, prop) || '';\n });\n\n return template;\n }\n\n getDefaultItemTemplate() {\n return `\n <div class=\"p-3 border-bottom\">\n <div class=\"fw-semibold text-dark\">{{name}}</div>\n <small class=\"text-muted\">{{id}}</small>\n </div>\n `;\n }\n\n async onPassThruActionSearchItems(event, element) {\n const searchValue = element.value || '';\n\n console.log(\"search change...\");\n this.searchValue = searchValue;\n this.hasSearched = true;\n\n // Clear existing timer\n if (this.searchTimer) {\n clearTimeout(this.searchTimer);\n }\n\n // Debounce the search\n this.performSearch();\n }\n\n async performSearch() {\n const searchParams = { ...this.collectionParams };\n if (this.searchValue && this.searchValue.length > 1) {\n searchParams.search = this.searchValue.trim();\n }\n this.collection.setParams(searchParams, true);\n }\n\n handleItemSelection(itemIndex) {\n if (isNaN(itemIndex) || itemIndex < 0 || itemIndex >= this.filteredItems.length) {\n console.error('Invalid item index:', itemIndex);\n return;\n }\n\n const item = this.filteredItems[itemIndex];\n let model = this.collection ? this.collection.get(item.id) : null;\n if (!model) {\n model = new this.collection.ModelClass({ id: item.id });\n const app = this.getApp();\n app.showLoading();\n model.fetch().then(() => {\n app.hideLoading();\n this.emit('item:selected', {\n item: item,\n model: model,\n index: itemIndex\n });\n });\n return;\n } else {\n this.emit('item:selected', {\n item: item,\n model: model,\n index: itemIndex\n });\n }\n\n }\n\n /**\n * Set the collection for this search view\n */\n setCollection(collection) {\n this.collection = collection;\n this.setupCollection();\n return this;\n }\n\n /**\n * Set the item template\n */\n setItemTemplate(template) {\n this.itemTemplate = template;\n this.updateResultsView();\n return this;\n }\n\n /**\n * Set search fields\n */\n setSearchFields(fields) {\n this.searchFields = Array.isArray(fields) ? fields : [fields];\n return this;\n }\n\n /**\n * Refresh items list\n */\n async refresh() {\n await this.loadItems();\n }\n\n /**\n * Focus the search input\n */\n focusSearch() {\n const searchInput = this.element?.querySelector('input[data-action=\"search-items\"]');\n if (searchInput) {\n searchInput.focus();\n }\n }\n\n /**\n * Handle exit button click - emits event instead of closing\n */\n async handleActionExitView(event, element) {\n this.emit('exit', { view: this });\n }\n\n /**\n * Clear search and reset\n */\n async handleActionClearSearch(event, element) {\n this.clearSearch();\n }\n\n clearSearch() {\n this.searchValue = '';\n this.hasSearched = false;\n const searchInput = this.element?.querySelector('input[data-change-action=\"search-items\"]');\n if (searchInput) {\n searchInput.value = '';\n searchInput.focus();\n }\n this.performSearch();\n }\n\n /**\n * Get the number of available items\n */\n getItemCount() {\n return this.collection ? this.collection.length() : 0;\n }\n\n /**\n * Get the number of filtered items\n */\n getFilteredItemCount() {\n return this.filteredItems.length;\n }\n\n /**\n * Check if items are loaded\n */\n hasItems() {\n return this.getItemCount() > 0;\n }\n\n /**\n * Get current search value\n */\n getSearchValue() {\n return this.searchValue;\n }\n\n /**\n * Set search value programmatically\n */\n setSearchValue(value) {\n this.searchValue = value || '';\n this.hasSearched = !!this.searchValue;\n\n const searchInput = this.element?.querySelector('input[data-action=\"search-items\"]');\n if (searchInput) {\n searchInput.value = this.searchValue;\n }\n\n this.performSearch();\n return this;\n }\n\n async onAfterRender() {\n await super.onAfterRender();\n\n // Mount results view to container if not already mounted\n if (this.resultsView && !this.resultsView.isMounted()) {\n const container = this.element?.querySelector('[data-container=\"results\"]');\n if (container) {\n await this.resultsView.render(true, container);\n }\n }\n // Update results view after main render\n this.updateResultsView();\n }\n\n /**\n * Cleanup on destroy\n */\n async onBeforeDestroy() {\n if (this.searchTimer) {\n clearTimeout(this.searchTimer);\n }\n\n if (this.collection) {\n this.collection.off('update');\n }\n\n await super.onBeforeDestroy();\n }\n}\n\nexport default SimpleSearchView;\n","/**\n * GroupSelectorButton - Button that shows current group and opens search dialog\n * Displays active group name in topnav, opens Dialog with SimpleSearchView for selection\n */\n\nimport View from '@core/View.js';\nimport ModalView from '@core/views/feedback/ModalView.js';\nimport SimpleSearchView from '@core/views/navigation/SimpleSearchView.js';\nimport { GroupList } from '@core/models/Group.js';\n\nclass GroupSelectorButton extends View {\n constructor(options = {}) {\n super({\n tagName: 'div',\n className: 'nav-item',\n ...options\n });\n\n const app = this.getApp();\n\n // Auto-detect Collection from app or use GroupList by default\n this.Collection = options.Collection || app?.GroupCollection || GroupList;\n \n // Use existing collection or create new one\n this.collection = options.collection || new this.Collection();\n \n // Auto-detect current group from app\n this.currentGroup = options.currentGroup !== undefined \n ? options.currentGroup \n : app?.activeGroup;\n \n // UI configuration with defaults\n this.buttonClass = options.buttonClass || 'btn btn-link nav-link';\n this.buttonIcon = options.buttonIcon || 'bi-building';\n this.defaultText = options.defaultText || 'Select Group';\n \n // SimpleSearchView configuration\n this.itemTemplate = options.itemTemplate;\n this.searchFields = options.searchFields || ['name'];\n this.headerText = options.headerText || 'Select Group';\n this.searchPlaceholder = options.searchPlaceholder || 'Search groups...';\n \n // Auto group selection handler\n this.autoSetActiveGroup = options.autoSetActiveGroup !== false;\n this.onGroupSelected = options.onGroupSelected;\n \n // Dialog reference\n this.dialog = null;\n\n // Listen for app-level group changes\n if (app?.events) {\n app.events.on('group:changed', (data) => {\n if (data.group !== this.currentGroup) {\n this.setCurrentGroup(data.group);\n }\n });\n }\n }\n\n async getTemplate() {\n return `\n <button class=\"{{buttonClass}}\" \n data-action=\"show-selector\"\n type=\"button\"\n style=\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\">\n <i class=\"{{buttonIcon}} me-1\"></i>\n <span class=\"group-name\">{{displayName}}</span>\n </button>\n `;\n }\n\n async onBeforeRender() {\n await super.onBeforeRender();\n \n console.log('GroupSelectorButton onBeforeRender - currentGroup:', this.currentGroup?.get?.('name') || this.currentGroup?.name || 'none');\n \n this.buttonClass = this.buttonClass;\n this.buttonIcon = this.buttonIcon;\n this.displayName = this.currentGroup?.get?.('name') || \n this.currentGroup?.name || \n this.defaultText;\n }\n\n /**\n * Show the group selector dialog\n */\n async onActionShowSelector(event) {\n // Create SimpleSearchView instance\n const searchView = new SimpleSearchView({\n Collection: this.Collection,\n collection: this.collection,\n itemTemplate: this.itemTemplate || this.getDefaultItemTemplate(),\n searchFields: this.searchFields,\n headerText: this.headerText,\n searchPlaceholder: this.searchPlaceholder,\n headerIcon: this.buttonIcon,\n showExitButton: false\n });\n\n // Use ModalView directly so we keep an instance handle for hide(),\n // on('hidden'), and destroy() — Modal.show() returns a Promise.\n this.dialog = new ModalView({\n title: this.headerText,\n body: searchView,\n size: 'md',\n scrollable: true,\n noBodyPadding: true,\n buttons: [],\n closeButton: true\n });\n\n // Listen for item selection (note: event is 'item:selected' not 'item-selected')\n searchView.on('item:selected', (data) => {\n this.handleGroupSelection(data.model || data.item);\n if (this.dialog) {\n this.dialog.hide();\n }\n });\n\n // Clean up dialog reference when closed\n this.dialog.on('hidden', () => {\n this.dialog.destroy();\n this.dialog = null;\n });\n\n // Render and show the dialog\n await this.dialog.render(true, document.body);\n this.dialog.show();\n \n return true; // Indicate action was handled\n }\n\n /**\n * Handle group selection\n */\n handleGroupSelection(group) {\n this.currentGroup = group;\n \n // Update button text\n this.displayName = group?.get?.('name') || group?.name || this.defaultText;\n this.render();\n\n const app = this.getApp();\n\n // Automatically set active group on app if enabled\n if (this.autoSetActiveGroup && app?.setActiveGroup) {\n app.setActiveGroup(group);\n }\n\n // Call custom handler if provided\n if (this.onGroupSelected) {\n this.onGroupSelected({ group });\n }\n\n // Emit event for parent/app to handle\n this.emit('group-selected', { group });\n \n // Also emit to app events if available\n if (app?.events) {\n app.events.emit('group:selected', { group });\n app.events.emit('group:changed', { group });\n }\n }\n\n /**\n * Default item template for groups (matches Sidebar pattern)\n * Note: data-action and data-item-index are added by ResultsView wrapper\n */\n getDefaultItemTemplate() {\n return `\n <div class=\"d-flex align-items-center p-3 border-bottom\">\n <div class=\"flex-grow-1\">\n <div class=\"fw-semibold text-dark\">{{name}}</div>\n <small class=\"text-muted\">#{{id}} {{kind}}</small>\n </div>\n </div>\n `;\n }\n\n /**\n * Set the current group programmatically\n */\n setCurrentGroup(group) {\n this.currentGroup = group;\n this.displayName = group?.get?.('name') || group?.name || this.defaultText;\n if (this.mounted) {\n this.render();\n }\n }\n\n /**\n * Get the current group\n */\n getCurrentGroup() {\n return this.currentGroup;\n }\n}\n\nexport default GroupSelectorButton;\n","/**\n * TopNav - Bootstrap navbar component for MOJO framework\n * Provides clean, responsive top navigation\n */\n\nimport View from '@core/View.js';\nimport GroupSelectorButton from '@core/views/navigation/GroupSelectorButton.js';\n\n// Theme tokens TopNav manages on its <nav> root. Used by the auto-theme\n// observer to swap class state without touching consumer-added classes.\nconst TOPNAV_THEME_TOKENS = ['navbar-light', 'navbar-dark', 'topnav-light', 'topnav-dark', 'topnav-clean', 'topnav-gradient'];\nconst TOPNAV_SHADOW_TOKENS = ['topnav-shadow-light', 'topnav-shadow-dark'];\n\nclass TopNav extends View {\n constructor(options = {}) {\n // Define theme-to-class mappings\n const themes = {\n light: 'navbar navbar-expand-lg navbar-light topnav-light',\n dark: 'navbar navbar-expand-lg navbar-dark topnav-dark',\n clean: 'navbar navbar-expand-lg navbar-light topnav-clean',\n gradient: 'navbar navbar-expand-lg navbar-dark topnav-gradient',\n };\n\n // Theme & shadow each accept the literal string `'auto'`, which means\n // \"follow `<html data-bs-theme>` live\". Resolve once at construction;\n // if either was 'auto' we install a MutationObserver after super() so\n // the class state mirrors the page theme as the user toggles it.\n const themeName = options.theme || 'light';\n const shadowName = options.shadow || null;\n const isAutoTheme = themeName === 'auto';\n const isAutoShadow = shadowName === 'auto';\n const resolveAuto = () => {\n if (typeof document === 'undefined') return 'light';\n return document.documentElement?.dataset?.bsTheme === 'dark' ? 'dark' : 'light';\n };\n const resolvedTheme = isAutoTheme ? resolveAuto() : themeName;\n const resolvedShadow = isAutoShadow ? resolveAuto() : shadowName;\n\n let navbarClass = themes[resolvedTheme] || themes.light;\n if (resolvedShadow) {\n navbarClass += ` topnav-shadow-${resolvedShadow}`;\n }\n\n super({\n tagName: 'nav',\n className: navbarClass,\n enableTooltips: true,\n style: 'position: relative; z-index: 1030;',\n ...options\n });\n\n // Stash for the auto-sync observer (initialised lazily below).\n this._themes = themes;\n this._themeOption = themeName;\n this._shadowOption = shadowName;\n this._themeObserver = null;\n\n // Display mode configuration\n // 'menu' | 'page' | 'both' | 'group' | 'group_page_titles'\n this.displayMode = options.displayMode || 'both';\n this.showPageIcon = options.showPageIcon !== false;\n this.showPageDescription = options.showPageDescription || false;\n this.showBreadcrumbs = options.showBreadcrumbs || false;\n this.groupIcon = options.groupIcon || 'bi-building';\n\n // Current page tracking\n this.currentPage = null;\n this.previousPage = null;\n\n // Store raw config for processing in onBeforeRender\n this.config = {\n brand: options.brand || 'MOJO App',\n brandIcon: options.brandIcon || 'bi bi-play-circle',\n brandRoute: options.brandRoute || '/',\n navItems: options.navItems || [],\n rightItems: options.rightItems || [],\n showSidebarToggle: options.showSidebarToggle || false,\n sidebarToggleAction: options.sidebarToggleAction || 'toggle-sidebar',\n ...options\n };\n this.userMenu = options.userMenu || this.findMenuItem('user');\n if (this.userMenu) this.userMenu.id = \"user\";\n this.loginMenu = options.loginMenu || this.findMenuItem('login');\n\n // Setup page event listeners\n this.setupPageListeners();\n\n // Setup group event listeners for group display modes\n this.setupGroupListeners();\n\n // Store reference to group selector for click-to-open functionality\n this.groupSelectorButton = null;\n\n // Track current group for display modes\n this.currentGroup = null;\n\n // Wire the auto-theme observer if either knob asked for it.\n if (isAutoTheme || isAutoShadow) {\n this._installAutoThemeSync();\n }\n }\n\n /**\n * Watch `<html data-bs-theme>` and re-apply theme/shadow class tokens on\n * the root `<nav>` whenever it flips. Only installed when the constructor\n * was called with `theme: 'auto'` and/or `shadow: 'auto'`.\n *\n * Surgical class swap — only the framework-managed theme tokens are\n * removed/added, so any consumer-supplied classes on the navbar element\n * are preserved.\n * @private\n */\n _installAutoThemeSync() {\n if (typeof window === 'undefined' || typeof MutationObserver === 'undefined') return;\n\n const html = document.documentElement;\n const isAutoTheme = this._themeOption === 'auto';\n const isAutoShadow = this._shadowOption === 'auto';\n\n const apply = () => {\n if (!this.element) return;\n const resolved = html?.dataset?.bsTheme === 'dark' ? 'dark' : 'light';\n if (isAutoTheme) {\n const themeClasses = (this._themes[resolved] || this._themes.light).split(/\\s+/);\n this.element.classList.remove(...TOPNAV_THEME_TOKENS);\n themeClasses\n .filter(c => TOPNAV_THEME_TOKENS.includes(c))\n .forEach(c => this.element.classList.add(c));\n }\n if (isAutoShadow) {\n this.element.classList.remove(...TOPNAV_SHADOW_TOKENS);\n this.element.classList.add(`topnav-shadow-${resolved}`);\n }\n };\n\n this._themeObserver = new MutationObserver((mutations) => {\n for (const m of mutations) {\n if (m.type === 'attributes' && m.attributeName === 'data-bs-theme') {\n apply();\n break;\n }\n }\n });\n this._themeObserver.observe(html, { attributes: true, attributeFilter: ['data-bs-theme'] });\n }\n\n async onBeforeDestroy() {\n if (this._themeObserver) {\n this._themeObserver.disconnect();\n this._themeObserver = null;\n }\n }\n\n findMenuItem(id) {\n let item = this.config.navItems.find(item => item.id === id);\n if (!item) {\n item = this.config.rightItems.find(item => item.id === id);\n }\n return item || null;\n }\n\n replaceMenuItem(id, new_menu) {\n // Find and replace in navItems\n const navIndex = this.config.navItems.findIndex(item => item.id === id);\n if (navIndex !== -1) {\n this.config.navItems[navIndex] = new_menu;\n return true;\n }\n\n // Find and replace in rightItems\n const rightIndex = this.config.rightItems.findIndex(item => item.id === id);\n if (rightIndex !== -1) {\n this.config.rightItems[rightIndex] = new_menu;\n return true;\n }\n\n return false;\n }\n\n setBrand(brand, icon=null) {\n this.config.brand = brand;\n this.config.brandIcon = icon || this.config.brandIcon;\n this.render();\n }\n\n setUser(user) {\n if (!user) {\n this.replaceMenuItem('user', this.loginMenu);\n } else {\n this.userMenu.label = user.get(\"display_name\");\n this._updateUserAvatar(user);\n this.replaceMenuItem('login', this.userMenu);\n }\n this.setModel(user);\n }\n\n _onModelChange() {\n if (this.model) {\n this.userMenu.label = this.model.get(\"display_name\");\n this._updateUserAvatar(this.model);\n }\n if (this.isMounted()) {\n this.render();\n }\n }\n\n _updateUserAvatar(user) {\n if (!this.userMenu || !user) return;\n const avatar = user.get('avatar');\n if (avatar) {\n // Extract URL from avatar object (try renditions first, then direct url)\n const url = avatar?.renditions?.square_sm?.url || avatar?.url || (typeof avatar === 'string' ? avatar : null);\n this.userMenu.avatarUrl = url || null;\n } else {\n this.userMenu.avatarUrl = null;\n }\n }\n\n /**\n * Get template based on display mode\n */\n async getTemplate() {\n return `\n <div class=\"container-fluid\">\n {{#data.showSidebarToggle}}\n <button class=\"topnav-sidebar-toggle me-2\" data-action=\"{{data.sidebarToggleAction}}\" aria-label=\"Toggle Sidebar\">\n <i class=\"bi bi-chevron-right toggle-chevron\"></i>\n </button>\n {{/data.showSidebarToggle}}\n\n {{#data.showGroupInfo}}\n <div class=\"navbar-brand d-flex align-items-center\">\n {{#data.groupIcon}}<i class=\"{{data.groupIcon}} me-2\"></i>{{/data.groupIcon}}\n <div>\n <span class=\"topnav-group-name\"\n role=\"button\"\n tabindex=\"0\"\n data-action=\"open-group-selector\"\n style=\"cursor: pointer;\">\n {{data.currentGroupName}}\n </span>\n {{#data.showPageTitle}}\n <span class=\"text-muted mx-2\">|</span>\n <span>{{data.currentPageName}}</span>\n {{/data.showPageTitle}}\n </div>\n </div>\n {{/data.showGroupInfo}}\n\n {{#data.showPageInfo}}\n <div class=\"navbar-brand d-flex align-items-center\">\n {{#data.currentPageIcon}}<i class=\"{{data.currentPageIcon}} me-2\"></i>{{/data.currentPageIcon}}\n <div>\n <span>{{data.currentPageName}}</span>\n {{#data.currentPageDescription}}\n <small class=\"d-block\" style=\"font-size: 0.75rem; line-height: 1;\">{{data.currentPageDescription}}</small>\n {{/data.currentPageDescription}}\n </div>\n </div>\n {{/data.showPageInfo}}\n\n {{#data.showBrand}}\n <a class=\"navbar-brand\" href=\"{{data.brandRoute}}\">\n {{#data.brandIcon}}<i class=\"{{data.brandIcon}} me-2\"></i>{{/data.brandIcon}}\n {{data.brand}}\n </a>\n {{/data.showBrand}}\n\n <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#{{data.navbarId}}\">\n <span class=\"navbar-toggler-icon\"></span>\n </button>\n\n <div class=\"collapse navbar-collapse\" id=\"{{data.navbarId}}\">\n {{#data.showNavItems}}\n <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n {{#data.navItems}}\n <li class=\"nav-item\">\n <a class=\"nav-link {{#active}}active{{/active}}\" href=\"{{route}}\" {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{text}}\n </a>\n </li>\n {{/data.navItems}}\n </ul>\n {{/data.showNavItems}}\n\n {{#data.hasRightItems}}\n <div class=\"navbar-nav ms-auto\">\n {{#data.rightItems}}\n {{#isGroupSelector}}\n <div data-container=\"group-selector-{{id}}\"></div>\n {{/isGroupSelector}}\n {{^isGroupSelector}}\n {{#isDropdown}}\n <div class=\"nav-item dropdown\">\n <a class=\"nav-link dropdown-toggle\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n {{#avatarUrl}}<img src=\"{{avatarUrl}}\" class=\"rounded-circle me-1\" style=\"width: 24px; height: 24px; object-fit: cover;\" alt=\"\" />{{/avatarUrl}}\n {{^avatarUrl}}{{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}{{/avatarUrl}}\n {{label}}\n </a>\n <ul class=\"dropdown-menu dropdown-menu-end\">\n {{#items}}\n {{#divider}}\n <li><hr class=\"dropdown-divider\"></li>\n {{/divider}}\n {{#isHeader}}\n <li><h6 class=\"dropdown-header\">{{header}}</h6></li>\n {{/isHeader}}\n {{#isHtml}}\n <li><span class=\"dropdown-item-text\">{{{html}}}</span></li>\n {{/isHtml}}\n {{^divider}}{{^isHeader}}{{^isHtml}}\n <li>\n <a class=\"dropdown-item {{#active}}active{{/active}}\" role=\"button\" {{#action}}data-action=\"{{action}}\"{{/action}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </a>\n </li>\n {{/isHtml}}{{/isHeader}}{{/divider}}\n {{/items}}\n </ul>\n </div>\n {{/isDropdown}}\n {{^isDropdown}}\n {{#isButton}}\n <button class=\"{{buttonClass}}\" data-action=\"{{action}}\" data-id=\"{{id}}\" {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#iconHtml}}{{{iconHtml}}}{{/iconHtml}}{{^iconHtml}}{{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}{{/iconHtml}}\n {{label}}\n </button>\n {{/isButton}}\n {{^isButton}}\n <a class=\"nav-link\" href=\"{{href}}\" {{#action}}data-action=\"{{action}}\"{{/action}} {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#iconHtml}}{{{iconHtml}}}{{/iconHtml}}{{^iconHtml}}{{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}{{/iconHtml}}\n {{label}}\n </a>\n {{/isButton}}\n {{/isDropdown}}\n {{/isGroupSelector}}\n {{/data.rightItems}}\n </div>\n {{/data.hasRightItems}}\n </div>\n </div>\n `;\n }\n\n /**\n * Process and normalize data before rendering (like Sidebar)\n */\n async onBeforeRender() {\n await super.onBeforeRender();\n\n const app = this.getApp();\n // Use cached currentGroup or fall back to app.activeGroup\n const activeGroup = this.currentGroup || app?.activeGroup;\n\n // Determine what to show based on display mode\n const showGroupInfo = this.displayMode === 'group' || this.displayMode === 'group_page_titles';\n const showPageTitle = this.displayMode === 'group_page_titles';\n const showPageInfo = this.displayMode === 'page' || this.displayMode === 'both';\n const showBrand = !showGroupInfo && !showPageInfo;\n const showNavItems = this.displayMode === 'menu' || this.displayMode === 'both';\n\n // Filter navItems based on permissions\n const navItems = this.filterItemsByPermissions(this.config.navItems || []);\n\n // Process right items\n const rightItems = this.processRightItems(this.config.rightItems || []);\n\n this.data = {\n // Brand information\n brand: this.config.brand,\n brandIcon: this.config.brandIcon,\n brandRoute: this.config.brandRoute,\n showBrand: showBrand,\n\n // Navbar configuration\n navbarId: `navbar-${this.id}`,\n\n // Navigation items\n navItems: navItems,\n showNavItems: showNavItems,\n\n // Right items\n rightItems: rightItems,\n hasRightItems: rightItems.length > 0,\n\n // Group display\n showGroupInfo: showGroupInfo,\n showPageTitle: showPageTitle,\n currentGroupName: activeGroup?.get?.('name') || activeGroup?.name || 'Select Group',\n groupIcon: this.groupIcon,\n\n // Page display\n showPageInfo: showPageInfo,\n currentPageName: this.currentPage?.title || this.currentPage?.name || '',\n currentPageIcon: this.currentPage?.icon || this.currentPage?.pageIcon || '',\n currentPageDescription: this.showPageDescription ? this.currentPage?.description : '',\n\n // Sidebar toggle\n showSidebarToggle: this.config.showSidebarToggle,\n sidebarToggleAction: this.config.sidebarToggleAction,\n\n // Display mode\n displayMode: this.displayMode\n };\n }\n\n /**\n * Process right items configuration\n */\n processRightItems(rightItems) {\n return this.filterItemsByPermissions(rightItems).map(item => {\n const processedItem = { ...item };\n\n // Filter dropdown items by permissions and annotate special item types\n if (item.items) {\n processedItem.items = this.filterItemsByPermissions(item.items).map(subItem => {\n const processed = { ...subItem };\n if (subItem.divider) {\n // already handled by template\n } else if (subItem.header !== undefined) {\n // Category heading — renders as Bootstrap dropdown-header\n processed.isHeader = true;\n } else if (subItem.text !== undefined) {\n // Raw HTML text node — renders as non-interactive dropdown-item-text\n processed.isHtml = true;\n processed.html = subItem.text;\n }\n return processed;\n });\n }\n\n // Check for group selector type\n if (item.type === 'group-selector') {\n processedItem.isGroupSelector = true;\n processedItem.isDropdown = false;\n processedItem.isButton = false;\n\n // Create group selector button with smart defaults\n // Only pass through explicitly provided options\n const groupSelectorOptions = {\n containerId: `group-selector-${item.id || 'default'}`\n };\n\n // Only add options if explicitly provided (allow auto-detection to work)\n if (item.Collection !== undefined) groupSelectorOptions.Collection = item.Collection;\n if (item.collection !== undefined) groupSelectorOptions.collection = item.collection;\n if (item.currentGroup !== undefined) groupSelectorOptions.currentGroup = item.currentGroup;\n if (item.buttonClass !== undefined) groupSelectorOptions.buttonClass = item.buttonClass;\n if (item.buttonIcon !== undefined) groupSelectorOptions.buttonIcon = item.buttonIcon;\n if (item.defaultText !== undefined) groupSelectorOptions.defaultText = item.defaultText;\n if (item.itemTemplate !== undefined) groupSelectorOptions.itemTemplate = item.itemTemplate;\n if (item.searchFields !== undefined) groupSelectorOptions.searchFields = item.searchFields;\n if (item.headerText !== undefined) groupSelectorOptions.headerText = item.headerText;\n if (item.searchPlaceholder !== undefined) groupSelectorOptions.searchPlaceholder = item.searchPlaceholder;\n if (item.autoSetActiveGroup !== undefined) groupSelectorOptions.autoSetActiveGroup = item.autoSetActiveGroup;\n if (item.onGroupSelected !== undefined) groupSelectorOptions.onGroupSelected = item.onGroupSelected;\n\n const groupSelector = new GroupSelectorButton(groupSelectorOptions);\n\n // Store reference for click-to-open functionality\n this.groupSelectorButton = groupSelector;\n\n // Add as child view\n this.addChild(groupSelector);\n\n } else if (processedItem.items && processedItem.items.length > 0) {\n // Dropdown menu\n processedItem.isDropdown = true;\n processedItem.isButton = false;\n } else if (item.buttonClass) {\n // Button\n processedItem.isButton = true;\n processedItem.isDropdown = false;\n } else {\n // Link\n processedItem.isButton = false;\n processedItem.isDropdown = false;\n }\n\n // Store handler if provided\n if (item.handler) {\n this.rightItemHandlers = this.rightItemHandlers || new Map();\n this.rightItemHandlers.set(item.id, item.handler);\n }\n\n return processedItem;\n });\n }\n\n /**\n * Setup listeners for page change events\n */\n setupPageListeners() {\n // Use global MOJO event bus if available\n this.getApp().events.on(\"page:show\", (data) => {\n this.onPageChanged(data);\n });\n }\n\n /**\n * Setup listeners for group change events\n */\n setupGroupListeners() {\n const app = this.getApp();\n if (!app?.events) return;\n\n // Listen for group changes and re-render if showing group info\n app.events.on(['group:changed', 'group:loaded'], (data) => {\n // Update our reference to current group\n if (data?.group) {\n this.currentGroup = data.group;\n }\n\n if (this.displayMode === 'group' || this.displayMode === 'group_page_titles') {\n if (this.mounted) {\n this.render();\n }\n }\n });\n }\n\n /**\n * Handle page before change event\n * @param {object} data - Event data\n */\n onPageBeforeChange(data) {\n // Can be used to show loading state\n if (this.displayMode === 'page' || this.displayMode === 'both') {\n // Optionally show loading indicator\n }\n }\n\n /**\n * Handle page changed event\n * @param {object} data - Event data with previousPage and currentPage\n */\n onPageChanged(data) {\n this.previousPage = this.currentPage;\n this.currentPage = data.page;\n\n // Update display based on mode\n if (this.displayMode === 'page' || this.displayMode === 'both') {\n this.updatePageDisplay();\n }\n\n // Update active menu items\n if (this.displayMode === 'menu' || this.displayMode === 'both') {\n if (this.currentPage && this.currentPage.route) {\n this.updateActiveItem(this.currentPage.route);\n }\n }\n }\n\n /**\n * Update the display to show current page info\n */\n updatePageDisplay() {\n if (!this.currentPage) return;\n\n // Just trigger re-render, onBeforeRender will handle the data processing\n if (this.mounted) {\n this.render();\n }\n }\n\n updateActiveItem(currentRoute) {\n // Normalize routes for comparison\n const normalizeRoute = (route) => {\n if (!route) return '/';\n return route.startsWith('/') ? route : `/${route}`;\n };\n\n const normalizedCurrentRoute = normalizeRoute(currentRoute);\n\n // Update active states with improved matching\n const navItems = this.data.navItems.map(item => {\n const normalizedItemRoute = normalizeRoute(item.route);\n\n // Check for active state\n let isActive = false;\n\n if (normalizedItemRoute === '/' && normalizedCurrentRoute === '/') {\n // Exact match for home route\n isActive = true;\n } else if (normalizedItemRoute !== '/' && normalizedCurrentRoute !== '/') {\n // For non-home routes, check if current route starts with nav item route\n // This allows /users to be active when on /users/123\n isActive = normalizedCurrentRoute.startsWith(normalizedItemRoute) ||\n normalizedCurrentRoute === normalizedItemRoute;\n }\n\n return {\n ...item,\n active: isActive\n };\n });\n\n this.updateData({ navItems }, true);\n }\n\n /**\n * Wire any [data-bs-toggle=\"dropdown\"] elements rendered inside this\n * TopNav so they open on click. Bootstrap's data-API doesn't always\n * pick up dynamically-rendered toggles, so we attach instances\n * eagerly. Re-runs after every render to cover the setUser swap\n * (login -> userMenu) and similar reflows.\n */\n _attachDropdowns() {\n if (!this.element) return;\n if (!window.bootstrap?.Dropdown) {\n if (!TopNav._warnedNoBootstrap) {\n TopNav._warnedNoBootstrap = true;\n console.warn('[TopNav] window.bootstrap.Dropdown not available — dropdown toggles will not auto-attach.');\n }\n return;\n }\n const toggles = this.element.querySelectorAll('[data-bs-toggle=\"dropdown\"]');\n toggles.forEach(el => window.bootstrap.Dropdown.getOrCreateInstance(el));\n }\n\n async onAfterRender() {\n await super.onAfterRender();\n this._attachDropdowns();\n }\n\n onPassThruActionProfile() {\n // Implement profile functionality here\n this.getApp().events.emit(\"portal:action\", {action: \"profile\"});\n }\n\n onActionSettings() {\n // Implement settings functionality here\n this.getApp().events.emit(\"portal:action\", {action: \"settings\"});\n }\n\n onActionLogout() {\n // Implement logout functionality here\n this.getApp().events.emit(\"auth:logout\", {action: \"logout\"});\n }\n\n /**\n * Handle open group selector action (from clicking group name in brand)\n */\n async onActionOpenGroupSelector(event) {\n // If we have a group selector button, trigger its dialog\n if (this.groupSelectorButton) {\n await this.groupSelectorButton.onActionShowSelector(event);\n return true;\n }\n\n // If no group selector in rightItems, create a temporary one\n const { GroupList } = await import('@core/models/Group.js');\n const tempSelector = new GroupSelectorButton({\n Collection: GroupList,\n currentGroup: this.getApp()?.activeGroup\n });\n\n await tempSelector.onActionShowSelector(event);\n return true;\n }\n\n /**\n * Handle dynamic action dispatch for right items\n */\n async handleAction(actionName, event, element) {\n // Check for custom handler first\n const itemId = element.getAttribute('data-id');\n if (itemId && this.rightItemHandlers && this.rightItemHandlers.has(itemId)) {\n const handler = this.rightItemHandlers.get(itemId);\n if (typeof handler === 'function') {\n return await handler.call(this, actionName, event, element);\n }\n }\n\n // Fallback to default action methods\n const methodName = `onAction${actionName.charAt(0).toUpperCase() + actionName.slice(1).replace(/-([a-z])/g, (g) => g[1].toUpperCase())}`;\n if (typeof this[methodName] === 'function') {\n return await this[methodName](event, element);\n }\n\n // Emit action event if no handler found\n this.emit('action', {\n action: actionName,\n event: event,\n element: element,\n topnav: this\n });\n }\n\n /**\n * Handle default actions by searching through rightItems and navItems\n */\n async onActionDefault(action, event, el) {\n // Check navItems first\n if (this.config.navItems) {\n for (const item of this.config.navItems) {\n if (item.action === action && item.handler) {\n await item.handler.call(this, action, event, el);\n return true;\n }\n }\n }\n\n // Check rightItems\n if (this.config.rightItems) {\n for (const item of this.config.rightItems) {\n if (item.action === action && item.handler) {\n await item.handler.call(this, action, event, el);\n return true;\n }\n // Also check dropdown items\n if (item.items) {\n for (const subItem of item.items) {\n if (subItem.action === action && subItem.handler) {\n await subItem.handler.call(this, action, event, el);\n return true;\n }\n }\n }\n }\n }\n\n this.getApp().events.emit(\"portal:action\", { action, event, el });\n\n return false;\n }\n\n /**\n * Filter items by user permissions\n */\n filterItemsByPermissions(items) {\n if (!items) return [];\n\n const app = this.getApp();\n const activeUser = app?.activeUser;\n\n return items.filter(item => {\n // If item has permissions and user exists, check permissions\n if (item.permissions && activeUser) {\n return activeUser.hasPermission(item.permissions);\n }\n // If no permissions required or no user, show the item\n return true;\n });\n }\n\n}\n\nexport default TopNav;\n","/**\n * AuthRequiredError - Thrown by the pre-request auth gate when the access\n * token is expired and cannot be refreshed. Rest recognizes it by name and\n * short-circuits the request to a 401 response without calling fetch.\n */\nexport class AuthRequiredError extends Error {\n constructor(message = 'Authentication required') {\n super(message);\n this.name = 'AuthRequiredError';\n this.reason = 'unauthorized';\n }\n}\n\n/**\n * Token - Individual JWT token handling\n * Handles decoding, validation, and data extraction for a single token\n */\nclass Token {\n constructor(token) {\n this.token = token;\n this.payload = null;\n this.uid = null;\n this.email = null;\n this.name = null;\n this.exp = null;\n this.iat = null;\n this.isValidToken = false;\n\n this._decode();\n }\n\n /**\n * Decode JWT token payload (client-side only, no verification)\n * @private\n */\n _decode() {\n if (!this.token || typeof this.token !== 'string') {\n return;\n }\n\n try {\n const parts = this.token.split('.');\n if (parts.length !== 3) {\n return;\n }\n\n // Decode the payload (second part)\n const payload = parts[1];\n\n // Handle URL-safe base64\n let base64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const padding = 4 - (base64.length % 4);\n if (padding !== 4) {\n base64 += '='.repeat(padding);\n }\n\n const decoded = atob(base64);\n this.payload = JSON.parse(decoded);\n\n // Extract common properties\n this.uid = this.payload.uid || this.payload.sub || this.payload.user_id || null;\n this.email = this.payload.email || null;\n this.name = this.payload.name || this.payload.username || null;\n this.exp = this.payload.exp ? new Date(this.payload.exp * 1000) : null;\n this.iat = this.payload.iat ? new Date(this.payload.iat * 1000) : null;\n\n // Determine validity\n this.isValidToken = this._checkValidity();\n } catch (error) {\n this.payload = null;\n }\n }\n\n /**\n * Check token validity\n * @private\n * @returns {boolean} True if token is valid\n */\n _checkValidity() {\n if (!this.token || !this.payload) {\n return false;\n }\n\n // Check expiry if present\n if (this.payload.exp) {\n const now = Math.floor(Date.now() / 1000);\n return now < this.payload.exp;\n }\n\n // If no expiry, consider valid\n return true;\n }\n\n /**\n * Decode JWT token payload (client-side only, no verification)\n * @returns {object|null} Decoded payload or null if invalid\n */\n decode() {\n return this.payload;\n }\n\n /**\n * Get user ID from token\n * @returns {string|null} User ID or null if not found\n */\n getUserId() {\n return this.uid;\n }\n\n /**\n * Check if token is valid (exists and not expired)\n * @returns {boolean} True if token is valid\n */\n isValid() {\n return this.isValidToken;\n }\n\n /**\n * Check if token will expire soon\n * @param {number} thresholdMinutes - Minutes before expiry to consider \"soon\"\n * @returns {boolean} True if expiring soon\n */\n isExpiringSoon(thresholdMinutes = 5) {\n if (!this.payload?.exp) {\n return false;\n }\n\n const now = Math.floor(Date.now() / 1000);\n const threshold = thresholdMinutes * 60;\n return (this.payload.exp - now) <= threshold;\n }\n\n /**\n * Check if token is expired\n * @returns {boolean} True if expired\n */\n isExpired() {\n if (!this.payload?.exp) {\n return false;\n }\n\n const now = Math.floor(Date.now() / 1000);\n return now >= this.payload.exp;\n }\n\n /**\n * Get token age in minutes\n * @returns {number|null} Age in minutes since token was issued, or null if no iat\n */\n getAgeMinutes() {\n if (!this.payload?.iat) {\n return null;\n }\n const now = Math.floor(Date.now() / 1000);\n const ageSeconds = now - this.payload.iat;\n return Math.floor(ageSeconds / 60);\n }\n\n /**\n * Get authorization header value\n * @returns {string|null} Bearer token string or null if no token\n */\n getAuthHeader() {\n return this.token ? `Bearer ${this.token}` : null;\n }\n\n /**\n * Get basic user info from token\n * @returns {object|null} User info or null\n */\n getUserInfo() {\n if (!this.payload) {\n return null;\n }\n\n return {\n uid: this.uid,\n email: this.email,\n name: this.name,\n exp: this.exp,\n iat: this.iat\n };\n }\n}\n\n/**\n * TokenManager - Simplified JWT token handling for MOJO Auth\n * Focuses on core token operations: storage, validation, and user ID extraction\n */\n\nexport default class TokenManager {\n constructor() {\n this.tokenKey = 'access_token';\n this.refreshTokenKey = 'refresh_token';\n this.authCodeKey = 'auth_code';\n this.tokenInstance = null;\n // Single-flight guard for refreshToken(). Holds the in-flight\n // Promise<boolean> while a refresh is pending; null otherwise.\n this._refreshPromise = null;\n // Single-flight guard for handleAuthCodeFromURL() / exchangeAuthCode().\n this._exchangePromise = null;\n }\n\n /**\n * Store authentication tokens\n * @param {string} token - Access token\n * @param {string} refreshToken - Refresh token (optional)\n * @param {boolean} persistent - Use localStorage if true, sessionStorage if false\n */\n setTokens(token, refreshToken = null, persistent = true) {\n const storage = persistent ? localStorage : sessionStorage;\n this.tokenInstance = new Token(token);\n if (token) {\n storage.setItem(this.tokenKey, token);\n }\n\n if (refreshToken) {\n storage.setItem(this.refreshTokenKey, refreshToken);\n }\n }\n\n /**\n * Get stored access token\n * @returns {string|null} Access token or null if not found\n */\n getToken() {\n return localStorage.getItem(this.tokenKey) ||\n sessionStorage.getItem(this.tokenKey);\n }\n\n /**\n * Get stored refresh token\n * @returns {string|null} Refresh token or null if not found\n */\n getRefreshToken() {\n return localStorage.getItem(this.refreshTokenKey) ||\n sessionStorage.getItem(this.refreshTokenKey);\n }\n\n /**\n * Clear all stored tokens\n */\n clearTokens() {\n localStorage.removeItem(this.tokenKey);\n localStorage.removeItem(this.refreshTokenKey);\n sessionStorage.removeItem(this.tokenKey);\n sessionStorage.removeItem(this.refreshTokenKey);\n }\n\n /**\n * Get Token instance for current stored token\n * @returns {Token|null} Token instance or null if no token\n */\n getTokenInstance() {\n const currentToken = this.getToken();\n\n // If no token stored, clear instance and return null\n if (!currentToken) {\n this.tokenInstance = null;\n return null;\n }\n\n // If instance doesn't exist or token changed, create new instance\n if (!this.tokenInstance || this.tokenInstance.token !== currentToken) {\n this.tokenInstance = new Token(currentToken);\n }\n\n return this.tokenInstance;\n }\n\n /**\n * Get Token instance for refresh token\n * @returns {Token|null} Token instance or null if no refresh token\n */\n getRefreshTokenInstance() {\n const currentRefreshToken = this.getRefreshToken();\n\n // If no refresh token stored, clear instance and return null\n if (!currentRefreshToken) {\n this._refreshTokenInstance = null;\n return null;\n }\n\n // If instance doesn't exist or token changed, create new instance\n if (!this._refreshTokenInstance || this._refreshTokenInstance.token !== currentRefreshToken) {\n this._refreshTokenInstance = new Token(currentRefreshToken);\n }\n\n return this._refreshTokenInstance;\n }\n\n /**\n * Decode JWT token payload (client-side only, no verification)\n * @param {string} token - JWT token\n * @returns {object|null} Decoded payload or null if invalid\n */\n decode(token = null) {\n const jwt = token || this.getToken();\n return new Token(jwt).decode();\n }\n\n /**\n * Get user ID from token\n * @returns {string|null} User ID or null if not found\n */\n getUserId() {\n const currentToken = this.getTokenInstance();\n return currentToken ? currentToken.getUserId() : null;\n }\n\n /**\n * Check if current token is valid (exists and not expired)\n * @returns {boolean} True if token is valid\n */\n isValid() {\n const currentToken = this.getTokenInstance();\n return currentToken ? currentToken.isValid() : false;\n }\n\n /**\n * Check if token will expire soon\n * @param {number} thresholdMinutes - Minutes before expiry to consider \"soon\"\n * @returns {boolean} True if expiring soon\n */\n isExpiringSoon(thresholdMinutes = 5) {\n const currentToken = this.getTokenInstance();\n return currentToken ? currentToken.isExpiringSoon(thresholdMinutes) : false;\n }\n\n /**\n * Get authorization header value\n * @returns {string|null} Bearer token string or null if no token\n */\n getAuthHeader() {\n const currentToken = this.getTokenInstance();\n return currentToken ? currentToken.getAuthHeader() : null;\n }\n\n /**\n * Get basic user info from token\n * @returns {object|null} User info or null\n */\n getUserInfo() {\n const currentToken = this.getTokenInstance();\n return currentToken ? currentToken.getUserInfo() : null;\n }\n\n /**\n * Check current token status and determine what action is needed\n * @returns {object} Status object with action and details\n */\n checkTokenStatus() {\n const token = this.getTokenInstance();\n const refreshToken = this.getRefreshTokenInstance();\n\n // If no access token or it's invalid/expired\n if (!token || !token.isValid() || token.isExpired()) {\n // Check if refresh is possible\n if (!refreshToken || !refreshToken.isValid() || refreshToken.isExpired()) {\n return {\n action: 'logout',\n reason: 'Both access and refresh tokens are invalid/expired'\n };\n }\n\n return {\n action: 'refresh',\n reason: 'Access token invalid/expired but refresh token valid'\n };\n }\n\n // Access token is valid - check if it needs refreshing soon\n if (token.isExpiringSoon(10) || (token.getAgeMinutes() && token.getAgeMinutes() > 60)) {\n // Only suggest refresh if refresh token is still valid\n if (!refreshToken || !refreshToken.isValid() || refreshToken.isExpired()) {\n return {\n action: 'none',\n reason: 'Access token expiring but refresh token invalid'\n };\n }\n\n return {\n action: 'refresh',\n reason: 'Access token expiring soon or aged'\n };\n }\n\n return {\n action: 'none',\n reason: 'All tokens valid and not expiring soon'\n };\n }\n\n /**\n * Check tokens and take appropriate action\n * @param {object} app - App instance for events and API calls\n * @returns {Promise<boolean>} True if action was taken\n */\n async checkAndRefreshTokens(app) {\n const status = this.checkTokenStatus();\n\n switch (status.action) {\n case 'logout':\n app.events.emit(\"auth:unauthorized\");\n this.stopAutoRefresh();\n return true;\n\n case 'refresh':\n await this.refreshToken(app);\n return true;\n\n default:\n return false;\n }\n }\n\n startAutoRefresh(app) {\n this.stopAutoRefresh();\n this._tokenWatcher = setInterval(() => {\n this.checkAndRefreshTokens(app);\n }, 60000);\n }\n\n stopAutoRefresh() {\n if (this._tokenWatcher) {\n clearInterval(this._tokenWatcher);\n this._tokenWatcher = null;\n }\n }\n\n /**\n * Refresh the access token using the stored refresh token.\n * Single-flight: concurrent callers share one in-flight POST.\n * @param {object} app - App instance (needs .rest and .events)\n * @returns {Promise<boolean>} true on success, false on any failure\n */\n async refreshToken(app) {\n if (this._refreshPromise) {\n return this._refreshPromise;\n }\n\n this._refreshPromise = this._doRefresh(app).finally(() => {\n this._refreshPromise = null;\n });\n\n return this._refreshPromise;\n }\n\n /**\n * Perform the actual refresh network call. Always resolves to a boolean;\n * never throws. Emits auth:unauthorized / auth:token:refresh:failed on\n * failure paths, matching prior behavior.\n * @private\n */\n async _doRefresh(app) {\n const refreshTokenInstance = this.getRefreshTokenInstance();\n\n // Double-check refresh token validity before attempting refresh\n if (!refreshTokenInstance || !refreshTokenInstance.isValid() || refreshTokenInstance.isExpired()) {\n app.events.emit(\"auth:unauthorized\");\n this.stopAutoRefresh();\n return false;\n }\n\n try {\n const response = await app.rest.POST('/api/token/refresh', {\n refresh_token: refreshTokenInstance.token\n });\n\n const { access_token, refresh_token } = response.data.data;\n\n // Clear old cached instances so new tokens are loaded\n this.tokenInstance = null;\n this._refreshTokenInstance = null;\n\n // Store new tokens\n this.setTokens(access_token, refresh_token);\n app.rest.setAuthToken(access_token);\n\n // Emit success event\n app.events.emit('auth:token:refreshed', {\n newToken: access_token,\n newRefreshToken: refresh_token\n });\n\n console.log('Token refreshed successfully');\n return true;\n } catch (error) {\n // Check if it's an authentication error (refresh token invalid)\n if (error.status === 401 || error.status === 403) {\n app.events.emit(\"auth:unauthorized\");\n this.stopAutoRefresh();\n } else {\n // For other errors, emit specific event but don't logout\n app.events.emit('auth:token:refresh:failed', { error });\n }\n return false;\n }\n }\n\n /**\n * Read an `?auth_code=` param from window.location, scrub it from the URL,\n * and exchange it for tokens. Resolves to the user dict on success or\n * null when no param is present or the exchange fails. Never throws.\n *\n * The URL scrub happens *synchronously*, before any await — the auth\n * code is held in a closure local so a slow exchange cannot leak it to\n * third-party scripts that read location.search after page load.\n *\n * Single-flight: concurrent callers (e.g. PortalApp boot + a custom\n * AuthApp boot) share the same in-flight POST.\n *\n * @param {object} app - App instance (needs .rest and .events)\n * @returns {Promise<object|null>} user dict on success, null otherwise\n */\n async handleAuthCodeFromURL(app) {\n if (this._exchangePromise) {\n return this._exchangePromise;\n }\n\n if (typeof window === 'undefined' || !window.location) {\n return null;\n }\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get(this.authCodeKey);\n if (!code) {\n return null;\n }\n\n // Scrub before any network call.\n params.delete(this.authCodeKey);\n const remaining = params.toString();\n const cleanUrl = window.location.pathname\n + (remaining ? `?${remaining}` : '')\n + (window.location.hash || '');\n window.history.replaceState({}, '', cleanUrl);\n\n return this.exchangeAuthCode(app, code);\n }\n\n /**\n * Exchange a one-time auth handoff code for access + refresh tokens.\n * Stores tokens via setTokens() and emits 'auth:login' on success;\n * emits 'auth:exchange:failed' on any failure. Never throws.\n *\n * Single-flight: concurrent callers share one in-flight POST. Mirrors\n * the refreshToken() pattern.\n *\n * @param {object} app - App instance (needs .rest and .events)\n * @param {string} code - 32-hex auth handoff code\n * @returns {Promise<object|null>} user dict on success, null on failure\n */\n async exchangeAuthCode(app, code) {\n if (this._exchangePromise) {\n return this._exchangePromise;\n }\n\n this._exchangePromise = this._doExchange(app, code).finally(() => {\n this._exchangePromise = null;\n });\n\n return this._exchangePromise;\n }\n\n /**\n * Perform the actual exchange network call. Always resolves; never\n * throws. Emits auth:login on success, auth:exchange:failed on failure.\n * @private\n */\n async _doExchange(app, code) {\n try {\n const response = await app.rest.POST('/api/auth/exchange', { code });\n // Tolerate the same response wrappers the rest of the codebase\n // does: { data: { data: {...} } } | { data: {...} } | flat.\n const payload = response?.data?.data || response?.data || response;\n const access_token = payload?.access_token;\n const refresh_token = payload?.refresh_token;\n const user = payload?.user;\n\n if (!access_token) {\n throw new Error('No access_token in /api/auth/exchange response');\n }\n\n // Clear cached instances so new tokens are loaded fresh.\n this.tokenInstance = null;\n this._refreshTokenInstance = null;\n\n this.setTokens(access_token, refresh_token);\n app.rest.setAuthToken(access_token);\n\n app.events.emit('auth:login', user);\n return user;\n } catch (error) {\n app.events.emit('auth:exchange:failed', { error });\n return null;\n }\n }\n\n /**\n * Gate an outgoing request on token validity. Intended for use by a\n * pre-request interceptor.\n *\n * - Valid access token: returns (no network).\n * - Expired access token + valid refresh: awaits the (single-flight)\n * refresh; throws AuthRequiredError if the refresh fails.\n * - Both tokens expired/invalid: emits auth:unauthorized and throws\n * AuthRequiredError without hitting the network.\n *\n * @param {object} app - App instance (needs .rest and .events)\n * @throws {AuthRequiredError} when the request must not be sent\n */\n async ensureValidToken(app) {\n const status = this.checkTokenStatus();\n\n if (status.action === 'logout') {\n app.events.emit('auth:unauthorized');\n this.stopAutoRefresh();\n throw new AuthRequiredError('Both access and refresh tokens are invalid');\n }\n\n if (status.action === 'refresh') {\n const ok = await this.refreshToken(app);\n if (!ok) {\n throw new AuthRequiredError('Token refresh failed');\n }\n }\n }\n}\n"],"names":["ResultsView","View","constructor","options","super","className","template","this","parentView","handleActionSelectItem","event","element","preventDefault","itemIndex","parseInt","getAttribute","handleItemSelection","handleActionClearSearch","_element","clearSearch","onAfterRender","maxHeight","container","querySelector","style","SimpleSearchView","Collection","collection","itemTemplate","getDefaultItemTemplate","searchFields","collectionParams","size","headerText","headerIcon","searchPlaceholder","loadingText","noResultsText","emptyText","emptySubtext","emptyIcon","footerContent","footerIcon","showExitButton","searchValue","filteredItems","loading","hasSearched","searchTimer","debounceMs","addClass","resultsView","addChild","onInit","setupCollection","autoLoad","loadItems","Object","assign","params","on","updateFilteredItems","updateResultsView","fetch","error","console","app","getApp","showError","warn","toJSON","getNestedValue","obj","path","split","reduce","current","key","getViewData","showFooter","hasItems","length","hasFilteredItems","hasSearchValue","processedItems","map","item","index","itemContent","processItemTemplate","data","items","showEmpty","showNoResults","showResultsCount","filteredCount","totalCount","restEnabled","meta","count","render","replace","match","prop","onPassThruActionSearchItems","value","clearTimeout","performSearch","searchParams","search","trim","setParams","isNaN","model","get","id","ModelClass","showLoading","then","hideLoading","emit","setCollection","setItemTemplate","setSearchFields","fields","Array","isArray","refresh","focusSearch","searchInput","focus","handleActionExitView","view","getItemCount","getFilteredItemCount","getSearchValue","setSearchValue","isMounted","onBeforeDestroy","off","GroupSelectorButton","tagName","GroupCollection","GroupList","currentGroup","activeGroup","buttonClass","buttonIcon","defaultText","autoSetActiveGroup","onGroupSelected","dialog","events","group","setCurrentGroup","getTemplate","onBeforeRender","displayName","name","onActionShowSelector","searchView","ModalView","title","body","scrollable","noBodyPadding","buttons","closeButton","handleGroupSelection","hide","destroy","document","show","setActiveGroup","mounted","getCurrentGroup","TOPNAV_THEME_TOKENS","TOPNAV_SHADOW_TOKENS","TopNav","themes","light","dark","clean","gradient","themeName","theme","shadowName","shadow","isAutoTheme","isAutoShadow","resolveAuto","documentElement","dataset","bsTheme","resolvedTheme","resolvedShadow","navbarClass","enableTooltips","_themes","_themeOption","_shadowOption","_themeObserver","displayMode","showPageIcon","showPageDescription","showBreadcrumbs","groupIcon","currentPage","previousPage","config","brand","brandIcon","brandRoute","navItems","rightItems","showSidebarToggle","sidebarToggleAction","userMenu","findMenuItem","loginMenu","setupPageListeners","setupGroupListeners","groupSelectorButton","_installAutoThemeSync","window","MutationObserver","html","apply","resolved","themeClasses","classList","remove","filter","c","includes","forEach","add","mutations","m","type","attributeName","observe","attributes","attributeFilter","disconnect","find","replaceMenuItem","new_menu","navIndex","findIndex","rightIndex","setBrand","icon","setUser","user","label","_updateUserAvatar","setModel","_onModelChange","avatar","url","renditions","square_sm","avatarUrl","showGroupInfo","showPageTitle","showPageInfo","showBrand","showNavItems","filterItemsByPermissions","processRightItems","navbarId","hasRightItems","currentGroupName","currentPageName","currentPageIcon","pageIcon","currentPageDescription","description","processedItem","subItem","processed","divider","header","isHeader","text","isHtml","isGroupSelector","isDropdown","isButton","groupSelectorOptions","containerId","groupSelector","handler","rightItemHandlers","Map","set","onPageChanged","onPageBeforeChange","page","updatePageDisplay","route","updateActiveItem","currentRoute","normalizeRoute","startsWith","normalizedCurrentRoute","normalizedItemRoute","isActive","active","updateData","_attachDropdowns","bootstrap","Dropdown","querySelectorAll","el","getOrCreateInstance","_warnedNoBootstrap","onPassThruActionProfile","action","onActionSettings","onActionLogout","onActionOpenGroupSelector","import","n","j","tempSelector","handleAction","actionName","itemId","has","call","methodName","charAt","toUpperCase","slice","g","topnav","onActionDefault","activeUser","permissions","hasPermission","AuthRequiredError","Error","message","reason","Token","token","payload","uid","email","exp","iat","isValidToken","_decode","parts","base64","padding","repeat","decoded","atob","JSON","parse","sub","user_id","username","Date","_checkValidity","Math","floor","now","decode","getUserId","isValid","isExpiringSoon","thresholdMinutes","threshold","isExpired","getAgeMinutes","ageSeconds","getAuthHeader","getUserInfo","TokenManager","tokenKey","refreshTokenKey","authCodeKey","tokenInstance","_refreshPromise","_exchangePromise","setTokens","refreshToken","persistent","storage","localStorage","sessionStorage","setItem","getToken","getItem","getRefreshToken","clearTokens","removeItem","getTokenInstance","currentToken","getRefreshTokenInstance","currentRefreshToken","_refreshTokenInstance","jwt","checkTokenStatus","checkAndRefreshTokens","stopAutoRefresh","startAutoRefresh","_tokenWatcher","setInterval","clearInterval","_doRefresh","finally","refreshTokenInstance","response","rest","POST","refresh_token","access_token","setAuthToken","newToken","newRefreshToken","status","handleAuthCodeFromURL","location","URLSearchParams","code","delete","remaining","toString","cleanUrl","pathname","hash","history","replaceState","exchangeAuthCode","_doExchange","ensureValidToken"],"mappings":"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"}
1
+ {"version":3,"file":"TokenManager-BRx2U5w4.js","sources":["../../src/core/views/navigation/SimpleSearchView.js","../../src/core/views/navigation/GroupSelectorButton.js","../../src/core/views/navigation/TopNav.js","../../src/core/services/TokenManager.js"],"sourcesContent":["/**\n * SimpleSearchView - Generic searchable list component\n * Displays a searchable, scrollable list of items from any Collection\n * Emits item:selected event when user selects an item\n */\n\nimport { View } from '@core/View.js';\n\n/**\n * ResultsView - Internal child view for rendering search results\n * This is only used within SimpleSearchView and handles the scrollable results area\n */\nclass ResultsView extends View {\n constructor(options = {}) {\n super({\n className: 'search-results-view flex-grow-1 overflow-auto d-flex flex-column',\n template: `\n <div id=\"results-container\" class=\"flex-grow-1 overflow-auto\">\n {{#data.loading}}\n <div class=\"text-center p-4\">\n <div class=\"spinner-border spinner-border-sm text-muted\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <div class=\"mt-2 small text-muted\">{{data.loadingText}}</div>\n </div>\n {{/data.loading}}\n\n {{^data.loading}}\n {{#data.items}}\n <div class=\"simple-search-item position-relative\"\n data-action=\"select-item\"\n data-item-index=\"{{index}}\">\n {{{itemContent}}}\n\n </div>\n {{/data.items}}\n\n {{#data.showNoResults}}\n <div class=\"text-center p-4\">\n <i class=\"bi bi-search text-muted mb-2\" style=\"font-size: 1.5rem;\"></i>\n <div class=\"text-muted small\">{{data.noResultsText}}</div>\n <button type=\"button\"\n class=\"btn btn-link btn-sm mt-2 p-0\"\n data-action=\"clear-search\">\n Clear search\n </button>\n </div>\n {{/data.showNoResults}}\n\n {{#data.showEmpty}}\n <div class=\"text-center p-4\">\n <i class=\"{{data.emptyIcon}} text-muted mb-2\" style=\"font-size: 2rem;\"></i>\n <div class=\"text-muted small mb-2\">{{data.emptyText}}</div>\n {{#data.emptySubtext}}\n <div class=\"text-muted\" style=\"font-size: 0.75rem;\">\n {{data.emptySubtext}}\n </div>\n {{/data.emptySubtext}}\n </div>\n {{/data.showEmpty}}\n {{/data.loading}}\n </div>\n\n {{#data.showResultsCount}}\n <div class=\"border-top bg-body-tertiary p-2 text-center\">\n <small class=\"text-muted\">\n {{data.filteredCount}} of {{data.totalCount}}\n </small>\n </div>\n {{/data.showResultsCount}}\n `,\n ...options\n });\n\n this.parentView = options.parentView;\n }\n\n async handleActionSelectItem(event, element) {\n event.preventDefault();\n const itemIndex = parseInt(element.getAttribute('data-item-index'));\n if (this.parentView) {\n this.parentView.handleItemSelection(itemIndex);\n }\n }\n\n async handleActionClearSearch(event, _element) {\n event.preventDefault();\n\n if (this.parentView) {\n this.parentView.clearSearch();\n }\n }\n\n async onAfterRender() {\n if (this.parentView && this.parentView.maxHeight) {\n const container = this.element.querySelector('#results-container');\n if (container) {\n container.style.maxHeight = `${this.parentView.maxHeight}px`;\n }\n }\n }\n}\n\nclass SimpleSearchView extends View {\n constructor(options = {}) {\n super({\n className: 'simple-search-view d-flex flex-column',\n template: `\n <div class=\"p-3 border-bottom bg-body-tertiary\">\n {{#data.headerText}}\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <h6 class=\"text-muted fw-semibold mb-0\">\n {{#data.headerIcon}}<i class=\"{{data.headerIcon}} me-2\"></i>{{/data.headerIcon}}\n {{{data.headerText}}}\n </h6>\n {{#data.showExitButton}}\n <button class=\"btn btn-link p-0 text-muted simple-search-exit-btn\"\n type=\"button\"\n data-action=\"exit-view\"\n title=\"Exit\"\n aria-label=\"Exit view\">\n <i class=\"bi bi-x-lg\" aria-hidden=\"true\"></i>\n </button>\n {{/data.showExitButton}}\n </div>\n {{/data.headerText}}\n <div class=\"position-relative\">\n <input type=\"text\"\n class=\"form-control form-control-sm pe-5\"\n placeholder=\"{{data.searchPlaceholder}}\"\n value=\"{{data.searchValue}}\"\n data-filter=\"live-search\"\n data-filter-debounce=\"{{data.debounceMs}}\"\n data-change-action=\"search-items\">\n <button class=\"btn btn-link p-0 position-absolute top-50 end-0 translate-middle-y me-2 text-muted simple-search-clear-btn\"\n type=\"button\"\n data-action=\"clear-search\"\n title=\"Clear search\"\n aria-label=\"Clear search\">\n <i class=\"bi bi-x-circle-fill\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n <div data-container=\"results\"></div>\n\n {{#data.showFooter}}\n <div class=\"p-3 border-top bg-body-tertiary\">\n <small class=\"text-muted\">\n <i class=\"{{data.footerIcon}} me-1\"></i>\n {{{data.footerContent}}}\n </small>\n </div>\n {{/data.showFooter}}\n `,\n ...options\n });\n\n // Configuration options\n this.Collection = options.Collection;\n this.collection = options.collection;\n this.itemTemplate = options.itemTemplate || this.getDefaultItemTemplate();\n this.searchFields = options.searchFields || ['name'];\n this.collectionParams = { size: 25, ...options.collectionParams };\n\n // UI text configuration\n if (options.headerText === undefined) this.headerText = 'Select Item';\n this.headerText = options.headerText;\n this.headerIcon = options.headerIcon || 'bi bi-list';\n this.searchPlaceholder = options.searchPlaceholder || 'Search...';\n this.loadingText = options.loadingText || 'Loading items...';\n this.noResultsText = options.noResultsText || 'No items match your search';\n this.emptyText = options.emptyText || 'No items available';\n this.emptySubtext = options.emptySubtext || null;\n this.emptyIcon = options.emptyIcon || 'bi bi-inbox';\n this.footerContent = options.footerContent || null;\n this.footerIcon = options.footerIcon || 'bi bi-info-circle';\n this.showExitButton = options.showExitButton || false;\n\n // State\n this.searchValue = '';\n this.filteredItems = [];\n this.loading = false;\n this.hasSearched = false;\n this.searchTimer = null;\n this.debounceMs = options.debounceMs || 300;\n if (options.maxHeight) {\n this.maxHeight = options.maxHeight;\n } else {\n this.addClass('h-100');\n }\n\n // Create results child view\n this.resultsView = new ResultsView({\n parentView: this\n });\n\n if (!this.collection && this.Collection) {\n this.collection = new this.Collection();\n }\n\n // Add as child view\n this.addChild(this.resultsView);\n\n }\n\n onInit() {\n // Initialize collection if provided\n if (this.collection) {\n this.setupCollection();\n }\n\n // Load items on init if collection is available\n if (this.collection && this.options.autoLoad !== false) {\n this.loadItems();\n }\n }\n\n setupCollection() {\n // Set collection parameters\n Object.assign(this.collection.params, this.collectionParams);\n\n // Listen for collection updates\n this.collection.on('fetch:success', () => {\n this.loading = false;\n this.updateFilteredItems();\n });\n\n this.collection.on('fetch:error', () => {\n this.loading = false;\n });\n }\n\n async loadItems() {\n if (!this.collection) {\n console.warn('SimpleSearchView: No collection provided');\n return;\n }\n\n this.loading = true;\n this.updateResultsView();\n\n try {\n await this.collection.fetch();\n this.updateFilteredItems();\n } catch (error) {\n console.error('Error loading items:', error);\n const app = this.getApp();\n app?.showError?.('Failed to load items. Please try again.');\n } finally {\n this.loading = false;\n this.updateFilteredItems();\n }\n }\n\n updateFilteredItems() {\n if (!this.collection) {\n this.filteredItems = [];\n return;\n }\n\n // Server-side filtering is handled in performSearch()\n // Just use the collection's items directly - they're already filtered by the server\n this.filteredItems = this.collection.toJSON();\n\n this.updateResultsView();\n }\n\n getNestedValue(obj, path) {\n return path.split('.').reduce((current, key) => current?.[key], obj);\n }\n\n async getViewData() {\n return {\n searchValue: this.searchValue,\n showFooter: !!this.footerContent,\n showExitButton: this.showExitButton,\n debounceMs: this.debounceMs,\n\n // UI text\n headerText: this.headerText,\n headerIcon: this.headerIcon,\n searchPlaceholder: this.searchPlaceholder,\n footerContent: this.footerContent,\n footerIcon: this.footerIcon\n };\n }\n\n updateResultsView() {\n if (!this.resultsView) return;\n\n const hasItems = this.collection && this.collection.length() > 0;\n const hasFilteredItems = this.filteredItems.length > 0;\n const hasSearchValue = this.searchValue.length > 0;\n\n // Process items with template\n const processedItems = this.filteredItems.map((item, index) => {\n return {\n ...item,\n index,\n itemContent: this.processItemTemplate(item)\n };\n });\n\n this.resultsView.data = {\n loading: this.loading,\n items: processedItems,\n showEmpty: !this.loading && !hasItems,\n showNoResults: !this.loading && hasItems && !hasFilteredItems && hasSearchValue,\n showResultsCount: !this.loading && hasItems,\n filteredCount: this.filteredItems.length,\n totalCount: this.collection?.restEnabled\n ? (this.collection?.meta?.count || 0)\n : (this.collection?.length() || 0),\n\n // UI text\n loadingText: this.loadingText,\n noResultsText: this.noResultsText,\n emptyText: this.emptyText,\n emptySubtext: this.emptySubtext,\n emptyIcon: this.emptyIcon\n };\n\n this.resultsView.render();\n }\n\n processItemTemplate(item) {\n let template = this.itemTemplate;\n\n // Simple template replacement for item properties\n template = template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, prop) => {\n return this.getNestedValue(item, prop) || '';\n });\n\n return template;\n }\n\n getDefaultItemTemplate() {\n return `\n <div class=\"p-3 border-bottom\">\n <div class=\"fw-semibold text-dark\">{{name}}</div>\n <small class=\"text-muted\">{{id}}</small>\n </div>\n `;\n }\n\n async onPassThruActionSearchItems(event, element) {\n const searchValue = element.value || '';\n\n console.log(\"search change...\");\n this.searchValue = searchValue;\n this.hasSearched = true;\n\n // Clear existing timer\n if (this.searchTimer) {\n clearTimeout(this.searchTimer);\n }\n\n // Debounce the search\n this.performSearch();\n }\n\n async performSearch() {\n const searchParams = { ...this.collectionParams };\n if (this.searchValue && this.searchValue.length > 1) {\n searchParams.search = this.searchValue.trim();\n }\n this.collection.setParams(searchParams, true);\n }\n\n handleItemSelection(itemIndex) {\n if (isNaN(itemIndex) || itemIndex < 0 || itemIndex >= this.filteredItems.length) {\n console.error('Invalid item index:', itemIndex);\n return;\n }\n\n const item = this.filteredItems[itemIndex];\n let model = this.collection ? this.collection.get(item.id) : null;\n if (!model) {\n model = new this.collection.ModelClass({ id: item.id });\n const app = this.getApp();\n app.showLoading();\n model.fetch().then(() => {\n app.hideLoading();\n this.emit('item:selected', {\n item: item,\n model: model,\n index: itemIndex\n });\n });\n return;\n } else {\n this.emit('item:selected', {\n item: item,\n model: model,\n index: itemIndex\n });\n }\n\n }\n\n /**\n * Set the collection for this search view\n */\n setCollection(collection) {\n this.collection = collection;\n this.setupCollection();\n return this;\n }\n\n /**\n * Set the item template\n */\n setItemTemplate(template) {\n this.itemTemplate = template;\n this.updateResultsView();\n return this;\n }\n\n /**\n * Set search fields\n */\n setSearchFields(fields) {\n this.searchFields = Array.isArray(fields) ? fields : [fields];\n return this;\n }\n\n /**\n * Refresh items list\n */\n async refresh() {\n await this.loadItems();\n }\n\n /**\n * Focus the search input\n */\n focusSearch() {\n const searchInput = this.element?.querySelector('input[data-action=\"search-items\"]');\n if (searchInput) {\n searchInput.focus();\n }\n }\n\n /**\n * Handle exit button click - emits event instead of closing\n */\n async handleActionExitView(event, element) {\n this.emit('exit', { view: this });\n }\n\n /**\n * Clear search and reset\n */\n async handleActionClearSearch(event, element) {\n this.clearSearch();\n }\n\n clearSearch() {\n this.searchValue = '';\n this.hasSearched = false;\n const searchInput = this.element?.querySelector('input[data-change-action=\"search-items\"]');\n if (searchInput) {\n searchInput.value = '';\n searchInput.focus();\n }\n this.performSearch();\n }\n\n /**\n * Get the number of available items\n */\n getItemCount() {\n return this.collection ? this.collection.length() : 0;\n }\n\n /**\n * Get the number of filtered items\n */\n getFilteredItemCount() {\n return this.filteredItems.length;\n }\n\n /**\n * Check if items are loaded\n */\n hasItems() {\n return this.getItemCount() > 0;\n }\n\n /**\n * Get current search value\n */\n getSearchValue() {\n return this.searchValue;\n }\n\n /**\n * Set search value programmatically\n */\n setSearchValue(value) {\n this.searchValue = value || '';\n this.hasSearched = !!this.searchValue;\n\n const searchInput = this.element?.querySelector('input[data-action=\"search-items\"]');\n if (searchInput) {\n searchInput.value = this.searchValue;\n }\n\n this.performSearch();\n return this;\n }\n\n async onAfterRender() {\n await super.onAfterRender();\n\n // Mount results view to container if not already mounted\n if (this.resultsView && !this.resultsView.isMounted()) {\n const container = this.element?.querySelector('[data-container=\"results\"]');\n if (container) {\n await this.resultsView.render(true, container);\n }\n }\n // Update results view after main render\n this.updateResultsView();\n }\n\n /**\n * Cleanup on destroy\n */\n async onBeforeDestroy() {\n if (this.searchTimer) {\n clearTimeout(this.searchTimer);\n }\n\n if (this.collection) {\n this.collection.off('update');\n }\n\n await super.onBeforeDestroy();\n }\n}\n\nexport default SimpleSearchView;\n","/**\n * GroupSelectorButton - Button that shows current group and opens search dialog\n * Displays active group name in topnav, opens Dialog with SimpleSearchView for selection\n */\n\nimport View from '@core/View.js';\nimport ModalView from '@core/views/feedback/ModalView.js';\nimport SimpleSearchView from '@core/views/navigation/SimpleSearchView.js';\nimport { GroupList } from '@core/models/Group.js';\n\nclass GroupSelectorButton extends View {\n constructor(options = {}) {\n super({\n tagName: 'div',\n className: 'nav-item',\n ...options\n });\n\n const app = this.getApp();\n\n // Auto-detect Collection from app or use GroupList by default\n this.Collection = options.Collection || app?.GroupCollection || GroupList;\n \n // Use existing collection or create new one\n this.collection = options.collection || new this.Collection();\n \n // Auto-detect current group from app\n this.currentGroup = options.currentGroup !== undefined \n ? options.currentGroup \n : app?.activeGroup;\n \n // UI configuration with defaults\n this.buttonClass = options.buttonClass || 'btn btn-link nav-link';\n this.buttonIcon = options.buttonIcon || 'bi-building';\n this.defaultText = options.defaultText || 'Select Group';\n \n // SimpleSearchView configuration\n this.itemTemplate = options.itemTemplate;\n this.searchFields = options.searchFields || ['name'];\n this.headerText = options.headerText || 'Select Group';\n this.searchPlaceholder = options.searchPlaceholder || 'Search groups...';\n \n // Auto group selection handler\n this.autoSetActiveGroup = options.autoSetActiveGroup !== false;\n this.onGroupSelected = options.onGroupSelected;\n \n // Dialog reference\n this.dialog = null;\n\n // Listen for app-level group changes\n if (app?.events) {\n app.events.on('group:changed', (data) => {\n if (data.group !== this.currentGroup) {\n this.setCurrentGroup(data.group);\n }\n });\n }\n }\n\n async getTemplate() {\n return `\n <button class=\"{{buttonClass}}\" \n data-action=\"show-selector\"\n type=\"button\"\n style=\"white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\">\n <i class=\"{{buttonIcon}} me-1\"></i>\n <span class=\"group-name\">{{displayName}}</span>\n </button>\n `;\n }\n\n async onBeforeRender() {\n await super.onBeforeRender();\n \n console.log('GroupSelectorButton onBeforeRender - currentGroup:', this.currentGroup?.get?.('name') || this.currentGroup?.name || 'none');\n \n this.buttonClass = this.buttonClass;\n this.buttonIcon = this.buttonIcon;\n this.displayName = this.currentGroup?.get?.('name') || \n this.currentGroup?.name || \n this.defaultText;\n }\n\n /**\n * Show the group selector dialog\n */\n async onActionShowSelector(event) {\n // Create SimpleSearchView instance\n const searchView = new SimpleSearchView({\n Collection: this.Collection,\n collection: this.collection,\n itemTemplate: this.itemTemplate || this.getDefaultItemTemplate(),\n searchFields: this.searchFields,\n headerText: this.headerText,\n searchPlaceholder: this.searchPlaceholder,\n headerIcon: this.buttonIcon,\n showExitButton: false\n });\n\n // Use ModalView directly so we keep an instance handle for hide(),\n // on('hidden'), and destroy() — Modal.show() returns a Promise.\n this.dialog = new ModalView({\n title: this.headerText,\n body: searchView,\n size: 'md',\n scrollable: true,\n noBodyPadding: true,\n buttons: [],\n closeButton: true\n });\n\n // Listen for item selection (note: event is 'item:selected' not 'item-selected')\n searchView.on('item:selected', (data) => {\n this.handleGroupSelection(data.model || data.item);\n if (this.dialog) {\n this.dialog.hide();\n }\n });\n\n // Clean up dialog reference when closed\n this.dialog.on('hidden', () => {\n this.dialog.destroy();\n this.dialog = null;\n });\n\n // Render and show the dialog\n await this.dialog.render(true, document.body);\n this.dialog.show();\n \n return true; // Indicate action was handled\n }\n\n /**\n * Handle group selection\n */\n handleGroupSelection(group) {\n this.currentGroup = group;\n \n // Update button text\n this.displayName = group?.get?.('name') || group?.name || this.defaultText;\n this.render();\n\n const app = this.getApp();\n\n // Automatically set active group on app if enabled\n if (this.autoSetActiveGroup && app?.setActiveGroup) {\n app.setActiveGroup(group);\n }\n\n // Call custom handler if provided\n if (this.onGroupSelected) {\n this.onGroupSelected({ group });\n }\n\n // Emit event for parent/app to handle\n this.emit('group-selected', { group });\n \n // Also emit to app events if available\n if (app?.events) {\n app.events.emit('group:selected', { group });\n app.events.emit('group:changed', { group });\n }\n }\n\n /**\n * Default item template for groups (matches Sidebar pattern)\n * Note: data-action and data-item-index are added by ResultsView wrapper\n */\n getDefaultItemTemplate() {\n return `\n <div class=\"d-flex align-items-center p-3 border-bottom\">\n <div class=\"flex-grow-1\">\n <div class=\"fw-semibold text-dark\">{{name}}</div>\n <small class=\"text-muted\">#{{id}} {{kind}}</small>\n </div>\n </div>\n `;\n }\n\n /**\n * Set the current group programmatically\n */\n setCurrentGroup(group) {\n this.currentGroup = group;\n this.displayName = group?.get?.('name') || group?.name || this.defaultText;\n if (this.mounted) {\n this.render();\n }\n }\n\n /**\n * Get the current group\n */\n getCurrentGroup() {\n return this.currentGroup;\n }\n}\n\nexport default GroupSelectorButton;\n","/**\n * TopNav - Bootstrap navbar component for MOJO framework\n * Provides clean, responsive top navigation\n */\n\nimport View from '@core/View.js';\nimport GroupSelectorButton from '@core/views/navigation/GroupSelectorButton.js';\n\n// Theme tokens TopNav manages on its <nav> root. Used by the auto-theme\n// observer to swap class state without touching consumer-added classes.\nconst TOPNAV_THEME_TOKENS = ['navbar-light', 'navbar-dark', 'topnav-light', 'topnav-dark', 'topnav-clean', 'topnav-gradient'];\nconst TOPNAV_SHADOW_TOKENS = ['topnav-shadow-light', 'topnav-shadow-dark'];\n\nclass TopNav extends View {\n constructor(options = {}) {\n // Define theme-to-class mappings\n const themes = {\n light: 'navbar navbar-expand-lg navbar-light topnav-light',\n dark: 'navbar navbar-expand-lg navbar-dark topnav-dark',\n clean: 'navbar navbar-expand-lg navbar-light topnav-clean',\n gradient: 'navbar navbar-expand-lg navbar-dark topnav-gradient',\n };\n\n // Theme & shadow each accept the literal string `'auto'`, which means\n // \"follow `<html data-bs-theme>` live\". Resolve once at construction;\n // if either was 'auto' we install a MutationObserver after super() so\n // the class state mirrors the page theme as the user toggles it.\n const themeName = options.theme || 'light';\n const shadowName = options.shadow || null;\n const isAutoTheme = themeName === 'auto';\n const isAutoShadow = shadowName === 'auto';\n const resolveAuto = () => {\n if (typeof document === 'undefined') return 'light';\n return document.documentElement?.dataset?.bsTheme === 'dark' ? 'dark' : 'light';\n };\n const resolvedTheme = isAutoTheme ? resolveAuto() : themeName;\n const resolvedShadow = isAutoShadow ? resolveAuto() : shadowName;\n\n let navbarClass = themes[resolvedTheme] || themes.light;\n if (resolvedShadow) {\n navbarClass += ` topnav-shadow-${resolvedShadow}`;\n }\n\n super({\n tagName: 'nav',\n className: navbarClass,\n enableTooltips: true,\n style: 'position: relative; z-index: 1030;',\n ...options\n });\n\n // Stash for the auto-sync observer (initialised lazily below).\n this._themes = themes;\n this._themeOption = themeName;\n this._shadowOption = shadowName;\n this._themeObserver = null;\n\n // Display mode configuration\n // 'menu' | 'page' | 'both' | 'group' | 'group_page_titles'\n this.displayMode = options.displayMode || 'both';\n this.showPageIcon = options.showPageIcon !== false;\n this.showPageDescription = options.showPageDescription || false;\n this.showBreadcrumbs = options.showBreadcrumbs || false;\n this.groupIcon = options.groupIcon || 'bi-building';\n\n // Current page tracking\n this.currentPage = null;\n this.previousPage = null;\n\n // Store raw config for processing in onBeforeRender\n this.config = {\n brand: options.brand || 'MOJO App',\n brandIcon: options.brandIcon || 'bi bi-play-circle',\n brandRoute: options.brandRoute || '/',\n navItems: options.navItems || [],\n rightItems: options.rightItems || [],\n showSidebarToggle: options.showSidebarToggle || false,\n sidebarToggleAction: options.sidebarToggleAction || 'toggle-sidebar',\n ...options\n };\n this.userMenu = options.userMenu || this.findMenuItem('user');\n if (this.userMenu) this.userMenu.id = \"user\";\n this.loginMenu = options.loginMenu || this.findMenuItem('login');\n\n // Setup page event listeners\n this.setupPageListeners();\n\n // Setup group event listeners for group display modes\n this.setupGroupListeners();\n\n // Store reference to group selector for click-to-open functionality\n this.groupSelectorButton = null;\n\n // Track current group for display modes\n this.currentGroup = null;\n\n // Wire the auto-theme observer if either knob asked for it.\n if (isAutoTheme || isAutoShadow) {\n this._installAutoThemeSync();\n }\n }\n\n /**\n * Watch `<html data-bs-theme>` and re-apply theme/shadow class tokens on\n * the root `<nav>` whenever it flips. Only installed when the constructor\n * was called with `theme: 'auto'` and/or `shadow: 'auto'`.\n *\n * Surgical class swap — only the framework-managed theme tokens are\n * removed/added, so any consumer-supplied classes on the navbar element\n * are preserved.\n * @private\n */\n _installAutoThemeSync() {\n if (typeof window === 'undefined' || typeof MutationObserver === 'undefined') return;\n\n const html = document.documentElement;\n const isAutoTheme = this._themeOption === 'auto';\n const isAutoShadow = this._shadowOption === 'auto';\n\n const apply = () => {\n if (!this.element) return;\n const resolved = html?.dataset?.bsTheme === 'dark' ? 'dark' : 'light';\n if (isAutoTheme) {\n const themeClasses = (this._themes[resolved] || this._themes.light).split(/\\s+/);\n this.element.classList.remove(...TOPNAV_THEME_TOKENS);\n themeClasses\n .filter(c => TOPNAV_THEME_TOKENS.includes(c))\n .forEach(c => this.element.classList.add(c));\n }\n if (isAutoShadow) {\n this.element.classList.remove(...TOPNAV_SHADOW_TOKENS);\n this.element.classList.add(`topnav-shadow-${resolved}`);\n }\n };\n\n this._themeObserver = new MutationObserver((mutations) => {\n for (const m of mutations) {\n if (m.type === 'attributes' && m.attributeName === 'data-bs-theme') {\n apply();\n break;\n }\n }\n });\n this._themeObserver.observe(html, { attributes: true, attributeFilter: ['data-bs-theme'] });\n }\n\n async onBeforeDestroy() {\n if (this._themeObserver) {\n this._themeObserver.disconnect();\n this._themeObserver = null;\n }\n }\n\n findMenuItem(id) {\n let item = this.config.navItems.find(item => item.id === id);\n if (!item) {\n item = this.config.rightItems.find(item => item.id === id);\n }\n return item || null;\n }\n\n replaceMenuItem(id, new_menu) {\n // Find and replace in navItems\n const navIndex = this.config.navItems.findIndex(item => item.id === id);\n if (navIndex !== -1) {\n this.config.navItems[navIndex] = new_menu;\n return true;\n }\n\n // Find and replace in rightItems\n const rightIndex = this.config.rightItems.findIndex(item => item.id === id);\n if (rightIndex !== -1) {\n this.config.rightItems[rightIndex] = new_menu;\n return true;\n }\n\n return false;\n }\n\n setBrand(brand, icon=null) {\n this.config.brand = brand;\n this.config.brandIcon = icon || this.config.brandIcon;\n this.render();\n }\n\n setUser(user) {\n if (!user) {\n this.replaceMenuItem('user', this.loginMenu);\n } else {\n this.userMenu.label = user.get(\"display_name\");\n this._updateUserAvatar(user);\n this.replaceMenuItem('login', this.userMenu);\n }\n this.setModel(user);\n }\n\n _onModelChange() {\n if (this.model) {\n this.userMenu.label = this.model.get(\"display_name\");\n this._updateUserAvatar(this.model);\n }\n if (this.isMounted()) {\n this.render();\n }\n }\n\n _updateUserAvatar(user) {\n if (!this.userMenu || !user) return;\n const avatar = user.get('avatar');\n if (avatar) {\n // Extract URL from avatar object (try renditions first, then direct url)\n const url = avatar?.renditions?.square_sm?.url || avatar?.url || (typeof avatar === 'string' ? avatar : null);\n this.userMenu.avatarUrl = url || null;\n } else {\n this.userMenu.avatarUrl = null;\n }\n }\n\n /**\n * Get template based on display mode\n */\n async getTemplate() {\n return `\n <div class=\"container-fluid\">\n {{#data.showSidebarToggle}}\n <button class=\"topnav-sidebar-toggle me-2\" data-action=\"{{data.sidebarToggleAction}}\" aria-label=\"Toggle Sidebar\">\n <i class=\"bi bi-chevron-right toggle-chevron\"></i>\n </button>\n {{/data.showSidebarToggle}}\n\n {{#data.showGroupInfo}}\n <div class=\"navbar-brand d-flex align-items-center\">\n {{#data.groupIcon}}<i class=\"{{data.groupIcon}} me-2\"></i>{{/data.groupIcon}}\n <div>\n <span class=\"topnav-group-name\"\n role=\"button\"\n tabindex=\"0\"\n data-action=\"open-group-selector\"\n style=\"cursor: pointer;\">\n {{data.currentGroupName}}\n </span>\n {{#data.showPageTitle}}\n <span class=\"text-muted mx-2\">|</span>\n <span>{{data.currentPageName}}</span>\n {{/data.showPageTitle}}\n </div>\n </div>\n {{/data.showGroupInfo}}\n\n {{#data.showPageInfo}}\n <div class=\"navbar-brand d-flex align-items-center\">\n {{#data.currentPageIcon}}<i class=\"{{data.currentPageIcon}} me-2\"></i>{{/data.currentPageIcon}}\n <div>\n <span>{{data.currentPageName}}</span>\n {{#data.currentPageDescription}}\n <small class=\"d-block\" style=\"font-size: 0.75rem; line-height: 1;\">{{data.currentPageDescription}}</small>\n {{/data.currentPageDescription}}\n </div>\n </div>\n {{/data.showPageInfo}}\n\n {{#data.showBrand}}\n <a class=\"navbar-brand\" href=\"{{data.brandRoute}}\">\n {{#data.brandIcon}}<i class=\"{{data.brandIcon}} me-2\"></i>{{/data.brandIcon}}\n {{data.brand}}\n </a>\n {{/data.showBrand}}\n\n <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#{{data.navbarId}}\">\n <span class=\"navbar-toggler-icon\"></span>\n </button>\n\n <div class=\"collapse navbar-collapse\" id=\"{{data.navbarId}}\">\n {{#data.showNavItems}}\n <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">\n {{#data.navItems}}\n <li class=\"nav-item\">\n <a class=\"nav-link {{#active}}active{{/active}}\" href=\"{{route}}\" {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{text}}\n </a>\n </li>\n {{/data.navItems}}\n </ul>\n {{/data.showNavItems}}\n\n {{#data.hasRightItems}}\n <div class=\"navbar-nav ms-auto\">\n {{#data.rightItems}}\n {{#isGroupSelector}}\n <div data-container=\"group-selector-{{id}}\"></div>\n {{/isGroupSelector}}\n {{^isGroupSelector}}\n {{#isDropdown}}\n <div class=\"nav-item dropdown\">\n <a class=\"nav-link dropdown-toggle\" role=\"button\" data-bs-toggle=\"dropdown\" aria-expanded=\"false\">\n {{#avatarUrl}}<img src=\"{{avatarUrl}}\" class=\"rounded-circle me-1\" style=\"width: 24px; height: 24px; object-fit: cover;\" alt=\"\" />{{/avatarUrl}}\n {{^avatarUrl}}{{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}{{/avatarUrl}}\n {{label}}\n </a>\n <ul class=\"dropdown-menu dropdown-menu-end\">\n {{#items}}\n {{#divider}}\n <li><hr class=\"dropdown-divider\"></li>\n {{/divider}}\n {{#isHeader}}\n <li><h6 class=\"dropdown-header\">{{header}}</h6></li>\n {{/isHeader}}\n {{#isHtml}}\n <li><span class=\"dropdown-item-text\">{{{html}}}</span></li>\n {{/isHtml}}\n {{^divider}}{{^isHeader}}{{^isHtml}}\n <li>\n <a class=\"dropdown-item {{#active}}active{{/active}}\" role=\"button\" {{#action}}data-action=\"{{action}}\"{{/action}}>\n {{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}\n {{label}}\n </a>\n </li>\n {{/isHtml}}{{/isHeader}}{{/divider}}\n {{/items}}\n </ul>\n </div>\n {{/isDropdown}}\n {{^isDropdown}}\n {{#isButton}}\n <button class=\"{{buttonClass}}\" data-action=\"{{action}}\" data-id=\"{{id}}\" {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#iconHtml}}{{{iconHtml}}}{{/iconHtml}}{{^iconHtml}}{{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}{{/iconHtml}}\n {{label}}\n </button>\n {{/isButton}}\n {{^isButton}}\n <a class=\"nav-link\" href=\"{{href}}\" {{#action}}data-action=\"{{action}}\"{{/action}} {{#tooltip}}data-bs-toggle=\"tooltip\" data-bs-placement=\"bottom\" data-bs-title=\"{{tooltip}}\"{{/tooltip}}>\n {{#iconHtml}}{{{iconHtml}}}{{/iconHtml}}{{^iconHtml}}{{#icon}}<i class=\"{{icon}} me-1\"></i>{{/icon}}{{/iconHtml}}\n {{label}}\n </a>\n {{/isButton}}\n {{/isDropdown}}\n {{/isGroupSelector}}\n {{/data.rightItems}}\n </div>\n {{/data.hasRightItems}}\n </div>\n </div>\n `;\n }\n\n /**\n * Process and normalize data before rendering (like Sidebar)\n */\n async onBeforeRender() {\n await super.onBeforeRender();\n\n const app = this.getApp();\n // Use cached currentGroup or fall back to app.activeGroup\n const activeGroup = this.currentGroup || app?.activeGroup;\n\n // Determine what to show based on display mode\n const showGroupInfo = this.displayMode === 'group' || this.displayMode === 'group_page_titles';\n const showPageTitle = this.displayMode === 'group_page_titles';\n const showPageInfo = this.displayMode === 'page' || this.displayMode === 'both';\n const showBrand = !showGroupInfo && !showPageInfo;\n const showNavItems = this.displayMode === 'menu' || this.displayMode === 'both';\n\n // Filter navItems based on permissions\n const navItems = this.filterItemsByPermissions(this.config.navItems || []);\n\n // Process right items\n const rightItems = this.processRightItems(this.config.rightItems || []);\n\n this.data = {\n // Brand information\n brand: this.config.brand,\n brandIcon: this.config.brandIcon,\n brandRoute: this.config.brandRoute,\n showBrand: showBrand,\n\n // Navbar configuration\n navbarId: `navbar-${this.id}`,\n\n // Navigation items\n navItems: navItems,\n showNavItems: showNavItems,\n\n // Right items\n rightItems: rightItems,\n hasRightItems: rightItems.length > 0,\n\n // Group display\n showGroupInfo: showGroupInfo,\n showPageTitle: showPageTitle,\n currentGroupName: activeGroup?.get?.('name') || activeGroup?.name || 'Select Group',\n groupIcon: this.groupIcon,\n\n // Page display\n showPageInfo: showPageInfo,\n currentPageName: this.currentPage?.title || this.currentPage?.name || '',\n currentPageIcon: this.currentPage?.icon || this.currentPage?.pageIcon || '',\n currentPageDescription: this.showPageDescription ? this.currentPage?.description : '',\n\n // Sidebar toggle\n showSidebarToggle: this.config.showSidebarToggle,\n sidebarToggleAction: this.config.sidebarToggleAction,\n\n // Display mode\n displayMode: this.displayMode\n };\n }\n\n /**\n * Process right items configuration\n */\n processRightItems(rightItems) {\n return this.filterItemsByPermissions(rightItems).map(item => {\n const processedItem = { ...item };\n\n // Filter dropdown items by permissions and annotate special item types\n if (item.items) {\n processedItem.items = this.filterItemsByPermissions(item.items).map(subItem => {\n const processed = { ...subItem };\n if (subItem.divider) {\n // already handled by template\n } else if (subItem.header !== undefined) {\n // Category heading — renders as Bootstrap dropdown-header\n processed.isHeader = true;\n } else if (subItem.text !== undefined) {\n // Raw HTML text node — renders as non-interactive dropdown-item-text\n processed.isHtml = true;\n processed.html = subItem.text;\n }\n return processed;\n });\n }\n\n // Check for group selector type\n if (item.type === 'group-selector') {\n processedItem.isGroupSelector = true;\n processedItem.isDropdown = false;\n processedItem.isButton = false;\n\n // Create group selector button with smart defaults\n // Only pass through explicitly provided options\n const groupSelectorOptions = {\n containerId: `group-selector-${item.id || 'default'}`\n };\n\n // Only add options if explicitly provided (allow auto-detection to work)\n if (item.Collection !== undefined) groupSelectorOptions.Collection = item.Collection;\n if (item.collection !== undefined) groupSelectorOptions.collection = item.collection;\n if (item.currentGroup !== undefined) groupSelectorOptions.currentGroup = item.currentGroup;\n if (item.buttonClass !== undefined) groupSelectorOptions.buttonClass = item.buttonClass;\n if (item.buttonIcon !== undefined) groupSelectorOptions.buttonIcon = item.buttonIcon;\n if (item.defaultText !== undefined) groupSelectorOptions.defaultText = item.defaultText;\n if (item.itemTemplate !== undefined) groupSelectorOptions.itemTemplate = item.itemTemplate;\n if (item.searchFields !== undefined) groupSelectorOptions.searchFields = item.searchFields;\n if (item.headerText !== undefined) groupSelectorOptions.headerText = item.headerText;\n if (item.searchPlaceholder !== undefined) groupSelectorOptions.searchPlaceholder = item.searchPlaceholder;\n if (item.autoSetActiveGroup !== undefined) groupSelectorOptions.autoSetActiveGroup = item.autoSetActiveGroup;\n if (item.onGroupSelected !== undefined) groupSelectorOptions.onGroupSelected = item.onGroupSelected;\n\n const groupSelector = new GroupSelectorButton(groupSelectorOptions);\n\n // Store reference for click-to-open functionality\n this.groupSelectorButton = groupSelector;\n\n // Add as child view\n this.addChild(groupSelector);\n\n } else if (processedItem.items && processedItem.items.length > 0) {\n // Dropdown menu\n processedItem.isDropdown = true;\n processedItem.isButton = false;\n } else if (item.buttonClass) {\n // Button\n processedItem.isButton = true;\n processedItem.isDropdown = false;\n } else {\n // Link\n processedItem.isButton = false;\n processedItem.isDropdown = false;\n }\n\n // Store handler if provided\n if (item.handler) {\n this.rightItemHandlers = this.rightItemHandlers || new Map();\n this.rightItemHandlers.set(item.id, item.handler);\n }\n\n return processedItem;\n });\n }\n\n /**\n * Setup listeners for page change events\n */\n setupPageListeners() {\n // Use global MOJO event bus if available\n this.getApp().events.on(\"page:show\", (data) => {\n this.onPageChanged(data);\n });\n }\n\n /**\n * Setup listeners for group change events\n */\n setupGroupListeners() {\n const app = this.getApp();\n if (!app?.events) return;\n\n // Listen for group changes and re-render if showing group info\n app.events.on(['group:changed', 'group:loaded'], (data) => {\n // Update our reference to current group\n if (data?.group) {\n this.currentGroup = data.group;\n }\n\n if (this.displayMode === 'group' || this.displayMode === 'group_page_titles') {\n if (this.mounted) {\n this.render();\n }\n }\n });\n }\n\n /**\n * Handle page before change event\n * @param {object} data - Event data\n */\n onPageBeforeChange(data) {\n // Can be used to show loading state\n if (this.displayMode === 'page' || this.displayMode === 'both') {\n // Optionally show loading indicator\n }\n }\n\n /**\n * Handle page changed event\n * @param {object} data - Event data with previousPage and currentPage\n */\n onPageChanged(data) {\n this.previousPage = this.currentPage;\n this.currentPage = data.page;\n\n // Update display based on mode\n if (this.displayMode === 'page' || this.displayMode === 'both') {\n this.updatePageDisplay();\n }\n\n // Update active menu items\n if (this.displayMode === 'menu' || this.displayMode === 'both') {\n if (this.currentPage && this.currentPage.route) {\n this.updateActiveItem(this.currentPage.route);\n }\n }\n }\n\n /**\n * Update the display to show current page info\n */\n updatePageDisplay() {\n if (!this.currentPage) return;\n\n // Just trigger re-render, onBeforeRender will handle the data processing\n if (this.mounted) {\n this.render();\n }\n }\n\n updateActiveItem(currentRoute) {\n // Normalize routes for comparison\n const normalizeRoute = (route) => {\n if (!route) return '/';\n return route.startsWith('/') ? route : `/${route}`;\n };\n\n const normalizedCurrentRoute = normalizeRoute(currentRoute);\n\n // Update active states with improved matching\n const navItems = this.data.navItems.map(item => {\n const normalizedItemRoute = normalizeRoute(item.route);\n\n // Check for active state\n let isActive = false;\n\n if (normalizedItemRoute === '/' && normalizedCurrentRoute === '/') {\n // Exact match for home route\n isActive = true;\n } else if (normalizedItemRoute !== '/' && normalizedCurrentRoute !== '/') {\n // For non-home routes, check if current route starts with nav item route\n // This allows /users to be active when on /users/123\n isActive = normalizedCurrentRoute.startsWith(normalizedItemRoute) ||\n normalizedCurrentRoute === normalizedItemRoute;\n }\n\n return {\n ...item,\n active: isActive\n };\n });\n\n this.updateData({ navItems }, true);\n }\n\n /**\n * Wire any [data-bs-toggle=\"dropdown\"] elements rendered inside this\n * TopNav so they open on click. Bootstrap's data-API doesn't always\n * pick up dynamically-rendered toggles, so we attach instances\n * eagerly. Re-runs after every render to cover the setUser swap\n * (login -> userMenu) and similar reflows.\n */\n _attachDropdowns() {\n if (!this.element) return;\n if (!window.bootstrap?.Dropdown) {\n if (!TopNav._warnedNoBootstrap) {\n TopNav._warnedNoBootstrap = true;\n console.warn('[TopNav] window.bootstrap.Dropdown not available — dropdown toggles will not auto-attach.');\n }\n return;\n }\n const toggles = this.element.querySelectorAll('[data-bs-toggle=\"dropdown\"]');\n toggles.forEach(el => window.bootstrap.Dropdown.getOrCreateInstance(el));\n }\n\n async onAfterRender() {\n await super.onAfterRender();\n this._attachDropdowns();\n }\n\n onPassThruActionProfile() {\n // Implement profile functionality here\n this.getApp().events.emit(\"portal:action\", {action: \"profile\"});\n }\n\n onActionSettings() {\n // Implement settings functionality here\n this.getApp().events.emit(\"portal:action\", {action: \"settings\"});\n }\n\n onActionLogout() {\n // Implement logout functionality here\n this.getApp().events.emit(\"auth:logout\", {action: \"logout\"});\n }\n\n /**\n * Handle open group selector action (from clicking group name in brand)\n */\n async onActionOpenGroupSelector(event) {\n // If we have a group selector button, trigger its dialog\n if (this.groupSelectorButton) {\n await this.groupSelectorButton.onActionShowSelector(event);\n return true;\n }\n\n // If no group selector in rightItems, create a temporary one\n const { GroupList } = await import('@core/models/Group.js');\n const tempSelector = new GroupSelectorButton({\n Collection: GroupList,\n currentGroup: this.getApp()?.activeGroup\n });\n\n await tempSelector.onActionShowSelector(event);\n return true;\n }\n\n /**\n * Handle dynamic action dispatch for right items\n */\n async handleAction(actionName, event, element) {\n // Check for custom handler first\n const itemId = element.getAttribute('data-id');\n if (itemId && this.rightItemHandlers && this.rightItemHandlers.has(itemId)) {\n const handler = this.rightItemHandlers.get(itemId);\n if (typeof handler === 'function') {\n return await handler.call(this, actionName, event, element);\n }\n }\n\n // Fallback to default action methods\n const methodName = `onAction${actionName.charAt(0).toUpperCase() + actionName.slice(1).replace(/-([a-z])/g, (g) => g[1].toUpperCase())}`;\n if (typeof this[methodName] === 'function') {\n return await this[methodName](event, element);\n }\n\n // Emit action event if no handler found\n this.emit('action', {\n action: actionName,\n event: event,\n element: element,\n topnav: this\n });\n }\n\n /**\n * Handle default actions by searching through rightItems and navItems\n */\n async onActionDefault(action, event, el) {\n // Check navItems first\n if (this.config.navItems) {\n for (const item of this.config.navItems) {\n if (item.action === action && item.handler) {\n await item.handler.call(this, action, event, el);\n return true;\n }\n }\n }\n\n // Check rightItems\n if (this.config.rightItems) {\n for (const item of this.config.rightItems) {\n if (item.action === action && item.handler) {\n await item.handler.call(this, action, event, el);\n return true;\n }\n // Also check dropdown items\n if (item.items) {\n for (const subItem of item.items) {\n if (subItem.action === action && subItem.handler) {\n await subItem.handler.call(this, action, event, el);\n return true;\n }\n }\n }\n }\n }\n\n this.getApp().events.emit(\"portal:action\", { action, event, el });\n\n return false;\n }\n\n /**\n * Filter items by user permissions\n */\n filterItemsByPermissions(items) {\n if (!items) return [];\n\n const app = this.getApp();\n const activeUser = app?.activeUser;\n\n return items.filter(item => {\n // If item has permissions and user exists, check permissions\n if (item.permissions && activeUser) {\n return activeUser.hasPermission(item.permissions);\n }\n // If no permissions required or no user, show the item\n return true;\n });\n }\n\n}\n\nexport default TopNav;\n","/**\n * AuthRequiredError - Thrown by the pre-request auth gate when the access\n * token is expired and cannot be refreshed. Rest recognizes it by name and\n * short-circuits the request to a 401 response without calling fetch.\n */\nexport class AuthRequiredError extends Error {\n constructor(message = 'Authentication required') {\n super(message);\n this.name = 'AuthRequiredError';\n this.reason = 'unauthorized';\n }\n}\n\n/**\n * Token - Individual JWT token handling\n * Handles decoding, validation, and data extraction for a single token\n */\nclass Token {\n constructor(token) {\n this.token = token;\n this.payload = null;\n this.uid = null;\n this.email = null;\n this.name = null;\n this.exp = null;\n this.iat = null;\n this.isValidToken = false;\n\n this._decode();\n }\n\n /**\n * Decode JWT token payload (client-side only, no verification)\n * @private\n */\n _decode() {\n if (!this.token || typeof this.token !== 'string') {\n return;\n }\n\n try {\n const parts = this.token.split('.');\n if (parts.length !== 3) {\n return;\n }\n\n // Decode the payload (second part)\n const payload = parts[1];\n\n // Handle URL-safe base64\n let base64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const padding = 4 - (base64.length % 4);\n if (padding !== 4) {\n base64 += '='.repeat(padding);\n }\n\n const decoded = atob(base64);\n this.payload = JSON.parse(decoded);\n\n // Extract common properties\n this.uid = this.payload.uid || this.payload.sub || this.payload.user_id || null;\n this.email = this.payload.email || null;\n this.name = this.payload.name || this.payload.username || null;\n this.exp = this.payload.exp ? new Date(this.payload.exp * 1000) : null;\n this.iat = this.payload.iat ? new Date(this.payload.iat * 1000) : null;\n\n // Determine validity\n this.isValidToken = this._checkValidity();\n } catch (error) {\n this.payload = null;\n }\n }\n\n /**\n * Check token validity\n * @private\n * @returns {boolean} True if token is valid\n */\n _checkValidity() {\n if (!this.token || !this.payload) {\n return false;\n }\n\n // Check expiry if present\n if (this.payload.exp) {\n const now = Math.floor(Date.now() / 1000);\n return now < this.payload.exp;\n }\n\n // If no expiry, consider valid\n return true;\n }\n\n /**\n * Decode JWT token payload (client-side only, no verification)\n * @returns {object|null} Decoded payload or null if invalid\n */\n decode() {\n return this.payload;\n }\n\n /**\n * Get user ID from token\n * @returns {string|null} User ID or null if not found\n */\n getUserId() {\n return this.uid;\n }\n\n /**\n * Check if token is valid (exists and not expired)\n * @returns {boolean} True if token is valid\n */\n isValid() {\n return this.isValidToken;\n }\n\n /**\n * Check if token will expire soon\n * @param {number} thresholdMinutes - Minutes before expiry to consider \"soon\"\n * @returns {boolean} True if expiring soon\n */\n isExpiringSoon(thresholdMinutes = 5) {\n if (!this.payload?.exp) {\n return false;\n }\n\n const now = Math.floor(Date.now() / 1000);\n const threshold = thresholdMinutes * 60;\n return (this.payload.exp - now) <= threshold;\n }\n\n /**\n * Check if token is expired\n * @returns {boolean} True if expired\n */\n isExpired() {\n if (!this.payload?.exp) {\n return false;\n }\n\n const now = Math.floor(Date.now() / 1000);\n return now >= this.payload.exp;\n }\n\n /**\n * Get token age in minutes\n * @returns {number|null} Age in minutes since token was issued, or null if no iat\n */\n getAgeMinutes() {\n if (!this.payload?.iat) {\n return null;\n }\n const now = Math.floor(Date.now() / 1000);\n const ageSeconds = now - this.payload.iat;\n return Math.floor(ageSeconds / 60);\n }\n\n /**\n * Get authorization header value\n * @returns {string|null} Bearer token string or null if no token\n */\n getAuthHeader() {\n return this.token ? `Bearer ${this.token}` : null;\n }\n\n /**\n * Get basic user info from token\n * @returns {object|null} User info or null\n */\n getUserInfo() {\n if (!this.payload) {\n return null;\n }\n\n return {\n uid: this.uid,\n email: this.email,\n name: this.name,\n exp: this.exp,\n iat: this.iat\n };\n }\n}\n\n/**\n * TokenManager - Simplified JWT token handling for MOJO Auth\n * Focuses on core token operations: storage, validation, and user ID extraction\n */\n\nexport default class TokenManager {\n constructor() {\n this.tokenKey = 'access_token';\n this.refreshTokenKey = 'refresh_token';\n this.authCodeKey = 'auth_code';\n this.tokenInstance = null;\n // Single-flight guard for refreshToken(). Holds the in-flight\n // Promise<boolean> while a refresh is pending; null otherwise.\n this._refreshPromise = null;\n // Single-flight guard for handleAuthCodeFromURL() / exchangeAuthCode().\n this._exchangePromise = null;\n }\n\n /**\n * Store authentication tokens\n * @param {string} token - Access token\n * @param {string} refreshToken - Refresh token (optional)\n * @param {boolean} persistent - Use localStorage if true, sessionStorage if false\n */\n setTokens(token, refreshToken = null, persistent = true) {\n const storage = persistent ? localStorage : sessionStorage;\n this.tokenInstance = new Token(token);\n if (token) {\n storage.setItem(this.tokenKey, token);\n }\n\n if (refreshToken) {\n storage.setItem(this.refreshTokenKey, refreshToken);\n }\n }\n\n /**\n * Get stored access token\n * @returns {string|null} Access token or null if not found\n */\n getToken() {\n return localStorage.getItem(this.tokenKey) ||\n sessionStorage.getItem(this.tokenKey);\n }\n\n /**\n * Get stored refresh token\n * @returns {string|null} Refresh token or null if not found\n */\n getRefreshToken() {\n return localStorage.getItem(this.refreshTokenKey) ||\n sessionStorage.getItem(this.refreshTokenKey);\n }\n\n /**\n * Clear all stored tokens\n */\n clearTokens() {\n localStorage.removeItem(this.tokenKey);\n localStorage.removeItem(this.refreshTokenKey);\n sessionStorage.removeItem(this.tokenKey);\n sessionStorage.removeItem(this.refreshTokenKey);\n }\n\n /**\n * Get Token instance for current stored token\n * @returns {Token|null} Token instance or null if no token\n */\n getTokenInstance() {\n const currentToken = this.getToken();\n\n // If no token stored, clear instance and return null\n if (!currentToken) {\n this.tokenInstance = null;\n return null;\n }\n\n // If instance doesn't exist or token changed, create new instance\n if (!this.tokenInstance || this.tokenInstance.token !== currentToken) {\n this.tokenInstance = new Token(currentToken);\n }\n\n return this.tokenInstance;\n }\n\n /**\n * Get Token instance for refresh token\n * @returns {Token|null} Token instance or null if no refresh token\n */\n getRefreshTokenInstance() {\n const currentRefreshToken = this.getRefreshToken();\n\n // If no refresh token stored, clear instance and return null\n if (!currentRefreshToken) {\n this._refreshTokenInstance = null;\n return null;\n }\n\n // If instance doesn't exist or token changed, create new instance\n if (!this._refreshTokenInstance || this._refreshTokenInstance.token !== currentRefreshToken) {\n this._refreshTokenInstance = new Token(currentRefreshToken);\n }\n\n return this._refreshTokenInstance;\n }\n\n /**\n * Decode JWT token payload (client-side only, no verification)\n * @param {string} token - JWT token\n * @returns {object|null} Decoded payload or null if invalid\n */\n decode(token = null) {\n const jwt = token || this.getToken();\n return new Token(jwt).decode();\n }\n\n /**\n * Get user ID from token\n * @returns {string|null} User ID or null if not found\n */\n getUserId() {\n const currentToken = this.getTokenInstance();\n return currentToken ? currentToken.getUserId() : null;\n }\n\n /**\n * Check if current token is valid (exists and not expired)\n * @returns {boolean} True if token is valid\n */\n isValid() {\n const currentToken = this.getTokenInstance();\n return currentToken ? currentToken.isValid() : false;\n }\n\n /**\n * Check if token will expire soon\n * @param {number} thresholdMinutes - Minutes before expiry to consider \"soon\"\n * @returns {boolean} True if expiring soon\n */\n isExpiringSoon(thresholdMinutes = 5) {\n const currentToken = this.getTokenInstance();\n return currentToken ? currentToken.isExpiringSoon(thresholdMinutes) : false;\n }\n\n /**\n * Get authorization header value\n * @returns {string|null} Bearer token string or null if no token\n */\n getAuthHeader() {\n const currentToken = this.getTokenInstance();\n return currentToken ? currentToken.getAuthHeader() : null;\n }\n\n /**\n * Get basic user info from token\n * @returns {object|null} User info or null\n */\n getUserInfo() {\n const currentToken = this.getTokenInstance();\n return currentToken ? currentToken.getUserInfo() : null;\n }\n\n /**\n * Check current token status and determine what action is needed\n * @returns {object} Status object with action and details\n */\n checkTokenStatus() {\n const token = this.getTokenInstance();\n const refreshToken = this.getRefreshTokenInstance();\n\n // If no access token or it's invalid/expired\n if (!token || !token.isValid() || token.isExpired()) {\n // Check if refresh is possible\n if (!refreshToken || !refreshToken.isValid() || refreshToken.isExpired()) {\n return {\n action: 'logout',\n reason: 'Both access and refresh tokens are invalid/expired'\n };\n }\n\n return {\n action: 'refresh',\n reason: 'Access token invalid/expired but refresh token valid'\n };\n }\n\n // Access token is valid - check if it needs refreshing soon\n if (token.isExpiringSoon(10) || (token.getAgeMinutes() && token.getAgeMinutes() > 60)) {\n // Only suggest refresh if refresh token is still valid\n if (!refreshToken || !refreshToken.isValid() || refreshToken.isExpired()) {\n return {\n action: 'none',\n reason: 'Access token expiring but refresh token invalid'\n };\n }\n\n return {\n action: 'refresh',\n reason: 'Access token expiring soon or aged'\n };\n }\n\n return {\n action: 'none',\n reason: 'All tokens valid and not expiring soon'\n };\n }\n\n /**\n * Check tokens and take appropriate action\n * @param {object} app - App instance for events and API calls\n * @returns {Promise<boolean>} True if action was taken\n */\n async checkAndRefreshTokens(app) {\n const status = this.checkTokenStatus();\n\n switch (status.action) {\n case 'logout':\n app.events.emit(\"auth:unauthorized\");\n this.stopAutoRefresh();\n return true;\n\n case 'refresh':\n await this.refreshToken(app);\n return true;\n\n default:\n return false;\n }\n }\n\n startAutoRefresh(app) {\n this.stopAutoRefresh();\n this._tokenWatcher = setInterval(() => {\n this.checkAndRefreshTokens(app);\n }, 60000);\n }\n\n stopAutoRefresh() {\n if (this._tokenWatcher) {\n clearInterval(this._tokenWatcher);\n this._tokenWatcher = null;\n }\n }\n\n /**\n * Refresh the access token using the stored refresh token.\n * Single-flight: concurrent callers share one in-flight POST.\n * @param {object} app - App instance (needs .rest and .events)\n * @returns {Promise<boolean>} true on success, false on any failure\n */\n async refreshToken(app) {\n if (this._refreshPromise) {\n return this._refreshPromise;\n }\n\n this._refreshPromise = this._doRefresh(app).finally(() => {\n this._refreshPromise = null;\n });\n\n return this._refreshPromise;\n }\n\n /**\n * Perform the actual refresh network call. Always resolves to a boolean;\n * never throws. Emits auth:unauthorized / auth:token:refresh:failed on\n * failure paths, matching prior behavior.\n * @private\n */\n async _doRefresh(app) {\n const refreshTokenInstance = this.getRefreshTokenInstance();\n\n // Double-check refresh token validity before attempting refresh\n if (!refreshTokenInstance || !refreshTokenInstance.isValid() || refreshTokenInstance.isExpired()) {\n app.events.emit(\"auth:unauthorized\");\n this.stopAutoRefresh();\n return false;\n }\n\n try {\n const response = await app.rest.POST('/api/token/refresh', {\n refresh_token: refreshTokenInstance.token\n });\n\n const { access_token, refresh_token } = response.data.data;\n\n // Clear old cached instances so new tokens are loaded\n this.tokenInstance = null;\n this._refreshTokenInstance = null;\n\n // Store new tokens\n this.setTokens(access_token, refresh_token);\n app.rest.setAuthToken(access_token);\n\n // Emit success event\n app.events.emit('auth:token:refreshed', {\n newToken: access_token,\n newRefreshToken: refresh_token\n });\n\n console.log('Token refreshed successfully');\n return true;\n } catch (error) {\n // Check if it's an authentication error (refresh token invalid)\n if (error.status === 401 || error.status === 403) {\n app.events.emit(\"auth:unauthorized\");\n this.stopAutoRefresh();\n } else {\n // For other errors, emit specific event but don't logout\n app.events.emit('auth:token:refresh:failed', { error });\n }\n return false;\n }\n }\n\n /**\n * Read an `?auth_code=` param from window.location, scrub it from the URL,\n * and exchange it for tokens. Resolves to the user dict on success or\n * null when no param is present or the exchange fails. Never throws.\n *\n * The URL scrub happens *synchronously*, before any await — the auth\n * code is held in a closure local so a slow exchange cannot leak it to\n * third-party scripts that read location.search after page load.\n *\n * Single-flight: concurrent callers (e.g. PortalApp boot + a custom\n * AuthApp boot) share the same in-flight POST.\n *\n * @param {object} app - App instance (needs .rest and .events)\n * @returns {Promise<object|null>} user dict on success, null otherwise\n */\n async handleAuthCodeFromURL(app) {\n if (this._exchangePromise) {\n return this._exchangePromise;\n }\n\n if (typeof window === 'undefined' || !window.location) {\n return null;\n }\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get(this.authCodeKey);\n if (!code) {\n return null;\n }\n\n // Scrub before any network call.\n params.delete(this.authCodeKey);\n const remaining = params.toString();\n const cleanUrl = window.location.pathname\n + (remaining ? `?${remaining}` : '')\n + (window.location.hash || '');\n window.history.replaceState({}, '', cleanUrl);\n\n return this.exchangeAuthCode(app, code);\n }\n\n /**\n * Exchange a one-time auth handoff code for access + refresh tokens.\n * Stores tokens via setTokens() and emits 'auth:login' on success;\n * emits 'auth:exchange:failed' on any failure. Never throws.\n *\n * Single-flight: concurrent callers share one in-flight POST. Mirrors\n * the refreshToken() pattern.\n *\n * @param {object} app - App instance (needs .rest and .events)\n * @param {string} code - 32-hex auth handoff code\n * @returns {Promise<object|null>} user dict on success, null on failure\n */\n async exchangeAuthCode(app, code) {\n if (this._exchangePromise) {\n return this._exchangePromise;\n }\n\n this._exchangePromise = this._doExchange(app, code).finally(() => {\n this._exchangePromise = null;\n });\n\n return this._exchangePromise;\n }\n\n /**\n * Perform the actual exchange network call. Always resolves; never\n * throws. Emits auth:login on success, auth:exchange:failed on failure.\n * @private\n */\n async _doExchange(app, code) {\n try {\n const response = await app.rest.POST('/api/auth/exchange', { code });\n // Tolerate the same response wrappers the rest of the codebase\n // does: { data: { data: {...} } } | { data: {...} } | flat.\n const payload = response?.data?.data || response?.data || response;\n const access_token = payload?.access_token;\n const refresh_token = payload?.refresh_token;\n const user = payload?.user;\n\n if (!access_token) {\n throw new Error('No access_token in /api/auth/exchange response');\n }\n\n // Clear cached instances so new tokens are loaded fresh.\n this.tokenInstance = null;\n this._refreshTokenInstance = null;\n\n this.setTokens(access_token, refresh_token);\n app.rest.setAuthToken(access_token);\n\n app.events.emit('auth:login', user);\n return user;\n } catch (error) {\n app.events.emit('auth:exchange:failed', { error });\n return null;\n }\n }\n\n /**\n * Gate an outgoing request on token validity. Intended for use by a\n * pre-request interceptor.\n *\n * - Valid access token: returns (no network).\n * - Expired access token + valid refresh: awaits the (single-flight)\n * refresh; throws AuthRequiredError if the refresh fails.\n * - Both tokens expired/invalid: emits auth:unauthorized and throws\n * AuthRequiredError without hitting the network.\n *\n * @param {object} app - App instance (needs .rest and .events)\n * @throws {AuthRequiredError} when the request must not be sent\n */\n async ensureValidToken(app) {\n const status = this.checkTokenStatus();\n\n if (status.action === 'logout') {\n app.events.emit('auth:unauthorized');\n this.stopAutoRefresh();\n throw new AuthRequiredError('Both access and refresh tokens are invalid');\n }\n\n if (status.action === 'refresh') {\n const ok = await this.refreshToken(app);\n if (!ok) {\n throw new AuthRequiredError('Token refresh failed');\n }\n }\n }\n}\n"],"names":["ResultsView","View","constructor","options","super","className","template","this","parentView","handleActionSelectItem","event","element","preventDefault","itemIndex","parseInt","getAttribute","handleItemSelection","handleActionClearSearch","_element","clearSearch","onAfterRender","maxHeight","container","querySelector","style","SimpleSearchView","Collection","collection","itemTemplate","getDefaultItemTemplate","searchFields","collectionParams","size","headerText","headerIcon","searchPlaceholder","loadingText","noResultsText","emptyText","emptySubtext","emptyIcon","footerContent","footerIcon","showExitButton","searchValue","filteredItems","loading","hasSearched","searchTimer","debounceMs","addClass","resultsView","addChild","onInit","setupCollection","autoLoad","loadItems","Object","assign","params","on","updateFilteredItems","updateResultsView","fetch","error","console","app","getApp","showError","warn","toJSON","getNestedValue","obj","path","split","reduce","current","key","getViewData","showFooter","hasItems","length","hasFilteredItems","hasSearchValue","processedItems","map","item","index","itemContent","processItemTemplate","data","items","showEmpty","showNoResults","showResultsCount","filteredCount","totalCount","restEnabled","meta","count","render","replace","match","prop","onPassThruActionSearchItems","value","clearTimeout","performSearch","searchParams","search","trim","setParams","isNaN","model","get","id","ModelClass","showLoading","then","hideLoading","emit","setCollection","setItemTemplate","setSearchFields","fields","Array","isArray","refresh","focusSearch","searchInput","focus","handleActionExitView","view","getItemCount","getFilteredItemCount","getSearchValue","setSearchValue","isMounted","onBeforeDestroy","off","GroupSelectorButton","tagName","GroupCollection","GroupList","currentGroup","activeGroup","buttonClass","buttonIcon","defaultText","autoSetActiveGroup","onGroupSelected","dialog","events","group","setCurrentGroup","getTemplate","onBeforeRender","displayName","name","onActionShowSelector","searchView","ModalView","title","body","scrollable","noBodyPadding","buttons","closeButton","handleGroupSelection","hide","destroy","document","show","setActiveGroup","mounted","getCurrentGroup","TOPNAV_THEME_TOKENS","TOPNAV_SHADOW_TOKENS","TopNav","themes","light","dark","clean","gradient","themeName","theme","shadowName","shadow","isAutoTheme","isAutoShadow","resolveAuto","documentElement","dataset","bsTheme","resolvedTheme","resolvedShadow","navbarClass","enableTooltips","_themes","_themeOption","_shadowOption","_themeObserver","displayMode","showPageIcon","showPageDescription","showBreadcrumbs","groupIcon","currentPage","previousPage","config","brand","brandIcon","brandRoute","navItems","rightItems","showSidebarToggle","sidebarToggleAction","userMenu","findMenuItem","loginMenu","setupPageListeners","setupGroupListeners","groupSelectorButton","_installAutoThemeSync","window","MutationObserver","html","apply","resolved","themeClasses","classList","remove","filter","c","includes","forEach","add","mutations","m","type","attributeName","observe","attributes","attributeFilter","disconnect","find","replaceMenuItem","new_menu","navIndex","findIndex","rightIndex","setBrand","icon","setUser","user","label","_updateUserAvatar","setModel","_onModelChange","avatar","url","renditions","square_sm","avatarUrl","showGroupInfo","showPageTitle","showPageInfo","showBrand","showNavItems","filterItemsByPermissions","processRightItems","navbarId","hasRightItems","currentGroupName","currentPageName","currentPageIcon","pageIcon","currentPageDescription","description","processedItem","subItem","processed","divider","header","isHeader","text","isHtml","isGroupSelector","isDropdown","isButton","groupSelectorOptions","containerId","groupSelector","handler","rightItemHandlers","Map","set","onPageChanged","onPageBeforeChange","page","updatePageDisplay","route","updateActiveItem","currentRoute","normalizeRoute","startsWith","normalizedCurrentRoute","normalizedItemRoute","isActive","active","updateData","_attachDropdowns","bootstrap","Dropdown","querySelectorAll","el","getOrCreateInstance","_warnedNoBootstrap","onPassThruActionProfile","action","onActionSettings","onActionLogout","onActionOpenGroupSelector","import","n","j","tempSelector","handleAction","actionName","itemId","has","call","methodName","charAt","toUpperCase","slice","g","topnav","onActionDefault","activeUser","permissions","hasPermission","AuthRequiredError","Error","message","reason","Token","token","payload","uid","email","exp","iat","isValidToken","_decode","parts","base64","padding","repeat","decoded","atob","JSON","parse","sub","user_id","username","Date","_checkValidity","Math","floor","now","decode","getUserId","isValid","isExpiringSoon","thresholdMinutes","threshold","isExpired","getAgeMinutes","ageSeconds","getAuthHeader","getUserInfo","TokenManager","tokenKey","refreshTokenKey","authCodeKey","tokenInstance","_refreshPromise","_exchangePromise","setTokens","refreshToken","persistent","storage","localStorage","sessionStorage","setItem","getToken","getItem","getRefreshToken","clearTokens","removeItem","getTokenInstance","currentToken","getRefreshTokenInstance","currentRefreshToken","_refreshTokenInstance","jwt","checkTokenStatus","checkAndRefreshTokens","stopAutoRefresh","startAutoRefresh","_tokenWatcher","setInterval","clearInterval","_doRefresh","finally","refreshTokenInstance","response","rest","POST","refresh_token","access_token","setAuthToken","newToken","newRefreshToken","status","handleAuthCodeFromURL","location","URLSearchParams","code","delete","remaining","toString","cleanUrl","pathname","hash","history","replaceState","exchangeAuthCode","_doExchange","ensureValidToken"],"mappings":"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"}