web-mojo 2.1.265 → 2.1.278

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/admin.cjs.js +1 -1
  2. package/dist/admin.cjs.js.map +1 -1
  3. package/dist/admin.es.js +27 -14
  4. package/dist/admin.es.js.map +1 -1
  5. package/dist/auth.cjs.js +1 -1
  6. package/dist/auth.cjs.js.map +1 -1
  7. package/dist/auth.es.js +3 -4
  8. package/dist/auth.es.js.map +1 -1
  9. package/dist/charts.cjs.js +1 -1
  10. package/dist/charts.es.js +2 -2
  11. package/dist/chunks/{ContextMenu-BqYO9pWG.js → ContextMenu-B5-M-xk4.js} +2 -2
  12. package/dist/chunks/{ContextMenu-BqYO9pWG.js.map → ContextMenu-B5-M-xk4.js.map} +1 -1
  13. package/dist/chunks/{ContextMenu-CsMXWcoQ.js → ContextMenu-BGyC31Bb.js} +2 -2
  14. package/dist/chunks/{ContextMenu-CsMXWcoQ.js.map → ContextMenu-BGyC31Bb.js.map} +1 -1
  15. package/dist/chunks/{DataView-Cy9OMdVA.js → DataView-D5GAnAwy.js} +2 -2
  16. package/dist/chunks/{DataView-Cy9OMdVA.js.map → DataView-D5GAnAwy.js.map} +1 -1
  17. package/dist/chunks/{DataView-Di2F5dQR.js → DataView-b_MvCVHp.js} +2 -2
  18. package/dist/chunks/{DataView-Di2F5dQR.js.map → DataView-b_MvCVHp.js.map} +1 -1
  19. package/dist/chunks/{Dialog-BIOrX51Q.js → Dialog-Cb3hStew.js} +2 -2
  20. package/dist/chunks/{Dialog-BIOrX51Q.js.map → Dialog-Cb3hStew.js.map} +1 -1
  21. package/dist/chunks/{Dialog-ia-mdhjA.js → Dialog-DL2qXPTj.js} +5 -5
  22. package/dist/chunks/{Dialog-ia-mdhjA.js.map → Dialog-DL2qXPTj.js.map} +1 -1
  23. package/dist/chunks/{FilePreviewView-BsV1ur-F.js → FilePreviewView-BrrJorTn.js} +26 -8
  24. package/dist/chunks/FilePreviewView-BrrJorTn.js.map +1 -0
  25. package/dist/chunks/{FilePreviewView-C550ZqA1.js → FilePreviewView-DfSMNOwF.js} +2 -2
  26. package/dist/chunks/FilePreviewView-DfSMNOwF.js.map +1 -0
  27. package/dist/chunks/{FormView-Ieig2Ikt.js → FormView-BCnSXvaY.js} +192 -3
  28. package/dist/chunks/FormView-BCnSXvaY.js.map +1 -0
  29. package/dist/chunks/FormView-FTVjKzXh.js +2 -0
  30. package/dist/chunks/FormView-FTVjKzXh.js.map +1 -0
  31. package/dist/chunks/{MetricsChart-Ch8QKUkf.js → MetricsChart-B0YqU0CD.js} +4 -4
  32. package/dist/chunks/{MetricsChart-Ch8QKUkf.js.map → MetricsChart-B0YqU0CD.js.map} +1 -1
  33. package/dist/chunks/{MetricsChart-BDhDGz4E.js → MetricsChart-DFNBNWw7.js} +2 -2
  34. package/dist/chunks/{MetricsChart-BDhDGz4E.js.map → MetricsChart-DFNBNWw7.js.map} +1 -1
  35. package/dist/chunks/{PDFViewer-CzjtNiNr.js → PDFViewer-B9OppITV.js} +3 -3
  36. package/dist/chunks/{PDFViewer-CzjtNiNr.js.map → PDFViewer-B9OppITV.js.map} +1 -1
  37. package/dist/chunks/{PDFViewer-DANtnv28.js → PDFViewer-uSCAzgto.js} +2 -2
  38. package/dist/chunks/{PDFViewer-DANtnv28.js.map → PDFViewer-uSCAzgto.js.map} +1 -1
  39. package/dist/chunks/{Page-BXT4XGsW.js → Page-BnWHjjZe.js} +2 -2
  40. package/dist/chunks/{Page-BXT4XGsW.js.map → Page-BnWHjjZe.js.map} +1 -1
  41. package/dist/chunks/{Page-D-s2pJ4R.js → Page-lvFkx9ma.js} +2 -2
  42. package/dist/chunks/{Page-D-s2pJ4R.js.map → Page-lvFkx9ma.js.map} +1 -1
  43. package/dist/chunks/{TopNav-E_ZvzFaL.js → TopNav-8K9q8FgL.js} +2 -2
  44. package/dist/chunks/{TopNav-E_ZvzFaL.js.map → TopNav-8K9q8FgL.js.map} +1 -1
  45. package/dist/chunks/{TopNav-WEv4D1Jr.js → TopNav-C86aDfet.js} +2 -2
  46. package/dist/chunks/{TopNav-WEv4D1Jr.js.map → TopNav-C86aDfet.js.map} +1 -1
  47. package/dist/chunks/User-DIhA4ryO.js +3 -0
  48. package/dist/chunks/User-DIhA4ryO.js.map +1 -0
  49. package/dist/chunks/{User-BH8gosuT.js → User-DbeMvd6x.js} +28 -26
  50. package/dist/chunks/User-DbeMvd6x.js.map +1 -0
  51. package/dist/chunks/{WebApp-AQ6Px5yi.js → WebApp-BIxs8_cJ.js} +2 -2
  52. package/dist/chunks/{WebApp-AQ6Px5yi.js.map → WebApp-BIxs8_cJ.js.map} +1 -1
  53. package/dist/chunks/{WebApp-Dr-gVl9H.js → WebApp-_cIASgB0.js} +14 -14
  54. package/dist/chunks/{WebApp-Dr-gVl9H.js.map → WebApp-_cIASgB0.js.map} +1 -1
  55. package/dist/chunks/{WebSocketClient-Dvl3AYx1.js → WebSocketClient-B6ribe3B.js} +145 -6
  56. package/dist/chunks/WebSocketClient-B6ribe3B.js.map +1 -0
  57. package/dist/chunks/WebSocketClient-Dbz1XNJA.js +2 -0
  58. package/dist/chunks/WebSocketClient-Dbz1XNJA.js.map +1 -0
  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.cjs.js.map +1 -1
  63. package/dist/index.es.js +84 -15
  64. package/dist/index.es.js.map +1 -1
  65. package/dist/lightbox.cjs.js +1 -1
  66. package/dist/lightbox.es.js +4 -4
  67. package/package.json +1 -1
  68. package/dist/chunks/FilePreviewView-BsV1ur-F.js.map +0 -1
  69. package/dist/chunks/FilePreviewView-C550ZqA1.js.map +0 -1
  70. package/dist/chunks/FormView-BxKY1Pua.js +0 -2
  71. package/dist/chunks/FormView-BxKY1Pua.js.map +0 -1
  72. package/dist/chunks/FormView-Ieig2Ikt.js.map +0 -1
  73. package/dist/chunks/User-BH8gosuT.js.map +0 -1
  74. package/dist/chunks/User-CHIu2dzX.js +0 -3
  75. package/dist/chunks/User-CHIu2dzX.js.map +0 -1
  76. package/dist/chunks/WebSocketClient-D6i85jl2.js +0 -2
  77. package/dist/chunks/WebSocketClient-D6i85jl2.js.map +0 -1
  78. package/dist/chunks/WebSocketClient-Dvl3AYx1.js.map +0 -1
@@ -6,7 +6,9 @@ class WebSocketClient {
6
6
  this.isConnected = false;
7
7
  this.isConnecting = false;
8
8
  this.shouldReconnect = options.autoReconnect !== false;
9
- this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
9
+ this._authed = false;
10
+ this._authFailed = false;
11
+ this.maxReconnectAttempts = Number.isFinite(options.maxReconnectAttempts) ? options.maxReconnectAttempts : Infinity;
10
12
  this.reconnectInterval = options.reconnectInterval || 3e3;
11
13
  this.reconnectBackoff = options.reconnectBackoff || 1.5;
12
14
  this.reconnectAttempts = 0;
@@ -15,6 +17,14 @@ class WebSocketClient {
15
17
  this.pongTimeout = options.pongTimeout || 5e3;
16
18
  this.pingTimer = null;
17
19
  this.pongTimer = null;
20
+ this.pausePingWhenHidden = options.pausePingWhenHidden !== false;
21
+ this._onVisibilityChange = this._onVisibilityChange?.bind ? this._onVisibilityChange.bind(this) : () => {
22
+ };
23
+ this.getToken = options.getToken || null;
24
+ this.tokenPrefix = options.tokenPrefix || "bearer";
25
+ this.refreshToken = options.refreshToken || null;
26
+ this.autoSubscribeOwnTopic = options.autoSubscribeOwnTopic !== false;
27
+ this.subscriptions = /* @__PURE__ */ new Set();
18
28
  this.eventBus = options.eventBus || null;
19
29
  this.listeners = {};
20
30
  this.dataTransform = options.dataTransform || null;
@@ -79,6 +89,36 @@ class WebSocketClient {
79
89
  this.socket.send(message);
80
90
  this.debug && console.log("[WebSocket] Sent:", message);
81
91
  }
92
+ /**
93
+ * Subscribe to a topic (queued until authenticated)
94
+ * @param {string} topic
95
+ */
96
+ subscribe(topic) {
97
+ if (!topic) return;
98
+ this.subscriptions.add(topic);
99
+ if (this._authed && this.isConnected) {
100
+ this.send({ action: "subscribe", topic });
101
+ }
102
+ }
103
+ /**
104
+ * Unsubscribe from a topic
105
+ * @param {string} topic
106
+ */
107
+ unsubscribe(topic) {
108
+ if (!topic) return;
109
+ this.subscriptions.delete(topic);
110
+ if (this.isConnected) {
111
+ this.send({ action: "unsubscribe", topic });
112
+ }
113
+ }
114
+ /**
115
+ * Send ping (application-level heartbeat)
116
+ */
117
+ ping() {
118
+ if (this.isConnected) {
119
+ this.send({ action: "ping" });
120
+ }
121
+ }
82
122
  /**
83
123
  * Add event listener
84
124
  * @param {string} event - Event name
@@ -157,7 +197,18 @@ class WebSocketClient {
157
197
  this.isConnected = true;
158
198
  this.isConnecting = false;
159
199
  this.reconnectAttempts = 0;
200
+ this._authed = false;
160
201
  this._startHeartbeat();
202
+ if (typeof document !== "undefined" && document.addEventListener) {
203
+ document.removeEventListener("visibilitychange", this._onVisibilityChange);
204
+ document.addEventListener("visibilitychange", this._onVisibilityChange);
205
+ }
206
+ const token = this.getToken ? this.getToken() : null;
207
+ if (!token) {
208
+ console.warn("[WebSocket] No token provided at open; waiting for auth_required or external authenticate");
209
+ } else {
210
+ this.send({ type: "authenticate", token, prefix: this.tokenPrefix || void 0 });
211
+ }
161
212
  if (this._connectResolve) {
162
213
  this._connectResolve();
163
214
  this._connectResolve = null;
@@ -173,7 +224,7 @@ class WebSocketClient {
173
224
  } catch (error) {
174
225
  data = event.data;
175
226
  }
176
- if (data === "pong" || data && data.type === "pong") {
227
+ if (data && data.type === "pong") {
177
228
  this._clearPongTimeout();
178
229
  return;
179
230
  }
@@ -186,6 +237,67 @@ class WebSocketClient {
186
237
  return;
187
238
  }
188
239
  }
240
+ if (data && typeof data === "object") {
241
+ switch (data.type) {
242
+ case "auth_required":
243
+ this.emit("auth-required", data);
244
+ break;
245
+ case "auth_timeout": {
246
+ this._authFailed = true;
247
+ this.emit("auth-timeout", data);
248
+ this.disconnect();
249
+ return;
250
+ }
251
+ case "auth_success": {
252
+ this._authed = true;
253
+ this.emit("auth-success", data);
254
+ if (this.autoSubscribeOwnTopic) {
255
+ const { instance_kind, instance_id } = data;
256
+ if (instance_kind && instance_id !== void 0) {
257
+ this.subscribe(`${instance_kind}:${instance_id}`);
258
+ }
259
+ }
260
+ for (const topic of this.subscriptions) {
261
+ this.send({ action: "subscribe", topic });
262
+ }
263
+ break;
264
+ }
265
+ case "subscribed":
266
+ this.emit("subscribed", data);
267
+ break;
268
+ case "unsubscribed":
269
+ this.emit("unsubscribed", data);
270
+ break;
271
+ case "notification":
272
+ this.emit("notification", data);
273
+ break;
274
+ case "error": {
275
+ this.emit("server-error", data);
276
+ const msg = (data.message || "").toString();
277
+ if (/token|auth/i.test(msg)) {
278
+ if (this.refreshToken) {
279
+ (async () => {
280
+ try {
281
+ await this.refreshToken();
282
+ this.disconnect();
283
+ setTimeout(() => this.connect().catch(() => {
284
+ }), 250);
285
+ } catch (e) {
286
+ console.warn("[WebSocket] Token refresh failed:", e);
287
+ this._authFailed = true;
288
+ this.disconnect();
289
+ }
290
+ })();
291
+ } else {
292
+ this._authFailed = true;
293
+ this.disconnect();
294
+ }
295
+ break;
296
+ }
297
+ break;
298
+ }
299
+ }
300
+ }
189
301
  this.emit("message", data);
190
302
  this.emit("data", data);
191
303
  }
@@ -205,7 +317,11 @@ class WebSocketClient {
205
317
  this.debug && console.log("[WebSocket] Closed:", event.code, event.reason);
206
318
  this.isConnected = false;
207
319
  this.isConnecting = false;
320
+ this._authed = false;
208
321
  this._clearTimers();
322
+ if (typeof document !== "undefined" && document.removeEventListener) {
323
+ document.removeEventListener("visibilitychange", this._onVisibilityChange);
324
+ }
209
325
  if (this.socket) {
210
326
  this.socket.removeEventListener("open", this._onOpen);
211
327
  this.socket.removeEventListener("message", this._onMessage);
@@ -218,7 +334,7 @@ class WebSocketClient {
218
334
  reason: event.reason,
219
335
  wasClean: event.wasClean
220
336
  });
221
- if (this.shouldReconnect && event.code !== 1e3) {
337
+ if (this.shouldReconnect && !this._authFailed && event.code !== 1e3) {
222
338
  this._attemptReconnect();
223
339
  }
224
340
  }
@@ -249,17 +365,24 @@ class WebSocketClient {
249
365
  }
250
366
  _startHeartbeat() {
251
367
  if (!this.pingInterval) return;
368
+ if (this.pingTimer) return;
252
369
  this.pingTimer = setInterval(() => {
253
370
  if (this.isConnected && this.socket) {
254
- this.send("ping");
371
+ if (this.pausePingWhenHidden && typeof document !== "undefined" && document.hidden) {
372
+ return;
373
+ }
374
+ this.ping();
255
375
  this._startPongTimeout();
256
376
  }
257
377
  }, this.pingInterval);
258
378
  }
259
379
  _startPongTimeout() {
380
+ this._clearPongTimeout();
260
381
  this.pongTimer = setTimeout(() => {
261
382
  console.warn("[WebSocket] Pong timeout - connection may be stale");
262
- this.socket?.close(1006, "Pong timeout");
383
+ if (this.socket && typeof this.socket.close === "function") {
384
+ this.socket.close(1006, "Pong timeout");
385
+ }
263
386
  }, this.pongTimeout);
264
387
  }
265
388
  _clearPongTimeout() {
@@ -279,6 +402,22 @@ class WebSocketClient {
279
402
  }
280
403
  this._clearPongTimeout();
281
404
  }
405
+ /**
406
+ * Handle visibility change for heartbeat
407
+ */
408
+ _onVisibilityChange() {
409
+ if (!this.pausePingWhenHidden) return;
410
+ if (typeof document === "undefined") return;
411
+ if (document.hidden) {
412
+ if (this.pingTimer) {
413
+ clearInterval(this.pingTimer);
414
+ this.pingTimer = null;
415
+ }
416
+ } else {
417
+ this._startHeartbeat();
418
+ this.ping();
419
+ }
420
+ }
282
421
  /**
283
422
  * Static method to create and connect WebSocket
284
423
  * @param {string} url - WebSocket URL
@@ -294,4 +433,4 @@ class WebSocketClient {
294
433
  export {
295
434
  WebSocketClient as W
296
435
  };
297
- //# sourceMappingURL=WebSocketClient-Dvl3AYx1.js.map
436
+ //# sourceMappingURL=WebSocketClient-B6ribe3B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketClient-B6ribe3B.js","sources":["../../src/core/services/WebSocketClient.js"],"sourcesContent":["/**\n * WebSocket - Real-time WebSocket client for MOJO framework\n * Provides connection management, auto-reconnect, and event integration\n */\n\nexport default class WebSocketClient {\n constructor(options = {}) {\n this.url = options.url || null;\n this.protocols = options.protocols || [];\n \n // Connection state\n this.socket = null;\n this.isConnected = false;\n this.isConnecting = false;\n this.shouldReconnect = options.autoReconnect !== false;\n this._authed = false;\n this._authFailed = false;\n \n // Reconnection settings\n this.maxReconnectAttempts = Number.isFinite(options.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/ping settings (JSON ping/pong)\n this.pingInterval = options.pingInterval || 30000;\n this.pongTimeout = options.pongTimeout || 5000;\n this.pingTimer = null;\n this.pongTimer = null;\n this.pausePingWhenHidden = options.pausePingWhenHidden !== false; // default true\n this._onVisibilityChange = this._onVisibilityChange?.bind ? this._onVisibilityChange.bind(this) : () => {};\n \n // Auth / token\n this.getToken = options.getToken || null; // () => string\n this.tokenPrefix = options.tokenPrefix || 'bearer';\n this.refreshToken = options.refreshToken || null; // async () => string\n this.autoSubscribeOwnTopic = options.autoSubscribeOwnTopic !== false; // default true\n \n // Subscriptions\n this.subscriptions = new Set(); // always re-subscribe post-auth_success\n \n // Event handling\n this.eventBus = options.eventBus || null;\n this.listeners = {};\n \n // Data transformation\n this.dataTransform = options.dataTransform || null;\n \n // Options\n this.debug = options.debug || false;\n \n // Bind methods\n this._onOpen = this._onOpen.bind(this);\n this._onMessage = this._onMessage.bind(this);\n this._onError = this._onError.bind(this);\n this._onClose = this._onClose.bind(this);\n }\n\n /**\n * Connect to WebSocket server\n * @param {string} url - WebSocket URL (optional if set in constructor)\n * @returns {Promise} Promise that resolves when connected\n */\n connect(url = null) {\n if (url) {\n this.url = url;\n }\n \n if (!this.url) {\n throw new Error('WebSocket URL is required');\n }\n \n if (this.isConnected || this.isConnecting) {\n return Promise.resolve();\n }\n \n this.isConnecting = true;\n \n return new Promise((resolve, reject) => {\n try {\n this.debug && console.log('[WebSocket] Connecting to:', this.url);\n \n this.socket = new WebSocket(this.url, this.protocols);\n \n this.socket.addEventListener('open', this._onOpen);\n this.socket.addEventListener('message', this._onMessage);\n this.socket.addEventListener('error', this._onError);\n this.socket.addEventListener('close', this._onClose);\n \n // Store promise resolvers for connection result\n this._connectResolve = resolve;\n this._connectReject = reject;\n \n } catch (error) {\n this.isConnecting = false;\n reject(error);\n }\n });\n }\n\n /**\n * Disconnect from WebSocket server\n */\n disconnect() {\n this.shouldReconnect = false;\n this._clearTimers();\n \n if (this.socket) {\n this.debug && console.log('[WebSocket] Disconnecting');\n this.socket.close(1000, 'Client disconnecting');\n }\n }\n\n /**\n * Send data to WebSocket server\n * @param {*} data - Data to send\n */\n send(data) {\n if (!this.isConnected || !this.socket) {\n throw new Error('WebSocket is not connected');\n }\n \n const message = typeof data === 'string' ? data : JSON.stringify(data);\n this.socket.send(message);\n \n this.debug && console.log('[WebSocket] Sent:', message);\n }\n\n /**\n * Subscribe to a topic (queued until authenticated)\n * @param {string} topic\n */\n subscribe(topic) {\n if (!topic) return;\n this.subscriptions.add(topic);\n if (this._authed && this.isConnected) {\n this.send({ action: 'subscribe', topic });\n }\n }\n\n /**\n * Unsubscribe from a topic\n * @param {string} topic\n */\n unsubscribe(topic) {\n if (!topic) return;\n this.subscriptions.delete(topic);\n if (this.isConnected) {\n this.send({ action: 'unsubscribe', topic });\n }\n }\n\n /**\n * Send ping (application-level heartbeat)\n */\n ping() {\n if (this.isConnected) {\n this.send({ action: 'ping' });\n }\n }\n\n /**\n * Add event listener\n * @param {string} event - Event name\n * @param {function} callback - Event callback\n */\n on(event, callback) {\n if (!this.listeners[event]) {\n this.listeners[event] = [];\n }\n this.listeners[event].push(callback);\n }\n\n /**\n * Remove event listener\n * @param {string} event - Event name\n * @param {function} callback - Event callback to remove\n */\n off(event, callback) {\n if (!this.listeners[event]) return;\n \n if (!callback) {\n delete this.listeners[event];\n return;\n }\n \n const index = this.listeners[event].indexOf(callback);\n if (index !== -1) {\n this.listeners[event].splice(index, 1);\n }\n }\n\n /**\n * Emit event to listeners\n * @param {string} event - Event name\n * @param {*} data - Event data\n */\n emit(event, data) {\n // Emit to local listeners\n if (this.listeners[event]) {\n this.listeners[event].forEach(callback => {\n try {\n callback(data);\n } catch (error) {\n console.error(`[WebSocket] Error in ${event} listener:`, error);\n }\n });\n }\n \n // Emit to external EventBus if available\n if (this.eventBus && typeof this.eventBus.emit === 'function') {\n this.eventBus.emit(`websocket:${event}`, {\n websocket: this,\n data\n });\n }\n }\n\n /**\n * Get current connection status\n * @returns {string} Connection status\n */\n getStatus() {\n if (this.isConnected) return 'connected';\n if (this.isConnecting) return 'connecting';\n return 'disconnected';\n }\n\n /**\n * Get connection statistics\n * @returns {object} Connection stats\n */\n getStats() {\n return {\n url: this.url,\n isConnected: this.isConnected,\n isConnecting: this.isConnecting,\n reconnectAttempts: this.reconnectAttempts,\n maxReconnectAttempts: this.maxReconnectAttempts,\n shouldReconnect: this.shouldReconnect\n };\n }\n\n // Private methods\n\n _onOpen(event) {\n this.debug && console.log('[WebSocket] Connected');\n \n this.isConnected = true;\n this.isConnecting = false;\n this.reconnectAttempts = 0;\n this._authed = false;\n \n // Start heartbeat\n this._startHeartbeat();\n // Visibility handling for heartbeat\n if (typeof document !== 'undefined' && document.addEventListener) {\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n }\n \n // Authenticate immediately\n const token = this.getToken ? this.getToken() : null;\n if (!token) {\n console.warn('[WebSocket] No token provided at open; waiting for auth_required or external authenticate');\n } else {\n this.send({ type: 'authenticate', token, prefix: this.tokenPrefix || undefined });\n }\n \n // Resolve connection promise\n if (this._connectResolve) {\n this._connectResolve();\n this._connectResolve = null;\n this._connectReject = null;\n }\n \n this.emit('connected', { url: this.url });\n }\n\n _onMessage(event) {\n this.debug && console.log('[WebSocket] 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 heartbeat pong (JSON only)\n if (data && data.type === 'pong') {\n this._clearPongTimeout();\n return;\n }\n \n // Transform data if transformer provided\n if (this.dataTransform && typeof this.dataTransform === 'function') {\n try {\n data = this.dataTransform(data);\n } catch (error) {\n console.error('[WebSocket] Error transforming data:', error);\n this.emit('error', { error, originalData: data });\n return;\n }\n }\n\n // Protocol handling\n if (data && typeof data === 'object') {\n switch (data.type) {\n case 'auth_required':\n this.emit('auth-required', data);\n break;\n case 'auth_timeout': {\n this._authFailed = true;\n this.emit('auth-timeout', data);\n this.disconnect();\n return;\n }\n case 'auth_success': {\n this._authed = true;\n this.emit('auth-success', data);\n // Auto-subscribe to own topic if enabled\n if (this.autoSubscribeOwnTopic) {\n const { instance_kind, instance_id } = data;\n if (instance_kind && instance_id !== undefined) {\n this.subscribe(`${instance_kind}:${instance_id}`);\n }\n }\n // Flush desired subscriptions\n for (const topic of this.subscriptions) {\n this.send({ action: 'subscribe', topic });\n }\n break;\n }\n case 'subscribed':\n this.emit('subscribed', data);\n break;\n case 'unsubscribed':\n this.emit('unsubscribed', data);\n break;\n case 'notification':\n this.emit('notification', data);\n break;\n case 'error': {\n this.emit('server-error', data);\n const msg = (data.message || '').toString();\n if (/token|auth/i.test(msg)) {\n if (this.refreshToken) {\n (async () => {\n try {\n await this.refreshToken();\n this.disconnect();\n // allow a short delay before reconnect to use refreshed token\n setTimeout(() => this.connect().catch(() => {}), 250);\n } catch (e) {\n console.warn('[WebSocket] Token refresh failed:', e);\n this._authFailed = true;\n this.disconnect();\n }\n })();\n } else {\n this._authFailed = true;\n this.disconnect();\n }\n break;\n }\n break;\n }\n default:\n break;\n }\n }\n \n this.emit('message', data);\n this.emit('data', data); // Alias for convenience\n }\n\n _onError(event) {\n console.error('[WebSocket] Error:', event);\n \n this.emit('error', { \n error: event.error || new Error('WebSocket error'),\n event \n });\n \n // Reject connection promise if still connecting\n if (this._connectReject) {\n this._connectReject(event.error || new Error('WebSocket connection failed'));\n this._connectResolve = null;\n this._connectReject = null;\n }\n }\n\n _onClose(event) {\n this.debug && console.log('[WebSocket] Closed:', event.code, event.reason);\n \n this.isConnected = false;\n this.isConnecting = false;\n this._authed = false;\n \n this._clearTimers();\n if (typeof document !== 'undefined' && document.removeEventListener) {\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n }\n \n // Clean up socket\n if (this.socket) {\n this.socket.removeEventListener('open', this._onOpen);\n this.socket.removeEventListener('message', this._onMessage);\n this.socket.removeEventListener('error', this._onError);\n this.socket.removeEventListener('close', this._onClose);\n this.socket = null;\n }\n \n this.emit('disconnected', { \n code: event.code, \n reason: event.reason,\n wasClean: event.wasClean\n });\n \n // Attempt reconnection if enabled and not a clean close and not due to auth failure\n if (this.shouldReconnect && !this._authFailed && event.code !== 1000) {\n this._attemptReconnect();\n }\n }\n\n _attemptReconnect() {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n this.debug && console.log('[WebSocket] Max reconnect attempts reached');\n this.emit('reconnect-failed', { \n attempts: this.reconnectAttempts,\n maxAttempts: this.maxReconnectAttempts\n });\n return;\n }\n \n this.reconnectAttempts++;\n const delay = this.reconnectInterval * Math.pow(this.reconnectBackoff, this.reconnectAttempts - 1);\n \n this.debug && console.log(`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n \n this.emit('reconnecting', { \n attempt: this.reconnectAttempts,\n delay,\n maxAttempts: this.maxReconnectAttempts\n });\n \n this.reconnectTimer = setTimeout(() => {\n if (this.shouldReconnect) {\n this.connect().catch(error => {\n console.error('[WebSocket] Reconnection failed:', error);\n });\n }\n }, delay);\n }\n\n _startHeartbeat() {\n if (!this.pingInterval) return;\n if (this.pingTimer) return; // avoid duplicates\n \n this.pingTimer = setInterval(() => {\n if (this.isConnected && this.socket) {\n // pause pings when hidden (optional)\n if (this.pausePingWhenHidden && typeof document !== 'undefined' && document.hidden) {\n return;\n }\n this.ping();\n this._startPongTimeout();\n }\n }, this.pingInterval);\n }\n\n _startPongTimeout() {\n this._clearPongTimeout();\n this.pongTimer = setTimeout(() => {\n console.warn('[WebSocket] Pong timeout - connection may be stale');\n if (this.socket && typeof this.socket.close === 'function') {\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 \n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n \n this._clearPongTimeout();\n }\n\n /**\n * Handle visibility change for heartbeat\n */\n _onVisibilityChange() {\n if (!this.pausePingWhenHidden) return;\n if (typeof document === 'undefined') return;\n if (document.hidden) {\n // Pause heartbeat to save power\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n } else {\n // Resume heartbeat and send a ping\n this._startHeartbeat();\n this.ping();\n }\n }\n\n /**\n * Static method to create and connect WebSocket\n * @param {string} url - WebSocket URL\n * @param {object} options - Connection options\n * @returns {Promise<WebSocketClient>} Connected WebSocket client\n */\n static async connect(url, options = {}) {\n const client = new WebSocketClient({ ...options, url });\n await client.connect();\n return client;\n }\n}"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";class WebSocketClient{constructor(e={}){this.url=e.url||null,this.protocols=e.protocols||[],this.socket=null,this.isConnected=!1,this.isConnecting=!1,this.shouldReconnect=!1!==e.autoReconnect,this._authed=!1,this._authFailed=!1,this.maxReconnectAttempts=Number.isFinite(e.maxReconnectAttempts)?e.maxReconnectAttempts:1/0,this.reconnectInterval=e.reconnectInterval||3e3,this.reconnectBackoff=e.reconnectBackoff||1.5,this.reconnectAttempts=0,this.reconnectTimer=null,this.pingInterval=e.pingInterval||3e4,this.pongTimeout=e.pongTimeout||5e3,this.pingTimer=null,this.pongTimer=null,this.pausePingWhenHidden=!1!==e.pausePingWhenHidden,this._onVisibilityChange=this._onVisibilityChange?.bind?this._onVisibilityChange.bind(this):()=>{},this.getToken=e.getToken||null,this.tokenPrefix=e.tokenPrefix||"bearer",this.refreshToken=e.refreshToken||null,this.autoSubscribeOwnTopic=!1!==e.autoSubscribeOwnTopic,this.subscriptions=/* @__PURE__ */new Set,this.eventBus=e.eventBus||null,this.listeners={},this.dataTransform=e.dataTransform||null,this.debug=e.debug||!1,this._onOpen=this._onOpen.bind(this),this._onMessage=this._onMessage.bind(this),this._onError=this._onError.bind(this),this._onClose=this._onClose.bind(this)}connect(e=null){if(e&&(this.url=e),!this.url)throw new Error("WebSocket URL is required");return this.isConnected||this.isConnecting?Promise.resolve():(this.isConnecting=!0,new Promise((e,t)=>{try{this.debug&&console.log("[WebSocket] Connecting to:",this.url),this.socket=new WebSocket(this.url,this.protocols),this.socket.addEventListener("open",this._onOpen),this.socket.addEventListener("message",this._onMessage),this.socket.addEventListener("error",this._onError),this.socket.addEventListener("close",this._onClose),this._connectResolve=e,this._connectReject=t}catch(i){this.isConnecting=!1,t(i)}}))}disconnect(){this.shouldReconnect=!1,this._clearTimers(),this.socket&&(this.debug&&console.log("[WebSocket] Disconnecting"),this.socket.close(1e3,"Client disconnecting"))}send(e){if(!this.isConnected||!this.socket)throw new Error("WebSocket is not connected");const t="string"==typeof e?e:JSON.stringify(e);this.socket.send(t),this.debug&&console.log("[WebSocket] Sent:",t)}subscribe(e){e&&(this.subscriptions.add(e),this._authed&&this.isConnected&&this.send({action:"subscribe",topic:e}))}unsubscribe(e){e&&(this.subscriptions.delete(e),this.isConnected&&this.send({action:"unsubscribe",topic:e}))}ping(){this.isConnected&&this.send({action:"ping"})}on(e,t){this.listeners[e]||(this.listeners[e]=[]),this.listeners[e].push(t)}off(e,t){if(!this.listeners[e])return;if(!t)return void delete this.listeners[e];const i=this.listeners[e].indexOf(t);-1!==i&&this.listeners[e].splice(i,1)}emit(e,t){this.listeners[e]&&this.listeners[e].forEach(i=>{try{i(t)}catch(n){console.error(`[WebSocket] Error in ${e} listener:`,n)}}),this.eventBus&&"function"==typeof this.eventBus.emit&&this.eventBus.emit(`websocket:${e}`,{websocket:this,data:t})}getStatus(){return this.isConnected?"connected":this.isConnecting?"connecting":"disconnected"}getStats(){return{url:this.url,isConnected:this.isConnected,isConnecting:this.isConnecting,reconnectAttempts:this.reconnectAttempts,maxReconnectAttempts:this.maxReconnectAttempts,shouldReconnect:this.shouldReconnect}}_onOpen(e){this.debug&&console.log("[WebSocket] Connected"),this.isConnected=!0,this.isConnecting=!1,this.reconnectAttempts=0,this._authed=!1,this._startHeartbeat(),"undefined"!=typeof document&&document.addEventListener&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),document.addEventListener("visibilitychange",this._onVisibilityChange));const t=this.getToken?this.getToken():null;t?this.send({type:"authenticate",token:t,prefix:this.tokenPrefix||void 0}):console.warn("[WebSocket] No token provided at open; waiting for auth_required or external authenticate"),this._connectResolve&&(this._connectResolve(),this._connectResolve=null,this._connectReject=null),this.emit("connected",{url:this.url})}_onMessage(e){let t;this.debug&&console.log("[WebSocket] Received:",e.data);try{t=JSON.parse(e.data)}catch(i){t=e.data}if(t&&"pong"===t.type)this._clearPongTimeout();else{if(this.dataTransform&&"function"==typeof this.dataTransform)try{t=this.dataTransform(t)}catch(i){return console.error("[WebSocket] Error transforming data:",i),void this.emit("error",{error:i,originalData:t})}if(t&&"object"==typeof t)switch(t.type){case"auth_required":this.emit("auth-required",t);break;case"auth_timeout":return this._authFailed=!0,this.emit("auth-timeout",t),void this.disconnect();case"auth_success":if(this._authed=!0,this.emit("auth-success",t),this.autoSubscribeOwnTopic){const{instance_kind:e,instance_id:i}=t;e&&void 0!==i&&this.subscribe(`${e}:${i}`)}for(const e of this.subscriptions)this.send({action:"subscribe",topic:e});break;case"subscribed":this.emit("subscribed",t);break;case"unsubscribed":this.emit("unsubscribed",t);break;case"notification":this.emit("notification",t);break;case"error":{this.emit("server-error",t);const e=(t.message||"").toString();if(/token|auth/i.test(e)){this.refreshToken?(async()=>{try{await this.refreshToken(),this.disconnect(),setTimeout(()=>this.connect().catch(()=>{}),250)}catch(e){console.warn("[WebSocket] Token refresh failed:",e),this._authFailed=!0,this.disconnect()}})():(this._authFailed=!0,this.disconnect());break}break}}this.emit("message",t),this.emit("data",t)}}_onError(e){console.error("[WebSocket] Error:",e),this.emit("error",{error:e.error||new Error("WebSocket error"),event:e}),this._connectReject&&(this._connectReject(e.error||new Error("WebSocket connection failed")),this._connectResolve=null,this._connectReject=null)}_onClose(e){this.debug&&console.log("[WebSocket] Closed:",e.code,e.reason),this.isConnected=!1,this.isConnecting=!1,this._authed=!1,this._clearTimers(),"undefined"!=typeof document&&document.removeEventListener&&document.removeEventListener("visibilitychange",this._onVisibilityChange),this.socket&&(this.socket.removeEventListener("open",this._onOpen),this.socket.removeEventListener("message",this._onMessage),this.socket.removeEventListener("error",this._onError),this.socket.removeEventListener("close",this._onClose),this.socket=null),this.emit("disconnected",{code:e.code,reason:e.reason,wasClean:e.wasClean}),this.shouldReconnect&&!this._authFailed&&1e3!==e.code&&this._attemptReconnect()}_attemptReconnect(){if(this.reconnectAttempts>=this.maxReconnectAttempts)return this.debug&&console.log("[WebSocket] Max reconnect attempts reached"),void this.emit("reconnect-failed",{attempts:this.reconnectAttempts,maxAttempts:this.maxReconnectAttempts});this.reconnectAttempts++;const e=this.reconnectInterval*Math.pow(this.reconnectBackoff,this.reconnectAttempts-1);this.debug&&console.log(`[WebSocket] Reconnecting in ${e}ms (attempt ${this.reconnectAttempts})`),this.emit("reconnecting",{attempt:this.reconnectAttempts,delay:e,maxAttempts:this.maxReconnectAttempts}),this.reconnectTimer=setTimeout(()=>{this.shouldReconnect&&this.connect().catch(e=>{console.error("[WebSocket] Reconnection failed:",e)})},e)}_startHeartbeat(){this.pingInterval&&(this.pingTimer||(this.pingTimer=setInterval(()=>{if(this.isConnected&&this.socket){if(this.pausePingWhenHidden&&"undefined"!=typeof document&&document.hidden)return;this.ping(),this._startPongTimeout()}},this.pingInterval)))}_startPongTimeout(){this._clearPongTimeout(),this.pongTimer=setTimeout(()=>{console.warn("[WebSocket] Pong timeout - connection may be stale"),this.socket&&"function"==typeof this.socket.close&&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()}_onVisibilityChange(){this.pausePingWhenHidden&&"undefined"!=typeof document&&(document.hidden?this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null):(this._startHeartbeat(),this.ping()))}static async connect(e,t={}){const i=new WebSocketClient({...t,url:e});return await i.connect(),i}}exports.WebSocketClient=WebSocketClient;
2
+ //# sourceMappingURL=WebSocketClient-Dbz1XNJA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketClient-Dbz1XNJA.js","sources":["../../src/core/services/WebSocketClient.js"],"sourcesContent":["/**\n * WebSocket - Real-time WebSocket client for MOJO framework\n * Provides connection management, auto-reconnect, and event integration\n */\n\nexport default class WebSocketClient {\n constructor(options = {}) {\n this.url = options.url || null;\n this.protocols = options.protocols || [];\n \n // Connection state\n this.socket = null;\n this.isConnected = false;\n this.isConnecting = false;\n this.shouldReconnect = options.autoReconnect !== false;\n this._authed = false;\n this._authFailed = false;\n \n // Reconnection settings\n this.maxReconnectAttempts = Number.isFinite(options.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/ping settings (JSON ping/pong)\n this.pingInterval = options.pingInterval || 30000;\n this.pongTimeout = options.pongTimeout || 5000;\n this.pingTimer = null;\n this.pongTimer = null;\n this.pausePingWhenHidden = options.pausePingWhenHidden !== false; // default true\n this._onVisibilityChange = this._onVisibilityChange?.bind ? this._onVisibilityChange.bind(this) : () => {};\n \n // Auth / token\n this.getToken = options.getToken || null; // () => string\n this.tokenPrefix = options.tokenPrefix || 'bearer';\n this.refreshToken = options.refreshToken || null; // async () => string\n this.autoSubscribeOwnTopic = options.autoSubscribeOwnTopic !== false; // default true\n \n // Subscriptions\n this.subscriptions = new Set(); // always re-subscribe post-auth_success\n \n // Event handling\n this.eventBus = options.eventBus || null;\n this.listeners = {};\n \n // Data transformation\n this.dataTransform = options.dataTransform || null;\n \n // Options\n this.debug = options.debug || false;\n \n // Bind methods\n this._onOpen = this._onOpen.bind(this);\n this._onMessage = this._onMessage.bind(this);\n this._onError = this._onError.bind(this);\n this._onClose = this._onClose.bind(this);\n }\n\n /**\n * Connect to WebSocket server\n * @param {string} url - WebSocket URL (optional if set in constructor)\n * @returns {Promise} Promise that resolves when connected\n */\n connect(url = null) {\n if (url) {\n this.url = url;\n }\n \n if (!this.url) {\n throw new Error('WebSocket URL is required');\n }\n \n if (this.isConnected || this.isConnecting) {\n return Promise.resolve();\n }\n \n this.isConnecting = true;\n \n return new Promise((resolve, reject) => {\n try {\n this.debug && console.log('[WebSocket] Connecting to:', this.url);\n \n this.socket = new WebSocket(this.url, this.protocols);\n \n this.socket.addEventListener('open', this._onOpen);\n this.socket.addEventListener('message', this._onMessage);\n this.socket.addEventListener('error', this._onError);\n this.socket.addEventListener('close', this._onClose);\n \n // Store promise resolvers for connection result\n this._connectResolve = resolve;\n this._connectReject = reject;\n \n } catch (error) {\n this.isConnecting = false;\n reject(error);\n }\n });\n }\n\n /**\n * Disconnect from WebSocket server\n */\n disconnect() {\n this.shouldReconnect = false;\n this._clearTimers();\n \n if (this.socket) {\n this.debug && console.log('[WebSocket] Disconnecting');\n this.socket.close(1000, 'Client disconnecting');\n }\n }\n\n /**\n * Send data to WebSocket server\n * @param {*} data - Data to send\n */\n send(data) {\n if (!this.isConnected || !this.socket) {\n throw new Error('WebSocket is not connected');\n }\n \n const message = typeof data === 'string' ? data : JSON.stringify(data);\n this.socket.send(message);\n \n this.debug && console.log('[WebSocket] Sent:', message);\n }\n\n /**\n * Subscribe to a topic (queued until authenticated)\n * @param {string} topic\n */\n subscribe(topic) {\n if (!topic) return;\n this.subscriptions.add(topic);\n if (this._authed && this.isConnected) {\n this.send({ action: 'subscribe', topic });\n }\n }\n\n /**\n * Unsubscribe from a topic\n * @param {string} topic\n */\n unsubscribe(topic) {\n if (!topic) return;\n this.subscriptions.delete(topic);\n if (this.isConnected) {\n this.send({ action: 'unsubscribe', topic });\n }\n }\n\n /**\n * Send ping (application-level heartbeat)\n */\n ping() {\n if (this.isConnected) {\n this.send({ action: 'ping' });\n }\n }\n\n /**\n * Add event listener\n * @param {string} event - Event name\n * @param {function} callback - Event callback\n */\n on(event, callback) {\n if (!this.listeners[event]) {\n this.listeners[event] = [];\n }\n this.listeners[event].push(callback);\n }\n\n /**\n * Remove event listener\n * @param {string} event - Event name\n * @param {function} callback - Event callback to remove\n */\n off(event, callback) {\n if (!this.listeners[event]) return;\n \n if (!callback) {\n delete this.listeners[event];\n return;\n }\n \n const index = this.listeners[event].indexOf(callback);\n if (index !== -1) {\n this.listeners[event].splice(index, 1);\n }\n }\n\n /**\n * Emit event to listeners\n * @param {string} event - Event name\n * @param {*} data - Event data\n */\n emit(event, data) {\n // Emit to local listeners\n if (this.listeners[event]) {\n this.listeners[event].forEach(callback => {\n try {\n callback(data);\n } catch (error) {\n console.error(`[WebSocket] Error in ${event} listener:`, error);\n }\n });\n }\n \n // Emit to external EventBus if available\n if (this.eventBus && typeof this.eventBus.emit === 'function') {\n this.eventBus.emit(`websocket:${event}`, {\n websocket: this,\n data\n });\n }\n }\n\n /**\n * Get current connection status\n * @returns {string} Connection status\n */\n getStatus() {\n if (this.isConnected) return 'connected';\n if (this.isConnecting) return 'connecting';\n return 'disconnected';\n }\n\n /**\n * Get connection statistics\n * @returns {object} Connection stats\n */\n getStats() {\n return {\n url: this.url,\n isConnected: this.isConnected,\n isConnecting: this.isConnecting,\n reconnectAttempts: this.reconnectAttempts,\n maxReconnectAttempts: this.maxReconnectAttempts,\n shouldReconnect: this.shouldReconnect\n };\n }\n\n // Private methods\n\n _onOpen(event) {\n this.debug && console.log('[WebSocket] Connected');\n \n this.isConnected = true;\n this.isConnecting = false;\n this.reconnectAttempts = 0;\n this._authed = false;\n \n // Start heartbeat\n this._startHeartbeat();\n // Visibility handling for heartbeat\n if (typeof document !== 'undefined' && document.addEventListener) {\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n }\n \n // Authenticate immediately\n const token = this.getToken ? this.getToken() : null;\n if (!token) {\n console.warn('[WebSocket] No token provided at open; waiting for auth_required or external authenticate');\n } else {\n this.send({ type: 'authenticate', token, prefix: this.tokenPrefix || undefined });\n }\n \n // Resolve connection promise\n if (this._connectResolve) {\n this._connectResolve();\n this._connectResolve = null;\n this._connectReject = null;\n }\n \n this.emit('connected', { url: this.url });\n }\n\n _onMessage(event) {\n this.debug && console.log('[WebSocket] 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 heartbeat pong (JSON only)\n if (data && data.type === 'pong') {\n this._clearPongTimeout();\n return;\n }\n \n // Transform data if transformer provided\n if (this.dataTransform && typeof this.dataTransform === 'function') {\n try {\n data = this.dataTransform(data);\n } catch (error) {\n console.error('[WebSocket] Error transforming data:', error);\n this.emit('error', { error, originalData: data });\n return;\n }\n }\n\n // Protocol handling\n if (data && typeof data === 'object') {\n switch (data.type) {\n case 'auth_required':\n this.emit('auth-required', data);\n break;\n case 'auth_timeout': {\n this._authFailed = true;\n this.emit('auth-timeout', data);\n this.disconnect();\n return;\n }\n case 'auth_success': {\n this._authed = true;\n this.emit('auth-success', data);\n // Auto-subscribe to own topic if enabled\n if (this.autoSubscribeOwnTopic) {\n const { instance_kind, instance_id } = data;\n if (instance_kind && instance_id !== undefined) {\n this.subscribe(`${instance_kind}:${instance_id}`);\n }\n }\n // Flush desired subscriptions\n for (const topic of this.subscriptions) {\n this.send({ action: 'subscribe', topic });\n }\n break;\n }\n case 'subscribed':\n this.emit('subscribed', data);\n break;\n case 'unsubscribed':\n this.emit('unsubscribed', data);\n break;\n case 'notification':\n this.emit('notification', data);\n break;\n case 'error': {\n this.emit('server-error', data);\n const msg = (data.message || '').toString();\n if (/token|auth/i.test(msg)) {\n if (this.refreshToken) {\n (async () => {\n try {\n await this.refreshToken();\n this.disconnect();\n // allow a short delay before reconnect to use refreshed token\n setTimeout(() => this.connect().catch(() => {}), 250);\n } catch (e) {\n console.warn('[WebSocket] Token refresh failed:', e);\n this._authFailed = true;\n this.disconnect();\n }\n })();\n } else {\n this._authFailed = true;\n this.disconnect();\n }\n break;\n }\n break;\n }\n default:\n break;\n }\n }\n \n this.emit('message', data);\n this.emit('data', data); // Alias for convenience\n }\n\n _onError(event) {\n console.error('[WebSocket] Error:', event);\n \n this.emit('error', { \n error: event.error || new Error('WebSocket error'),\n event \n });\n \n // Reject connection promise if still connecting\n if (this._connectReject) {\n this._connectReject(event.error || new Error('WebSocket connection failed'));\n this._connectResolve = null;\n this._connectReject = null;\n }\n }\n\n _onClose(event) {\n this.debug && console.log('[WebSocket] Closed:', event.code, event.reason);\n \n this.isConnected = false;\n this.isConnecting = false;\n this._authed = false;\n \n this._clearTimers();\n if (typeof document !== 'undefined' && document.removeEventListener) {\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n }\n \n // Clean up socket\n if (this.socket) {\n this.socket.removeEventListener('open', this._onOpen);\n this.socket.removeEventListener('message', this._onMessage);\n this.socket.removeEventListener('error', this._onError);\n this.socket.removeEventListener('close', this._onClose);\n this.socket = null;\n }\n \n this.emit('disconnected', { \n code: event.code, \n reason: event.reason,\n wasClean: event.wasClean\n });\n \n // Attempt reconnection if enabled and not a clean close and not due to auth failure\n if (this.shouldReconnect && !this._authFailed && event.code !== 1000) {\n this._attemptReconnect();\n }\n }\n\n _attemptReconnect() {\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n this.debug && console.log('[WebSocket] Max reconnect attempts reached');\n this.emit('reconnect-failed', { \n attempts: this.reconnectAttempts,\n maxAttempts: this.maxReconnectAttempts\n });\n return;\n }\n \n this.reconnectAttempts++;\n const delay = this.reconnectInterval * Math.pow(this.reconnectBackoff, this.reconnectAttempts - 1);\n \n this.debug && console.log(`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n \n this.emit('reconnecting', { \n attempt: this.reconnectAttempts,\n delay,\n maxAttempts: this.maxReconnectAttempts\n });\n \n this.reconnectTimer = setTimeout(() => {\n if (this.shouldReconnect) {\n this.connect().catch(error => {\n console.error('[WebSocket] Reconnection failed:', error);\n });\n }\n }, delay);\n }\n\n _startHeartbeat() {\n if (!this.pingInterval) return;\n if (this.pingTimer) return; // avoid duplicates\n \n this.pingTimer = setInterval(() => {\n if (this.isConnected && this.socket) {\n // pause pings when hidden (optional)\n if (this.pausePingWhenHidden && typeof document !== 'undefined' && document.hidden) {\n return;\n }\n this.ping();\n this._startPongTimeout();\n }\n }, this.pingInterval);\n }\n\n _startPongTimeout() {\n this._clearPongTimeout();\n this.pongTimer = setTimeout(() => {\n console.warn('[WebSocket] Pong timeout - connection may be stale');\n if (this.socket && typeof this.socket.close === 'function') {\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 \n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n \n this._clearPongTimeout();\n }\n\n /**\n * Handle visibility change for heartbeat\n */\n _onVisibilityChange() {\n if (!this.pausePingWhenHidden) return;\n if (typeof document === 'undefined') return;\n if (document.hidden) {\n // Pause heartbeat to save power\n if (this.pingTimer) {\n clearInterval(this.pingTimer);\n this.pingTimer = null;\n }\n } else {\n // Resume heartbeat and send a ping\n this._startHeartbeat();\n this.ping();\n }\n }\n\n /**\n * Static method to create and connect WebSocket\n * @param {string} url - WebSocket URL\n * @param {object} options - Connection options\n * @returns {Promise<WebSocketClient>} Connected WebSocket client\n */\n static async connect(url, options = {}) {\n const client = new WebSocketClient({ ...options, url });\n await client.connect();\n return client;\n }\n}"],"names":["WebSocketClient","constructor","options","this","url","protocols","socket","isConnected","isConnecting","shouldReconnect","autoReconnect","_authed","_authFailed","maxReconnectAttempts","Number","isFinite","Infinity","reconnectInterval","reconnectBackoff","reconnectAttempts","reconnectTimer","pingInterval","pongTimeout","pingTimer","pongTimer","pausePingWhenHidden","_onVisibilityChange","bind","getToken","tokenPrefix","refreshToken","autoSubscribeOwnTopic","subscriptions","Set","eventBus","listeners","dataTransform","debug","_onOpen","_onMessage","_onError","_onClose","connect","Error","Promise","resolve","reject","console","log","WebSocket","addEventListener","_connectResolve","_connectReject","error","disconnect","_clearTimers","close","send","data","message","JSON","stringify","subscribe","topic","add","action","unsubscribe","delete","ping","on","event","callback","push","off","index","indexOf","splice","emit","forEach","websocket","getStatus","getStats","_startHeartbeat","document","removeEventListener","token","type","prefix","warn","parse","_clearPongTimeout","originalData","instance_kind","instance_id","msg","toString","test","setTimeout","catch","e","code","reason","wasClean","_attemptReconnect","attempts","maxAttempts","delay","Math","pow","attempt","setInterval","hidden","_startPongTimeout","clearTimeout","clearInterval","client"],"mappings":"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"}
package/dist/docit.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./chunks/WebApp-AQ6Px5yi.js"),e=require("./chunks/TopNav-E_ZvzFaL.js"),o=require("./chunks/Page-D-s2pJ4R.js"),i=require("./chunks/ContextMenu-CsMXWcoQ.js"),s=require("./chunks/User-CHIu2dzX.js"),a=require("./chunks/TokenManager-gp1JKXNd.js");class DocNavSidebar extends t.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 o.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 o.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 o.Page{constructor(t={}){super({pageName:"edit",title:"Edit Page",className:"docit-edit-page",...t}),this.model=null,this.editor=null,this.isDirty=!1}async getTemplate(){return'\n <div class="docit-edit-container vh-100">\n {{#loading}}\n <div class="docit-loading"><div class="spinner-border"></div></div>\n {{/loading}}\n\n {{^loading}}{{#model}}\n <header class="docit-edit-header">\n <div class="docit-edit-title-row">\n <h2>Editing: {{model.title}}</h2>\n <div class="docit-edit-actions">\n <button class="btn btn-outline-secondary" data-action="cancel-edit">Cancel</button>\n <button class="btn btn-success" data-action="save-page">\n <i class="bi bi-check-lg"></i> Save Changes\n </button>\n </div>\n </div>\n </header>\n <div class="docit-edit-body flex-grow-1">\n <div id="editor"></div>\n </div>\n {{/model}}\n {{^model}}\n <div class="docit-error-state">\n <h3>Page Not Found</h3>\n <p>The page you\'re trying to edit could not be found.</p>\n <button class="btn btn-primary" data-action="go-back">Go Back</button>\n </div>\n {{/model}}{{/loading}}\n </div>\n '}async onParams(t={},e={}){await super.onParams(t,e);let o=null;e.id?o=new DocitPage({id:e.id}):e.doc_page&&(o=new DocitPage({slug:e.doc_page})),this.model=o,this.model&&await this.model.fetch({graph:"detail"})}initEditor(){!this.editor&&this.element.querySelector("#editor")&&(this.editor=new toastui.Editor({el:this.element.querySelector("#editor"),height:"100%",initialEditType:"markdown",previewStyle:"tab",initialValue:this.model.get("content")||""}),this.editor.on("change",()=>{this.isDirty=!0}))}async onActionSavePage(){if(this.model&&this.editor){this.saving=!0,this.getApp().showLoading("saving...");try{const t=this.editor.getMarkdown();await this.model.save({content:t}),this.isDirty=!1,this.getApp().toast.success("Page saved successfully."),this.getApp().showPage("docs",this.query)}catch(t){console.error("Failed to save page:",t),this.getApp().toast.error("Failed to save page.")}finally{this.saving=!1,this.getApp().hideLoading()}}}async onActionCancelEdit(){this.getApp().showPage("docs",this.query)}onActionGoBack(){this.getApp().showPage("docs")}async onBeforeRender(){await super.onBeforeRender(),this.editor&&(this.editor.destroy(),this.editor=null)}async onAfterRender(){await super.onAfterRender(),this.model&&this.initEditor()}async onBeforeDestroy(){this.editor?.destroy(),await super.onBeforeDestroy()}}class DocItApp extends t.WebApp{constructor(t={}){super({name:t.title||"DocIt Portal",version:t.version||"1.0.0",debug:t.debug||!1,container:t.container||"#app",defaultRoute:"home",basePath:t.basePath||"",...t}),this.bookSlug=t.bookSlug||null,this.showBookNav=void 0!==t.showBookNav?t.showBookNav:!this.bookSlug,this.theme=t.theme||"light",this.editPermissions=t.permissions?.edit||["manage_docit"],this.sidebarConfig={showSearch:!0,defaultCollapsed:!1,...t.sidebar},this.books=new DocitBookList,this.books.params.sort="-order_priority",this.docPages=new DocitPageList,this.docPages.params.sort="-order_priority",this.toast=new s.ToastService,this.currentBook=null,this.sidebar=null,this.isDocItReady=!1,this.tokenManager=new a.TokenManager,this.activeUser=null}async start(){try{console.log("Starting DocIt Portal..."),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}),console.log("✅ DocIt Portal ready")}catch(t){throw console.error("Failed to start DocIt:",t),this.showError("Failed to initialize documentation portal"),t}}setupDocItLayout(){const t="string"==typeof this.container?document.querySelector(this.container):this.container;if(!t)throw new Error(`Container not found: ${this.container}`);t.classList.add("docit-app",`docit-theme-${this.theme}`),t.innerHTML='\n <div class="docit-app-layout">\n <div id="topnav-container"></div>\n <div class="docit-body-layout">\n <div class="docit-sidebar" id="docit-sidebar"></div>\n <div class="docit-main">\n <div class="docit-content" id="page-container"></div>\n </div>\n </div>\n </div>\n ',this.pageContainer="#page-container"}async setupTopNav(){this.topnav=new e.TopNav({containerId:"topnav-container",brand:this.name,theme:"navbar navbar-expand-lg bg-dark navbar-dark",showSidebarToggle:!1,displayMode:"brand",rightItems:[{id:"login",icon:"bi-box-arrow-in-right",href:"/examples/auth/",label:"Login"}],userMenu:{label:"User",icon:"bi-person-circle",items:[{label:"Profile",icon:"bi-person",action:"profile"},{divider:!0},{label:"Logout",icon:"bi-box-arrow-right",action:"logout"}]}}),await this.topnav.render()}onActionToggleSidebar(){document.querySelector(".docit-layout").classList.toggle("sidebar-collapsed")}async setupSidebar(){this.sidebar=new DocNavSidebar({containerId:"docit-sidebar",app:this,singleBookMode:!!this.bookSlug,showBookNav:this.showBookNav,books:this.books,docPages:this.docPages,activeUser:this.activeUser,...this.sidebarConfig}),await this.sidebar.render()}registerDocItPages(){this.registerPage("home",DocHomePage,{route:"/",permissions:null}),this.registerPage("docs",DocPage,{route:"/docs",permissions:null}),this.registerPage("edit",DocEditPage,{route:"/edit",permissions:this.editPermissions})}async loadInitialData(){try{if(this.bookSlug){const t=new DocitBook({slug:this.bookSlug});if(await t.fetch(),!t.id)throw new Error(`Book with slug '${this.bookSlug}' not found.`);this.books.add(t),await this.setActiveBook(t)}else await this.books.fetch({graph:"list"}),this.sidebar.render()}catch(t){console.error("Failed to load initial data:",t),this.showError("Failed to load documentation")}}async setActiveBook(t){this.currentBook&&t&&this.currentBook.id===t.id||(this.currentBook=t,this.docPages.reset(),t&&await this.docPages.fetch({book:t.get("id"),graph:"list"}),this.sidebar.setCurrentBook(t),this.sidebar.setDocPages(this.docPages),this.events.emit("docit:book-changed",{book:t}))}async saveDocPageContent(t,e){const o=this.docPages.get(t)||new DocitPage({id:t});o.set("content",e);const i=await o.save();if(!i.success||!i.data.status)throw new Error("Failed to save doc page");return o}canEdit(){const t=this.activeUser;return!!t&&this.editPermissions.some(e=>!!t.hasPermission&&t.hasPermission(e))}async checkAuthStatus(){try{const t=this.tokenManager.getTokenInstance();if(!t||!t.isValid())return void this.events.emit("auth:unauthorized",{app:this});if(t.isExpired())return void this.events.emit("auth:expired",{app:this});this.tokenManager.startAutoRefresh(this),this.rest.setAuthToken(t.token);const e=new s.User({id:t.getUserId()});await e.fetch(),this.setActiveUser(e)}catch(t){console.error("Failed to check auth status:",t),this.events.emit("auth:error",{error:t,app:this})}}setActiveUser(t){return this.activeUser=t,this.sidebar&&this.sidebar.setUser(t),this.topnav&&(this.topnav.setUser(t),this.tonnav.render()),this.events.emit("user:changed",{user:t,app:this}),this}clearActiveUser(){return this.activeUser=null,this.tokenManager.clearTokens(),this.rest.clearAuth(),this.sidebar&&this.sidebar.setUser(null),this.events.emit("user:cleared",{app:this}),this}async logout(){this.clearActiveUser(),this.books.reset(),this.docPages.reset(),this.currentBook=null,this.events.emit("auth:logout",{app:this}),window.location.reload()}async createNewBook(){const t=await this.showModelForm({title:"Create New Book",model:new DocitBook,fields:[{name:"title",label:"Title",required:!0},{name:"slug",label:"Slug",required:!0,helpText:"A URL-friendly identifier."}]});t&&t.success&&(this.books.add(t.data),this.sidebar.render(),this.showSuccess("Book created successfully."))}async createNewPage(t){if(!t)return;const e=await this.showForm({title:"Create New Page",fields:[{name:"title",label:"Title",required:!0},{name:"slug",label:"Slug",required:!0,helpText:"A URL-friendly identifier."}]});e&&e.slug&&(e.book=t.id);const o=new DocitPage,i=await o.save(e);i&&i.success&&(this.docPages.add(o),this.sidebar.render(),this.showPage("edit",{id:o.id,doc_book:t.get("slug"),doc_page:o.get("slug")},{}))}static create(t={}){return new DocItApp(t)}static createForBook(t,e={}){return new DocItApp({...e,bookSlug:t,showBookNav:!1})}}exports.BUILD_TIME=t.BUILD_TIME,exports.VERSION=t.VERSION,exports.VERSION_INFO=t.VERSION_INFO,exports.VERSION_MAJOR=t.VERSION_MAJOR,exports.VERSION_MINOR=t.VERSION_MINOR,exports.VERSION_REVISION=t.VERSION_REVISION,exports.WebApp=t.WebApp,exports.DocEditPage=DocEditPage,exports.DocHomePage=DocHomePage,exports.DocItApp=DocItApp,exports.DocNavSidebar=DocNavSidebar,exports.DocPage=DocPage,exports.DocitBook=DocitBook,exports.DocitBookList=DocitBookList,exports.DocitPage=DocitPage,exports.DocitPageList=DocitPageList;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./chunks/WebApp-BIxs8_cJ.js"),e=require("./chunks/TopNav-8K9q8FgL.js"),o=require("./chunks/Page-lvFkx9ma.js"),i=require("./chunks/ContextMenu-BGyC31Bb.js"),s=require("./chunks/User-DIhA4ryO.js"),a=require("./chunks/TokenManager-gp1JKXNd.js");class DocNavSidebar extends t.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 o.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 o.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 o.Page{constructor(t={}){super({pageName:"edit",title:"Edit Page",className:"docit-edit-page",...t}),this.model=null,this.editor=null,this.isDirty=!1}async getTemplate(){return'\n <div class="docit-edit-container vh-100">\n {{#loading}}\n <div class="docit-loading"><div class="spinner-border"></div></div>\n {{/loading}}\n\n {{^loading}}{{#model}}\n <header class="docit-edit-header">\n <div class="docit-edit-title-row">\n <h2>Editing: {{model.title}}</h2>\n <div class="docit-edit-actions">\n <button class="btn btn-outline-secondary" data-action="cancel-edit">Cancel</button>\n <button class="btn btn-success" data-action="save-page">\n <i class="bi bi-check-lg"></i> Save Changes\n </button>\n </div>\n </div>\n </header>\n <div class="docit-edit-body flex-grow-1">\n <div id="editor"></div>\n </div>\n {{/model}}\n {{^model}}\n <div class="docit-error-state">\n <h3>Page Not Found</h3>\n <p>The page you\'re trying to edit could not be found.</p>\n <button class="btn btn-primary" data-action="go-back">Go Back</button>\n </div>\n {{/model}}{{/loading}}\n </div>\n '}async onParams(t={},e={}){await super.onParams(t,e);let o=null;e.id?o=new DocitPage({id:e.id}):e.doc_page&&(o=new DocitPage({slug:e.doc_page})),this.model=o,this.model&&await this.model.fetch({graph:"detail"})}initEditor(){!this.editor&&this.element.querySelector("#editor")&&(this.editor=new toastui.Editor({el:this.element.querySelector("#editor"),height:"100%",initialEditType:"markdown",previewStyle:"tab",initialValue:this.model.get("content")||""}),this.editor.on("change",()=>{this.isDirty=!0}))}async onActionSavePage(){if(this.model&&this.editor){this.saving=!0,this.getApp().showLoading("saving...");try{const t=this.editor.getMarkdown();await this.model.save({content:t}),this.isDirty=!1,this.getApp().toast.success("Page saved successfully."),this.getApp().showPage("docs",this.query)}catch(t){console.error("Failed to save page:",t),this.getApp().toast.error("Failed to save page.")}finally{this.saving=!1,this.getApp().hideLoading()}}}async onActionCancelEdit(){this.getApp().showPage("docs",this.query)}onActionGoBack(){this.getApp().showPage("docs")}async onBeforeRender(){await super.onBeforeRender(),this.editor&&(this.editor.destroy(),this.editor=null)}async onAfterRender(){await super.onAfterRender(),this.model&&this.initEditor()}async onBeforeDestroy(){this.editor?.destroy(),await super.onBeforeDestroy()}}class DocItApp extends t.WebApp{constructor(t={}){super({name:t.title||"DocIt Portal",version:t.version||"1.0.0",debug:t.debug||!1,container:t.container||"#app",defaultRoute:"home",basePath:t.basePath||"",...t}),this.bookSlug=t.bookSlug||null,this.showBookNav=void 0!==t.showBookNav?t.showBookNav:!this.bookSlug,this.theme=t.theme||"light",this.editPermissions=t.permissions?.edit||["manage_docit"],this.sidebarConfig={showSearch:!0,defaultCollapsed:!1,...t.sidebar},this.books=new DocitBookList,this.books.params.sort="-order_priority",this.docPages=new DocitPageList,this.docPages.params.sort="-order_priority",this.toast=new s.ToastService,this.currentBook=null,this.sidebar=null,this.isDocItReady=!1,this.tokenManager=new a.TokenManager,this.activeUser=null}async start(){try{console.log("Starting DocIt Portal..."),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}),console.log("✅ DocIt Portal ready")}catch(t){throw console.error("Failed to start DocIt:",t),this.showError("Failed to initialize documentation portal"),t}}setupDocItLayout(){const t="string"==typeof this.container?document.querySelector(this.container):this.container;if(!t)throw new Error(`Container not found: ${this.container}`);t.classList.add("docit-app",`docit-theme-${this.theme}`),t.innerHTML='\n <div class="docit-app-layout">\n <div id="topnav-container"></div>\n <div class="docit-body-layout">\n <div class="docit-sidebar" id="docit-sidebar"></div>\n <div class="docit-main">\n <div class="docit-content" id="page-container"></div>\n </div>\n </div>\n </div>\n ',this.pageContainer="#page-container"}async setupTopNav(){this.topnav=new e.TopNav({containerId:"topnav-container",brand:this.name,theme:"navbar navbar-expand-lg bg-dark navbar-dark",showSidebarToggle:!1,displayMode:"brand",rightItems:[{id:"login",icon:"bi-box-arrow-in-right",href:"/examples/auth/",label:"Login"}],userMenu:{label:"User",icon:"bi-person-circle",items:[{label:"Profile",icon:"bi-person",action:"profile"},{divider:!0},{label:"Logout",icon:"bi-box-arrow-right",action:"logout"}]}}),await this.topnav.render()}onActionToggleSidebar(){document.querySelector(".docit-layout").classList.toggle("sidebar-collapsed")}async setupSidebar(){this.sidebar=new DocNavSidebar({containerId:"docit-sidebar",app:this,singleBookMode:!!this.bookSlug,showBookNav:this.showBookNav,books:this.books,docPages:this.docPages,activeUser:this.activeUser,...this.sidebarConfig}),await this.sidebar.render()}registerDocItPages(){this.registerPage("home",DocHomePage,{route:"/",permissions:null}),this.registerPage("docs",DocPage,{route:"/docs",permissions:null}),this.registerPage("edit",DocEditPage,{route:"/edit",permissions:this.editPermissions})}async loadInitialData(){try{if(this.bookSlug){const t=new DocitBook({slug:this.bookSlug});if(await t.fetch(),!t.id)throw new Error(`Book with slug '${this.bookSlug}' not found.`);this.books.add(t),await this.setActiveBook(t)}else await this.books.fetch({graph:"list"}),this.sidebar.render()}catch(t){console.error("Failed to load initial data:",t),this.showError("Failed to load documentation")}}async setActiveBook(t){this.currentBook&&t&&this.currentBook.id===t.id||(this.currentBook=t,this.docPages.reset(),t&&await this.docPages.fetch({book:t.get("id"),graph:"list"}),this.sidebar.setCurrentBook(t),this.sidebar.setDocPages(this.docPages),this.events.emit("docit:book-changed",{book:t}))}async saveDocPageContent(t,e){const o=this.docPages.get(t)||new DocitPage({id:t});o.set("content",e);const i=await o.save();if(!i.success||!i.data.status)throw new Error("Failed to save doc page");return o}canEdit(){const t=this.activeUser;return!!t&&this.editPermissions.some(e=>!!t.hasPermission&&t.hasPermission(e))}async checkAuthStatus(){try{const t=this.tokenManager.getTokenInstance();if(!t||!t.isValid())return void this.events.emit("auth:unauthorized",{app:this});if(t.isExpired())return void this.events.emit("auth:expired",{app:this});this.tokenManager.startAutoRefresh(this),this.rest.setAuthToken(t.token);const e=new s.User({id:t.getUserId()});await e.fetch(),this.setActiveUser(e)}catch(t){console.error("Failed to check auth status:",t),this.events.emit("auth:error",{error:t,app:this})}}setActiveUser(t){return this.activeUser=t,this.sidebar&&this.sidebar.setUser(t),this.topnav&&(this.topnav.setUser(t),this.tonnav.render()),this.events.emit("user:changed",{user:t,app:this}),this}clearActiveUser(){return this.activeUser=null,this.tokenManager.clearTokens(),this.rest.clearAuth(),this.sidebar&&this.sidebar.setUser(null),this.events.emit("user:cleared",{app:this}),this}async logout(){this.clearActiveUser(),this.books.reset(),this.docPages.reset(),this.currentBook=null,this.events.emit("auth:logout",{app:this}),window.location.reload()}async createNewBook(){const t=await this.showModelForm({title:"Create New Book",model:new DocitBook,fields:[{name:"title",label:"Title",required:!0},{name:"slug",label:"Slug",required:!0,helpText:"A URL-friendly identifier."}]});t&&t.success&&(this.books.add(t.data),this.sidebar.render(),this.showSuccess("Book created successfully."))}async createNewPage(t){if(!t)return;const e=await this.showForm({title:"Create New Page",fields:[{name:"title",label:"Title",required:!0},{name:"slug",label:"Slug",required:!0,helpText:"A URL-friendly identifier."}]});e&&e.slug&&(e.book=t.id);const o=new DocitPage,i=await o.save(e);i&&i.success&&(this.docPages.add(o),this.sidebar.render(),this.showPage("edit",{id:o.id,doc_book:t.get("slug"),doc_page:o.get("slug")},{}))}static create(t={}){return new DocItApp(t)}static createForBook(t,e={}){return new DocItApp({...e,bookSlug:t,showBookNav:!1})}}exports.BUILD_TIME=t.BUILD_TIME,exports.VERSION=t.VERSION,exports.VERSION_INFO=t.VERSION_INFO,exports.VERSION_MAJOR=t.VERSION_MAJOR,exports.VERSION_MINOR=t.VERSION_MINOR,exports.VERSION_REVISION=t.VERSION_REVISION,exports.WebApp=t.WebApp,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 { V as View, W as WebApp } from "./chunks/WebApp-Dr-gVl9H.js";
2
- import { B, b, a, c, e, f } from "./chunks/WebApp-Dr-gVl9H.js";
3
- import { T as TopNav } from "./chunks/TopNav-WEv4D1Jr.js";
4
- import { P as Page } from "./chunks/Page-BXT4XGsW.js";
5
- import { C as ContextMenu } from "./chunks/ContextMenu-BqYO9pWG.js";
6
- import { M as Model, C as Collection, T as ToastService, U as User } from "./chunks/User-BH8gosuT.js";
1
+ import { V as View, W as WebApp } from "./chunks/WebApp-_cIASgB0.js";
2
+ import { B, b, a, c, e, f } from "./chunks/WebApp-_cIASgB0.js";
3
+ import { T as TopNav } from "./chunks/TopNav-C86aDfet.js";
4
+ import { P as Page } from "./chunks/Page-BnWHjjZe.js";
5
+ import { C as ContextMenu } from "./chunks/ContextMenu-B5-M-xk4.js";
6
+ import { M as Model, C as Collection, T as ToastService, U as User } from "./chunks/User-DbeMvd6x.js";
7
7
  import { T as TokenManager } from "./chunks/TokenManager-5HHjYzTo.js";
8
8
  class DocNavSidebar extends View {
9
9
  constructor(options = {}) {