web-mojo 2.2.2 → 2.2.4

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 (75) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.es.js +12 -12
  3. package/dist/auth.cjs.js +1 -1
  4. package/dist/auth.es.js +1 -1
  5. package/dist/charts.cjs.js +1 -1
  6. package/dist/charts.es.js +4 -4
  7. package/dist/chunks/{ChatView-CWNoGk-B.js → ChatView-DZ7f2Rl5.js} +7 -7
  8. package/dist/chunks/{ChatView-CWNoGk-B.js.map → ChatView-DZ7f2Rl5.js.map} +1 -1
  9. package/dist/chunks/{ChatView-DSEEMnNt.js → ChatView-DoA8h81E.js} +2 -2
  10. package/dist/chunks/{ChatView-DSEEMnNt.js.map → ChatView-DoA8h81E.js.map} +1 -1
  11. package/dist/chunks/{Collection-CqLcHgQG.js → Collection-CREl6Fa9.js} +2 -2
  12. package/dist/chunks/{Collection-CqLcHgQG.js.map → Collection-CREl6Fa9.js.map} +1 -1
  13. package/dist/chunks/{Collection-C7rteLDn.js → Collection-DJaa8dhS.js} +2 -2
  14. package/dist/chunks/{Collection-C7rteLDn.js.map → Collection-DJaa8dhS.js.map} +1 -1
  15. package/dist/chunks/{ContextMenu-JJ51hS-a.js → ContextMenu-CvLNRuJb.js} +3 -3
  16. package/dist/chunks/{ContextMenu-JJ51hS-a.js.map → ContextMenu-CvLNRuJb.js.map} +1 -1
  17. package/dist/chunks/{ContextMenu-BY3BgxM5.js → ContextMenu-DvsDXgOe.js} +2 -2
  18. package/dist/chunks/{ContextMenu-BY3BgxM5.js.map → ContextMenu-DvsDXgOe.js.map} +1 -1
  19. package/dist/chunks/{DataView-CsYXM9vF.js → DataView-Bm8dffDn.js} +2 -2
  20. package/dist/chunks/{DataView-CsYXM9vF.js.map → DataView-Bm8dffDn.js.map} +1 -1
  21. package/dist/chunks/{DataView-CHvoEI4M.js → DataView-gc1VouWJ.js} +2 -2
  22. package/dist/chunks/{DataView-CHvoEI4M.js.map → DataView-gc1VouWJ.js.map} +1 -1
  23. package/dist/chunks/{Dialog-BKPwBCDC.js → Dialog-Cl_rBFSh.js} +2 -2
  24. package/dist/chunks/{Dialog-BKPwBCDC.js.map → Dialog-Cl_rBFSh.js.map} +1 -1
  25. package/dist/chunks/{Dialog-D4DsB-N1.js → Dialog-LPU__Q7Z.js} +5 -5
  26. package/dist/chunks/{Dialog-D4DsB-N1.js.map → Dialog-LPU__Q7Z.js.map} +1 -1
  27. package/dist/chunks/{FormView-DYX_yeho.js → FormView-Cc5iagqS.js} +2 -2
  28. package/dist/chunks/{FormView-DYX_yeho.js.map → FormView-Cc5iagqS.js.map} +1 -1
  29. package/dist/chunks/{FormView-B9nIO_AX.js → FormView-DMMUiciW.js} +2 -2
  30. package/dist/chunks/{FormView-B9nIO_AX.js.map → FormView-DMMUiciW.js.map} +1 -1
  31. package/dist/chunks/{ListView-DWaowghQ.js → ListView-8FqhYWRU.js} +3 -3
  32. package/dist/chunks/{ListView-DWaowghQ.js.map → ListView-8FqhYWRU.js.map} +1 -1
  33. package/dist/chunks/{ListView-D2vt0koT.js → ListView-Xcsfu4IL.js} +2 -2
  34. package/dist/chunks/{ListView-D2vt0koT.js.map → ListView-Xcsfu4IL.js.map} +1 -1
  35. package/dist/chunks/{MetricsMiniChartWidget-BfZ-dcbW.js → MetricsMiniChartWidget-BuvhT5NG.js} +2 -2
  36. package/dist/chunks/{MetricsMiniChartWidget-BfZ-dcbW.js.map → MetricsMiniChartWidget-BuvhT5NG.js.map} +1 -1
  37. package/dist/chunks/{MetricsMiniChartWidget-Cl-TA-Q9.js → MetricsMiniChartWidget-CsawNLeS.js} +4 -4
  38. package/dist/chunks/{MetricsMiniChartWidget-Cl-TA-Q9.js.map → MetricsMiniChartWidget-CsawNLeS.js.map} +1 -1
  39. package/dist/chunks/{PDFViewer-Cgr3T15i.js → PDFViewer-Bg06w3Cu.js} +2 -2
  40. package/dist/chunks/{PDFViewer-Cgr3T15i.js.map → PDFViewer-Bg06w3Cu.js.map} +1 -1
  41. package/dist/chunks/{PDFViewer-Bzifr-dn.js → PDFViewer-Dr72gJt1.js} +3 -3
  42. package/dist/chunks/{PDFViewer-Bzifr-dn.js.map → PDFViewer-Dr72gJt1.js.map} +1 -1
  43. package/dist/chunks/Rest-29RtA7pe.js +2 -0
  44. package/dist/chunks/Rest-29RtA7pe.js.map +1 -0
  45. package/dist/chunks/{Rest-C3fPzCIA.js → Rest-BoKp2Hj4.js} +34 -1
  46. package/dist/chunks/Rest-BoKp2Hj4.js.map +1 -0
  47. package/dist/chunks/TokenManager-Bh1WshcR.js +2 -0
  48. package/dist/chunks/TokenManager-Bh1WshcR.js.map +1 -0
  49. package/dist/chunks/{TokenManager-CoHKTGNX.js → TokenManager-BoolOcxQ.js} +26 -11
  50. package/dist/chunks/TokenManager-BoolOcxQ.js.map +1 -0
  51. package/dist/chunks/{WebSocketClient-Dzwprd15.js → WebSocketClient-DcJBT-iB.js} +2 -2
  52. package/dist/chunks/{WebSocketClient-Dzwprd15.js.map → WebSocketClient-DcJBT-iB.js.map} +1 -1
  53. package/dist/chunks/{WebSocketClient-CkAL55qy.js → WebSocketClient-Do0CazHx.js} +2 -2
  54. package/dist/chunks/{WebSocketClient-CkAL55qy.js.map → WebSocketClient-Do0CazHx.js.map} +1 -1
  55. package/dist/chunks/{version-CJnLNR-g.js → version-Bado0p82.js} +2 -2
  56. package/dist/chunks/{version-CJnLNR-g.js.map → version-Bado0p82.js.map} +1 -1
  57. package/dist/chunks/{version-Bl3PlRrV.js → version-_FYWwpG_.js} +4 -4
  58. package/dist/chunks/{version-Bl3PlRrV.js.map → version-_FYWwpG_.js.map} +1 -1
  59. package/dist/docit.cjs.js +1 -1
  60. package/dist/docit.es.js +6 -6
  61. package/dist/index.cjs.js +1 -1
  62. package/dist/index.es.js +15 -15
  63. package/dist/lightbox.cjs.js +1 -1
  64. package/dist/lightbox.es.js +5 -5
  65. package/dist/map.cjs.js +1 -1
  66. package/dist/map.es.js +2 -2
  67. package/dist/timeline.cjs.js +1 -1
  68. package/dist/timeline.es.js +4 -4
  69. package/package.json +1 -1
  70. package/dist/chunks/Rest-C3fPzCIA.js.map +0 -1
  71. package/dist/chunks/Rest-DYPLEzNy.js +0 -2
  72. package/dist/chunks/Rest-DYPLEzNy.js.map +0 -1
  73. package/dist/chunks/TokenManager-CoHKTGNX.js.map +0 -1
  74. package/dist/chunks/TokenManager-DSyRWlvc.js +0 -2
  75. package/dist/chunks/TokenManager-DSyRWlvc.js.map +0 -1
@@ -1,2 +1,2 @@
1
- "use strict";const t=require("./Rest-DYPLEzNy.js");class WebSocketClient{constructor(t={}){this.url=t.url,this.socket=null,this.isConnected=!1,this.isConnecting=!1,this.getToken=t.getToken||null,this.tokenPrefix=t.tokenPrefix||"bearer",this.shouldReconnect=!1!==t.autoReconnect,this.maxReconnectAttempts=t.maxReconnectAttempts||1/0,this.reconnectInterval=t.reconnectInterval||3e3,this.reconnectBackoff=t.reconnectBackoff||1.5,this.reconnectAttempts=0,this.reconnectTimer=null,this.pingInterval=t.pingInterval||3e4,this.pongTimeout=t.pongTimeout||5e3,this.pingTimer=null,this.pongTimer=null,this.debug=t.debug||!1}async connect(t=null){if(t&&(this.url=t),!this.url)throw new Error("WebSocket URL is required");if(!this.isConnected&&!this.isConnecting)return this.isConnecting=!0,this._log("Connecting to:",this.url),new Promise((t,e)=>{try{this.socket=new WebSocket(this.url),this.socket.onopen=()=>{this._log("Connected"),this.isConnected=!0,this.isConnecting=!1,this.reconnectAttempts=0,this._authenticate(),this._startHeartbeat(),this.emit("connected"),t()},this.socket.onmessage=t=>this._handleMessage(t),this.socket.onerror=t=>this._handleError(t,e),this.socket.onclose=t=>this._handleClose(t)}catch(n){this.isConnecting=!1,e(n)}})}disconnect(){this.shouldReconnect=!1,this._clearTimers(),this.socket&&(this._log("Disconnecting"),this.socket.close(1e3,"Client disconnect"))}send(t){if(!this.isConnected)throw new Error("WebSocket not connected");const e="string"==typeof t?t:JSON.stringify(t);this.socket.send(e),this._log("Sent:",e)}_authenticate(){const t=this.getToken?this.getToken():null;t?this.send({type:"authenticate",token:t,prefix:this.tokenPrefix}):console.warn("[WebSocket] No token available")}_handleMessage(t){let e;this._log("Received:",t.data);try{e=JSON.parse(t.data)}catch(n){e=t.data}"pong"!==e?.type?(e?.type&&this.emit(`message:${e.type}`,e),this.emit("message",e)):this._clearPongTimeout()}_handleError(t,e){console.error("[WebSocket] Error:",t),this.emit("error",t),e&&e(new Error("WebSocket connection failed"))}_handleClose(t){this._log("Closed:",t.code,t.reason),this.isConnected=!1,this.isConnecting=!1,this._clearTimers(),this.socket=null,this.emit("disconnected",{code:t.code,reason:t.reason,wasClean:t.wasClean}),this.shouldReconnect&&1e3!==t.code&&this._reconnect()}_reconnect(){if(this.reconnectAttempts>=this.maxReconnectAttempts)return this._log("Max reconnect attempts reached"),void this.emit("reconnect-failed",{attempts:this.reconnectAttempts});this.reconnectAttempts++;const t=this.reconnectInterval*Math.pow(this.reconnectBackoff,this.reconnectAttempts-1);this._log(`Reconnecting in ${t}ms (attempt ${this.reconnectAttempts})`),this.emit("reconnecting",{attempt:this.reconnectAttempts,delay:t}),this.reconnectTimer=setTimeout(()=>{this.shouldReconnect&&this.connect().catch(t=>console.error("[WebSocket] Reconnect failed:",t))},t)}_startHeartbeat(){this.pingInterval&&(this.pingTimer=setInterval(()=>{this.isConnected&&(this.send({action:"ping"}),this._startPongTimeout())},this.pingInterval))}_startPongTimeout(){this._clearPongTimeout(),this.pongTimer=setTimeout(()=>{console.warn("[WebSocket] Pong timeout - closing connection"),this.socket&&this.socket.close(1006,"Pong timeout")},this.pongTimeout)}_clearPongTimeout(){this.pongTimer&&(clearTimeout(this.pongTimer),this.pongTimer=null)}_clearTimers(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null),this._clearPongTimeout()}_log(...t){this.debug}static deriveURL(t,e="/ws/realtime/"){if(!t)throw new Error("baseURL is required");const n=new URL(t);return n.protocol="https:"===n.protocol?"wss:":"ws:",n.pathname=e.startsWith("/")?e:`/${e}`,n.toString()}}Object.assign(WebSocketClient.prototype,t.EventEmitter),exports.WebSocketClient=WebSocketClient;
2
- //# sourceMappingURL=WebSocketClient-Dzwprd15.js.map
1
+ "use strict";const t=require("./Rest-29RtA7pe.js");class WebSocketClient{constructor(t={}){this.url=t.url,this.socket=null,this.isConnected=!1,this.isConnecting=!1,this.getToken=t.getToken||null,this.tokenPrefix=t.tokenPrefix||"bearer",this.shouldReconnect=!1!==t.autoReconnect,this.maxReconnectAttempts=t.maxReconnectAttempts||1/0,this.reconnectInterval=t.reconnectInterval||3e3,this.reconnectBackoff=t.reconnectBackoff||1.5,this.reconnectAttempts=0,this.reconnectTimer=null,this.pingInterval=t.pingInterval||3e4,this.pongTimeout=t.pongTimeout||5e3,this.pingTimer=null,this.pongTimer=null,this.debug=t.debug||!1}async connect(t=null){if(t&&(this.url=t),!this.url)throw new Error("WebSocket URL is required");if(!this.isConnected&&!this.isConnecting)return this.isConnecting=!0,this._log("Connecting to:",this.url),new Promise((t,e)=>{try{this.socket=new WebSocket(this.url),this.socket.onopen=()=>{this._log("Connected"),this.isConnected=!0,this.isConnecting=!1,this.reconnectAttempts=0,this._authenticate(),this._startHeartbeat(),this.emit("connected"),t()},this.socket.onmessage=t=>this._handleMessage(t),this.socket.onerror=t=>this._handleError(t,e),this.socket.onclose=t=>this._handleClose(t)}catch(n){this.isConnecting=!1,e(n)}})}disconnect(){this.shouldReconnect=!1,this._clearTimers(),this.socket&&(this._log("Disconnecting"),this.socket.close(1e3,"Client disconnect"))}send(t){if(!this.isConnected)throw new Error("WebSocket not connected");const e="string"==typeof t?t:JSON.stringify(t);this.socket.send(e),this._log("Sent:",e)}_authenticate(){const t=this.getToken?this.getToken():null;t?this.send({type:"authenticate",token:t,prefix:this.tokenPrefix}):console.warn("[WebSocket] No token available")}_handleMessage(t){let e;this._log("Received:",t.data);try{e=JSON.parse(t.data)}catch(n){e=t.data}"pong"!==e?.type?(e?.type&&this.emit(`message:${e.type}`,e),this.emit("message",e)):this._clearPongTimeout()}_handleError(t,e){console.error("[WebSocket] Error:",t),this.emit("error",t),e&&e(new Error("WebSocket connection failed"))}_handleClose(t){this._log("Closed:",t.code,t.reason),this.isConnected=!1,this.isConnecting=!1,this._clearTimers(),this.socket=null,this.emit("disconnected",{code:t.code,reason:t.reason,wasClean:t.wasClean}),this.shouldReconnect&&1e3!==t.code&&this._reconnect()}_reconnect(){if(this.reconnectAttempts>=this.maxReconnectAttempts)return this._log("Max reconnect attempts reached"),void this.emit("reconnect-failed",{attempts:this.reconnectAttempts});this.reconnectAttempts++;const t=this.reconnectInterval*Math.pow(this.reconnectBackoff,this.reconnectAttempts-1);this._log(`Reconnecting in ${t}ms (attempt ${this.reconnectAttempts})`),this.emit("reconnecting",{attempt:this.reconnectAttempts,delay:t}),this.reconnectTimer=setTimeout(()=>{this.shouldReconnect&&this.connect().catch(t=>console.error("[WebSocket] Reconnect failed:",t))},t)}_startHeartbeat(){this.pingInterval&&(this.pingTimer=setInterval(()=>{this.isConnected&&(this.send({action:"ping"}),this._startPongTimeout())},this.pingInterval))}_startPongTimeout(){this._clearPongTimeout(),this.pongTimer=setTimeout(()=>{console.warn("[WebSocket] Pong timeout - closing connection"),this.socket&&this.socket.close(1006,"Pong timeout")},this.pongTimeout)}_clearPongTimeout(){this.pongTimer&&(clearTimeout(this.pongTimer),this.pongTimer=null)}_clearTimers(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null),this._clearPongTimeout()}_log(...t){this.debug}static deriveURL(t,e="/ws/realtime/"){if(!t)throw new Error("baseURL is required");const n=new URL(t);return n.protocol="https:"===n.protocol?"wss:":"ws:",n.pathname=e.startsWith("/")?e:`/${e}`,n.toString()}}Object.assign(WebSocketClient.prototype,t.EventEmitter),exports.WebSocketClient=WebSocketClient;
2
+ //# sourceMappingURL=WebSocketClient-DcJBT-iB.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WebSocketClient-Dzwprd15.js","sources":["../../src/core/services/WebSocketClient.js"],"sourcesContent":["/**\n * WebSocketClient - Simple, robust WebSocket client with auto-reconnect\n *\n * Features:\n * - Auto-reconnect with exponential backoff\n * - Heartbeat ping/pong with timeout disconnect\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 * });\n *\n * ws.on('connected', () => console.log('Connected!'));\n * ws.on('message', (data) => console.log('Received:', data));\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 this.shouldReconnect = options.autoReconnect !== false;\n this.maxReconnectAttempts = options.maxReconnectAttempts || Infinity;\n this.reconnectInterval = options.reconnectInterval || 3000;\n this.reconnectBackoff = options.reconnectBackoff || 1.5;\n this.reconnectAttempts = 0;\n this.reconnectTimer = null;\n\n // Heartbeat\n this.pingInterval = options.pingInterval || 30000;\n this.pongTimeout = options.pongTimeout || 5000;\n this.pingTimer = null;\n this.pongTimer = null;\n\n // Debug\n this.debug = options.debug || false;\n }\n\n /**\n * Connect to WebSocket server\n */\n async connect(url = null) {\n if (url) this.url = url;\n if (!this.url) throw new Error('WebSocket URL is required');\n if (this.isConnected || this.isConnecting) return;\n\n this.isConnecting = true;\n this._log('Connecting to:', this.url);\n\n return new Promise((resolve, reject) => {\n try {\n this.socket = new WebSocket(this.url);\n\n this.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 resolve();\n };\n\n this.socket.onmessage = (event) => this._handleMessage(event);\n this.socket.onerror = (event) => this._handleError(event, reject);\n this.socket.onclose = (event) => this._handleClose(event);\n\n } catch (error) {\n this.isConnecting = false;\n reject(error);\n }\n });\n }\n\n /**\n * Disconnect from server\n */\n disconnect() {\n this.shouldReconnect = false;\n this._clearTimers();\n\n if (this.socket) {\n this._log('Disconnecting');\n this.socket.close(1000, 'Client disconnect');\n }\n }\n\n /**\n * Send data (auto-stringifies objects)\n */\n send(data) {\n if (!this.isConnected) {\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 // Private methods\n\n _authenticate() {\n const token = this.getToken ? this.getToken() : null;\n if (!token) {\n console.warn('[WebSocket] No token available');\n return;\n }\n\n this.send({\n type: 'authenticate',\n token,\n prefix: this.tokenPrefix\n });\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 (error) {\n data = event.data;\n }\n\n // Handle pong response\n if (data?.type === 'pong') {\n this._clearPongTimeout();\n return;\n }\n\n // Emit specific event types\n if (data?.type) {\n this.emit(`message:${data.type}`, data);\n }\n\n // Always emit generic message event\n this.emit('message', data);\n }\n\n _handleError(event, rejectFn) {\n console.error('[WebSocket] Error:', event);\n this.emit('error', event);\n\n if (rejectFn) {\n rejectFn(new Error('WebSocket connection failed'));\n }\n }\n\n _handleClose(event) {\n this._log('Closed:', event.code, event.reason);\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 // Auto-reconnect (except for clean closes)\n if (this.shouldReconnect && event.code !== 1000) {\n this._reconnect();\n }\n }\n\n _reconnect() {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n this._log('Max reconnect attempts reached');\n this.emit('reconnect-failed', { attempts: this.reconnectAttempts });\n return;\n }\n\n this.reconnectAttempts++;\n const delay = this.reconnectInterval * Math.pow(this.reconnectBackoff, this.reconnectAttempts - 1);\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 if (this.shouldReconnect) {\n this.connect().catch(err => console.error('[WebSocket] Reconnect failed:', err));\n }\n }, delay);\n }\n\n _startHeartbeat() {\n if (!this.pingInterval) return;\n\n this.pingTimer = setInterval(() => {\n if (this.isConnected) {\n this.send({ action: 'ping' });\n this._startPongTimeout();\n }\n }, this.pingInterval);\n }\n\n _startPongTimeout() {\n this._clearPongTimeout();\n\n this.pongTimer = setTimeout(() => {\n console.warn('[WebSocket] Pong timeout - closing connection');\n if (this.socket) {\n this.socket.close(1006, 'Pong timeout');\n }\n }, this.pongTimeout);\n }\n\n _clearPongTimeout() {\n if (this.pongTimer) {\n clearTimeout(this.pongTimer);\n this.pongTimer = null;\n }\n }\n\n _clearTimers() {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n this._clearPongTimeout();\n }\n\n _log(...args) {\n if (this.debug) {\n console.log('[WebSocket]', ...args);\n }\n }\n\n // Static methods\n\n /**\n * Convert REST API base URL to WebSocket URL\n * @param {string} baseURL - REST API base URL (http/https)\n * @param {string} path - WebSocket path (default: '/ws')\n * @returns {string} WebSocket URL (ws/wss)\n *\n * @example\n * WebSocketClient.deriveURL('https://api.example.com', '/ws/realtime')\n * // Returns: 'wss://api.example.com/ws/realtime'\n *\n * WebSocketClient.deriveURL('http://localhost:3000')\n * // Returns: 'ws://localhost:3000/ws'\n */\n static deriveURL(baseURL, path = '/ws/realtime/') {\n if (!baseURL) throw new Error('baseURL is required');\n\n // Parse the base URL\n const url = new URL(baseURL);\n\n // Convert http(s) to ws(s)\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n\n // Set the path (ensure it starts with /)\n url.pathname = path.startsWith('/') ? path : `/${path}`;\n\n return url.toString();\n }\n}\n\n// Add EventEmitter mixin\nObject.assign(WebSocketClient.prototype, EventEmitter);\n\nexport default WebSocketClient;\n"],"names":["WebSocketClient","constructor","options","this","url","socket","isConnected","isConnecting","getToken","tokenPrefix","shouldReconnect","autoReconnect","maxReconnectAttempts","Infinity","reconnectInterval","reconnectBackoff","reconnectAttempts","reconnectTimer","pingInterval","pongTimeout","pingTimer","pongTimer","debug","connect","Error","_log","Promise","resolve","reject","WebSocket","onopen","_authenticate","_startHeartbeat","emit","onmessage","event","_handleMessage","onerror","_handleError","onclose","_handleClose","error","disconnect","_clearTimers","close","send","data","message","JSON","stringify","token","type","prefix","console","warn","parse","_clearPongTimeout","rejectFn","code","reason","wasClean","_reconnect","attempts","delay","Math","pow","attempt","setTimeout","catch","err","setInterval","action","_startPongTimeout","clearTimeout","clearInterval","args","deriveURL","baseURL","path","URL","protocol","pathname","startsWith","toString","Object","assign","prototype","EventEmitter"],"mappings":"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"}
1
+ {"version":3,"file":"WebSocketClient-DcJBT-iB.js","sources":["../../src/core/services/WebSocketClient.js"],"sourcesContent":["/**\n * WebSocketClient - Simple, robust WebSocket client with auto-reconnect\n *\n * Features:\n * - Auto-reconnect with exponential backoff\n * - Heartbeat ping/pong with timeout disconnect\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 * });\n *\n * ws.on('connected', () => console.log('Connected!'));\n * ws.on('message', (data) => console.log('Received:', data));\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 this.shouldReconnect = options.autoReconnect !== false;\n this.maxReconnectAttempts = options.maxReconnectAttempts || Infinity;\n this.reconnectInterval = options.reconnectInterval || 3000;\n this.reconnectBackoff = options.reconnectBackoff || 1.5;\n this.reconnectAttempts = 0;\n this.reconnectTimer = null;\n\n // Heartbeat\n this.pingInterval = options.pingInterval || 30000;\n this.pongTimeout = options.pongTimeout || 5000;\n this.pingTimer = null;\n this.pongTimer = null;\n\n // Debug\n this.debug = options.debug || false;\n }\n\n /**\n * Connect to WebSocket server\n */\n async connect(url = null) {\n if (url) this.url = url;\n if (!this.url) throw new Error('WebSocket URL is required');\n if (this.isConnected || this.isConnecting) return;\n\n this.isConnecting = true;\n this._log('Connecting to:', this.url);\n\n return new Promise((resolve, reject) => {\n try {\n this.socket = new WebSocket(this.url);\n\n this.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 resolve();\n };\n\n this.socket.onmessage = (event) => this._handleMessage(event);\n this.socket.onerror = (event) => this._handleError(event, reject);\n this.socket.onclose = (event) => this._handleClose(event);\n\n } catch (error) {\n this.isConnecting = false;\n reject(error);\n }\n });\n }\n\n /**\n * Disconnect from server\n */\n disconnect() {\n this.shouldReconnect = false;\n this._clearTimers();\n\n if (this.socket) {\n this._log('Disconnecting');\n this.socket.close(1000, 'Client disconnect');\n }\n }\n\n /**\n * Send data (auto-stringifies objects)\n */\n send(data) {\n if (!this.isConnected) {\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 // Private methods\n\n _authenticate() {\n const token = this.getToken ? this.getToken() : null;\n if (!token) {\n console.warn('[WebSocket] No token available');\n return;\n }\n\n this.send({\n type: 'authenticate',\n token,\n prefix: this.tokenPrefix\n });\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 (error) {\n data = event.data;\n }\n\n // Handle pong response\n if (data?.type === 'pong') {\n this._clearPongTimeout();\n return;\n }\n\n // Emit specific event types\n if (data?.type) {\n this.emit(`message:${data.type}`, data);\n }\n\n // Always emit generic message event\n this.emit('message', data);\n }\n\n _handleError(event, rejectFn) {\n console.error('[WebSocket] Error:', event);\n this.emit('error', event);\n\n if (rejectFn) {\n rejectFn(new Error('WebSocket connection failed'));\n }\n }\n\n _handleClose(event) {\n this._log('Closed:', event.code, event.reason);\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 // Auto-reconnect (except for clean closes)\n if (this.shouldReconnect && event.code !== 1000) {\n this._reconnect();\n }\n }\n\n _reconnect() {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n this._log('Max reconnect attempts reached');\n this.emit('reconnect-failed', { attempts: this.reconnectAttempts });\n return;\n }\n\n this.reconnectAttempts++;\n const delay = this.reconnectInterval * Math.pow(this.reconnectBackoff, this.reconnectAttempts - 1);\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 if (this.shouldReconnect) {\n this.connect().catch(err => console.error('[WebSocket] Reconnect failed:', err));\n }\n }, delay);\n }\n\n _startHeartbeat() {\n if (!this.pingInterval) return;\n\n this.pingTimer = setInterval(() => {\n if (this.isConnected) {\n this.send({ action: 'ping' });\n this._startPongTimeout();\n }\n }, this.pingInterval);\n }\n\n _startPongTimeout() {\n this._clearPongTimeout();\n\n this.pongTimer = setTimeout(() => {\n console.warn('[WebSocket] Pong timeout - closing connection');\n if (this.socket) {\n this.socket.close(1006, 'Pong timeout');\n }\n }, this.pongTimeout);\n }\n\n _clearPongTimeout() {\n if (this.pongTimer) {\n clearTimeout(this.pongTimer);\n this.pongTimer = null;\n }\n }\n\n _clearTimers() {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n this._clearPongTimeout();\n }\n\n _log(...args) {\n if (this.debug) {\n console.log('[WebSocket]', ...args);\n }\n }\n\n // Static methods\n\n /**\n * Convert REST API base URL to WebSocket URL\n * @param {string} baseURL - REST API base URL (http/https)\n * @param {string} path - WebSocket path (default: '/ws')\n * @returns {string} WebSocket URL (ws/wss)\n *\n * @example\n * WebSocketClient.deriveURL('https://api.example.com', '/ws/realtime')\n * // Returns: 'wss://api.example.com/ws/realtime'\n *\n * WebSocketClient.deriveURL('http://localhost:3000')\n * // Returns: 'ws://localhost:3000/ws'\n */\n static deriveURL(baseURL, path = '/ws/realtime/') {\n if (!baseURL) throw new Error('baseURL is required');\n\n // Parse the base URL\n const url = new URL(baseURL);\n\n // Convert http(s) to ws(s)\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n\n // Set the path (ensure it starts with /)\n url.pathname = path.startsWith('/') ? path : `/${path}`;\n\n return url.toString();\n }\n}\n\n// Add EventEmitter mixin\nObject.assign(WebSocketClient.prototype, EventEmitter);\n\nexport default WebSocketClient;\n"],"names":["WebSocketClient","constructor","options","this","url","socket","isConnected","isConnecting","getToken","tokenPrefix","shouldReconnect","autoReconnect","maxReconnectAttempts","Infinity","reconnectInterval","reconnectBackoff","reconnectAttempts","reconnectTimer","pingInterval","pongTimeout","pingTimer","pongTimer","debug","connect","Error","_log","Promise","resolve","reject","WebSocket","onopen","_authenticate","_startHeartbeat","emit","onmessage","event","_handleMessage","onerror","_handleError","onclose","_handleClose","error","disconnect","_clearTimers","close","send","data","message","JSON","stringify","token","type","prefix","console","warn","parse","_clearPongTimeout","rejectFn","code","reason","wasClean","_reconnect","attempts","delay","Math","pow","attempt","setTimeout","catch","err","setInterval","action","_startPongTimeout","clearTimeout","clearInterval","args","deriveURL","baseURL","path","URL","protocol","pathname","startsWith","toString","Object","assign","prototype","EventEmitter"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { E as EventEmitter } from "./Rest-C3fPzCIA.js";
1
+ import { E as EventEmitter } from "./Rest-BoKp2Hj4.js";
2
2
  class WebSocketClient {
3
3
  constructor(options = {}) {
4
4
  this.url = options.url;
@@ -206,4 +206,4 @@ Object.assign(WebSocketClient.prototype, EventEmitter);
206
206
  export {
207
207
  WebSocketClient as W
208
208
  };
209
- //# sourceMappingURL=WebSocketClient-CkAL55qy.js.map
209
+ //# sourceMappingURL=WebSocketClient-Do0CazHx.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WebSocketClient-CkAL55qy.js","sources":["../../src/core/services/WebSocketClient.js"],"sourcesContent":["/**\n * WebSocketClient - Simple, robust WebSocket client with auto-reconnect\n *\n * Features:\n * - Auto-reconnect with exponential backoff\n * - Heartbeat ping/pong with timeout disconnect\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 * });\n *\n * ws.on('connected', () => console.log('Connected!'));\n * ws.on('message', (data) => console.log('Received:', data));\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 this.shouldReconnect = options.autoReconnect !== false;\n this.maxReconnectAttempts = options.maxReconnectAttempts || Infinity;\n this.reconnectInterval = options.reconnectInterval || 3000;\n this.reconnectBackoff = options.reconnectBackoff || 1.5;\n this.reconnectAttempts = 0;\n this.reconnectTimer = null;\n\n // Heartbeat\n this.pingInterval = options.pingInterval || 30000;\n this.pongTimeout = options.pongTimeout || 5000;\n this.pingTimer = null;\n this.pongTimer = null;\n\n // Debug\n this.debug = options.debug || false;\n }\n\n /**\n * Connect to WebSocket server\n */\n async connect(url = null) {\n if (url) this.url = url;\n if (!this.url) throw new Error('WebSocket URL is required');\n if (this.isConnected || this.isConnecting) return;\n\n this.isConnecting = true;\n this._log('Connecting to:', this.url);\n\n return new Promise((resolve, reject) => {\n try {\n this.socket = new WebSocket(this.url);\n\n this.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 resolve();\n };\n\n this.socket.onmessage = (event) => this._handleMessage(event);\n this.socket.onerror = (event) => this._handleError(event, reject);\n this.socket.onclose = (event) => this._handleClose(event);\n\n } catch (error) {\n this.isConnecting = false;\n reject(error);\n }\n });\n }\n\n /**\n * Disconnect from server\n */\n disconnect() {\n this.shouldReconnect = false;\n this._clearTimers();\n\n if (this.socket) {\n this._log('Disconnecting');\n this.socket.close(1000, 'Client disconnect');\n }\n }\n\n /**\n * Send data (auto-stringifies objects)\n */\n send(data) {\n if (!this.isConnected) {\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 // Private methods\n\n _authenticate() {\n const token = this.getToken ? this.getToken() : null;\n if (!token) {\n console.warn('[WebSocket] No token available');\n return;\n }\n\n this.send({\n type: 'authenticate',\n token,\n prefix: this.tokenPrefix\n });\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 (error) {\n data = event.data;\n }\n\n // Handle pong response\n if (data?.type === 'pong') {\n this._clearPongTimeout();\n return;\n }\n\n // Emit specific event types\n if (data?.type) {\n this.emit(`message:${data.type}`, data);\n }\n\n // Always emit generic message event\n this.emit('message', data);\n }\n\n _handleError(event, rejectFn) {\n console.error('[WebSocket] Error:', event);\n this.emit('error', event);\n\n if (rejectFn) {\n rejectFn(new Error('WebSocket connection failed'));\n }\n }\n\n _handleClose(event) {\n this._log('Closed:', event.code, event.reason);\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 // Auto-reconnect (except for clean closes)\n if (this.shouldReconnect && event.code !== 1000) {\n this._reconnect();\n }\n }\n\n _reconnect() {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n this._log('Max reconnect attempts reached');\n this.emit('reconnect-failed', { attempts: this.reconnectAttempts });\n return;\n }\n\n this.reconnectAttempts++;\n const delay = this.reconnectInterval * Math.pow(this.reconnectBackoff, this.reconnectAttempts - 1);\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 if (this.shouldReconnect) {\n this.connect().catch(err => console.error('[WebSocket] Reconnect failed:', err));\n }\n }, delay);\n }\n\n _startHeartbeat() {\n if (!this.pingInterval) return;\n\n this.pingTimer = setInterval(() => {\n if (this.isConnected) {\n this.send({ action: 'ping' });\n this._startPongTimeout();\n }\n }, this.pingInterval);\n }\n\n _startPongTimeout() {\n this._clearPongTimeout();\n\n this.pongTimer = setTimeout(() => {\n console.warn('[WebSocket] Pong timeout - closing connection');\n if (this.socket) {\n this.socket.close(1006, 'Pong timeout');\n }\n }, this.pongTimeout);\n }\n\n _clearPongTimeout() {\n if (this.pongTimer) {\n clearTimeout(this.pongTimer);\n this.pongTimer = null;\n }\n }\n\n _clearTimers() {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n this._clearPongTimeout();\n }\n\n _log(...args) {\n if (this.debug) {\n console.log('[WebSocket]', ...args);\n }\n }\n\n // Static methods\n\n /**\n * Convert REST API base URL to WebSocket URL\n * @param {string} baseURL - REST API base URL (http/https)\n * @param {string} path - WebSocket path (default: '/ws')\n * @returns {string} WebSocket URL (ws/wss)\n *\n * @example\n * WebSocketClient.deriveURL('https://api.example.com', '/ws/realtime')\n * // Returns: 'wss://api.example.com/ws/realtime'\n *\n * WebSocketClient.deriveURL('http://localhost:3000')\n * // Returns: 'ws://localhost:3000/ws'\n */\n static deriveURL(baseURL, path = '/ws/realtime/') {\n if (!baseURL) throw new Error('baseURL is required');\n\n // Parse the base URL\n const url = new URL(baseURL);\n\n // Convert http(s) to ws(s)\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n\n // Set the path (ensure it starts with /)\n url.pathname = path.startsWith('/') ? path : `/${path}`;\n\n return url.toString();\n }\n}\n\n// Add EventEmitter mixin\nObject.assign(WebSocketClient.prototype, EventEmitter);\n\nexport default WebSocketClient;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"WebSocketClient-Do0CazHx.js","sources":["../../src/core/services/WebSocketClient.js"],"sourcesContent":["/**\n * WebSocketClient - Simple, robust WebSocket client with auto-reconnect\n *\n * Features:\n * - Auto-reconnect with exponential backoff\n * - Heartbeat ping/pong with timeout disconnect\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 * });\n *\n * ws.on('connected', () => console.log('Connected!'));\n * ws.on('message', (data) => console.log('Received:', data));\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 this.shouldReconnect = options.autoReconnect !== false;\n this.maxReconnectAttempts = options.maxReconnectAttempts || Infinity;\n this.reconnectInterval = options.reconnectInterval || 3000;\n this.reconnectBackoff = options.reconnectBackoff || 1.5;\n this.reconnectAttempts = 0;\n this.reconnectTimer = null;\n\n // Heartbeat\n this.pingInterval = options.pingInterval || 30000;\n this.pongTimeout = options.pongTimeout || 5000;\n this.pingTimer = null;\n this.pongTimer = null;\n\n // Debug\n this.debug = options.debug || false;\n }\n\n /**\n * Connect to WebSocket server\n */\n async connect(url = null) {\n if (url) this.url = url;\n if (!this.url) throw new Error('WebSocket URL is required');\n if (this.isConnected || this.isConnecting) return;\n\n this.isConnecting = true;\n this._log('Connecting to:', this.url);\n\n return new Promise((resolve, reject) => {\n try {\n this.socket = new WebSocket(this.url);\n\n this.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 resolve();\n };\n\n this.socket.onmessage = (event) => this._handleMessage(event);\n this.socket.onerror = (event) => this._handleError(event, reject);\n this.socket.onclose = (event) => this._handleClose(event);\n\n } catch (error) {\n this.isConnecting = false;\n reject(error);\n }\n });\n }\n\n /**\n * Disconnect from server\n */\n disconnect() {\n this.shouldReconnect = false;\n this._clearTimers();\n\n if (this.socket) {\n this._log('Disconnecting');\n this.socket.close(1000, 'Client disconnect');\n }\n }\n\n /**\n * Send data (auto-stringifies objects)\n */\n send(data) {\n if (!this.isConnected) {\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 // Private methods\n\n _authenticate() {\n const token = this.getToken ? this.getToken() : null;\n if (!token) {\n console.warn('[WebSocket] No token available');\n return;\n }\n\n this.send({\n type: 'authenticate',\n token,\n prefix: this.tokenPrefix\n });\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 (error) {\n data = event.data;\n }\n\n // Handle pong response\n if (data?.type === 'pong') {\n this._clearPongTimeout();\n return;\n }\n\n // Emit specific event types\n if (data?.type) {\n this.emit(`message:${data.type}`, data);\n }\n\n // Always emit generic message event\n this.emit('message', data);\n }\n\n _handleError(event, rejectFn) {\n console.error('[WebSocket] Error:', event);\n this.emit('error', event);\n\n if (rejectFn) {\n rejectFn(new Error('WebSocket connection failed'));\n }\n }\n\n _handleClose(event) {\n this._log('Closed:', event.code, event.reason);\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 // Auto-reconnect (except for clean closes)\n if (this.shouldReconnect && event.code !== 1000) {\n this._reconnect();\n }\n }\n\n _reconnect() {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n this._log('Max reconnect attempts reached');\n this.emit('reconnect-failed', { attempts: this.reconnectAttempts });\n return;\n }\n\n this.reconnectAttempts++;\n const delay = this.reconnectInterval * Math.pow(this.reconnectBackoff, this.reconnectAttempts - 1);\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 if (this.shouldReconnect) {\n this.connect().catch(err => console.error('[WebSocket] Reconnect failed:', err));\n }\n }, delay);\n }\n\n _startHeartbeat() {\n if (!this.pingInterval) return;\n\n this.pingTimer = setInterval(() => {\n if (this.isConnected) {\n this.send({ action: 'ping' });\n this._startPongTimeout();\n }\n }, this.pingInterval);\n }\n\n _startPongTimeout() {\n this._clearPongTimeout();\n\n this.pongTimer = setTimeout(() => {\n console.warn('[WebSocket] Pong timeout - closing connection');\n if (this.socket) {\n this.socket.close(1006, 'Pong timeout');\n }\n }, this.pongTimeout);\n }\n\n _clearPongTimeout() {\n if (this.pongTimer) {\n clearTimeout(this.pongTimer);\n this.pongTimer = null;\n }\n }\n\n _clearTimers() {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n this._clearPongTimeout();\n }\n\n _log(...args) {\n if (this.debug) {\n console.log('[WebSocket]', ...args);\n }\n }\n\n // Static methods\n\n /**\n * Convert REST API base URL to WebSocket URL\n * @param {string} baseURL - REST API base URL (http/https)\n * @param {string} path - WebSocket path (default: '/ws')\n * @returns {string} WebSocket URL (ws/wss)\n *\n * @example\n * WebSocketClient.deriveURL('https://api.example.com', '/ws/realtime')\n * // Returns: 'wss://api.example.com/ws/realtime'\n *\n * WebSocketClient.deriveURL('http://localhost:3000')\n * // Returns: 'ws://localhost:3000/ws'\n */\n static deriveURL(baseURL, path = '/ws/realtime/') {\n if (!baseURL) throw new Error('baseURL is required');\n\n // Parse the base URL\n const url = new URL(baseURL);\n\n // Convert http(s) to ws(s)\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n\n // Set the path (ensure it starts with /)\n url.pathname = path.startsWith('/') ? path : `/${path}`;\n\n return url.toString();\n }\n}\n\n// Add EventEmitter mixin\nObject.assign(WebSocketClient.prototype, EventEmitter);\n\nexport default WebSocketClient;\n"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";const O="2.2.2",o="2025-11-30T00:36:12.650Z",i={full:O,major:2,minor:2,revision:2,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=2;
2
- //# sourceMappingURL=version-CJnLNR-g.js.map
1
+ "use strict";const O="2.2.4",o="2025-12-03T01:26:24.943Z",i={full:O,major:2,minor:2,revision:4,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=4;
2
+ //# sourceMappingURL=version-Bado0p82.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-CJnLNR-g.js","sources":["../../src/version.js"],"sourcesContent":["/**\n * MOJO Framework Version Information\n * Auto-generated on 2025-11-30T00:36:12.650Z\n */\n\nexport const VERSION = '2.2.2';\nexport const VERSION_MAJOR = 2;\nexport const VERSION_MINOR = 2;\nexport const VERSION_REVISION = 2;\nexport const BUILD_TIME = '2025-11-30T00:36:12.650Z';\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,QAIVC,EAAa,2BAGbC,EAAe,CACxBC,KAAMH,EACNI,MARyB,EASzBC,MARyB,EASzBC,SAR4B,EAS5BC,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-Bado0p82.js","sources":["../../src/version.js"],"sourcesContent":["/**\n * MOJO Framework Version Information\n * Auto-generated on 2025-12-03T01:26:24.943Z\n */\n\nexport const VERSION = '2.2.4';\nexport const VERSION_MAJOR = 2;\nexport const VERSION_MINOR = 2;\nexport const VERSION_REVISION = 4;\nexport const BUILD_TIME = '2025-12-03T01:26:24.943Z';\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,QAIVC,EAAa,2BAGbC,EAAe,CACxBC,KAAMH,EACNI,MARyB,EASzBC,MARyB,EASzBC,SAR4B,EAS5BC,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,8 +1,8 @@
1
- const VERSION = "2.2.2";
1
+ const VERSION = "2.2.4";
2
2
  const VERSION_MAJOR = 2;
3
3
  const VERSION_MINOR = 2;
4
- const VERSION_REVISION = 2;
5
- const BUILD_TIME = "2025-11-30T00:36:12.650Z";
4
+ const VERSION_REVISION = 4;
5
+ const BUILD_TIME = "2025-12-03T01:26:24.943Z";
6
6
  const VERSION_INFO = {
7
7
  full: VERSION,
8
8
  major: VERSION_MAJOR,
@@ -35,4 +35,4 @@ export {
35
35
  VERSION_MINOR as c,
36
36
  VERSION_REVISION as d
37
37
  };
38
- //# sourceMappingURL=version-Bl3PlRrV.js.map
38
+ //# sourceMappingURL=version-_FYWwpG_.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-Bl3PlRrV.js","sources":["../../src/version.js"],"sourcesContent":["/**\n * MOJO Framework Version Information\n * Auto-generated on 2025-11-30T00:36:12.650Z\n */\n\nexport const VERSION = '2.2.2';\nexport const VERSION_MAJOR = 2;\nexport const VERSION_MINOR = 2;\nexport const VERSION_REVISION = 2;\nexport const BUILD_TIME = '2025-11-30T00:36:12.650Z';\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":[],"mappings":"AAKY,MAAC,UAAU;AACX,MAAC,gBAAgB;AACjB,MAAC,gBAAgB;AACjB,MAAC,mBAAmB;AACpB,MAAC,aAAa;AAGd,MAAC,eAAe;AAAA,EACxB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAAW;AACP,WAAO,KAAK;AAAA,EAChB;AAAA,EACA,QAAQ,OAAO;AACX,UAAM,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AAC/C,UAAM,CAAC,IAAI,IAAI,EAAE,IAAI,SAAS,KAAK,IAAI;AACvC,UAAM,CAAC,IAAI,IAAI,EAAE,IAAI,SAAS,KAAK;AAEnC,QAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,QAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,WAAO,KAAK;AAAA,EAChB;AACJ;AAGA,IAAI,OAAO,WAAW,aAAa;AAC/B,SAAO,OAAO,OAAO,QAAQ,CAAA;AAC7B,SAAO,KAAK,UAAU;AACtB,SAAO,KAAK,eAAe;AAG3B,SAAO,KAAK,UAAU;AAC1B;"}
1
+ {"version":3,"file":"version-_FYWwpG_.js","sources":["../../src/version.js"],"sourcesContent":["/**\n * MOJO Framework Version Information\n * Auto-generated on 2025-12-03T01:26:24.943Z\n */\n\nexport const VERSION = '2.2.4';\nexport const VERSION_MAJOR = 2;\nexport const VERSION_MINOR = 2;\nexport const VERSION_REVISION = 4;\nexport const BUILD_TIME = '2025-12-03T01:26:24.943Z';\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":[],"mappings":"AAKY,MAAC,UAAU;AACX,MAAC,gBAAgB;AACjB,MAAC,gBAAgB;AACjB,MAAC,mBAAmB;AACpB,MAAC,aAAa;AAGd,MAAC,eAAe;AAAA,EACxB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAAW;AACP,WAAO,KAAK;AAAA,EAChB;AAAA,EACA,QAAQ,OAAO;AACX,UAAM,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM;AAC/C,UAAM,CAAC,IAAI,IAAI,EAAE,IAAI,SAAS,KAAK,IAAI;AACvC,UAAM,CAAC,IAAI,IAAI,EAAE,IAAI,SAAS,KAAK;AAEnC,QAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,QAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,WAAO,KAAK;AAAA,EAChB;AACJ;AAGA,IAAI,OAAO,WAAW,aAAa;AAC/B,SAAO,OAAO,OAAO,QAAQ,CAAA;AAC7B,SAAO,KAAK,UAAU;AACtB,SAAO,KAAK,eAAe;AAG3B,SAAO,KAAK,UAAU;AAC1B;"}
package/dist/docit.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./chunks/Dialog-BKPwBCDC.js"),e=require("./chunks/TokenManager-DSyRWlvc.js"),o=require("./chunks/Rest-DYPLEzNy.js"),i=require("./chunks/ContextMenu-BY3BgxM5.js"),s=require("./chunks/Collection-CqLcHgQG.js"),a=require("./chunks/version-CJnLNR-g.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 i.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 i.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;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./chunks/Dialog-Cl_rBFSh.js"),e=require("./chunks/TokenManager-Bh1WshcR.js"),o=require("./chunks/Rest-29RtA7pe.js"),i=require("./chunks/ContextMenu-DvsDXgOe.js"),s=require("./chunks/Collection-CREl6Fa9.js"),a=require("./chunks/version-Bado0p82.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 i.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 i.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
package/dist/docit.es.js CHANGED
@@ -1,9 +1,9 @@
1
- import { W as WebApp } from "./chunks/Dialog-D4DsB-N1.js";
2
- import { T as TokenManager, a as TopNav } from "./chunks/TokenManager-CoHKTGNX.js";
3
- import { V as View } from "./chunks/Rest-C3fPzCIA.js";
4
- import { P as Page, C as ContextMenu, T as ToastService, U as User } from "./chunks/ContextMenu-JJ51hS-a.js";
5
- import { M as Model, C as Collection } from "./chunks/Collection-C7rteLDn.js";
6
- import { B, a, V, b, c, d } from "./chunks/version-Bl3PlRrV.js";
1
+ import { W as WebApp } from "./chunks/Dialog-LPU__Q7Z.js";
2
+ import { T as TokenManager, a as TopNav } from "./chunks/TokenManager-BoolOcxQ.js";
3
+ import { V as View } from "./chunks/Rest-BoKp2Hj4.js";
4
+ import { P as Page, C as ContextMenu, T as ToastService, U as User } from "./chunks/ContextMenu-CvLNRuJb.js";
5
+ import { M as Model, C as Collection } from "./chunks/Collection-DJaa8dhS.js";
6
+ import { B, a, V, b, c, d } from "./chunks/version-_FYWwpG_.js";
7
7
  class DocNavSidebar extends View {
8
8
  constructor(options = {}) {
9
9
  super({