web-mojo 2.2.69 → 2.2.70

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 (157) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.cjs.js.map +1 -1
  3. package/dist/admin.es.js +1 -1
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.es.js +1 -1
  7. package/dist/charts.cjs.js +1 -1
  8. package/dist/charts.cjs.js.map +1 -1
  9. package/dist/charts.es.js +1 -1
  10. package/dist/charts.es.js.map +1 -1
  11. package/dist/chunks/ChatView-DH42WXgV.js +2 -0
  12. package/dist/chunks/{ChatView-Dw-iVmht.js.map → ChatView-DH42WXgV.js.map} +1 -1
  13. package/dist/chunks/ChatView-_8eQTETQ.js +2 -0
  14. package/dist/chunks/{ChatView-CZ3Key2k.js.map → ChatView-_8eQTETQ.js.map} +1 -1
  15. package/dist/chunks/Collection-BUv4E9op.js +2 -0
  16. package/dist/chunks/Collection-BUv4E9op.js.map +1 -0
  17. package/dist/chunks/Collection-r1ACzUeh.js +2 -0
  18. package/dist/chunks/Collection-r1ACzUeh.js.map +1 -0
  19. package/dist/chunks/ContextMenu-BFxliZ03.js +2 -0
  20. package/dist/chunks/{ContextMenu-8vTiZZQV.js.map → ContextMenu-BFxliZ03.js.map} +1 -1
  21. package/dist/chunks/ContextMenu-BwJJ4QJE.js +2 -0
  22. package/dist/chunks/{ContextMenu-DBw0WMTO.js.map → ContextMenu-BwJJ4QJE.js.map} +1 -1
  23. package/dist/chunks/DataView-DMpNXerv.js +2 -0
  24. package/dist/chunks/{DataView-DyJKgOn3.js.map → DataView-DMpNXerv.js.map} +1 -1
  25. package/dist/chunks/DataView-_CACqzRt.js +2 -0
  26. package/dist/chunks/{DataView-BEovBggn.js.map → DataView-_CACqzRt.js.map} +1 -1
  27. package/dist/chunks/Dialog-BVCCpLPw.js +3 -0
  28. package/dist/chunks/Dialog-BVCCpLPw.js.map +1 -0
  29. package/dist/chunks/Dialog-BYiynSW-.js +3 -0
  30. package/dist/chunks/Dialog-BYiynSW-.js.map +1 -0
  31. package/dist/chunks/FormView-Dw7HDwzy.js +3 -0
  32. package/dist/chunks/{FormView-BRHAIawp.js.map → FormView-Dw7HDwzy.js.map} +1 -1
  33. package/dist/chunks/FormView-OgrZ7x0z.js +3 -0
  34. package/dist/chunks/{FormView-B1CXO2t8.js.map → FormView-OgrZ7x0z.js.map} +1 -1
  35. package/dist/chunks/ListView-2M4I8KHF.js +2 -0
  36. package/dist/chunks/{ListView-CMZpwyyC.js.map → ListView-2M4I8KHF.js.map} +1 -1
  37. package/dist/chunks/ListView-B0QbqSPv.js +2 -0
  38. package/dist/chunks/{ListView-BLFFK_Ir.js.map → ListView-B0QbqSPv.js.map} +1 -1
  39. package/dist/chunks/MetricsCountryMapView-DDdDJQFA.js +2 -0
  40. package/dist/chunks/{MetricsCountryMapView-B0kWK-Js.js.map → MetricsCountryMapView-DDdDJQFA.js.map} +1 -1
  41. package/dist/chunks/MetricsCountryMapView-DIlezla0.js +2 -0
  42. package/dist/chunks/{MetricsCountryMapView-DuBKO7gz.js.map → MetricsCountryMapView-DIlezla0.js.map} +1 -1
  43. package/dist/chunks/MetricsMiniChartWidget-Dt2V0eXP.js +2 -0
  44. package/dist/chunks/{MetricsMiniChartWidget-Dg1e6EQJ.js.map → MetricsMiniChartWidget-Dt2V0eXP.js.map} +1 -1
  45. package/dist/chunks/MetricsMiniChartWidget-_N4kzNY_.js +2 -0
  46. package/dist/chunks/{MetricsMiniChartWidget-D1w608Jy.js.map → MetricsMiniChartWidget-_N4kzNY_.js.map} +1 -1
  47. package/dist/chunks/PDFViewer-BruR1RFn.js +2 -0
  48. package/dist/chunks/{PDFViewer-D_3V8QJe.js.map → PDFViewer-BruR1RFn.js.map} +1 -1
  49. package/dist/chunks/PDFViewer-CyGFVcvX.js +2 -0
  50. package/dist/chunks/{PDFViewer-CDeV9OBs.js.map → PDFViewer-CyGFVcvX.js.map} +1 -1
  51. package/dist/chunks/TableView-CxYpxZvr.js +2 -0
  52. package/dist/chunks/{TableView-CI_7a-kD.js.map → TableView-CxYpxZvr.js.map} +1 -1
  53. package/dist/chunks/TableView-DemRVhnX.js +2 -0
  54. package/dist/chunks/{TableView-CWk5k4LQ.js.map → TableView-DemRVhnX.js.map} +1 -1
  55. package/dist/chunks/TokenManager-BFaxNsXO.js +2 -0
  56. package/dist/chunks/{TokenManager-sZgt--C9.js.map → TokenManager-BFaxNsXO.js.map} +1 -1
  57. package/dist/chunks/TokenManager-IlBEFXqZ.js +2 -0
  58. package/dist/chunks/{TokenManager-ien2XzwO.js.map → TokenManager-IlBEFXqZ.js.map} +1 -1
  59. package/dist/chunks/UserProfileView-9vkfCPsp.js +2 -0
  60. package/dist/chunks/{UserProfileView-kupeq2rN.js.map → UserProfileView-9vkfCPsp.js.map} +1 -1
  61. package/dist/chunks/UserProfileView-tcBT6XcE.js +2 -0
  62. package/dist/chunks/{UserProfileView-DnVMHcLH.js.map → UserProfileView-tcBT6XcE.js.map} +1 -1
  63. package/dist/chunks/WebApp-BFR1zozS.js +2 -0
  64. package/dist/chunks/{WebApp-CcVF73yg.js.map → WebApp-BFR1zozS.js.map} +1 -1
  65. package/dist/chunks/WebApp-C82womPC.js +2 -0
  66. package/dist/chunks/{WebApp-Bti0Gqqo.js.map → WebApp-C82womPC.js.map} +1 -1
  67. package/dist/chunks/WebSocketClient-Ibi7mLQu.js +2 -0
  68. package/dist/chunks/{WebSocketClient-Bh0Mmtje.js.map → WebSocketClient-Ibi7mLQu.js.map} +1 -1
  69. package/dist/chunks/WebSocketClient-QaCUN3EQ.js +2 -0
  70. package/dist/chunks/{WebSocketClient-CLgYPxWX.js.map → WebSocketClient-QaCUN3EQ.js.map} +1 -1
  71. package/dist/chunks/index-BaPQHxbL.js +2 -0
  72. package/dist/chunks/{index-Da9sT-tE.js.map → index-BaPQHxbL.js.map} +1 -1
  73. package/dist/chunks/index-BdfwxVMZ.js +2 -0
  74. package/dist/chunks/{index-Aq9ke4vg.js.map → index-BdfwxVMZ.js.map} +1 -1
  75. package/dist/chunks/{version-XmirKYWA.js → version-C2yYRyPn.js} +2 -2
  76. package/dist/chunks/{version-XmirKYWA.js.map → version-C2yYRyPn.js.map} +1 -1
  77. package/dist/chunks/{version-D8JjsPW0.js → version-CaiqhdME.js} +2 -2
  78. package/dist/chunks/{version-D8JjsPW0.js.map → version-CaiqhdME.js.map} +1 -1
  79. package/dist/docit.cjs.js +1 -1
  80. package/dist/docit.cjs.js.map +1 -1
  81. package/dist/docit.es.js +1 -1
  82. package/dist/docit.es.js.map +1 -1
  83. package/dist/index.cjs.js +1 -1
  84. package/dist/index.cjs.js.map +1 -1
  85. package/dist/index.es.js +1 -1
  86. package/dist/index.es.js.map +1 -1
  87. package/dist/lightbox.cjs.js +1 -1
  88. package/dist/lightbox.cjs.js.map +1 -1
  89. package/dist/lightbox.es.js +1 -1
  90. package/dist/lightbox.es.js.map +1 -1
  91. package/dist/map.cjs.js +1 -1
  92. package/dist/map.cjs.js.map +1 -1
  93. package/dist/map.es.js +1 -1
  94. package/dist/map.es.js.map +1 -1
  95. package/dist/timeline.cjs.js +1 -1
  96. package/dist/timeline.cjs.js.map +1 -1
  97. package/dist/timeline.es.js +1 -1
  98. package/dist/timeline.es.js.map +1 -1
  99. package/dist/user-profile.cjs.js +1 -1
  100. package/dist/user-profile.es.js +1 -1
  101. package/dist/web-mojo.lite.iife.js +5435 -5435
  102. package/dist/web-mojo.lite.iife.js.map +1 -1
  103. package/dist/web-mojo.lite.iife.min.js +68 -68
  104. package/dist/web-mojo.lite.iife.min.js.map +1 -1
  105. package/package.json +1 -1
  106. package/dist/chunks/ChatView-CZ3Key2k.js +0 -2
  107. package/dist/chunks/ChatView-Dw-iVmht.js +0 -2
  108. package/dist/chunks/Collection-BWKmydl5.js +0 -2
  109. package/dist/chunks/Collection-BWKmydl5.js.map +0 -1
  110. package/dist/chunks/Collection-CmjTsmrP.js +0 -2
  111. package/dist/chunks/Collection-CmjTsmrP.js.map +0 -1
  112. package/dist/chunks/ContextMenu-8vTiZZQV.js +0 -2
  113. package/dist/chunks/ContextMenu-DBw0WMTO.js +0 -2
  114. package/dist/chunks/DataView-BEovBggn.js +0 -2
  115. package/dist/chunks/DataView-DyJKgOn3.js +0 -2
  116. package/dist/chunks/Dialog-Dhqtd9Yz.js +0 -2
  117. package/dist/chunks/Dialog-Dhqtd9Yz.js.map +0 -1
  118. package/dist/chunks/Dialog-t_9l2Mou.js +0 -2
  119. package/dist/chunks/Dialog-t_9l2Mou.js.map +0 -1
  120. package/dist/chunks/Files-6eRT5k3r.js +0 -2
  121. package/dist/chunks/Files-6eRT5k3r.js.map +0 -1
  122. package/dist/chunks/Files-Dh_5PFBn.js +0 -2
  123. package/dist/chunks/Files-Dh_5PFBn.js.map +0 -1
  124. package/dist/chunks/FormView-B1CXO2t8.js +0 -3
  125. package/dist/chunks/FormView-BRHAIawp.js +0 -3
  126. package/dist/chunks/ListView-BLFFK_Ir.js +0 -2
  127. package/dist/chunks/ListView-CMZpwyyC.js +0 -2
  128. package/dist/chunks/MetricsCountryMapView-B0kWK-Js.js +0 -2
  129. package/dist/chunks/MetricsCountryMapView-DuBKO7gz.js +0 -2
  130. package/dist/chunks/MetricsMiniChartWidget-D1w608Jy.js +0 -2
  131. package/dist/chunks/MetricsMiniChartWidget-Dg1e6EQJ.js +0 -2
  132. package/dist/chunks/PDFViewer-CDeV9OBs.js +0 -2
  133. package/dist/chunks/PDFViewer-D_3V8QJe.js +0 -2
  134. package/dist/chunks/Rest-B1eUyLX5.js +0 -2
  135. package/dist/chunks/Rest-B1eUyLX5.js.map +0 -1
  136. package/dist/chunks/Rest-BJ3Mvx1L.js +0 -2
  137. package/dist/chunks/Rest-BJ3Mvx1L.js.map +0 -1
  138. package/dist/chunks/TableView-CI_7a-kD.js +0 -2
  139. package/dist/chunks/TableView-CWk5k4LQ.js +0 -2
  140. package/dist/chunks/ToastService-C2tTooFn.js +0 -3
  141. package/dist/chunks/ToastService-C2tTooFn.js.map +0 -1
  142. package/dist/chunks/ToastService-nUaGVpSl.js +0 -3
  143. package/dist/chunks/ToastService-nUaGVpSl.js.map +0 -1
  144. package/dist/chunks/TokenManager-ien2XzwO.js +0 -2
  145. package/dist/chunks/TokenManager-sZgt--C9.js +0 -2
  146. package/dist/chunks/User-BL9M_PWB.js +0 -2
  147. package/dist/chunks/User-BL9M_PWB.js.map +0 -1
  148. package/dist/chunks/User-DqHG5Gr1.js +0 -2
  149. package/dist/chunks/User-DqHG5Gr1.js.map +0 -1
  150. package/dist/chunks/UserProfileView-DnVMHcLH.js +0 -2
  151. package/dist/chunks/UserProfileView-kupeq2rN.js +0 -2
  152. package/dist/chunks/WebApp-Bti0Gqqo.js +0 -2
  153. package/dist/chunks/WebApp-CcVF73yg.js +0 -2
  154. package/dist/chunks/WebSocketClient-Bh0Mmtje.js +0 -2
  155. package/dist/chunks/WebSocketClient-CLgYPxWX.js +0 -2
  156. package/dist/chunks/index-Aq9ke4vg.js +0 -2
  157. package/dist/chunks/index-Da9sT-tE.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"WebSocketClient-CLgYPxWX.js","sources":["../../src/core/services/WebSocketClient.js"],"sourcesContent":["/**\n * WebSocketClient - Robust, infinitely-resilient WebSocket client\n *\n * Features:\n * - Infinite auto-reconnect with capped exponential backoff + jitter (never gives up)\n * - Separate \"intentional disconnect\" flag so clean closes still reconnect\n * - Heartbeat ping/pong with timeout-driven force-reconnect\n * - Immediate reconnect nudge on browser visibility / window focus restore\n * - Optional integration with WebApp's browser:focus event bus\n * - Event-driven architecture using EventEmitter\n * - Token-based authentication\n *\n * Usage:\n * const ws = new WebSocketClient({\n * url: \"wss://api.example.com/ws/realtime\",\n * tokenPrefix: \"bearer\",\n * getToken: () => app.tokenManager.getToken(),\n * app, // optional – hooks into app.events 'browser:focus'\n * });\n *\n * ws.on('connected', () => console.log('Connected!'));\n * ws.on('message', (data) => console.log('Received:', data));\n * ws.on('reconnecting', ({attempt, delay}) => console.log(`Retry #${attempt} in ${delay}ms`));\n * ws.connect();\n */\n\nimport EventEmitter from '@core/mixins/EventEmitter.js';\n\nclass WebSocketClient {\n constructor(options = {}) {\n // ── Connection ──────────────────────────────────────────────────────────\n this.url = options.url;\n this.socket = null;\n this.isConnected = false;\n this.isConnecting = false;\n\n // ── Auth ────────────────────────────────────────────────────────────────\n this.getToken = options.getToken || null;\n this.tokenPrefix = options.tokenPrefix || 'bearer';\n\n // ── Reconnection ────────────────────────────────────────────────────────\n // `autoReconnect` – master switch (default true)\n // `_intentionalDisconnect` – set only when the caller explicitly calls\n // disconnect(), so that clean server closes\n // (code 1000) are still retried automatically.\n this.autoReconnect = options.autoReconnect !== false;\n this._intentionalDisconnect = false;\n\n this.reconnectInterval = options.reconnectInterval || 2000; // base delay ms\n this.reconnectBackoff = options.reconnectBackoff || 1.5; // multiplier\n this.maxReconnectDelay = options.maxReconnectDelay || 30000; // hard cap ms\n this.reconnectJitter = options.reconnectJitter !== false; // ±20 % jitter\n this.reconnectAttempts = 0;\n this.reconnectTimer = null;\n\n // ── Heartbeat ────────────────────────────────────────────────────────────\n this.pingInterval = options.pingInterval || 30000;\n this.pongTimeout = options.pongTimeout || 10000;\n this.pingTimer = null;\n this.pongTimer = null;\n\n // ── Optional WebApp integration ──────────────────────────────────────────\n // Pass `app` to also hook into app.events 'browser:focus' in addition to\n // the native visibility / window focus events we listen to below.\n this._app = options.app || null;\n\n // ── Debug ────────────────────────────────────────────────────────────────\n this.debug = options.debug || false;\n\n // Bind handlers so they can be cleanly removed later\n this._onVisibilityChange = this._handleVisibilityChange.bind(this);\n this._onWindowFocus = this._handleWindowFocus.bind(this);\n\n this._setupVisibilityHandlers();\n this._setupAppFocusHandler();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Public API\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Connect to the WebSocket server.\n * Safe to call multiple times – extra calls are no-ops when already\n * connected or connecting.\n */\n async connect(url = null) {\n if (url) this.url = url;\n if (!this.url) throw new Error('WebSocket URL is required');\n\n // Any explicit call to connect() resets the intentional-disconnect flag.\n this._intentionalDisconnect = false;\n\n if (this.isConnected || this.isConnecting) return;\n\n return this._doConnect();\n }\n\n /**\n * Permanently stop the client. No further reconnect attempts are made\n * until connect() is called again.\n */\n disconnect() {\n this._intentionalDisconnect = true;\n this._clearTimers();\n\n if (this.socket) {\n this._log('Disconnecting (intentional)');\n this.socket.close(1000, 'Client disconnect');\n this.socket = null;\n }\n\n this.isConnected = false;\n this.isConnecting = false;\n }\n\n /**\n * Send data. Objects are auto-serialised to JSON.\n */\n send(data) {\n if (!this.isConnected || !this.socket) {\n throw new Error('WebSocket not connected');\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data);\n this.socket.send(message);\n this._log('Sent:', message);\n }\n\n /**\n * Fully tear down the client, removing all DOM/app listeners.\n * Call this when the owning view/page is destroyed.\n */\n destroy() {\n this.disconnect();\n this._teardownVisibilityHandlers();\n this._teardownAppFocusHandler();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Connection internals\n // ═══════════════════════════════════════════════════════════════════════════\n\n _doConnect() {\n if (this.isConnected || this.isConnecting) return Promise.resolve();\n\n this.isConnecting = true;\n this._log('Connecting to:', this.url);\n\n return new Promise((resolve, reject) => {\n // `settled` ensures the promise is only resolved/rejected once even\n // though onerror and onclose can both fire on a failed connection.\n let settled = false;\n const settle = (fn, value) => {\n if (settled) return;\n settled = true;\n fn(value);\n };\n\n try {\n const socket = new WebSocket(this.url);\n this.socket = socket;\n\n socket.onopen = () => {\n this._log('Connected');\n this.isConnected = true;\n this.isConnecting = false;\n this.reconnectAttempts = 0;\n\n this._authenticate();\n this._startHeartbeat();\n\n this.emit('connected');\n settle(resolve, undefined);\n };\n\n socket.onmessage = (event) => this._handleMessage(event);\n\n // onerror in browsers is always followed by onclose, so we let\n // _handleClose drive the reconnect to avoid double-scheduling.\n // We only use the error event to emit and to reject the initial\n // connect() promise if we haven't successfully opened yet.\n socket.onerror = (event) => {\n this._log('Socket error:', event);\n this.emit('error', event);\n\n if (!this.isConnected) {\n settle(reject, new Error('WebSocket connection failed'));\n }\n };\n\n // Pass the settle-reject so _handleClose can reject the promise when\n // the socket closes before we ever reached onopen.\n socket.onclose = (event) => this._handleClose(event, settle.bind(null, reject));\n\n } catch (error) {\n // Synchronous WebSocket constructor failure (bad URL, etc.)\n this.isConnecting = false;\n this.socket = null;\n settle(reject, error);\n this._scheduleReconnect();\n }\n });\n }\n\n _authenticate() {\n const token = this.getToken ? this.getToken() : null;\n if (!token) {\n console.warn('[WebSocket] No token available — skipping authentication');\n return;\n }\n\n this.send({ type: 'authenticate', token, prefix: this.tokenPrefix });\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Event handlers\n // ═══════════════════════════════════════════════════════════════════════════\n\n _handleMessage(event) {\n this._log('Received:', event.data);\n\n let data;\n try {\n data = JSON.parse(event.data);\n } catch {\n data = event.data;\n }\n\n // Absorb pong heartbeat response\n if (data?.type === 'pong') {\n this._clearPongTimeout();\n return;\n }\n\n if (data?.type) {\n this.emit(`message:${data.type}`, data);\n }\n this.emit('message', data);\n }\n\n _handleClose(event, initialRejectFn) {\n this._log('Closed:', event.code, event.reason);\n\n const wasConnecting = this.isConnecting;\n\n this.isConnected = false;\n this.isConnecting = false;\n this._clearTimers();\n this.socket = null;\n\n this.emit('disconnected', {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean\n });\n\n // Reject the caller's connect() promise if we closed before onopen fired\n if (wasConnecting && typeof initialRejectFn === 'function') {\n initialRejectFn(new Error(`WebSocket closed before connecting (code ${event.code})`));\n }\n\n // Always reconnect unless the user explicitly called disconnect()\n if (!this._intentionalDisconnect && this.autoReconnect) {\n this._scheduleReconnect();\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Reconnect\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Schedule the next reconnect with capped exponential backoff + optional\n * jitter. This is the single code path through which all retries are queued.\n */\n _scheduleReconnect() {\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n\n // Guard against queuing a second timer while one is already pending\n if (this.reconnectTimer !== null) return;\n\n this.reconnectAttempts++;\n\n // Exponential backoff, hard-capped\n let delay = Math.min(\n this.reconnectInterval * Math.pow(this.reconnectBackoff, this.reconnectAttempts - 1),\n this.maxReconnectDelay\n );\n\n // ±20 % jitter to avoid thundering-herd problems after server restarts\n if (this.reconnectJitter) {\n delay = delay * (0.8 + Math.random() * 0.4);\n }\n\n delay = Math.round(delay);\n\n this._log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n this.emit('reconnecting', { attempt: this.reconnectAttempts, delay });\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n\n this._doConnect().catch((err) => {\n this._log('Reconnect attempt failed:', err.message);\n // _handleClose already called _scheduleReconnect for us; if somehow it\n // didn't (e.g. synchronous throw path), make sure we still retry.\n if (this.reconnectTimer === null && !this._intentionalDisconnect) {\n this._scheduleReconnect();\n }\n });\n }, delay);\n }\n\n /**\n * Skip the remaining backoff wait and attempt a reconnect right away.\n * Called when the browser regains visibility or focus so we don't leave\n * the user sitting at the max-delay cap after waking a laptop or switching\n * back to the tab.\n */\n _nudgeReconnect() {\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n if (this.isConnected || this.isConnecting) return;\n\n this._log('Focus/visibility restored — nudging reconnect immediately');\n\n // Cancel the pending backoff timer; we're going now\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n\n // Small grace period (200 ms) to let the network stack wake up before\n // we try to open the socket.\n setTimeout(() => {\n if (this._intentionalDisconnect || this.isConnected || this.isConnecting) return;\n\n // Don't inflate the attempts counter for a nudge — treat it as\n // retrying the current attempt rather than starting a new one.\n const savedAttempts = this.reconnectAttempts;\n this._doConnect().catch((err) => {\n this._log('Nudge reconnect failed:', err.message);\n // Restore attempt count so backoff calculation stays consistent\n this.reconnectAttempts = savedAttempts;\n });\n }, 200);\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Heartbeat\n // ═══════════════════════════════════════════════════════════════════════════\n\n _startHeartbeat() {\n if (!this.pingInterval) return;\n\n this.pingTimer = setInterval(() => {\n if (!this.isConnected) return;\n try {\n this.send({ action: 'ping' });\n this._startPongTimeout();\n } catch (err) {\n this._log('Ping send failed:', err.message);\n }\n }, this.pingInterval);\n }\n\n _startPongTimeout() {\n this._clearPongTimeout();\n\n this.pongTimer = setTimeout(() => {\n console.warn('[WebSocket] Pong timeout — forcing reconnect');\n this.emit('pong-timeout');\n if (this.socket) {\n // Non-1000 code so _handleClose knows this was not an intentional close\n this.socket.close(4001, 'Pong timeout');\n }\n }, this.pongTimeout);\n }\n\n _clearPongTimeout() {\n if (this.pongTimer !== null) {\n clearTimeout(this.pongTimer);\n this.pongTimer = null;\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Timer cleanup\n // ═══════════════════════════════════════════════════════════════════════════\n\n _clearTimers() {\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.pingTimer !== null) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n this._clearPongTimeout();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Visibility / focus integration\n // ═══════════════════════════════════════════════════════════════════════════\n\n _setupVisibilityHandlers() {\n if (typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n if (typeof window !== 'undefined') {\n window.addEventListener('focus', this._onWindowFocus);\n }\n }\n\n _teardownVisibilityHandlers() {\n if (typeof document === 'undefined') return;\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n if (typeof window !== 'undefined') {\n window.removeEventListener('focus', this._onWindowFocus);\n }\n }\n\n _handleVisibilityChange() {\n if (!document.hidden) {\n this._nudgeReconnect();\n }\n }\n\n _handleWindowFocus() {\n this._nudgeReconnect();\n }\n\n /**\n * Hook into a WebApp instance's unified event bus so that PortalApp's\n * existing browser:focus handling also triggers a reconnect nudge.\n * This is intentionally additive — the native DOM listeners above already\n * cover the common cases; the app bus is a belt-and-suspenders extra.\n */\n _setupAppFocusHandler() {\n if (!this._app?.events) return;\n this._appFocusHandler = () => this._nudgeReconnect();\n this._app.events.on('browser:focus', this._appFocusHandler);\n }\n\n _teardownAppFocusHandler() {\n if (!this._app?.events || !this._appFocusHandler) return;\n this._app.events.off('browser:focus', this._appFocusHandler);\n this._appFocusHandler = null;\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Logging\n // ═══════════════════════════════════════════════════════════════════════════\n\n _log(...args) {\n if (this.debug) {\n console.log('[WebSocket]', ...args);\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Static helpers\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Convert a REST API base URL to a WebSocket URL.\n *\n * @param {string} baseURL - REST base URL (http / https)\n * @param {string} path - WebSocket path (default: '/ws/realtime/')\n * @returns {string} WebSocket URL (ws / wss)\n *\n * @example\n * WebSocketClient.deriveURL('https://api.example.com', '/ws/realtime')\n * // → 'wss://api.example.com/ws/realtime'\n *\n * WebSocketClient.deriveURL('http://localhost:3000')\n * // → 'ws://localhost:3000/ws/realtime/'\n */\n static deriveURL(baseURL, path = '/ws/realtime/') {\n if (!baseURL) throw new Error('baseURL is required');\n const url = new URL(baseURL);\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n url.pathname = path.startsWith('/') ? path : `/${path}`;\n return url.toString();\n }\n}\n\n// Mix in EventEmitter (adds on / off / emit / once)\nObject.assign(WebSocketClient.prototype, EventEmitter);\n\nexport default WebSocketClient;"],"names":["WebSocketClient","constructor","options","this","url","socket","isConnected","isConnecting","getToken","tokenPrefix","autoReconnect","_intentionalDisconnect","reconnectInterval","reconnectBackoff","maxReconnectDelay","reconnectJitter","reconnectAttempts","reconnectTimer","pingInterval","pongTimeout","pingTimer","pongTimer","_app","app","debug","_onVisibilityChange","_handleVisibilityChange","bind","_onWindowFocus","_handleWindowFocus","_setupVisibilityHandlers","_setupAppFocusHandler","connect","Error","_doConnect","disconnect","_clearTimers","_log","close","send","data","message","JSON","stringify","destroy","_teardownVisibilityHandlers","_teardownAppFocusHandler","Promise","resolve","reject","settled","settle","fn","value","WebSocket","onopen","_authenticate","_startHeartbeat","emit","onmessage","event","_handleMessage","onerror","onclose","_handleClose","error","_scheduleReconnect","token","type","prefix","console","warn","parse","_clearPongTimeout","initialRejectFn","code","reason","wasConnecting","wasClean","delay","Math","min","pow","random","round","attempt","setTimeout","catch","err","_nudgeReconnect","clearTimeout","savedAttempts","setInterval","action","_startPongTimeout","clearInterval","document","addEventListener","window","removeEventListener","hidden","events","_appFocusHandler","on","off","args","deriveURL","baseURL","path","URL","protocol","pathname","startsWith","toString","Object","assign","prototype","EventEmitter"],"mappings":"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"}
1
+ {"version":3,"file":"WebSocketClient-QaCUN3EQ.js","sources":["../../src/core/services/WebSocketClient.js"],"sourcesContent":["/**\n * WebSocketClient - Robust, infinitely-resilient WebSocket client\n *\n * Features:\n * - Infinite auto-reconnect with capped exponential backoff + jitter (never gives up)\n * - Separate \"intentional disconnect\" flag so clean closes still reconnect\n * - Heartbeat ping/pong with timeout-driven force-reconnect\n * - Immediate reconnect nudge on browser visibility / window focus restore\n * - Optional integration with WebApp's browser:focus event bus\n * - Event-driven architecture using EventEmitter\n * - Token-based authentication\n *\n * Usage:\n * const ws = new WebSocketClient({\n * url: \"wss://api.example.com/ws/realtime\",\n * tokenPrefix: \"bearer\",\n * getToken: () => app.tokenManager.getToken(),\n * app, // optional – hooks into app.events 'browser:focus'\n * });\n *\n * ws.on('connected', () => console.log('Connected!'));\n * ws.on('message', (data) => console.log('Received:', data));\n * ws.on('reconnecting', ({attempt, delay}) => console.log(`Retry #${attempt} in ${delay}ms`));\n * ws.connect();\n */\n\nimport EventEmitter from '@core/mixins/EventEmitter.js';\n\nclass WebSocketClient {\n constructor(options = {}) {\n // ── Connection ──────────────────────────────────────────────────────────\n this.url = options.url;\n this.socket = null;\n this.isConnected = false;\n this.isConnecting = false;\n\n // ── Auth ────────────────────────────────────────────────────────────────\n this.getToken = options.getToken || null;\n this.tokenPrefix = options.tokenPrefix || 'bearer';\n\n // ── Reconnection ────────────────────────────────────────────────────────\n // `autoReconnect` – master switch (default true)\n // `_intentionalDisconnect` – set only when the caller explicitly calls\n // disconnect(), so that clean server closes\n // (code 1000) are still retried automatically.\n this.autoReconnect = options.autoReconnect !== false;\n this._intentionalDisconnect = false;\n\n this.reconnectInterval = options.reconnectInterval || 2000; // base delay ms\n this.reconnectBackoff = options.reconnectBackoff || 1.5; // multiplier\n this.maxReconnectDelay = options.maxReconnectDelay || 30000; // hard cap ms\n this.reconnectJitter = options.reconnectJitter !== false; // ±20 % jitter\n this.reconnectAttempts = 0;\n this.reconnectTimer = null;\n\n // ── Heartbeat ────────────────────────────────────────────────────────────\n this.pingInterval = options.pingInterval || 30000;\n this.pongTimeout = options.pongTimeout || 10000;\n this.pingTimer = null;\n this.pongTimer = null;\n\n // ── Optional WebApp integration ──────────────────────────────────────────\n // Pass `app` to also hook into app.events 'browser:focus' in addition to\n // the native visibility / window focus events we listen to below.\n this._app = options.app || null;\n\n // ── Debug ────────────────────────────────────────────────────────────────\n this.debug = options.debug || false;\n\n // Bind handlers so they can be cleanly removed later\n this._onVisibilityChange = this._handleVisibilityChange.bind(this);\n this._onWindowFocus = this._handleWindowFocus.bind(this);\n\n this._setupVisibilityHandlers();\n this._setupAppFocusHandler();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Public API\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Connect to the WebSocket server.\n * Safe to call multiple times – extra calls are no-ops when already\n * connected or connecting.\n */\n async connect(url = null) {\n if (url) this.url = url;\n if (!this.url) throw new Error('WebSocket URL is required');\n\n // Any explicit call to connect() resets the intentional-disconnect flag.\n this._intentionalDisconnect = false;\n\n if (this.isConnected || this.isConnecting) return;\n\n return this._doConnect();\n }\n\n /**\n * Permanently stop the client. No further reconnect attempts are made\n * until connect() is called again.\n */\n disconnect() {\n this._intentionalDisconnect = true;\n this._clearTimers();\n\n if (this.socket) {\n this._log('Disconnecting (intentional)');\n this.socket.close(1000, 'Client disconnect');\n this.socket = null;\n }\n\n this.isConnected = false;\n this.isConnecting = false;\n }\n\n /**\n * Send data. Objects are auto-serialised to JSON.\n */\n send(data) {\n if (!this.isConnected || !this.socket) {\n throw new Error('WebSocket not connected');\n }\n\n const message = typeof data === 'string' ? data : JSON.stringify(data);\n this.socket.send(message);\n this._log('Sent:', message);\n }\n\n /**\n * Fully tear down the client, removing all DOM/app listeners.\n * Call this when the owning view/page is destroyed.\n */\n destroy() {\n this.disconnect();\n this._teardownVisibilityHandlers();\n this._teardownAppFocusHandler();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Connection internals\n // ═══════════════════════════════════════════════════════════════════════════\n\n _doConnect() {\n if (this.isConnected || this.isConnecting) return Promise.resolve();\n\n this.isConnecting = true;\n this._log('Connecting to:', this.url);\n\n return new Promise((resolve, reject) => {\n // `settled` ensures the promise is only resolved/rejected once even\n // though onerror and onclose can both fire on a failed connection.\n let settled = false;\n const settle = (fn, value) => {\n if (settled) return;\n settled = true;\n fn(value);\n };\n\n try {\n const socket = new WebSocket(this.url);\n this.socket = socket;\n\n socket.onopen = () => {\n this._log('Connected');\n this.isConnected = true;\n this.isConnecting = false;\n this.reconnectAttempts = 0;\n\n this._authenticate();\n this._startHeartbeat();\n\n this.emit('connected');\n settle(resolve, undefined);\n };\n\n socket.onmessage = (event) => this._handleMessage(event);\n\n // onerror in browsers is always followed by onclose, so we let\n // _handleClose drive the reconnect to avoid double-scheduling.\n // We only use the error event to emit and to reject the initial\n // connect() promise if we haven't successfully opened yet.\n socket.onerror = (event) => {\n this._log('Socket error:', event);\n this.emit('error', event);\n\n if (!this.isConnected) {\n settle(reject, new Error('WebSocket connection failed'));\n }\n };\n\n // Pass the settle-reject so _handleClose can reject the promise when\n // the socket closes before we ever reached onopen.\n socket.onclose = (event) => this._handleClose(event, settle.bind(null, reject));\n\n } catch (error) {\n // Synchronous WebSocket constructor failure (bad URL, etc.)\n this.isConnecting = false;\n this.socket = null;\n settle(reject, error);\n this._scheduleReconnect();\n }\n });\n }\n\n _authenticate() {\n const token = this.getToken ? this.getToken() : null;\n if (!token) {\n console.warn('[WebSocket] No token available — skipping authentication');\n return;\n }\n\n this.send({ type: 'authenticate', token, prefix: this.tokenPrefix });\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Event handlers\n // ═══════════════════════════════════════════════════════════════════════════\n\n _handleMessage(event) {\n this._log('Received:', event.data);\n\n let data;\n try {\n data = JSON.parse(event.data);\n } catch {\n data = event.data;\n }\n\n // Absorb pong heartbeat response\n if (data?.type === 'pong') {\n this._clearPongTimeout();\n return;\n }\n\n if (data?.type) {\n this.emit(`message:${data.type}`, data);\n }\n this.emit('message', data);\n }\n\n _handleClose(event, initialRejectFn) {\n this._log('Closed:', event.code, event.reason);\n\n const wasConnecting = this.isConnecting;\n\n this.isConnected = false;\n this.isConnecting = false;\n this._clearTimers();\n this.socket = null;\n\n this.emit('disconnected', {\n code: event.code,\n reason: event.reason,\n wasClean: event.wasClean\n });\n\n // Reject the caller's connect() promise if we closed before onopen fired\n if (wasConnecting && typeof initialRejectFn === 'function') {\n initialRejectFn(new Error(`WebSocket closed before connecting (code ${event.code})`));\n }\n\n // Always reconnect unless the user explicitly called disconnect()\n if (!this._intentionalDisconnect && this.autoReconnect) {\n this._scheduleReconnect();\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Reconnect\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Schedule the next reconnect with capped exponential backoff + optional\n * jitter. This is the single code path through which all retries are queued.\n */\n _scheduleReconnect() {\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n\n // Guard against queuing a second timer while one is already pending\n if (this.reconnectTimer !== null) return;\n\n this.reconnectAttempts++;\n\n // Exponential backoff, hard-capped\n let delay = Math.min(\n this.reconnectInterval * Math.pow(this.reconnectBackoff, this.reconnectAttempts - 1),\n this.maxReconnectDelay\n );\n\n // ±20 % jitter to avoid thundering-herd problems after server restarts\n if (this.reconnectJitter) {\n delay = delay * (0.8 + Math.random() * 0.4);\n }\n\n delay = Math.round(delay);\n\n this._log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n this.emit('reconnecting', { attempt: this.reconnectAttempts, delay });\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n\n this._doConnect().catch((err) => {\n this._log('Reconnect attempt failed:', err.message);\n // _handleClose already called _scheduleReconnect for us; if somehow it\n // didn't (e.g. synchronous throw path), make sure we still retry.\n if (this.reconnectTimer === null && !this._intentionalDisconnect) {\n this._scheduleReconnect();\n }\n });\n }, delay);\n }\n\n /**\n * Skip the remaining backoff wait and attempt a reconnect right away.\n * Called when the browser regains visibility or focus so we don't leave\n * the user sitting at the max-delay cap after waking a laptop or switching\n * back to the tab.\n */\n _nudgeReconnect() {\n if (this._intentionalDisconnect || !this.autoReconnect) return;\n if (this.isConnected || this.isConnecting) return;\n\n this._log('Focus/visibility restored — nudging reconnect immediately');\n\n // Cancel the pending backoff timer; we're going now\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n\n // Small grace period (200 ms) to let the network stack wake up before\n // we try to open the socket.\n setTimeout(() => {\n if (this._intentionalDisconnect || this.isConnected || this.isConnecting) return;\n\n // Don't inflate the attempts counter for a nudge — treat it as\n // retrying the current attempt rather than starting a new one.\n const savedAttempts = this.reconnectAttempts;\n this._doConnect().catch((err) => {\n this._log('Nudge reconnect failed:', err.message);\n // Restore attempt count so backoff calculation stays consistent\n this.reconnectAttempts = savedAttempts;\n });\n }, 200);\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Heartbeat\n // ═══════════════════════════════════════════════════════════════════════════\n\n _startHeartbeat() {\n if (!this.pingInterval) return;\n\n this.pingTimer = setInterval(() => {\n if (!this.isConnected) return;\n try {\n this.send({ action: 'ping' });\n this._startPongTimeout();\n } catch (err) {\n this._log('Ping send failed:', err.message);\n }\n }, this.pingInterval);\n }\n\n _startPongTimeout() {\n this._clearPongTimeout();\n\n this.pongTimer = setTimeout(() => {\n console.warn('[WebSocket] Pong timeout — forcing reconnect');\n this.emit('pong-timeout');\n if (this.socket) {\n // Non-1000 code so _handleClose knows this was not an intentional close\n this.socket.close(4001, 'Pong timeout');\n }\n }, this.pongTimeout);\n }\n\n _clearPongTimeout() {\n if (this.pongTimer !== null) {\n clearTimeout(this.pongTimer);\n this.pongTimer = null;\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Timer cleanup\n // ═══════════════════════════════════════════════════════════════════════════\n\n _clearTimers() {\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.pingTimer !== null) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n this._clearPongTimeout();\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Visibility / focus integration\n // ═══════════════════════════════════════════════════════════════════════════\n\n _setupVisibilityHandlers() {\n if (typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n if (typeof window !== 'undefined') {\n window.addEventListener('focus', this._onWindowFocus);\n }\n }\n\n _teardownVisibilityHandlers() {\n if (typeof document === 'undefined') return;\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n if (typeof window !== 'undefined') {\n window.removeEventListener('focus', this._onWindowFocus);\n }\n }\n\n _handleVisibilityChange() {\n if (!document.hidden) {\n this._nudgeReconnect();\n }\n }\n\n _handleWindowFocus() {\n this._nudgeReconnect();\n }\n\n /**\n * Hook into a WebApp instance's unified event bus so that PortalApp's\n * existing browser:focus handling also triggers a reconnect nudge.\n * This is intentionally additive — the native DOM listeners above already\n * cover the common cases; the app bus is a belt-and-suspenders extra.\n */\n _setupAppFocusHandler() {\n if (!this._app?.events) return;\n this._appFocusHandler = () => this._nudgeReconnect();\n this._app.events.on('browser:focus', this._appFocusHandler);\n }\n\n _teardownAppFocusHandler() {\n if (!this._app?.events || !this._appFocusHandler) return;\n this._app.events.off('browser:focus', this._appFocusHandler);\n this._appFocusHandler = null;\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Logging\n // ═══════════════════════════════════════════════════════════════════════════\n\n _log(...args) {\n if (this.debug) {\n console.log('[WebSocket]', ...args);\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // Static helpers\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Convert a REST API base URL to a WebSocket URL.\n *\n * @param {string} baseURL - REST base URL (http / https)\n * @param {string} path - WebSocket path (default: '/ws/realtime/')\n * @returns {string} WebSocket URL (ws / wss)\n *\n * @example\n * WebSocketClient.deriveURL('https://api.example.com', '/ws/realtime')\n * // → 'wss://api.example.com/ws/realtime'\n *\n * WebSocketClient.deriveURL('http://localhost:3000')\n * // → 'ws://localhost:3000/ws/realtime/'\n */\n static deriveURL(baseURL, path = '/ws/realtime/') {\n if (!baseURL) throw new Error('baseURL is required');\n const url = new URL(baseURL);\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n url.pathname = path.startsWith('/') ? path : `/${path}`;\n return url.toString();\n }\n}\n\n// Mix in EventEmitter (adds on / off / emit / once)\nObject.assign(WebSocketClient.prototype, EventEmitter);\n\nexport default WebSocketClient;"],"names":["WebSocketClient","constructor","options","this","url","socket","isConnected","isConnecting","getToken","tokenPrefix","autoReconnect","_intentionalDisconnect","reconnectInterval","reconnectBackoff","maxReconnectDelay","reconnectJitter","reconnectAttempts","reconnectTimer","pingInterval","pongTimeout","pingTimer","pongTimer","_app","app","debug","_onVisibilityChange","_handleVisibilityChange","bind","_onWindowFocus","_handleWindowFocus","_setupVisibilityHandlers","_setupAppFocusHandler","connect","Error","_doConnect","disconnect","_clearTimers","_log","close","send","data","message","JSON","stringify","destroy","_teardownVisibilityHandlers","_teardownAppFocusHandler","Promise","resolve","reject","settled","settle","fn","value","WebSocket","onopen","_authenticate","_startHeartbeat","emit","onmessage","event","_handleMessage","onerror","onclose","_handleClose","error","_scheduleReconnect","token","type","prefix","console","warn","parse","_clearPongTimeout","initialRejectFn","code","reason","wasConnecting","wasClean","delay","Math","min","pow","random","round","attempt","setTimeout","catch","err","_nudgeReconnect","clearTimeout","savedAttempts","setInterval","action","_startPongTimeout","clearInterval","document","addEventListener","window","removeEventListener","hidden","events","_appFocusHandler","on","off","args","deriveURL","baseURL","path","URL","protocol","pathname","startsWith","toString","Object","assign","prototype","EventEmitter"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./UserProfileView-9vkfCPsp.js"),t=require("./Collection-BUv4E9op.js"),i=require("./TableView-CxYpxZvr.js");class ActivityRow extends i.TableRow{get logMessage(){const e=this.model?.get("log");if(!e)return"";if("string"==typeof e)try{const t=JSON.parse(e);return t.message||t.type||e.substring(0,120)}catch{return e.length>120?e.substring(0,120)+"…":e}return"object"==typeof e?e.message||e.type||JSON.stringify(e).substring(0,120):String(e)}get levelText(){return this.model?.get("level")||"log"}get levelBadgeClass(){const e=this.model?.get("level");return"error"===e?"bg-danger":"warn"===e?"bg-warning text-dark":"info"===e?"bg-info":"bg-secondary"}get methodPath(){const e=this.model?.get("method")||"",t=this.model?.get("path")||"";return e||t?`${e} ${t}`.trim():""}}class ProfileActivitySection extends t.View{constructor(e={}){super({className:"profile-activity-section",template:'<div id="activity-table"></div>',...e})}async onInit(){await super.onInit(),this.tableView=new i.TableView({containerId:"activity-table",collection:new i.LogList({size:10}),defaultQuery:{uid:this.model.id,sort:"-created"},hideActivePillNames:["uid","sort"],itemClass:ActivityRow,columns:[{key:"level",label:"Level",sortable:!0,template:'<span class="badge {{levelBadgeClass}}">{{levelText}}</span>',filter:{type:"select",options:["info","warn","error"]}},{key:"log",label:"Message",template:"{{logMessage}}"},{key:"kind",label:"Kind",sortable:!0,visibility:"md"},{key:"path",label:"Path",visibility:"lg",template:"{{methodPath}}"},{key:"ip",label:"IP",visibility:"xl"},{key:"created|relative",label:"Time",sortable:!0}],searchable:!0,sortable:!0,filterable:!0,paginated:!0,showAdd:!1,showExport:!1,tableOptions:{striped:!1,hover:!0,size:"sm"},emptyMessage:"No activity logs available"}),this.addChild(this.tableView)}}exports.PasskeySetupView=e.PasskeySetupView,exports.ProfileApiKeysSection=e.ProfileApiKeysSection,exports.ProfileConnectedSection=e.ProfileConnectedSection,exports.ProfileDevicesSection=e.ProfileDevicesSection,exports.ProfileGroupsSection=e.ProfileGroupsSection,exports.ProfileNotificationsSection=e.ProfileNotificationsSection,exports.ProfileOverviewSection=e.ProfileOverviewSection,exports.ProfilePermissionsSection=e.ProfilePermissionsSection,exports.ProfilePersonalSection=e.ProfilePersonalSection,exports.ProfileSecurityEventsSection=e.ProfileSecurityEventsSection,exports.ProfileSecuritySection=e.ProfileSecuritySection,exports.ProfileSessionsSection=e.ProfileSessionsSection,exports.UserProfileView=e.UserProfileView,exports.ProfileActivitySection=ProfileActivitySection;
2
+ //# sourceMappingURL=index-BaPQHxbL.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-Da9sT-tE.js","sources":["../../src/extensions/user-profile/views/ProfileActivitySection.js"],"sourcesContent":["/**\n * ProfileActivitySection - Activity log tab\n *\n * Uses TableView + LogList to show user activity\n * with parsed log messages, level badges, kind, path, and time.\n * Includes pagination, search, and level filtering.\n */\nimport View from '@core/View.js';\nimport TableView from '@core/views/table/TableView.js';\nimport TableRow from '@core/views/table/TableRow.js';\nimport { LogList } from '@core/models/Log.js';\n\nclass ActivityRow extends TableRow {\n get logMessage() {\n const log = this.model?.get('log');\n if (!log) return '';\n if (typeof log === 'string') {\n try {\n const parsed = JSON.parse(log);\n return parsed.message || parsed.type || log.substring(0, 120);\n } catch {\n return log.length > 120 ? log.substring(0, 120) + '…' : log;\n }\n }\n if (typeof log === 'object') {\n return log.message || log.type || JSON.stringify(log).substring(0, 120);\n }\n return String(log);\n }\n\n get levelText() {\n return this.model?.get('level') || 'log';\n }\n\n get levelBadgeClass() {\n const level = this.model?.get('level');\n if (level === 'error') return 'bg-danger';\n if (level === 'warn') return 'bg-warning text-dark';\n if (level === 'info') return 'bg-info';\n return 'bg-secondary';\n }\n\n get methodPath() {\n const method = this.model?.get('method') || '';\n const path = this.model?.get('path') || '';\n if (!method && !path) return '';\n return `${method} ${path}`.trim();\n }\n}\n\nexport default class ProfileActivitySection extends View {\n constructor(options = {}) {\n super({\n className: 'profile-activity-section',\n template: `<div id=\"activity-table\"></div>`,\n ...options\n });\n }\n\n async onInit() {\n await super.onInit();\n this.tableView = new TableView({\n containerId: 'activity-table',\n collection: new LogList({ size: 10 }),\n defaultQuery: { uid: this.model.id, sort: '-created' },\n hideActivePillNames: ['uid', 'sort'],\n itemClass: ActivityRow,\n columns: [\n {\n key: 'level',\n label: 'Level',\n sortable: true,\n template: '<span class=\"badge {{levelBadgeClass}}\">{{levelText}}</span>',\n filter: { type: 'select', options: ['info', 'warn', 'error'] }\n },\n {\n key: 'log',\n label: 'Message',\n template: '{{logMessage}}'\n },\n { key: 'kind', label: 'Kind', sortable: true, visibility: 'md' },\n {\n key: 'path',\n label: 'Path',\n visibility: 'lg',\n template: '{{methodPath}}'\n },\n { key: 'ip', label: 'IP', visibility: 'xl' },\n { key: 'created|relative', label: 'Time', sortable: true }\n ],\n searchable: true,\n sortable: true,\n filterable: true,\n paginated: true,\n showAdd: false,\n showExport: false,\n tableOptions: {\n striped: false,\n hover: true,\n size: 'sm'\n },\n emptyMessage: 'No activity logs available'\n });\n this.addChild(this.tableView);\n }\n}\n"],"names":["ActivityRow","TableRow","logMessage","log","this","model","get","parsed","JSON","parse","message","type","substring","length","stringify","String","levelText","levelBadgeClass","level","methodPath","method","path","trim","ProfileActivitySection","View","constructor","options","super","className","template","onInit","tableView","TableView","containerId","collection","LogList","size","defaultQuery","uid","id","sort","hideActivePillNames","itemClass","columns","key","label","sortable","filter","visibility","searchable","filterable","paginated","showAdd","showExport","tableOptions","striped","hover","emptyMessage","addChild"],"mappings":"sMAYA,MAAMA,oBAAoBC,EAAAA,SACtB,cAAIC,GACA,MAAMC,EAAMC,KAAKC,OAAOC,IAAI,OAC5B,IAAKH,EAAK,MAAO,GACjB,GAAmB,iBAARA,EACP,IACI,MAAMI,EAASC,KAAKC,MAAMN,GAC1B,OAAOI,EAAOG,SAAWH,EAAOI,MAAQR,EAAIS,UAAU,EAAG,IAC7D,CAAA,MACI,OAAOT,EAAIU,OAAS,IAAMV,EAAIS,UAAU,EAAG,KAAO,IAAMT,CAC5D,CAEJ,MAAmB,iBAARA,EACAA,EAAIO,SAAWP,EAAIQ,MAAQH,KAAKM,UAAUX,GAAKS,UAAU,EAAG,KAEhEG,OAAOZ,EAClB,CAEA,aAAIa,GACA,OAAOZ,KAAKC,OAAOC,IAAI,UAAY,KACvC,CAEA,mBAAIW,GACA,MAAMC,EAAQd,KAAKC,OAAOC,IAAI,SAC9B,MAAc,UAAVY,EAA0B,YAChB,SAAVA,EAAyB,uBACf,SAAVA,EAAyB,UACtB,cACX,CAEA,cAAIC,GACA,MAAMC,EAAShB,KAAKC,OAAOC,IAAI,WAAa,GACtCe,EAAOjB,KAAKC,OAAOC,IAAI,SAAW,GACxC,OAAKc,GAAWC,EACT,GAAGD,KAAUC,IAAOC,OADE,EAEjC,EAGW,MAAMC,+BAA+BC,EAAAA,KAChD,WAAAC,CAAYC,EAAU,IAClBC,MAAM,CACFC,UAAW,2BACXC,SAAU,qCACPH,GAEX,CAEA,YAAMI,SACIH,MAAMG,SACZ1B,KAAK2B,UAAY,IAAIC,YAAU,CAC3BC,YAAa,iBACbC,WAAY,IAAIC,EAAAA,QAAQ,CAAEC,KAAM,KAChCC,aAAc,CAAEC,IAAKlC,KAAKC,MAAMkC,GAAIC,KAAM,YAC1CC,oBAAqB,CAAC,MAAO,QAC7BC,UAAW1C,YACX2C,QAAS,CACL,CACIC,IAAK,QACLC,MAAO,QACPC,UAAU,EACVjB,SAAU,+DACVkB,OAAQ,CAAEpC,KAAM,SAAUe,QAAS,CAAC,OAAQ,OAAQ,WAExD,CACIkB,IAAK,MACLC,MAAO,UACPhB,SAAU,kBAEd,CAAEe,IAAK,OAAQC,MAAO,OAAQC,UAAU,EAAME,WAAY,MAC1D,CACIJ,IAAK,OACLC,MAAO,OACPG,WAAY,KACZnB,SAAU,kBAEd,CAAEe,IAAK,KAAMC,MAAO,KAAMG,WAAY,MACtC,CAAEJ,IAAK,mBAAoBC,MAAO,OAAQC,UAAU,IAExDG,YAAY,EACZH,UAAU,EACVI,YAAY,EACZC,WAAW,EACXC,SAAS,EACTC,YAAY,EACZC,aAAc,CACVC,SAAS,EACTC,OAAO,EACPpB,KAAM,MAEVqB,aAAc,+BAElBrD,KAAKsD,SAAStD,KAAK2B,UACvB"}
1
+ {"version":3,"file":"index-BaPQHxbL.js","sources":["../../src/extensions/user-profile/views/ProfileActivitySection.js"],"sourcesContent":["/**\n * ProfileActivitySection - Activity log tab\n *\n * Uses TableView + LogList to show user activity\n * with parsed log messages, level badges, kind, path, and time.\n * Includes pagination, search, and level filtering.\n */\nimport View from '@core/View.js';\nimport TableView from '@core/views/table/TableView.js';\nimport TableRow from '@core/views/table/TableRow.js';\nimport { LogList } from '@core/models/Log.js';\n\nclass ActivityRow extends TableRow {\n get logMessage() {\n const log = this.model?.get('log');\n if (!log) return '';\n if (typeof log === 'string') {\n try {\n const parsed = JSON.parse(log);\n return parsed.message || parsed.type || log.substring(0, 120);\n } catch {\n return log.length > 120 ? log.substring(0, 120) + '…' : log;\n }\n }\n if (typeof log === 'object') {\n return log.message || log.type || JSON.stringify(log).substring(0, 120);\n }\n return String(log);\n }\n\n get levelText() {\n return this.model?.get('level') || 'log';\n }\n\n get levelBadgeClass() {\n const level = this.model?.get('level');\n if (level === 'error') return 'bg-danger';\n if (level === 'warn') return 'bg-warning text-dark';\n if (level === 'info') return 'bg-info';\n return 'bg-secondary';\n }\n\n get methodPath() {\n const method = this.model?.get('method') || '';\n const path = this.model?.get('path') || '';\n if (!method && !path) return '';\n return `${method} ${path}`.trim();\n }\n}\n\nexport default class ProfileActivitySection extends View {\n constructor(options = {}) {\n super({\n className: 'profile-activity-section',\n template: `<div id=\"activity-table\"></div>`,\n ...options\n });\n }\n\n async onInit() {\n await super.onInit();\n this.tableView = new TableView({\n containerId: 'activity-table',\n collection: new LogList({ size: 10 }),\n defaultQuery: { uid: this.model.id, sort: '-created' },\n hideActivePillNames: ['uid', 'sort'],\n itemClass: ActivityRow,\n columns: [\n {\n key: 'level',\n label: 'Level',\n sortable: true,\n template: '<span class=\"badge {{levelBadgeClass}}\">{{levelText}}</span>',\n filter: { type: 'select', options: ['info', 'warn', 'error'] }\n },\n {\n key: 'log',\n label: 'Message',\n template: '{{logMessage}}'\n },\n { key: 'kind', label: 'Kind', sortable: true, visibility: 'md' },\n {\n key: 'path',\n label: 'Path',\n visibility: 'lg',\n template: '{{methodPath}}'\n },\n { key: 'ip', label: 'IP', visibility: 'xl' },\n { key: 'created|relative', label: 'Time', sortable: true }\n ],\n searchable: true,\n sortable: true,\n filterable: true,\n paginated: true,\n showAdd: false,\n showExport: false,\n tableOptions: {\n striped: false,\n hover: true,\n size: 'sm'\n },\n emptyMessage: 'No activity logs available'\n });\n this.addChild(this.tableView);\n }\n}\n"],"names":["ActivityRow","TableRow","logMessage","log","this","model","get","parsed","JSON","parse","message","type","substring","length","stringify","String","levelText","levelBadgeClass","level","methodPath","method","path","trim","ProfileActivitySection","View","constructor","options","super","className","template","onInit","tableView","TableView","containerId","collection","LogList","size","defaultQuery","uid","id","sort","hideActivePillNames","itemClass","columns","key","label","sortable","filter","visibility","searchable","filterable","paginated","showAdd","showExport","tableOptions","striped","hover","emptyMessage","addChild"],"mappings":"4MAYA,MAAMA,oBAAoBC,EAAAA,SACtB,cAAIC,GACA,MAAMC,EAAMC,KAAKC,OAAOC,IAAI,OAC5B,IAAKH,EAAK,MAAO,GACjB,GAAmB,iBAARA,EACP,IACI,MAAMI,EAASC,KAAKC,MAAMN,GAC1B,OAAOI,EAAOG,SAAWH,EAAOI,MAAQR,EAAIS,UAAU,EAAG,IAC7D,CAAA,MACI,OAAOT,EAAIU,OAAS,IAAMV,EAAIS,UAAU,EAAG,KAAO,IAAMT,CAC5D,CAEJ,MAAmB,iBAARA,EACAA,EAAIO,SAAWP,EAAIQ,MAAQH,KAAKM,UAAUX,GAAKS,UAAU,EAAG,KAEhEG,OAAOZ,EAClB,CAEA,aAAIa,GACA,OAAOZ,KAAKC,OAAOC,IAAI,UAAY,KACvC,CAEA,mBAAIW,GACA,MAAMC,EAAQd,KAAKC,OAAOC,IAAI,SAC9B,MAAc,UAAVY,EAA0B,YAChB,SAAVA,EAAyB,uBACf,SAAVA,EAAyB,UACtB,cACX,CAEA,cAAIC,GACA,MAAMC,EAAShB,KAAKC,OAAOC,IAAI,WAAa,GACtCe,EAAOjB,KAAKC,OAAOC,IAAI,SAAW,GACxC,OAAKc,GAAWC,EACT,GAAGD,KAAUC,IAAOC,OADE,EAEjC,EAGW,MAAMC,+BAA+BC,EAAAA,KAChD,WAAAC,CAAYC,EAAU,IAClBC,MAAM,CACFC,UAAW,2BACXC,SAAU,qCACPH,GAEX,CAEA,YAAMI,SACIH,MAAMG,SACZ1B,KAAK2B,UAAY,IAAIC,YAAU,CAC3BC,YAAa,iBACbC,WAAY,IAAIC,EAAAA,QAAQ,CAAEC,KAAM,KAChCC,aAAc,CAAEC,IAAKlC,KAAKC,MAAMkC,GAAIC,KAAM,YAC1CC,oBAAqB,CAAC,MAAO,QAC7BC,UAAW1C,YACX2C,QAAS,CACL,CACIC,IAAK,QACLC,MAAO,QACPC,UAAU,EACVjB,SAAU,+DACVkB,OAAQ,CAAEpC,KAAM,SAAUe,QAAS,CAAC,OAAQ,OAAQ,WAExD,CACIkB,IAAK,MACLC,MAAO,UACPhB,SAAU,kBAEd,CAAEe,IAAK,OAAQC,MAAO,OAAQC,UAAU,EAAME,WAAY,MAC1D,CACIJ,IAAK,OACLC,MAAO,OACPG,WAAY,KACZnB,SAAU,kBAEd,CAAEe,IAAK,KAAMC,MAAO,KAAMG,WAAY,MACtC,CAAEJ,IAAK,mBAAoBC,MAAO,OAAQC,UAAU,IAExDG,YAAY,EACZH,UAAU,EACVI,YAAY,EACZC,WAAW,EACXC,SAAS,EACTC,YAAY,EACZC,aAAc,CACVC,SAAS,EACTC,OAAO,EACPpB,KAAM,MAEVqB,aAAc,+BAElBrD,KAAKsD,SAAStD,KAAK2B,UACvB"}
@@ -0,0 +1,2 @@
1
+ import{U as e,P as t}from"./UserProfileView-tcBT6XcE.js";import{a as i,b as s,c as l,d as o,e as a,f as r,g as n,h as c,i as g,j as d,k as m}from"./UserProfileView-tcBT6XcE.js";import{V as f}from"./Collection-r1ACzUeh.js";import{T as b,d as p,a as y}from"./TableView-DemRVhnX.js";class ActivityRow extends y{get logMessage(){const e=this.model?.get("log");if(!e)return"";if("string"==typeof e)try{const t=JSON.parse(e);return t.message||t.type||e.substring(0,120)}catch{return e.length>120?e.substring(0,120)+"…":e}return"object"==typeof e?e.message||e.type||JSON.stringify(e).substring(0,120):String(e)}get levelText(){return this.model?.get("level")||"log"}get levelBadgeClass(){const e=this.model?.get("level");return"error"===e?"bg-danger":"warn"===e?"bg-warning text-dark":"info"===e?"bg-info":"bg-secondary"}get methodPath(){const e=this.model?.get("method")||"",t=this.model?.get("path")||"";return e||t?`${e} ${t}`.trim():""}}class ProfileActivitySection extends f{constructor(e={}){super({className:"profile-activity-section",template:'<div id="activity-table"></div>',...e})}async onInit(){await super.onInit(),this.tableView=new b({containerId:"activity-table",collection:new p({size:10}),defaultQuery:{uid:this.model.id,sort:"-created"},hideActivePillNames:["uid","sort"],itemClass:ActivityRow,columns:[{key:"level",label:"Level",sortable:!0,template:'<span class="badge {{levelBadgeClass}}">{{levelText}}</span>',filter:{type:"select",options:["info","warn","error"]}},{key:"log",label:"Message",template:"{{logMessage}}"},{key:"kind",label:"Kind",sortable:!0,visibility:"md"},{key:"path",label:"Path",visibility:"lg",template:"{{methodPath}}"},{key:"ip",label:"IP",visibility:"xl"},{key:"created|relative",label:"Time",sortable:!0}],searchable:!0,sortable:!0,filterable:!0,paginated:!0,showAdd:!1,showExport:!1,tableOptions:{striped:!1,hover:!0,size:"sm"},emptyMessage:"No activity logs available"}),this.addChild(this.tableView)}}export{t as PasskeySetupView,ProfileActivitySection,i as ProfileApiKeysSection,s as ProfileConnectedSection,l as ProfileDevicesSection,o as ProfileGroupsSection,a as ProfileNotificationsSection,r as ProfileOverviewSection,n as ProfilePermissionsSection,c as ProfilePersonalSection,g as ProfileSecurityEventsSection,d as ProfileSecuritySection,m as ProfileSessionsSection,e as UserProfileView};
2
+ //# sourceMappingURL=index-BdfwxVMZ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-Aq9ke4vg.js","sources":["../../src/extensions/user-profile/views/ProfileActivitySection.js"],"sourcesContent":["/**\n * ProfileActivitySection - Activity log tab\n *\n * Uses TableView + LogList to show user activity\n * with parsed log messages, level badges, kind, path, and time.\n * Includes pagination, search, and level filtering.\n */\nimport View from '@core/View.js';\nimport TableView from '@core/views/table/TableView.js';\nimport TableRow from '@core/views/table/TableRow.js';\nimport { LogList } from '@core/models/Log.js';\n\nclass ActivityRow extends TableRow {\n get logMessage() {\n const log = this.model?.get('log');\n if (!log) return '';\n if (typeof log === 'string') {\n try {\n const parsed = JSON.parse(log);\n return parsed.message || parsed.type || log.substring(0, 120);\n } catch {\n return log.length > 120 ? log.substring(0, 120) + '…' : log;\n }\n }\n if (typeof log === 'object') {\n return log.message || log.type || JSON.stringify(log).substring(0, 120);\n }\n return String(log);\n }\n\n get levelText() {\n return this.model?.get('level') || 'log';\n }\n\n get levelBadgeClass() {\n const level = this.model?.get('level');\n if (level === 'error') return 'bg-danger';\n if (level === 'warn') return 'bg-warning text-dark';\n if (level === 'info') return 'bg-info';\n return 'bg-secondary';\n }\n\n get methodPath() {\n const method = this.model?.get('method') || '';\n const path = this.model?.get('path') || '';\n if (!method && !path) return '';\n return `${method} ${path}`.trim();\n }\n}\n\nexport default class ProfileActivitySection extends View {\n constructor(options = {}) {\n super({\n className: 'profile-activity-section',\n template: `<div id=\"activity-table\"></div>`,\n ...options\n });\n }\n\n async onInit() {\n await super.onInit();\n this.tableView = new TableView({\n containerId: 'activity-table',\n collection: new LogList({ size: 10 }),\n defaultQuery: { uid: this.model.id, sort: '-created' },\n hideActivePillNames: ['uid', 'sort'],\n itemClass: ActivityRow,\n columns: [\n {\n key: 'level',\n label: 'Level',\n sortable: true,\n template: '<span class=\"badge {{levelBadgeClass}}\">{{levelText}}</span>',\n filter: { type: 'select', options: ['info', 'warn', 'error'] }\n },\n {\n key: 'log',\n label: 'Message',\n template: '{{logMessage}}'\n },\n { key: 'kind', label: 'Kind', sortable: true, visibility: 'md' },\n {\n key: 'path',\n label: 'Path',\n visibility: 'lg',\n template: '{{methodPath}}'\n },\n { key: 'ip', label: 'IP', visibility: 'xl' },\n { key: 'created|relative', label: 'Time', sortable: true }\n ],\n searchable: true,\n sortable: true,\n filterable: true,\n paginated: true,\n showAdd: false,\n showExport: false,\n tableOptions: {\n striped: false,\n hover: true,\n size: 'sm'\n },\n emptyMessage: 'No activity logs available'\n });\n this.addChild(this.tableView);\n }\n}\n"],"names":["ActivityRow","TableRow","logMessage","log","this","model","get","parsed","JSON","parse","message","type","substring","length","stringify","String","levelText","levelBadgeClass","level","methodPath","method","path","trim","ProfileActivitySection","View","constructor","options","super","className","template","onInit","tableView","TableView","containerId","collection","LogList","size","defaultQuery","uid","id","sort","hideActivePillNames","itemClass","columns","key","label","sortable","filter","visibility","searchable","filterable","paginated","showAdd","showExport","tableOptions","striped","hover","emptyMessage","addChild"],"mappings":"kRAYA,MAAMA,oBAAoBC,EACtB,cAAIC,GACA,MAAMC,EAAMC,KAAKC,OAAOC,IAAI,OAC5B,IAAKH,EAAK,MAAO,GACjB,GAAmB,iBAARA,EACP,IACI,MAAMI,EAASC,KAAKC,MAAMN,GAC1B,OAAOI,EAAOG,SAAWH,EAAOI,MAAQR,EAAIS,UAAU,EAAG,IAC7D,CAAA,MACI,OAAOT,EAAIU,OAAS,IAAMV,EAAIS,UAAU,EAAG,KAAO,IAAMT,CAC5D,CAEJ,MAAmB,iBAARA,EACAA,EAAIO,SAAWP,EAAIQ,MAAQH,KAAKM,UAAUX,GAAKS,UAAU,EAAG,KAEhEG,OAAOZ,EAClB,CAEA,aAAIa,GACA,OAAOZ,KAAKC,OAAOC,IAAI,UAAY,KACvC,CAEA,mBAAIW,GACA,MAAMC,EAAQd,KAAKC,OAAOC,IAAI,SAC9B,MAAc,UAAVY,EAA0B,YAChB,SAAVA,EAAyB,uBACf,SAAVA,EAAyB,UACtB,cACX,CAEA,cAAIC,GACA,MAAMC,EAAShB,KAAKC,OAAOC,IAAI,WAAa,GACtCe,EAAOjB,KAAKC,OAAOC,IAAI,SAAW,GACxC,OAAKc,GAAWC,EACT,GAAGD,KAAUC,IAAOC,OADE,EAEjC,EAGW,MAAMC,+BAA+BC,EAChD,WAAAC,CAAYC,EAAU,IAClBC,MAAM,CACFC,UAAW,2BACXC,SAAU,qCACPH,GAEX,CAEA,YAAMI,SACIH,MAAMG,SACZ1B,KAAK2B,UAAY,IAAIC,EAAU,CAC3BC,YAAa,iBACbC,WAAY,IAAIC,EAAQ,CAAEC,KAAM,KAChCC,aAAc,CAAEC,IAAKlC,KAAKC,MAAMkC,GAAIC,KAAM,YAC1CC,oBAAqB,CAAC,MAAO,QAC7BC,UAAW1C,YACX2C,QAAS,CACL,CACIC,IAAK,QACLC,MAAO,QACPC,UAAU,EACVjB,SAAU,+DACVkB,OAAQ,CAAEpC,KAAM,SAAUe,QAAS,CAAC,OAAQ,OAAQ,WAExD,CACIkB,IAAK,MACLC,MAAO,UACPhB,SAAU,kBAEd,CAAEe,IAAK,OAAQC,MAAO,OAAQC,UAAU,EAAME,WAAY,MAC1D,CACIJ,IAAK,OACLC,MAAO,OACPG,WAAY,KACZnB,SAAU,kBAEd,CAAEe,IAAK,KAAMC,MAAO,KAAMG,WAAY,MACtC,CAAEJ,IAAK,mBAAoBC,MAAO,OAAQC,UAAU,IAExDG,YAAY,EACZH,UAAU,EACVI,YAAY,EACZC,WAAW,EACXC,SAAS,EACTC,YAAY,EACZC,aAAc,CACVC,SAAS,EACTC,OAAO,EACPpB,KAAM,MAEVqB,aAAc,+BAElBrD,KAAKsD,SAAStD,KAAK2B,UACvB"}
1
+ {"version":3,"file":"index-BdfwxVMZ.js","sources":["../../src/extensions/user-profile/views/ProfileActivitySection.js"],"sourcesContent":["/**\n * ProfileActivitySection - Activity log tab\n *\n * Uses TableView + LogList to show user activity\n * with parsed log messages, level badges, kind, path, and time.\n * Includes pagination, search, and level filtering.\n */\nimport View from '@core/View.js';\nimport TableView from '@core/views/table/TableView.js';\nimport TableRow from '@core/views/table/TableRow.js';\nimport { LogList } from '@core/models/Log.js';\n\nclass ActivityRow extends TableRow {\n get logMessage() {\n const log = this.model?.get('log');\n if (!log) return '';\n if (typeof log === 'string') {\n try {\n const parsed = JSON.parse(log);\n return parsed.message || parsed.type || log.substring(0, 120);\n } catch {\n return log.length > 120 ? log.substring(0, 120) + '…' : log;\n }\n }\n if (typeof log === 'object') {\n return log.message || log.type || JSON.stringify(log).substring(0, 120);\n }\n return String(log);\n }\n\n get levelText() {\n return this.model?.get('level') || 'log';\n }\n\n get levelBadgeClass() {\n const level = this.model?.get('level');\n if (level === 'error') return 'bg-danger';\n if (level === 'warn') return 'bg-warning text-dark';\n if (level === 'info') return 'bg-info';\n return 'bg-secondary';\n }\n\n get methodPath() {\n const method = this.model?.get('method') || '';\n const path = this.model?.get('path') || '';\n if (!method && !path) return '';\n return `${method} ${path}`.trim();\n }\n}\n\nexport default class ProfileActivitySection extends View {\n constructor(options = {}) {\n super({\n className: 'profile-activity-section',\n template: `<div id=\"activity-table\"></div>`,\n ...options\n });\n }\n\n async onInit() {\n await super.onInit();\n this.tableView = new TableView({\n containerId: 'activity-table',\n collection: new LogList({ size: 10 }),\n defaultQuery: { uid: this.model.id, sort: '-created' },\n hideActivePillNames: ['uid', 'sort'],\n itemClass: ActivityRow,\n columns: [\n {\n key: 'level',\n label: 'Level',\n sortable: true,\n template: '<span class=\"badge {{levelBadgeClass}}\">{{levelText}}</span>',\n filter: { type: 'select', options: ['info', 'warn', 'error'] }\n },\n {\n key: 'log',\n label: 'Message',\n template: '{{logMessage}}'\n },\n { key: 'kind', label: 'Kind', sortable: true, visibility: 'md' },\n {\n key: 'path',\n label: 'Path',\n visibility: 'lg',\n template: '{{methodPath}}'\n },\n { key: 'ip', label: 'IP', visibility: 'xl' },\n { key: 'created|relative', label: 'Time', sortable: true }\n ],\n searchable: true,\n sortable: true,\n filterable: true,\n paginated: true,\n showAdd: false,\n showExport: false,\n tableOptions: {\n striped: false,\n hover: true,\n size: 'sm'\n },\n emptyMessage: 'No activity logs available'\n });\n this.addChild(this.tableView);\n }\n}\n"],"names":["ActivityRow","TableRow","logMessage","log","this","model","get","parsed","JSON","parse","message","type","substring","length","stringify","String","levelText","levelBadgeClass","level","methodPath","method","path","trim","ProfileActivitySection","View","constructor","options","super","className","template","onInit","tableView","TableView","containerId","collection","LogList","size","defaultQuery","uid","id","sort","hideActivePillNames","itemClass","columns","key","label","sortable","filter","visibility","searchable","filterable","paginated","showAdd","showExport","tableOptions","striped","hover","emptyMessage","addChild"],"mappings":"wRAYA,MAAMA,oBAAoBC,EACtB,cAAIC,GACA,MAAMC,EAAMC,KAAKC,OAAOC,IAAI,OAC5B,IAAKH,EAAK,MAAO,GACjB,GAAmB,iBAARA,EACP,IACI,MAAMI,EAASC,KAAKC,MAAMN,GAC1B,OAAOI,EAAOG,SAAWH,EAAOI,MAAQR,EAAIS,UAAU,EAAG,IAC7D,CAAA,MACI,OAAOT,EAAIU,OAAS,IAAMV,EAAIS,UAAU,EAAG,KAAO,IAAMT,CAC5D,CAEJ,MAAmB,iBAARA,EACAA,EAAIO,SAAWP,EAAIQ,MAAQH,KAAKM,UAAUX,GAAKS,UAAU,EAAG,KAEhEG,OAAOZ,EAClB,CAEA,aAAIa,GACA,OAAOZ,KAAKC,OAAOC,IAAI,UAAY,KACvC,CAEA,mBAAIW,GACA,MAAMC,EAAQd,KAAKC,OAAOC,IAAI,SAC9B,MAAc,UAAVY,EAA0B,YAChB,SAAVA,EAAyB,uBACf,SAAVA,EAAyB,UACtB,cACX,CAEA,cAAIC,GACA,MAAMC,EAAShB,KAAKC,OAAOC,IAAI,WAAa,GACtCe,EAAOjB,KAAKC,OAAOC,IAAI,SAAW,GACxC,OAAKc,GAAWC,EACT,GAAGD,KAAUC,IAAOC,OADE,EAEjC,EAGW,MAAMC,+BAA+BC,EAChD,WAAAC,CAAYC,EAAU,IAClBC,MAAM,CACFC,UAAW,2BACXC,SAAU,qCACPH,GAEX,CAEA,YAAMI,SACIH,MAAMG,SACZ1B,KAAK2B,UAAY,IAAIC,EAAU,CAC3BC,YAAa,iBACbC,WAAY,IAAIC,EAAQ,CAAEC,KAAM,KAChCC,aAAc,CAAEC,IAAKlC,KAAKC,MAAMkC,GAAIC,KAAM,YAC1CC,oBAAqB,CAAC,MAAO,QAC7BC,UAAW1C,YACX2C,QAAS,CACL,CACIC,IAAK,QACLC,MAAO,QACPC,UAAU,EACVjB,SAAU,+DACVkB,OAAQ,CAAEpC,KAAM,SAAUe,QAAS,CAAC,OAAQ,OAAQ,WAExD,CACIkB,IAAK,MACLC,MAAO,UACPhB,SAAU,kBAEd,CAAEe,IAAK,OAAQC,MAAO,OAAQC,UAAU,EAAME,WAAY,MAC1D,CACIJ,IAAK,OACLC,MAAO,OACPG,WAAY,KACZnB,SAAU,kBAEd,CAAEe,IAAK,KAAMC,MAAO,KAAMG,WAAY,MACtC,CAAEJ,IAAK,mBAAoBC,MAAO,OAAQC,UAAU,IAExDG,YAAY,EACZH,UAAU,EACVI,YAAY,EACZC,WAAW,EACXC,SAAS,EACTC,YAAY,EACZC,aAAc,CACVC,SAAS,EACTC,OAAO,EACPpB,KAAM,MAEVqB,aAAc,+BAElBrD,KAAKsD,SAAStD,KAAK2B,UACvB"}
@@ -1,2 +1,2 @@
1
- "use strict";const O="2.2.69",o="2026-03-17T02:53:43.933Z",i={full:O,major:2,minor:2,revision:69,buildTime:o,toString(){return this.full},compare(O){const o=O=>O.split(".").map(Number),[i,r,t]=o(this.full),[e,n,s]=o(O);return i!==e?i-e:r!==n?r-n:t-s}};"undefined"!=typeof window&&(window.MOJO=window.MOJO||{},window.MOJO.VERSION=O,window.MOJO.VERSION_INFO=i,window.MOJO.version=O),exports.BUILD_TIME=o,exports.VERSION=O,exports.VERSION_INFO=i,exports.VERSION_MAJOR=2,exports.VERSION_MINOR=2,exports.VERSION_REVISION=69;
2
- //# sourceMappingURL=version-XmirKYWA.js.map
1
+ "use strict";const O="2.2.70",o="2026-03-17T05:15:44.610Z",i={full:O,major:2,minor:2,revision:70,buildTime:o,toString(){return this.full},compare(O){const o=O=>O.split(".").map(Number),[i,r,t]=o(this.full),[e,n,s]=o(O);return i!==e?i-e:r!==n?r-n:t-s}};"undefined"!=typeof window&&(window.MOJO=window.MOJO||{},window.MOJO.VERSION=O,window.MOJO.VERSION_INFO=i,window.MOJO.version=O),exports.BUILD_TIME=o,exports.VERSION=O,exports.VERSION_INFO=i,exports.VERSION_MAJOR=2,exports.VERSION_MINOR=2,exports.VERSION_REVISION=70;
2
+ //# sourceMappingURL=version-C2yYRyPn.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-XmirKYWA.js","sources":["../../src/version.js"],"sourcesContent":["/**\n * MOJO Framework Version Information\n * Auto-generated on 2026-03-17T02:53:43.933Z\n */\n\nexport const VERSION = '2.2.69';\nexport const VERSION_MAJOR = 2;\nexport const VERSION_MINOR = 2;\nexport const VERSION_REVISION = 69;\nexport const BUILD_TIME = '2026-03-17T02:53:43.933Z';\n\n// Version object for easy access\nexport const VERSION_INFO = {\n full: VERSION,\n major: VERSION_MAJOR,\n minor: VERSION_MINOR,\n revision: VERSION_REVISION,\n buildTime: BUILD_TIME,\n toString() {\n return this.full;\n },\n compare(other) {\n const parseVer = (v) => v.split('.').map(Number);\n const [a1, a2, a3] = parseVer(this.full);\n const [b1, b2, b3] = parseVer(other);\n\n if (a1 !== b1) return a1 - b1;\n if (a2 !== b2) return a2 - b2;\n return a3 - b3;\n }\n};\n\n// Make version globally available if in browser\nif (typeof window !== 'undefined') {\n window.MOJO = window.MOJO || {};\n window.MOJO.VERSION = VERSION;\n window.MOJO.VERSION_INFO = VERSION_INFO;\n\n // Also add to MOJO.version for convenience\n window.MOJO.version = VERSION;\n}\n\nexport default VERSION_INFO;\n"],"names":["VERSION","BUILD_TIME","VERSION_INFO","full","major","minor","revision","buildTime","toString","this","compare","other","parseVer","v","split","map","Number","a1","a2","a3","b1","b2","b3","window","MOJO","version"],"mappings":"aAKY,MAACA,EAAU,SAIVC,EAAa,2BAGbC,EAAe,CACxBC,KAAMH,EACNI,MARyB,EASzBC,MARyB,EASzBC,SAR4B,GAS5BC,UAAWN,EACX,QAAAO,GACI,OAAOC,KAAKN,IAChB,EACA,OAAAO,CAAQC,GACJ,MAAMC,EAAYC,GAAMA,EAAEC,MAAM,KAAKC,IAAIC,SAClCC,EAAIC,EAAIC,GAAMP,EAASH,KAAKN,OAC5BiB,EAAIC,EAAIC,GAAMV,EAASD,GAE9B,OAAIM,IAAOG,EAAWH,EAAKG,EACvBF,IAAOG,EAAWH,EAAKG,EACpBF,EAAKG,CAChB,GAIkB,oBAAXC,SACPA,OAAOC,KAAOD,OAAOC,MAAQ,CAAA,EAC7BD,OAAOC,KAAKxB,QAAUA,EACtBuB,OAAOC,KAAKtB,aAAeA,EAG3BqB,OAAOC,KAAKC,QAAUzB,uFAjCG,wBACA,2BACG"}
1
+ {"version":3,"file":"version-C2yYRyPn.js","sources":["../../src/version.js"],"sourcesContent":["/**\n * MOJO Framework Version Information\n * Auto-generated on 2026-03-17T05:15:44.610Z\n */\n\nexport const VERSION = '2.2.70';\nexport const VERSION_MAJOR = 2;\nexport const VERSION_MINOR = 2;\nexport const VERSION_REVISION = 70;\nexport const BUILD_TIME = '2026-03-17T05:15:44.610Z';\n\n// Version object for easy access\nexport const VERSION_INFO = {\n full: VERSION,\n major: VERSION_MAJOR,\n minor: VERSION_MINOR,\n revision: VERSION_REVISION,\n buildTime: BUILD_TIME,\n toString() {\n return this.full;\n },\n compare(other) {\n const parseVer = (v) => v.split('.').map(Number);\n const [a1, a2, a3] = parseVer(this.full);\n const [b1, b2, b3] = parseVer(other);\n\n if (a1 !== b1) return a1 - b1;\n if (a2 !== b2) return a2 - b2;\n return a3 - b3;\n }\n};\n\n// Make version globally available if in browser\nif (typeof window !== 'undefined') {\n window.MOJO = window.MOJO || {};\n window.MOJO.VERSION = VERSION;\n window.MOJO.VERSION_INFO = VERSION_INFO;\n\n // Also add to MOJO.version for convenience\n window.MOJO.version = VERSION;\n}\n\nexport default VERSION_INFO;\n"],"names":["VERSION","BUILD_TIME","VERSION_INFO","full","major","minor","revision","buildTime","toString","this","compare","other","parseVer","v","split","map","Number","a1","a2","a3","b1","b2","b3","window","MOJO","version"],"mappings":"aAKY,MAACA,EAAU,SAIVC,EAAa,2BAGbC,EAAe,CACxBC,KAAMH,EACNI,MARyB,EASzBC,MARyB,EASzBC,SAR4B,GAS5BC,UAAWN,EACX,QAAAO,GACI,OAAOC,KAAKN,IAChB,EACA,OAAAO,CAAQC,GACJ,MAAMC,EAAYC,GAAMA,EAAEC,MAAM,KAAKC,IAAIC,SAClCC,EAAIC,EAAIC,GAAMP,EAASH,KAAKN,OAC5BiB,EAAIC,EAAIC,GAAMV,EAASD,GAE9B,OAAIM,IAAOG,EAAWH,EAAKG,EACvBF,IAAOG,EAAWH,EAAKG,EACpBF,EAAKG,CAChB,GAIkB,oBAAXC,SACPA,OAAOC,KAAOD,OAAOC,MAAQ,CAAA,EAC7BD,OAAOC,KAAKxB,QAAUA,EACtBuB,OAAOC,KAAKtB,aAAeA,EAG3BqB,OAAOC,KAAKC,QAAUzB,uFAjCG,wBACA,2BACG"}
@@ -1,2 +1,2 @@
1
- const i="2.2.69",n=2,o=2,s=69,O="2026-03-17T02:53:43.933Z",r={full:i,major:2,minor:2,revision:69,buildTime:O,toString(){return this.full},compare(i){const n=i=>i.split(".").map(Number),[o,s,O]=n(this.full),[r,w,e]=n(i);return o!==r?o-r:s!==w?s-w:O-e}};"undefined"!=typeof window&&(window.MOJO=window.MOJO||{},window.MOJO.VERSION=i,window.MOJO.VERSION_INFO=r,window.MOJO.version=i);export{O as B,i as V,r as a,n as b,o as c,s as d};
2
- //# sourceMappingURL=version-D8JjsPW0.js.map
1
+ const i="2.2.70",n=2,o=2,s=70,O="2026-03-17T05:15:44.610Z",r={full:i,major:2,minor:2,revision:70,buildTime:O,toString(){return this.full},compare(i){const n=i=>i.split(".").map(Number),[o,s,O]=n(this.full),[r,w,e]=n(i);return o!==r?o-r:s!==w?s-w:O-e}};"undefined"!=typeof window&&(window.MOJO=window.MOJO||{},window.MOJO.VERSION=i,window.MOJO.VERSION_INFO=r,window.MOJO.version=i);export{O as B,i as V,r as a,n as b,o as c,s as d};
2
+ //# sourceMappingURL=version-CaiqhdME.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-D8JjsPW0.js","sources":["../../src/version.js"],"sourcesContent":["/**\n * MOJO Framework Version Information\n * Auto-generated on 2026-03-17T02:53:43.933Z\n */\n\nexport const VERSION = '2.2.69';\nexport const VERSION_MAJOR = 2;\nexport const VERSION_MINOR = 2;\nexport const VERSION_REVISION = 69;\nexport const BUILD_TIME = '2026-03-17T02:53:43.933Z';\n\n// Version object for easy access\nexport const VERSION_INFO = {\n full: VERSION,\n major: VERSION_MAJOR,\n minor: VERSION_MINOR,\n revision: VERSION_REVISION,\n buildTime: BUILD_TIME,\n toString() {\n return this.full;\n },\n compare(other) {\n const parseVer = (v) => v.split('.').map(Number);\n const [a1, a2, a3] = parseVer(this.full);\n const [b1, b2, b3] = parseVer(other);\n\n if (a1 !== b1) return a1 - b1;\n if (a2 !== b2) return a2 - b2;\n return a3 - b3;\n }\n};\n\n// Make version globally available if in browser\nif (typeof window !== 'undefined') {\n window.MOJO = window.MOJO || {};\n window.MOJO.VERSION = VERSION;\n window.MOJO.VERSION_INFO = VERSION_INFO;\n\n // Also add to MOJO.version for convenience\n window.MOJO.version = VERSION;\n}\n\nexport default VERSION_INFO;\n"],"names":["VERSION","VERSION_MAJOR","VERSION_MINOR","VERSION_REVISION","BUILD_TIME","VERSION_INFO","full","major","minor","revision","buildTime","toString","this","compare","other","parseVer","v","split","map","Number","a1","a2","a3","b1","b2","b3","window","MOJO","version"],"mappings":"AAKY,MAACA,EAAU,SACVC,EAAgB,EAChBC,EAAgB,EAChBC,EAAmB,GACnBC,EAAa,2BAGbC,EAAe,CACxBC,KAAMN,EACNO,MARyB,EASzBC,MARyB,EASzBC,SAR4B,GAS5BC,UAAWN,EACX,QAAAO,GACI,OAAOC,KAAKN,IAChB,EACA,OAAAO,CAAQC,GACJ,MAAMC,EAAYC,GAAMA,EAAEC,MAAM,KAAKC,IAAIC,SAClCC,EAAIC,EAAIC,GAAMP,EAASH,KAAKN,OAC5BiB,EAAIC,EAAIC,GAAMV,EAASD,GAE9B,OAAIM,IAAOG,EAAWH,EAAKG,EACvBF,IAAOG,EAAWH,EAAKG,EACpBF,EAAKG,CAChB,GAIkB,oBAAXC,SACPA,OAAOC,KAAOD,OAAOC,MAAQ,CAAA,EAC7BD,OAAOC,KAAK3B,QAAUA,EACtB0B,OAAOC,KAAKtB,aAAeA,EAG3BqB,OAAOC,KAAKC,QAAU5B"}
1
+ {"version":3,"file":"version-CaiqhdME.js","sources":["../../src/version.js"],"sourcesContent":["/**\n * MOJO Framework Version Information\n * Auto-generated on 2026-03-17T05:15:44.610Z\n */\n\nexport const VERSION = '2.2.70';\nexport const VERSION_MAJOR = 2;\nexport const VERSION_MINOR = 2;\nexport const VERSION_REVISION = 70;\nexport const BUILD_TIME = '2026-03-17T05:15:44.610Z';\n\n// Version object for easy access\nexport const VERSION_INFO = {\n full: VERSION,\n major: VERSION_MAJOR,\n minor: VERSION_MINOR,\n revision: VERSION_REVISION,\n buildTime: BUILD_TIME,\n toString() {\n return this.full;\n },\n compare(other) {\n const parseVer = (v) => v.split('.').map(Number);\n const [a1, a2, a3] = parseVer(this.full);\n const [b1, b2, b3] = parseVer(other);\n\n if (a1 !== b1) return a1 - b1;\n if (a2 !== b2) return a2 - b2;\n return a3 - b3;\n }\n};\n\n// Make version globally available if in browser\nif (typeof window !== 'undefined') {\n window.MOJO = window.MOJO || {};\n window.MOJO.VERSION = VERSION;\n window.MOJO.VERSION_INFO = VERSION_INFO;\n\n // Also add to MOJO.version for convenience\n window.MOJO.version = VERSION;\n}\n\nexport default VERSION_INFO;\n"],"names":["VERSION","VERSION_MAJOR","VERSION_MINOR","VERSION_REVISION","BUILD_TIME","VERSION_INFO","full","major","minor","revision","buildTime","toString","this","compare","other","parseVer","v","split","map","Number","a1","a2","a3","b1","b2","b3","window","MOJO","version"],"mappings":"AAKY,MAACA,EAAU,SACVC,EAAgB,EAChBC,EAAgB,EAChBC,EAAmB,GACnBC,EAAa,2BAGbC,EAAe,CACxBC,KAAMN,EACNO,MARyB,EASzBC,MARyB,EASzBC,SAR4B,GAS5BC,UAAWN,EACX,QAAAO,GACI,OAAOC,KAAKN,IAChB,EACA,OAAAO,CAAQC,GACJ,MAAMC,EAAYC,GAAMA,EAAEC,MAAM,KAAKC,IAAIC,SAClCC,EAAIC,EAAIC,GAAMP,EAASH,KAAKN,OAC5BiB,EAAIC,EAAIC,GAAMV,EAASD,GAE9B,OAAIM,IAAOG,EAAWH,EAAKG,EACvBF,IAAOG,EAAWH,EAAKG,EACpBF,EAAKG,CAChB,GAIkB,oBAAXC,SACPA,OAAOC,KAAOD,OAAOC,MAAQ,CAAA,EAC7BD,OAAOC,KAAK3B,QAAUA,EACtB0B,OAAOC,KAAKtB,aAAeA,EAG3BqB,OAAOC,KAAKC,QAAU5B"}
package/dist/docit.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./chunks/WebApp-CcVF73yg.js"),e=require("./chunks/TokenManager-sZgt--C9.js"),o=require("./chunks/Rest-B1eUyLX5.js"),i=require("./chunks/ContextMenu-8vTiZZQV.js"),s=require("./chunks/Collection-CmjTsmrP.js"),a=require("./chunks/User-DqHG5Gr1.js"),n=require("./chunks/ToastService-C2tTooFn.js"),r=require("./chunks/version-XmirKYWA.js");class DocNavSidebar extends o.View{constructor(t={}){super({className:"docit-sidebar-nav",tagName:"nav",...t}),this.singleBookMode=t.singleBookMode||!1,this.books=t.books,this.docPages=t.docPages,this.activeUser=t.activeUser,this.currentBook=null,this.currentDocPage=null}async onInit(){await super.onInit(),this.getApp().events.on("page:show",this._onPageShow.bind(this))}async _onPageShow({query:t}){const e=t.doc_book,o=t.doc_page,i=this.getApp();if(e){const t=this.books.findWhere({slug:e});!t||this.currentBook&&this.currentBook.id===t.id||(await i.setActiveBook(t),this.currentBook=i.currentBook)}else this.currentBook&&(await i.setActiveBook(null),this.currentBook=null);this.currentDocPage=o?this.docPages.findWhere({slug:o}):null,this.render()}getTemplate(){return'\n <div class="docit-nav-body pt-3">\n {{#currentBook}}\n {{#docPages.models}}\n <a href="#" class="docit-page-link {{#isActive}}active{{/isActive}}"\n data-action="select-page" data-page-slug="{{slug}}">\n <i class="{{#metadata.icon}}{{metadata.icon}}{{/metadata.icon}}{{^metadata.icon}}bi bi-file-earmark-text{{/metadata.icon}} me-2"></i>\n <span>{{title|capitalize}}</span>\n </a>\n {{/docPages.models}}\n {{^docPages.models}}\n <div class="docit-nav-empty"><p>No pages in this book.</p></div>\n {{/docPages.models}}\n {{/currentBook}}\n {{^currentBook}}\n {{#books.models}}\n <a href="#" class="docit-book-item" data-action="select-book" data-book-slug="{{slug}}">\n <i class="bi bi-book me-2"></i>\n <span class="book-title">{{title}}</span>\n <span class="badge bg-secondary ms-auto">{{page_count}}</span>\n </a>\n {{/books.models}}\n {{/currentBook}}\n </div>\n {{#currentBook}}\n <div class="docit-nav-footer">\n {{#canEdit}}\n <button class="btn btn-link w-100 mb-2" data-action="create-page">\n <i class="bi bi-plus-circle me-2"></i>\n Create New Page\n </button>\n {{/canEdit}}\n <button class="btn btn-link w-100" data-action="back-to-books">\n <i class="bi bi-arrow-left me-2"></i>\n Back to Books\n </button>\n </div>\n {{/currentBook}}\n {{^currentBook}}\n {{#canEdit}}\n <div class="docit-nav-footer">\n <button class="btn btn-link w-100" data-action="create-book">\n <i class="bi bi-plus-circle me-2"></i>\n Create New Book\n </button>\n </div>\n {{/canEdit}}\n {{/currentBook}}\n '}async onBeforeRender(){await super.onBeforeRender(),this.canEdit=this.getApp().canEdit(),this.docPages&&this.currentDocPage&&this.docPages.forEach(t=>{t.isActive=t.id===this.currentDocPage.id})}async onActionSelectBook(t,e){t.preventDefault();const o=e.dataset.bookSlug,i=this.books.findWhere({slug:o});if(i){await this.getApp().setActiveBook(i);const t=this.docPages.at(0),e={doc_book:i.get("slug")};t&&(e.doc_page=t.get("slug")),this.getApp().showPage("docs",e,{})}}onActionSelectPage(t,e){t.preventDefault();const o=e.dataset.pageSlug;this.currentBook&&o&&this.getApp().showPage("docs",{doc_book:this.currentBook.get("slug"),doc_page:o},{})}async onActionBackToBooks(t,e){t.preventDefault(),await this.getApp().setActiveBook(null),this.getApp().showPage("home")}async onActionCreateBook(){await this.getApp().createNewBook()}async onActionCreatePage(){this.currentBook&&await this.getApp().createNewPage(this.currentBook)}setBooks(t){this.books=t,this.render()}setDocPages(t){this.docPages=t,this.render()}setCurrentBook(t){this.currentBook=t,this.render(),this.getApp().topnav&&(t?this.getApp().topnav.setBrand(t.get("title")):this.getApp().topnav.setBrand("Documentation"))}setUser(t){this.activeUser=t,this.render()}}class DocHomePage extends i.Page{constructor(t={}){super({pageName:"home",title:"Documentation",className:"docit-home-page",...t})}async getTemplate(){return'\n <div class="docit-empty-state vh-100 d-flex flex-column align-items-center justify-content-center">\n <i class="bi bi-collection" style="font-size: 4rem;"></i>\n <h3 class="mt-4">Welcome to the Documentation Portal</h3>\n <p class="text-muted">Please select a book from the sidebar to get started.</p>\n </div>\n '}}class DocitBook extends s.Model{static endpoint="/api/docit/book";buildUrl(t=null){return this.get("slug")&&!this.id?`/api/docit/book/slug/${this.get("slug")}`:this.id?`/api/docit/book/${this.id}`:this.endpoint}}class DocitBookList extends s.Collection{constructor(t={}){super({ModelClass:DocitBook,endpoint:"/api/docit/book",...t})}parse(t){return t.data&&t.data.status?(this.meta={...this.meta,total:t.data.count||0,graph:t.data.graph},t.data.data||[]):super.parse(t)}}class DocitPage extends s.Model{static endpoint="/api/docit/page";buildUrl(t=null){return this.get("slug")&&!this.id?`/api/docit/page/slug/${this.get("slug")}`:this.id?`/api/docit/page/${this.id}`:this.endpoint}}class DocitPageList extends s.Collection{constructor(t={}){super({ModelClass:DocitPage,endpoint:"/api/docit/page",...t})}parse(t){return t.data&&t.data.status?(this.meta={...this.meta,total:t.data.count||0,graph:t.data.graph,book:t.data.book},t.data.data||[]):super.parse(t)}}class DocPage extends i.Page{constructor(t={}){super({pageName:"docs",title:"Documentation",className:"docit-page",...t}),this.bookModel=null,this.model=null}async onInit(){await super.onInit(),this.pageContextMenu=new i.ContextMenu({config:this.getPageContextMenuConfig(),containerId:"page-context-menu"}),this.addChild(this.pageContextMenu)}getPageContextMenuConfig(){return{icon:"bi-three-dots",buttonClass:"btn btn-outline-secondary btn-sm",items:[{label:"View History",action:"view-history",icon:"bi-clock-history"},{type:"divider"},{label:"Edit Page Content",action:"edit-page",icon:"bi-pencil"},{label:"Edit Page Info",action:"edit-page-info",icon:"bi-list-ol"},{type:"divider"},{label:"Edit Book Info",action:"edit-book",icon:"bi-book"},{type:"divider"},{label:"Delete Page",action:"delete-page",icon:"bi-trash",danger:!0}]}}async getTemplate(){return'\n <div class="docit-page-container position-relative">\n {{#loading}}\n <div class="docit-loading">\n <div class="spinner-border" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n </div>\n {{/loading}}\n\n {{^loading}}\n {{#model|bool}}\n <div class="docit-page-toolbar">\n <div class="row">\n <div class="col d-flex justify-content-end">\n <div data-container="page-context-menu"></div>\n </div>\n </div>\n </div>\n <article class="docit-page-content">\n {{{model.html}}}\n </article>\n\n <nav class="docit-page-nav">\n {{#prevPage}}\n <a href="#" class="docit-nav-prev" data-action="navigate-to-page" data-page-slug="{{slug}}">\n <i class="bi bi-arrow-left"></i>\n <div>\n <small>Previous</small>\n <span>{{title}}</span>\n </div>\n </a>\n {{/prevPage}}\n {{^prevPage}}\n <div></div>\n {{/prevPage}}\n\n {{#nextPage}}\n <a href="#" class="docit-nav-next" data-action="navigate-to-page" data-page-slug="{{slug}}">\n <div>\n <small>Next</small>\n <span>{{title}}</span>\n </div>\n <i class="bi bi-arrow-right"></i>\n </a>\n {{/nextPage}}\n\n <div class="my-3">\n <span class="text-muted">Last updated: {{model.modified|datetime}}</span>\n </div>\n </nav>\n {{/model|bool}}\n\n {{^model|bool}}\n <div class="docit-empty-state">\n <i class="bi bi-file-earmark-text"></i>\n <h3>Select a page</h3>\n <p>Choose a page from the sidebar to view its content.</p>\n </div>\n {{/model|bool}}\n {{/loading}}\n </div>\n '}async onParams(t={},e={}){await super.onParams(t,e),this.loading=!0,await this.render();const o=this.getApp(),i=o.bookSlug||e.doc_book,s=e.doc_page;if(i)try{if(i){if(!o.currentBook||o.currentBook.get("slug")!==i){const t=new DocitBook({slug:i});await t.fetch(),await o.setActiveBook(t)}if(this.bookModel=o.currentBook,s)this.model=new DocitPage({slug:s}),await this.model.fetch({graph:"html"});else{const t=o.docPages.at(0);t?(this.model=new DocitPage({id:t.id}),await this.model.fetch({graph:"html"})):this.model=null}}else this.bookModel=null,this.model=null;this.canEdit=o.canEdit(),this.setupNavigation()}catch(a){console.error("Failed to load page:",a),this.showError("Failed to load documentation page"),this.model=null}finally{this.loading=!1,await this.render(),o.events.emit("docit:page-rendered",{book:this.bookModel,page:this.model})}else setTimeout(()=>{o.showPage("home")},100)}setupNavigation(){if(!this.model)return this.prevPage=null,void(this.nextPage=null);const t=this.getApp().docPages.models,e=t.findIndex(t=>t.id===this.model.id);this.prevPage=e>0?{slug:t[e-1].get("slug"),title:t[e-1].get("title")}:null,this.nextPage=e<t.length-1?{slug:t[e+1].get("slug"),title:t[e+1].get("title")}:null}async onActionNavigateToPage(t,e){t.preventDefault();const o=e.dataset.pageSlug;o&&this.getApp().showPage("docs",{},{doc_book:this.bookModel.get("slug"),doc_page:o})}async onActionEditPage(t,e){t.preventDefault(),this.model&&this.getApp().showPage("edit",{id:this.model.id,doc_book:this.bookModel.get("slug"),doc_page:this.model.get("slug")},{})}async onActionViewHistory(t,e){this.showInfo("Revision history coming soon.")}async onActionEditPageInfo(t,e){this.model&&this.getApp().showModelForm({model:this.model,fields:[{label:"Title",name:"title",type:"text"},{label:"Order Priority",name:"order_priority"},{label:"Slug",name:"slug"}]})}async onActionEditBook(t,e){if(this.model){const t=this.getApp().sidebar.currentBook;if(!t)return;this.getApp().showModelForm({model:t,fields:[{label:"Title",name:"title",type:"text"},{label:"Order Priority",name:"order_priority"},{label:"Slug",name:"slug"},{label:"Is Active",name:"is_active",type:"switch"}]})}}async onActionDeletePage(t,e){if(this.model&&await this.getApp().showConfirm({title:"Delete Page",body:`Are you sure you want to delete "${this.model.get("title")}"?`,confirmText:"Delete",confirmClass:"btn-danger"})){await this.model.destroy(),this.showSuccess("Page deleted."),await this.getApp().setActiveBook(this.bookModel);const t=this.getApp().docPages.at(0);t?this.getApp().showPage("docs",{},{doc_book:this.bookModel.get("slug"),doc_page:t.get("slug")}):this.getApp().showPage("docs",{},{doc_book:this.bookModel.get("slug")})}}async onAfterRender(){await super.onAfterRender(),"undefined"!=typeof Prism&&this.model&&Prism.highlightAll()}}class DocEditPage extends i.Page{constructor(t={}){super({pageName:"edit",title:"Edit Page",className:"docit-edit-page",...t}),this.model=null,this.editor=null,this.isDirty=!1}async getTemplate(){return'\n <div class="docit-edit-container vh-100">\n {{#loading}}\n <div class="docit-loading"><div class="spinner-border"></div></div>\n {{/loading}}\n\n {{^loading}}{{#model}}\n <header class="docit-edit-header">\n <div class="docit-edit-title-row">\n <h2>Editing: {{model.title}}</h2>\n <div class="docit-edit-actions">\n <button class="btn btn-outline-secondary" data-action="cancel-edit">Cancel</button>\n <button class="btn btn-success" data-action="save-page">\n <i class="bi bi-check-lg"></i> Save Changes\n </button>\n </div>\n </div>\n </header>\n <div class="docit-edit-body flex-grow-1">\n <div id="editor"></div>\n </div>\n {{/model}}\n {{^model}}\n <div class="docit-error-state">\n <h3>Page Not Found</h3>\n <p>The page you\'re trying to edit could not be found.</p>\n <button class="btn btn-primary" data-action="go-back">Go Back</button>\n </div>\n {{/model}}{{/loading}}\n </div>\n '}async onParams(t={},e={}){await super.onParams(t,e);let o=null;e.id?o=new DocitPage({id:e.id}):e.doc_page&&(o=new DocitPage({slug:e.doc_page})),this.model=o,this.model&&await this.model.fetch({graph:"detail"})}initEditor(){!this.editor&&this.element.querySelector("#editor")&&(this.editor=new toastui.Editor({el:this.element.querySelector("#editor"),height:"100%",initialEditType:"markdown",previewStyle:"tab",initialValue:this.model.get("content")||""}),this.editor.on("change",()=>{this.isDirty=!0}))}async onActionSavePage(){if(this.model&&this.editor){this.saving=!0,this.getApp().showLoading("saving...");try{const t=this.editor.getMarkdown();await this.model.save({content:t}),this.isDirty=!1,this.getApp().toast.success("Page saved successfully."),this.getApp().showPage("docs",this.query)}catch(t){console.error("Failed to save page:",t),this.getApp().toast.error("Failed to save page.")}finally{this.saving=!1,this.getApp().hideLoading()}}}async onActionCancelEdit(){this.getApp().showPage("docs",this.query)}onActionGoBack(){this.getApp().showPage("docs")}async onBeforeRender(){await super.onBeforeRender(),this.editor&&(this.editor.destroy(),this.editor=null)}async onAfterRender(){await super.onAfterRender(),this.model&&this.initEditor()}async onBeforeDestroy(){this.editor?.destroy(),await super.onBeforeDestroy()}}class DocItApp extends t.WebApp{constructor(t={}){super({name:t.title||"DocIt Portal",version:t.version||"1.0.0",debug:t.debug||!1,container:t.container||"#app",defaultRoute:"home",basePath:t.basePath||"",...t}),this.bookSlug=t.bookSlug||null,this.showBookNav=void 0!==t.showBookNav?t.showBookNav:!this.bookSlug,this.theme=t.theme||"light",this.editPermissions=t.permissions?.edit||["manage_docit"],this.sidebarConfig={showSearch:!0,defaultCollapsed:!1,...t.sidebar},this.books=new DocitBookList,this.books.params.sort="-order_priority",this.docPages=new DocitPageList,this.docPages.params.sort="-order_priority",this.toast=new n.ToastService,this.currentBook=null,this.sidebar=null,this.isDocItReady=!1,this.tokenManager=new e.TokenManager,this.activeUser=null}async start(){try{this.setupDocItLayout(),await this.setupTopNav(),await this.setupSidebar(),await this.checkAuthStatus(),this.registerDocItPages(),await super.start(),await this.loadInitialData(),this.isDocItReady=!0,this.events.emit("docit:ready",{app:this})}catch(t){throw console.error("Failed to start DocIt:",t),this.showError("Failed to initialize documentation portal"),t}}setupDocItLayout(){const t="string"==typeof this.container?document.querySelector(this.container):this.container;if(!t)throw new Error(`Container not found: ${this.container}`);t.classList.add("docit-app",`docit-theme-${this.theme}`),t.innerHTML='\n <div class="docit-app-layout">\n <div id="topnav-container"></div>\n <div class="docit-body-layout">\n <div class="docit-sidebar" id="docit-sidebar"></div>\n <div class="docit-main">\n <div class="docit-content" id="page-container"></div>\n </div>\n </div>\n </div>\n ',this.pageContainer="#page-container"}async setupTopNav(){this.topnav=new e.TopNav({containerId:"topnav-container",brand:this.name,theme:"navbar navbar-expand-lg bg-dark navbar-dark",showSidebarToggle:!1,displayMode:"brand",rightItems:[{id:"login",icon:"bi-box-arrow-in-right",href:"/examples/auth/",label:"Login"}],userMenu:{label:"User",icon:"bi-person-circle",items:[{label:"Profile",icon:"bi-person",action:"profile"},{divider:!0},{label:"Logout",icon:"bi-box-arrow-right",action:"logout"}]}}),await this.topnav.render()}onActionToggleSidebar(){document.querySelector(".docit-layout").classList.toggle("sidebar-collapsed")}async setupSidebar(){this.sidebar=new DocNavSidebar({containerId:"docit-sidebar",app:this,singleBookMode:!!this.bookSlug,showBookNav:this.showBookNav,books:this.books,docPages:this.docPages,activeUser:this.activeUser,...this.sidebarConfig}),await this.sidebar.render()}registerDocItPages(){this.registerPage("home",DocHomePage,{route:"/",permissions:null}),this.registerPage("docs",DocPage,{route:"/docs",permissions:null}),this.registerPage("edit",DocEditPage,{route:"/edit",permissions:this.editPermissions})}async loadInitialData(){try{if(this.bookSlug){const t=new DocitBook({slug:this.bookSlug});if(await t.fetch(),!t.id)throw new Error(`Book with slug '${this.bookSlug}' not found.`);this.books.add(t),await this.setActiveBook(t)}else await this.books.fetch({graph:"list"}),this.sidebar.render()}catch(t){console.error("Failed to load initial data:",t),this.showError("Failed to load documentation")}}async setActiveBook(t){this.currentBook&&t&&this.currentBook.id===t.id||(this.currentBook=t,this.docPages.reset(),t&&await this.docPages.fetch({book:t.get("id"),graph:"list"}),this.sidebar.setCurrentBook(t),this.sidebar.setDocPages(this.docPages),this.events.emit("docit:book-changed",{book:t}))}async saveDocPageContent(t,e){const o=this.docPages.get(t)||new DocitPage({id:t});o.set("content",e);const i=await o.save();if(!i.success||!i.data.status)throw new Error("Failed to save doc page");return o}canEdit(){const t=this.activeUser;return!!t&&this.editPermissions.some(e=>!!t.hasPermission&&t.hasPermission(e))}async checkAuthStatus(){try{const t=this.tokenManager.getTokenInstance();if(!t||!t.isValid())return void this.events.emit("auth:unauthorized",{app:this});if(t.isExpired())return void this.events.emit("auth:expired",{app:this});this.tokenManager.startAutoRefresh(this),this.rest.setAuthToken(t.token);const e=new a.User({id:t.getUserId()});await e.fetch(),this.setActiveUser(e)}catch(t){console.error("Failed to check auth status:",t),this.events.emit("auth:error",{error:t,app:this})}}setActiveUser(t){return this.activeUser=t,this.sidebar&&this.sidebar.setUser(t),this.topnav&&(this.topnav.setUser(t),this.tonnav.render()),this.events.emit("user:changed",{user:t,app:this}),this}clearActiveUser(){return this.activeUser=null,this.tokenManager.clearTokens(),this.rest.clearAuth(),this.sidebar&&this.sidebar.setUser(null),this.events.emit("user:cleared",{app:this}),this}async logout(){this.clearActiveUser(),this.books.reset(),this.docPages.reset(),this.currentBook=null,this.events.emit("auth:logout",{app:this}),window.location.reload()}async createNewBook(){const t=await this.showModelForm({title:"Create New Book",model:new DocitBook,fields:[{name:"title",label:"Title",required:!0},{name:"slug",label:"Slug",required:!0,helpText:"A URL-friendly identifier."}]});t&&t.success&&(this.books.add(t.data),this.sidebar.render(),this.showSuccess("Book created successfully."))}async createNewPage(t){if(!t)return;const e=await this.showForm({title:"Create New Page",fields:[{name:"title",label:"Title",required:!0},{name:"slug",label:"Slug",required:!0,helpText:"A URL-friendly identifier."}]});e&&e.slug&&(e.book=t.id);const o=new DocitPage,i=await o.save(e);i&&i.success&&(this.docPages.add(o),this.sidebar.render(),this.showPage("edit",{id:o.id,doc_book:t.get("slug"),doc_page:o.get("slug")},{}))}static create(t={}){return new DocItApp(t)}static createForBook(t,e={}){return new DocItApp({...e,bookSlug:t,showBookNav:!1})}}exports.WebApp=t.WebApp,exports.BUILD_TIME=r.BUILD_TIME,exports.VERSION=r.VERSION,exports.VERSION_INFO=r.VERSION_INFO,exports.VERSION_MAJOR=r.VERSION_MAJOR,exports.VERSION_MINOR=r.VERSION_MINOR,exports.VERSION_REVISION=r.VERSION_REVISION,exports.DocEditPage=DocEditPage,exports.DocHomePage=DocHomePage,exports.DocItApp=DocItApp,exports.DocNavSidebar=DocNavSidebar,exports.DocPage=DocPage,exports.DocitBook=DocitBook,exports.DocitBookList=DocitBookList,exports.DocitPage=DocitPage,exports.DocitPageList=DocitPageList;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./chunks/WebApp-BFR1zozS.js"),e=require("./chunks/TokenManager-BFaxNsXO.js"),o=require("./chunks/Collection-BUv4E9op.js"),i=require("./chunks/ContextMenu-BFxliZ03.js"),s=require("./chunks/Dialog-BYiynSW-.js"),a=require("./chunks/version-C2yYRyPn.js");class DocNavSidebar extends o.View{constructor(t={}){super({className:"docit-sidebar-nav",tagName:"nav",...t}),this.singleBookMode=t.singleBookMode||!1,this.books=t.books,this.docPages=t.docPages,this.activeUser=t.activeUser,this.currentBook=null,this.currentDocPage=null}async onInit(){await super.onInit(),this.getApp().events.on("page:show",this._onPageShow.bind(this))}async _onPageShow({query:t}){const e=t.doc_book,o=t.doc_page,i=this.getApp();if(e){const t=this.books.findWhere({slug:e});!t||this.currentBook&&this.currentBook.id===t.id||(await i.setActiveBook(t),this.currentBook=i.currentBook)}else this.currentBook&&(await i.setActiveBook(null),this.currentBook=null);this.currentDocPage=o?this.docPages.findWhere({slug:o}):null,this.render()}getTemplate(){return'\n <div class="docit-nav-body pt-3">\n {{#currentBook}}\n {{#docPages.models}}\n <a href="#" class="docit-page-link {{#isActive}}active{{/isActive}}"\n data-action="select-page" data-page-slug="{{slug}}">\n <i class="{{#metadata.icon}}{{metadata.icon}}{{/metadata.icon}}{{^metadata.icon}}bi bi-file-earmark-text{{/metadata.icon}} me-2"></i>\n <span>{{title|capitalize}}</span>\n </a>\n {{/docPages.models}}\n {{^docPages.models}}\n <div class="docit-nav-empty"><p>No pages in this book.</p></div>\n {{/docPages.models}}\n {{/currentBook}}\n {{^currentBook}}\n {{#books.models}}\n <a href="#" class="docit-book-item" data-action="select-book" data-book-slug="{{slug}}">\n <i class="bi bi-book me-2"></i>\n <span class="book-title">{{title}}</span>\n <span class="badge bg-secondary ms-auto">{{page_count}}</span>\n </a>\n {{/books.models}}\n {{/currentBook}}\n </div>\n {{#currentBook}}\n <div class="docit-nav-footer">\n {{#canEdit}}\n <button class="btn btn-link w-100 mb-2" data-action="create-page">\n <i class="bi bi-plus-circle me-2"></i>\n Create New Page\n </button>\n {{/canEdit}}\n <button class="btn btn-link w-100" data-action="back-to-books">\n <i class="bi bi-arrow-left me-2"></i>\n Back to Books\n </button>\n </div>\n {{/currentBook}}\n {{^currentBook}}\n {{#canEdit}}\n <div class="docit-nav-footer">\n <button class="btn btn-link w-100" data-action="create-book">\n <i class="bi bi-plus-circle me-2"></i>\n Create New Book\n </button>\n </div>\n {{/canEdit}}\n {{/currentBook}}\n '}async onBeforeRender(){await super.onBeforeRender(),this.canEdit=this.getApp().canEdit(),this.docPages&&this.currentDocPage&&this.docPages.forEach(t=>{t.isActive=t.id===this.currentDocPage.id})}async onActionSelectBook(t,e){t.preventDefault();const o=e.dataset.bookSlug,i=this.books.findWhere({slug:o});if(i){await this.getApp().setActiveBook(i);const t=this.docPages.at(0),e={doc_book:i.get("slug")};t&&(e.doc_page=t.get("slug")),this.getApp().showPage("docs",e,{})}}onActionSelectPage(t,e){t.preventDefault();const o=e.dataset.pageSlug;this.currentBook&&o&&this.getApp().showPage("docs",{doc_book:this.currentBook.get("slug"),doc_page:o},{})}async onActionBackToBooks(t,e){t.preventDefault(),await this.getApp().setActiveBook(null),this.getApp().showPage("home")}async onActionCreateBook(){await this.getApp().createNewBook()}async onActionCreatePage(){this.currentBook&&await this.getApp().createNewPage(this.currentBook)}setBooks(t){this.books=t,this.render()}setDocPages(t){this.docPages=t,this.render()}setCurrentBook(t){this.currentBook=t,this.render(),this.getApp().topnav&&(t?this.getApp().topnav.setBrand(t.get("title")):this.getApp().topnav.setBrand("Documentation"))}setUser(t){this.activeUser=t,this.render()}}class DocHomePage extends i.Page{constructor(t={}){super({pageName:"home",title:"Documentation",className:"docit-home-page",...t})}async getTemplate(){return'\n <div class="docit-empty-state vh-100 d-flex flex-column align-items-center justify-content-center">\n <i class="bi bi-collection" style="font-size: 4rem;"></i>\n <h3 class="mt-4">Welcome to the Documentation Portal</h3>\n <p class="text-muted">Please select a book from the sidebar to get started.</p>\n </div>\n '}}class DocitBook extends o.Model{static endpoint="/api/docit/book";buildUrl(t=null){return this.get("slug")&&!this.id?`/api/docit/book/slug/${this.get("slug")}`:this.id?`/api/docit/book/${this.id}`:this.endpoint}}class DocitBookList extends o.Collection{constructor(t={}){super({ModelClass:DocitBook,endpoint:"/api/docit/book",...t})}parse(t){return t.data&&t.data.status?(this.meta={...this.meta,total:t.data.count||0,graph:t.data.graph},t.data.data||[]):super.parse(t)}}class DocitPage extends o.Model{static endpoint="/api/docit/page";buildUrl(t=null){return this.get("slug")&&!this.id?`/api/docit/page/slug/${this.get("slug")}`:this.id?`/api/docit/page/${this.id}`:this.endpoint}}class DocitPageList extends o.Collection{constructor(t={}){super({ModelClass:DocitPage,endpoint:"/api/docit/page",...t})}parse(t){return t.data&&t.data.status?(this.meta={...this.meta,total:t.data.count||0,graph:t.data.graph,book:t.data.book},t.data.data||[]):super.parse(t)}}class DocPage extends i.Page{constructor(t={}){super({pageName:"docs",title:"Documentation",className:"docit-page",...t}),this.bookModel=null,this.model=null}async onInit(){await super.onInit(),this.pageContextMenu=new i.ContextMenu({config:this.getPageContextMenuConfig(),containerId:"page-context-menu"}),this.addChild(this.pageContextMenu)}getPageContextMenuConfig(){return{icon:"bi-three-dots",buttonClass:"btn btn-outline-secondary btn-sm",items:[{label:"View History",action:"view-history",icon:"bi-clock-history"},{type:"divider"},{label:"Edit Page Content",action:"edit-page",icon:"bi-pencil"},{label:"Edit Page Info",action:"edit-page-info",icon:"bi-list-ol"},{type:"divider"},{label:"Edit Book Info",action:"edit-book",icon:"bi-book"},{type:"divider"},{label:"Delete Page",action:"delete-page",icon:"bi-trash",danger:!0}]}}async getTemplate(){return'\n <div class="docit-page-container position-relative">\n {{#loading}}\n <div class="docit-loading">\n <div class="spinner-border" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n </div>\n {{/loading}}\n\n {{^loading}}\n {{#model|bool}}\n <div class="docit-page-toolbar">\n <div class="row">\n <div class="col d-flex justify-content-end">\n <div data-container="page-context-menu"></div>\n </div>\n </div>\n </div>\n <article class="docit-page-content">\n {{{model.html}}}\n </article>\n\n <nav class="docit-page-nav">\n {{#prevPage}}\n <a href="#" class="docit-nav-prev" data-action="navigate-to-page" data-page-slug="{{slug}}">\n <i class="bi bi-arrow-left"></i>\n <div>\n <small>Previous</small>\n <span>{{title}}</span>\n </div>\n </a>\n {{/prevPage}}\n {{^prevPage}}\n <div></div>\n {{/prevPage}}\n\n {{#nextPage}}\n <a href="#" class="docit-nav-next" data-action="navigate-to-page" data-page-slug="{{slug}}">\n <div>\n <small>Next</small>\n <span>{{title}}</span>\n </div>\n <i class="bi bi-arrow-right"></i>\n </a>\n {{/nextPage}}\n\n <div class="my-3">\n <span class="text-muted">Last updated: {{model.modified|datetime}}</span>\n </div>\n </nav>\n {{/model|bool}}\n\n {{^model|bool}}\n <div class="docit-empty-state">\n <i class="bi bi-file-earmark-text"></i>\n <h3>Select a page</h3>\n <p>Choose a page from the sidebar to view its content.</p>\n </div>\n {{/model|bool}}\n {{/loading}}\n </div>\n '}async onParams(t={},e={}){await super.onParams(t,e),this.loading=!0,await this.render();const o=this.getApp(),i=o.bookSlug||e.doc_book,s=e.doc_page;if(i)try{if(i){if(!o.currentBook||o.currentBook.get("slug")!==i){const t=new DocitBook({slug:i});await t.fetch(),await o.setActiveBook(t)}if(this.bookModel=o.currentBook,s)this.model=new DocitPage({slug:s}),await this.model.fetch({graph:"html"});else{const t=o.docPages.at(0);t?(this.model=new DocitPage({id:t.id}),await this.model.fetch({graph:"html"})):this.model=null}}else this.bookModel=null,this.model=null;this.canEdit=o.canEdit(),this.setupNavigation()}catch(a){console.error("Failed to load page:",a),this.showError("Failed to load documentation page"),this.model=null}finally{this.loading=!1,await this.render(),o.events.emit("docit:page-rendered",{book:this.bookModel,page:this.model})}else setTimeout(()=>{o.showPage("home")},100)}setupNavigation(){if(!this.model)return this.prevPage=null,void(this.nextPage=null);const t=this.getApp().docPages.models,e=t.findIndex(t=>t.id===this.model.id);this.prevPage=e>0?{slug:t[e-1].get("slug"),title:t[e-1].get("title")}:null,this.nextPage=e<t.length-1?{slug:t[e+1].get("slug"),title:t[e+1].get("title")}:null}async onActionNavigateToPage(t,e){t.preventDefault();const o=e.dataset.pageSlug;o&&this.getApp().showPage("docs",{},{doc_book:this.bookModel.get("slug"),doc_page:o})}async onActionEditPage(t,e){t.preventDefault(),this.model&&this.getApp().showPage("edit",{id:this.model.id,doc_book:this.bookModel.get("slug"),doc_page:this.model.get("slug")},{})}async onActionViewHistory(t,e){this.showInfo("Revision history coming soon.")}async onActionEditPageInfo(t,e){this.model&&this.getApp().showModelForm({model:this.model,fields:[{label:"Title",name:"title",type:"text"},{label:"Order Priority",name:"order_priority"},{label:"Slug",name:"slug"}]})}async onActionEditBook(t,e){if(this.model){const t=this.getApp().sidebar.currentBook;if(!t)return;this.getApp().showModelForm({model:t,fields:[{label:"Title",name:"title",type:"text"},{label:"Order Priority",name:"order_priority"},{label:"Slug",name:"slug"},{label:"Is Active",name:"is_active",type:"switch"}]})}}async onActionDeletePage(t,e){if(this.model&&await this.getApp().showConfirm({title:"Delete Page",body:`Are you sure you want to delete "${this.model.get("title")}"?`,confirmText:"Delete",confirmClass:"btn-danger"})){await this.model.destroy(),this.showSuccess("Page deleted."),await this.getApp().setActiveBook(this.bookModel);const t=this.getApp().docPages.at(0);t?this.getApp().showPage("docs",{},{doc_book:this.bookModel.get("slug"),doc_page:t.get("slug")}):this.getApp().showPage("docs",{},{doc_book:this.bookModel.get("slug")})}}async onAfterRender(){await super.onAfterRender(),"undefined"!=typeof Prism&&this.model&&Prism.highlightAll()}}class DocEditPage extends i.Page{constructor(t={}){super({pageName:"edit",title:"Edit Page",className:"docit-edit-page",...t}),this.model=null,this.editor=null,this.isDirty=!1}async getTemplate(){return'\n <div class="docit-edit-container vh-100">\n {{#loading}}\n <div class="docit-loading"><div class="spinner-border"></div></div>\n {{/loading}}\n\n {{^loading}}{{#model}}\n <header class="docit-edit-header">\n <div class="docit-edit-title-row">\n <h2>Editing: {{model.title}}</h2>\n <div class="docit-edit-actions">\n <button class="btn btn-outline-secondary" data-action="cancel-edit">Cancel</button>\n <button class="btn btn-success" data-action="save-page">\n <i class="bi bi-check-lg"></i> Save Changes\n </button>\n </div>\n </div>\n </header>\n <div class="docit-edit-body flex-grow-1">\n <div id="editor"></div>\n </div>\n {{/model}}\n {{^model}}\n <div class="docit-error-state">\n <h3>Page Not Found</h3>\n <p>The page you\'re trying to edit could not be found.</p>\n <button class="btn btn-primary" data-action="go-back">Go Back</button>\n </div>\n {{/model}}{{/loading}}\n </div>\n '}async onParams(t={},e={}){await super.onParams(t,e);let o=null;e.id?o=new DocitPage({id:e.id}):e.doc_page&&(o=new DocitPage({slug:e.doc_page})),this.model=o,this.model&&await this.model.fetch({graph:"detail"})}initEditor(){!this.editor&&this.element.querySelector("#editor")&&(this.editor=new toastui.Editor({el:this.element.querySelector("#editor"),height:"100%",initialEditType:"markdown",previewStyle:"tab",initialValue:this.model.get("content")||""}),this.editor.on("change",()=>{this.isDirty=!0}))}async onActionSavePage(){if(this.model&&this.editor){this.saving=!0,this.getApp().showLoading("saving...");try{const t=this.editor.getMarkdown();await this.model.save({content:t}),this.isDirty=!1,this.getApp().toast.success("Page saved successfully."),this.getApp().showPage("docs",this.query)}catch(t){console.error("Failed to save page:",t),this.getApp().toast.error("Failed to save page.")}finally{this.saving=!1,this.getApp().hideLoading()}}}async onActionCancelEdit(){this.getApp().showPage("docs",this.query)}onActionGoBack(){this.getApp().showPage("docs")}async onBeforeRender(){await super.onBeforeRender(),this.editor&&(this.editor.destroy(),this.editor=null)}async onAfterRender(){await super.onAfterRender(),this.model&&this.initEditor()}async onBeforeDestroy(){this.editor?.destroy(),await super.onBeforeDestroy()}}class DocItApp extends t.WebApp{constructor(t={}){super({name:t.title||"DocIt Portal",version:t.version||"1.0.0",debug:t.debug||!1,container:t.container||"#app",defaultRoute:"home",basePath:t.basePath||"",...t}),this.bookSlug=t.bookSlug||null,this.showBookNav=void 0!==t.showBookNav?t.showBookNav:!this.bookSlug,this.theme=t.theme||"light",this.editPermissions=t.permissions?.edit||["manage_docit"],this.sidebarConfig={showSearch:!0,defaultCollapsed:!1,...t.sidebar},this.books=new DocitBookList,this.books.params.sort="-order_priority",this.docPages=new DocitPageList,this.docPages.params.sort="-order_priority",this.toast=new s.ToastService,this.currentBook=null,this.sidebar=null,this.isDocItReady=!1,this.tokenManager=new e.TokenManager,this.activeUser=null}async start(){try{this.setupDocItLayout(),await this.setupTopNav(),await this.setupSidebar(),await this.checkAuthStatus(),this.registerDocItPages(),await super.start(),await this.loadInitialData(),this.isDocItReady=!0,this.events.emit("docit:ready",{app:this})}catch(t){throw console.error("Failed to start DocIt:",t),this.showError("Failed to initialize documentation portal"),t}}setupDocItLayout(){const t="string"==typeof this.container?document.querySelector(this.container):this.container;if(!t)throw new Error(`Container not found: ${this.container}`);t.classList.add("docit-app",`docit-theme-${this.theme}`),t.innerHTML='\n <div class="docit-app-layout">\n <div id="topnav-container"></div>\n <div class="docit-body-layout">\n <div class="docit-sidebar" id="docit-sidebar"></div>\n <div class="docit-main">\n <div class="docit-content" id="page-container"></div>\n </div>\n </div>\n </div>\n ',this.pageContainer="#page-container"}async setupTopNav(){this.topnav=new e.TopNav({containerId:"topnav-container",brand:this.name,theme:"navbar navbar-expand-lg bg-dark navbar-dark",showSidebarToggle:!1,displayMode:"brand",rightItems:[{id:"login",icon:"bi-box-arrow-in-right",href:"/examples/auth/",label:"Login"}],userMenu:{label:"User",icon:"bi-person-circle",items:[{label:"Profile",icon:"bi-person",action:"profile"},{divider:!0},{label:"Logout",icon:"bi-box-arrow-right",action:"logout"}]}}),await this.topnav.render()}onActionToggleSidebar(){document.querySelector(".docit-layout").classList.toggle("sidebar-collapsed")}async setupSidebar(){this.sidebar=new DocNavSidebar({containerId:"docit-sidebar",app:this,singleBookMode:!!this.bookSlug,showBookNav:this.showBookNav,books:this.books,docPages:this.docPages,activeUser:this.activeUser,...this.sidebarConfig}),await this.sidebar.render()}registerDocItPages(){this.registerPage("home",DocHomePage,{route:"/",permissions:null}),this.registerPage("docs",DocPage,{route:"/docs",permissions:null}),this.registerPage("edit",DocEditPage,{route:"/edit",permissions:this.editPermissions})}async loadInitialData(){try{if(this.bookSlug){const t=new DocitBook({slug:this.bookSlug});if(await t.fetch(),!t.id)throw new Error(`Book with slug '${this.bookSlug}' not found.`);this.books.add(t),await this.setActiveBook(t)}else await this.books.fetch({graph:"list"}),this.sidebar.render()}catch(t){console.error("Failed to load initial data:",t),this.showError("Failed to load documentation")}}async setActiveBook(t){this.currentBook&&t&&this.currentBook.id===t.id||(this.currentBook=t,this.docPages.reset(),t&&await this.docPages.fetch({book:t.get("id"),graph:"list"}),this.sidebar.setCurrentBook(t),this.sidebar.setDocPages(this.docPages),this.events.emit("docit:book-changed",{book:t}))}async saveDocPageContent(t,e){const o=this.docPages.get(t)||new DocitPage({id:t});o.set("content",e);const i=await o.save();if(!i.success||!i.data.status)throw new Error("Failed to save doc page");return o}canEdit(){const t=this.activeUser;return!!t&&this.editPermissions.some(e=>!!t.hasPermission&&t.hasPermission(e))}async checkAuthStatus(){try{const t=this.tokenManager.getTokenInstance();if(!t||!t.isValid())return void this.events.emit("auth:unauthorized",{app:this});if(t.isExpired())return void this.events.emit("auth:expired",{app:this});this.tokenManager.startAutoRefresh(this),this.rest.setAuthToken(t.token);const e=new s.User({id:t.getUserId()});await e.fetch(),this.setActiveUser(e)}catch(t){console.error("Failed to check auth status:",t),this.events.emit("auth:error",{error:t,app:this})}}setActiveUser(t){return this.activeUser=t,this.sidebar&&this.sidebar.setUser(t),this.topnav&&(this.topnav.setUser(t),this.tonnav.render()),this.events.emit("user:changed",{user:t,app:this}),this}clearActiveUser(){return this.activeUser=null,this.tokenManager.clearTokens(),this.rest.clearAuth(),this.sidebar&&this.sidebar.setUser(null),this.events.emit("user:cleared",{app:this}),this}async logout(){this.clearActiveUser(),this.books.reset(),this.docPages.reset(),this.currentBook=null,this.events.emit("auth:logout",{app:this}),window.location.reload()}async createNewBook(){const t=await this.showModelForm({title:"Create New Book",model:new DocitBook,fields:[{name:"title",label:"Title",required:!0},{name:"slug",label:"Slug",required:!0,helpText:"A URL-friendly identifier."}]});t&&t.success&&(this.books.add(t.data),this.sidebar.render(),this.showSuccess("Book created successfully."))}async createNewPage(t){if(!t)return;const e=await this.showForm({title:"Create New Page",fields:[{name:"title",label:"Title",required:!0},{name:"slug",label:"Slug",required:!0,helpText:"A URL-friendly identifier."}]});e&&e.slug&&(e.book=t.id);const o=new DocitPage,i=await o.save(e);i&&i.success&&(this.docPages.add(o),this.sidebar.render(),this.showPage("edit",{id:o.id,doc_book:t.get("slug"),doc_page:o.get("slug")},{}))}static create(t={}){return new DocItApp(t)}static createForBook(t,e={}){return new DocItApp({...e,bookSlug:t,showBookNav:!1})}}exports.WebApp=t.WebApp,exports.BUILD_TIME=a.BUILD_TIME,exports.VERSION=a.VERSION,exports.VERSION_INFO=a.VERSION_INFO,exports.VERSION_MAJOR=a.VERSION_MAJOR,exports.VERSION_MINOR=a.VERSION_MINOR,exports.VERSION_REVISION=a.VERSION_REVISION,exports.DocEditPage=DocEditPage,exports.DocHomePage=DocHomePage,exports.DocItApp=DocItApp,exports.DocNavSidebar=DocNavSidebar,exports.DocPage=DocPage,exports.DocitBook=DocitBook,exports.DocitBookList=DocitBookList,exports.DocitPage=DocitPage,exports.DocitPageList=DocitPageList;
2
2
  //# sourceMappingURL=docit.cjs.js.map