web-mojo 2.1.46

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 (91) hide show
  1. package/LICENSE +198 -0
  2. package/README.md +510 -0
  3. package/dist/admin.cjs.js +2 -0
  4. package/dist/admin.cjs.js.map +1 -0
  5. package/dist/admin.css +621 -0
  6. package/dist/admin.es.js +7973 -0
  7. package/dist/admin.es.js.map +1 -0
  8. package/dist/auth.cjs.js +2 -0
  9. package/dist/auth.cjs.js.map +1 -0
  10. package/dist/auth.css +804 -0
  11. package/dist/auth.es.js +2168 -0
  12. package/dist/auth.es.js.map +1 -0
  13. package/dist/charts.cjs.js +2 -0
  14. package/dist/charts.cjs.js.map +1 -0
  15. package/dist/charts.css +1002 -0
  16. package/dist/charts.es.js +16 -0
  17. package/dist/charts.es.js.map +1 -0
  18. package/dist/chunks/ContextMenu-BrHqj0fn.js +80 -0
  19. package/dist/chunks/ContextMenu-BrHqj0fn.js.map +1 -0
  20. package/dist/chunks/ContextMenu-gEcpSz56.js +2 -0
  21. package/dist/chunks/ContextMenu-gEcpSz56.js.map +1 -0
  22. package/dist/chunks/DataView-DPryYpEW.js +2 -0
  23. package/dist/chunks/DataView-DPryYpEW.js.map +1 -0
  24. package/dist/chunks/DataView-DjZQrpba.js +843 -0
  25. package/dist/chunks/DataView-DjZQrpba.js.map +1 -0
  26. package/dist/chunks/Dialog-BsRx4eg3.js +2 -0
  27. package/dist/chunks/Dialog-BsRx4eg3.js.map +1 -0
  28. package/dist/chunks/Dialog-DSlctbon.js +1377 -0
  29. package/dist/chunks/Dialog-DSlctbon.js.map +1 -0
  30. package/dist/chunks/FilePreviewView-BmFHzK5K.js +5868 -0
  31. package/dist/chunks/FilePreviewView-BmFHzK5K.js.map +1 -0
  32. package/dist/chunks/FilePreviewView-DcdRl_ta.js +2 -0
  33. package/dist/chunks/FilePreviewView-DcdRl_ta.js.map +1 -0
  34. package/dist/chunks/FormView-CmBuwKGD.js +2 -0
  35. package/dist/chunks/FormView-CmBuwKGD.js.map +1 -0
  36. package/dist/chunks/FormView-DqUBMPJ9.js +5054 -0
  37. package/dist/chunks/FormView-DqUBMPJ9.js.map +1 -0
  38. package/dist/chunks/MetricsChart-CM4CI6eA.js +2095 -0
  39. package/dist/chunks/MetricsChart-CM4CI6eA.js.map +1 -0
  40. package/dist/chunks/MetricsChart-CPidSMaN.js +2 -0
  41. package/dist/chunks/MetricsChart-CPidSMaN.js.map +1 -0
  42. package/dist/chunks/PDFViewer-BNQlnS83.js +2 -0
  43. package/dist/chunks/PDFViewer-BNQlnS83.js.map +1 -0
  44. package/dist/chunks/PDFViewer-Dyo-Oeyd.js +946 -0
  45. package/dist/chunks/PDFViewer-Dyo-Oeyd.js.map +1 -0
  46. package/dist/chunks/Page-B524zSQs.js +351 -0
  47. package/dist/chunks/Page-B524zSQs.js.map +1 -0
  48. package/dist/chunks/Page-BFgj0pAA.js +2 -0
  49. package/dist/chunks/Page-BFgj0pAA.js.map +1 -0
  50. package/dist/chunks/TokenManager-BXNva8Jk.js +287 -0
  51. package/dist/chunks/TokenManager-BXNva8Jk.js.map +1 -0
  52. package/dist/chunks/TokenManager-Bzn4guFm.js +2 -0
  53. package/dist/chunks/TokenManager-Bzn4guFm.js.map +1 -0
  54. package/dist/chunks/TopNav-D3I3_25f.js +371 -0
  55. package/dist/chunks/TopNav-D3I3_25f.js.map +1 -0
  56. package/dist/chunks/TopNav-MDjL4kV0.js +2 -0
  57. package/dist/chunks/TopNav-MDjL4kV0.js.map +1 -0
  58. package/dist/chunks/User-BalfYTEF.js +3 -0
  59. package/dist/chunks/User-BalfYTEF.js.map +1 -0
  60. package/dist/chunks/User-DwIT-CTQ.js +1937 -0
  61. package/dist/chunks/User-DwIT-CTQ.js.map +1 -0
  62. package/dist/chunks/WebApp-B6mgbNn2.js +4767 -0
  63. package/dist/chunks/WebApp-B6mgbNn2.js.map +1 -0
  64. package/dist/chunks/WebApp-DqDowtkl.js +2 -0
  65. package/dist/chunks/WebApp-DqDowtkl.js.map +1 -0
  66. package/dist/chunks/WebSocketClient-D6i85jl2.js +2 -0
  67. package/dist/chunks/WebSocketClient-D6i85jl2.js.map +1 -0
  68. package/dist/chunks/WebSocketClient-Dvl3AYx1.js +297 -0
  69. package/dist/chunks/WebSocketClient-Dvl3AYx1.js.map +1 -0
  70. package/dist/core.css +1181 -0
  71. package/dist/css/web-mojo.css +17 -0
  72. package/dist/css-manifest.json +6 -0
  73. package/dist/docit.cjs.js +2 -0
  74. package/dist/docit.cjs.js.map +1 -0
  75. package/dist/docit.es.js +959 -0
  76. package/dist/docit.es.js.map +1 -0
  77. package/dist/index.cjs.js +2 -0
  78. package/dist/index.cjs.js.map +1 -0
  79. package/dist/index.es.js +2681 -0
  80. package/dist/index.es.js.map +1 -0
  81. package/dist/lightbox.cjs.js +2 -0
  82. package/dist/lightbox.cjs.js.map +1 -0
  83. package/dist/lightbox.css +606 -0
  84. package/dist/lightbox.es.js +3737 -0
  85. package/dist/lightbox.es.js.map +1 -0
  86. package/dist/loader.es.js +115 -0
  87. package/dist/loader.umd.js +85 -0
  88. package/dist/portal.css +2446 -0
  89. package/dist/table.css +639 -0
  90. package/dist/toast.css +181 -0
  91. package/package.json +179 -0
@@ -0,0 +1,959 @@
1
+ import { V as View, W as WebApp } from "./chunks/WebApp-B6mgbNn2.js";
2
+ import { B, b, a, c, e, f } from "./chunks/WebApp-B6mgbNn2.js";
3
+ import { T as TopNav } from "./chunks/TopNav-D3I3_25f.js";
4
+ import { P as Page } from "./chunks/Page-B524zSQs.js";
5
+ import { C as ContextMenu } from "./chunks/ContextMenu-BrHqj0fn.js";
6
+ import { M as Model, C as Collection, T as ToastService, U as User } from "./chunks/User-DwIT-CTQ.js";
7
+ import { T as TokenManager } from "./chunks/TokenManager-BXNva8Jk.js";
8
+ class DocNavSidebar extends View {
9
+ constructor(options = {}) {
10
+ super({
11
+ className: "docit-sidebar-nav",
12
+ tagName: "nav",
13
+ ...options
14
+ });
15
+ this.singleBookMode = options.singleBookMode || false;
16
+ this.books = options.books;
17
+ this.docPages = options.docPages;
18
+ this.activeUser = options.activeUser;
19
+ this.currentBook = null;
20
+ this.currentDocPage = null;
21
+ }
22
+ async onInit() {
23
+ await super.onInit();
24
+ this.getApp().events.on("page:show", this._onPageShow.bind(this));
25
+ }
26
+ async _onPageShow({ query }) {
27
+ const bookSlug = query.doc_book;
28
+ const pageSlug = query.doc_page;
29
+ const app = this.getApp();
30
+ if (bookSlug) {
31
+ const book = this.books.findWhere({ slug: bookSlug });
32
+ if (book && (!this.currentBook || this.currentBook.id !== book.id)) {
33
+ await app.setActiveBook(book);
34
+ this.currentBook = app.currentBook;
35
+ }
36
+ } else {
37
+ if (this.currentBook) {
38
+ await app.setActiveBook(null);
39
+ this.currentBook = null;
40
+ }
41
+ }
42
+ this.currentDocPage = pageSlug ? this.docPages.findWhere({ slug: pageSlug }) : null;
43
+ this.render();
44
+ }
45
+ getTemplate() {
46
+ return `
47
+ <div class="docit-nav-body pt-3">
48
+ {{#currentBook}}
49
+ {{#docPages.models}}
50
+ <a href="#" class="docit-page-link {{#isActive}}active{{/isActive}}"
51
+ data-action="select-page" data-page-slug="{{slug}}">
52
+ <i class="{{#metadata.icon}}{{metadata.icon}}{{/metadata.icon}}{{^metadata.icon}}bi bi-file-earmark-text{{/metadata.icon}} me-2"></i>
53
+ <span>{{title|capitalize}}</span>
54
+ </a>
55
+ {{/docPages.models}}
56
+ {{^docPages.models}}
57
+ <div class="docit-nav-empty"><p>No pages in this book.</p></div>
58
+ {{/docPages.models}}
59
+ {{/currentBook}}
60
+ {{^currentBook}}
61
+ {{#books.models}}
62
+ <a href="#" class="docit-book-item" data-action="select-book" data-book-slug="{{slug}}">
63
+ <i class="bi bi-book me-2"></i>
64
+ <span class="book-title">{{title}}</span>
65
+ <span class="badge bg-secondary ms-auto">{{page_count}}</span>
66
+ </a>
67
+ {{/books.models}}
68
+ {{/currentBook}}
69
+ </div>
70
+ {{#currentBook}}
71
+ <div class="docit-nav-footer">
72
+ {{#canEdit}}
73
+ <button class="btn btn-link w-100 mb-2" data-action="create-page">
74
+ <i class="bi bi-plus-circle me-2"></i>
75
+ Create New Page
76
+ </button>
77
+ {{/canEdit}}
78
+ <button class="btn btn-link w-100" data-action="back-to-books">
79
+ <i class="bi bi-arrow-left me-2"></i>
80
+ Back to Books
81
+ </button>
82
+ </div>
83
+ {{/currentBook}}
84
+ {{^currentBook}}
85
+ {{#canEdit}}
86
+ <div class="docit-nav-footer">
87
+ <button class="btn btn-link w-100" data-action="create-book">
88
+ <i class="bi bi-plus-circle me-2"></i>
89
+ Create New Book
90
+ </button>
91
+ </div>
92
+ {{/canEdit}}
93
+ {{/currentBook}}
94
+ `;
95
+ }
96
+ async onBeforeRender() {
97
+ await super.onBeforeRender();
98
+ this.canEdit = this.getApp().canEdit();
99
+ if (this.docPages && this.currentDocPage) {
100
+ this.docPages.forEach((page) => {
101
+ page.isActive = page.id === this.currentDocPage.id;
102
+ });
103
+ }
104
+ }
105
+ async onActionSelectBook(event, element) {
106
+ event.preventDefault();
107
+ const bookSlug = element.dataset.bookSlug;
108
+ const book = this.books.findWhere({ slug: bookSlug });
109
+ if (book) {
110
+ await this.getApp().setActiveBook(book);
111
+ const firstPage = this.docPages.at(0);
112
+ const query = { doc_book: book.get("slug") };
113
+ if (firstPage) {
114
+ query.doc_page = firstPage.get("slug");
115
+ }
116
+ this.getApp().showPage("docs", query, {});
117
+ }
118
+ }
119
+ onActionSelectPage(event, element) {
120
+ event.preventDefault();
121
+ const pageSlug = element.dataset.pageSlug;
122
+ if (this.currentBook && pageSlug) {
123
+ this.getApp().showPage("docs", {
124
+ doc_book: this.currentBook.get("slug"),
125
+ doc_page: pageSlug
126
+ }, {});
127
+ }
128
+ }
129
+ async onActionBackToBooks(event, element) {
130
+ event.preventDefault();
131
+ await this.getApp().setActiveBook(null);
132
+ this.getApp().showPage("home");
133
+ }
134
+ async onActionCreateBook() {
135
+ await this.getApp().createNewBook();
136
+ }
137
+ async onActionCreatePage() {
138
+ if (this.currentBook) {
139
+ await this.getApp().createNewPage(this.currentBook);
140
+ }
141
+ }
142
+ setBooks(books) {
143
+ this.books = books;
144
+ this.render();
145
+ }
146
+ setDocPages(docPages) {
147
+ this.docPages = docPages;
148
+ this.render();
149
+ }
150
+ setCurrentBook(book) {
151
+ this.currentBook = book;
152
+ this.render();
153
+ if (this.getApp().topnav) {
154
+ if (book) {
155
+ this.getApp().topnav.setBrand(book.get("title"));
156
+ } else {
157
+ this.getApp().topnav.setBrand("Documentation");
158
+ }
159
+ }
160
+ }
161
+ setUser(user) {
162
+ this.activeUser = user;
163
+ this.render();
164
+ }
165
+ }
166
+ class DocHomePage extends Page {
167
+ constructor(options = {}) {
168
+ super({
169
+ pageName: "home",
170
+ title: "Documentation",
171
+ className: "docit-home-page",
172
+ ...options
173
+ });
174
+ }
175
+ async getTemplate() {
176
+ return `
177
+ <div class="docit-empty-state vh-100 d-flex flex-column align-items-center justify-content-center">
178
+ <i class="bi bi-collection" style="font-size: 4rem;"></i>
179
+ <h3 class="mt-4">Welcome to the Documentation Portal</h3>
180
+ <p class="text-muted">Please select a book from the sidebar to get started.</p>
181
+ </div>
182
+ `;
183
+ }
184
+ }
185
+ class DocitBook extends Model {
186
+ static endpoint = "/api/docit/book";
187
+ buildUrl(id = null) {
188
+ if (this.get("slug") && !this.id) {
189
+ return `/api/docit/book/slug/${this.get("slug")}`;
190
+ } else if (this.id) {
191
+ return `/api/docit/book/${this.id}`;
192
+ }
193
+ return this.endpoint;
194
+ }
195
+ }
196
+ class DocitBookList extends Collection {
197
+ constructor(options = {}) {
198
+ super({
199
+ ModelClass: DocitBook,
200
+ endpoint: "/api/docit/book",
201
+ ...options
202
+ });
203
+ }
204
+ /**
205
+ * Custom parsing to handle the specific API response structure for book lists.
206
+ */
207
+ parse(response) {
208
+ if (response.data && response.data.status) {
209
+ this.meta = {
210
+ ...this.meta,
211
+ total: response.data.count || 0,
212
+ graph: response.data.graph
213
+ };
214
+ return response.data.data || [];
215
+ }
216
+ return super.parse(response);
217
+ }
218
+ }
219
+ class DocitPage extends Model {
220
+ static endpoint = "/api/docit/page";
221
+ buildUrl(id = null) {
222
+ if (this.get("slug") && !this.id) {
223
+ return `/api/docit/page/slug/${this.get("slug")}`;
224
+ } else if (this.id) {
225
+ return `/api/docit/page/${this.id}`;
226
+ }
227
+ return this.endpoint;
228
+ }
229
+ }
230
+ class DocitPageList extends Collection {
231
+ constructor(options = {}) {
232
+ super({
233
+ ModelClass: DocitPage,
234
+ endpoint: "/api/docit/page",
235
+ ...options
236
+ });
237
+ }
238
+ /**
239
+ * Custom parsing to handle the specific API response structure for page lists.
240
+ */
241
+ parse(response) {
242
+ if (response.data && response.data.status) {
243
+ this.meta = {
244
+ ...this.meta,
245
+ total: response.data.count || 0,
246
+ graph: response.data.graph,
247
+ book: response.data.book
248
+ };
249
+ return response.data.data || [];
250
+ }
251
+ return super.parse(response);
252
+ }
253
+ }
254
+ class DocPage extends Page {
255
+ constructor(options = {}) {
256
+ super({
257
+ pageName: "docs",
258
+ title: "Documentation",
259
+ className: "docit-page",
260
+ ...options
261
+ });
262
+ this.bookModel = null;
263
+ this.model = null;
264
+ }
265
+ async onInit() {
266
+ await super.onInit();
267
+ this.pageContextMenu = new ContextMenu({
268
+ config: this.getPageContextMenuConfig(),
269
+ containerId: "page-context-menu"
270
+ });
271
+ this.addChild(this.pageContextMenu);
272
+ }
273
+ getPageContextMenuConfig() {
274
+ return {
275
+ icon: "bi-three-dots",
276
+ buttonClass: "btn btn-outline-secondary btn-sm",
277
+ items: [
278
+ { label: "View History", action: "view-history", icon: "bi-clock-history" },
279
+ { type: "divider" },
280
+ { label: "Edit Page Content", action: "edit-page", icon: "bi-pencil" },
281
+ { label: "Edit Page Info", action: "edit-page-info", icon: "bi-list-ol" },
282
+ { type: "divider" },
283
+ { label: "Edit Book Info", action: "edit-book", icon: "bi-book" },
284
+ { type: "divider" },
285
+ { label: "Delete Page", action: "delete-page", icon: "bi-trash", danger: true }
286
+ ]
287
+ };
288
+ }
289
+ async getTemplate() {
290
+ return `
291
+ <div class="docit-page-container position-relative">
292
+ {{#loading}}
293
+ <div class="docit-loading">
294
+ <div class="spinner-border" role="status">
295
+ <span class="visually-hidden">Loading...</span>
296
+ </div>
297
+ </div>
298
+ {{/loading}}
299
+
300
+ {{^loading}}
301
+ {{#model|bool}}
302
+ <div class="docit-page-toolbar">
303
+ <div class="row">
304
+ <div class="col d-flex justify-content-end">
305
+ <div data-container="page-context-menu"></div>
306
+ </div>
307
+ </div>
308
+ </div>
309
+ <article class="docit-page-content">
310
+ {{{model.html}}}
311
+ </article>
312
+
313
+ <nav class="docit-page-nav">
314
+ {{#prevPage}}
315
+ <a href="#" class="docit-nav-prev" data-action="navigate-to-page" data-page-slug="{{slug}}">
316
+ <i class="bi bi-arrow-left"></i>
317
+ <div>
318
+ <small>Previous</small>
319
+ <span>{{title}}</span>
320
+ </div>
321
+ </a>
322
+ {{/prevPage}}
323
+ {{^prevPage}}
324
+ <div></div>
325
+ {{/prevPage}}
326
+
327
+ {{#nextPage}}
328
+ <a href="#" class="docit-nav-next" data-action="navigate-to-page" data-page-slug="{{slug}}">
329
+ <div>
330
+ <small>Next</small>
331
+ <span>{{title}}</span>
332
+ </div>
333
+ <i class="bi bi-arrow-right"></i>
334
+ </a>
335
+ {{/nextPage}}
336
+
337
+ <div class="my-3">
338
+ <span class="text-muted">Last updated: {{model.modified|datetime}}</span>
339
+ </div>
340
+ </nav>
341
+ {{/model|bool}}
342
+
343
+ {{^model|bool}}
344
+ <div class="docit-empty-state">
345
+ <i class="bi bi-file-earmark-text"></i>
346
+ <h3>Select a page</h3>
347
+ <p>Choose a page from the sidebar to view its content.</p>
348
+ </div>
349
+ {{/model|bool}}
350
+ {{/loading}}
351
+ </div>
352
+ `;
353
+ }
354
+ async onParams(params = {}, query = {}) {
355
+ await super.onParams(params, query);
356
+ this.loading = true;
357
+ await this.render();
358
+ const app = this.getApp();
359
+ const bookSlug = app.bookSlug || query.doc_book;
360
+ const pageSlug = query.doc_page;
361
+ if (!bookSlug) {
362
+ setTimeout(() => {
363
+ app.showPage("home");
364
+ }, 100);
365
+ return;
366
+ }
367
+ try {
368
+ if (bookSlug) {
369
+ if (!app.currentBook || app.currentBook.get("slug") !== bookSlug) {
370
+ const book = new DocitBook({ slug: bookSlug });
371
+ await book.fetch();
372
+ await app.setActiveBook(book);
373
+ }
374
+ this.bookModel = app.currentBook;
375
+ if (pageSlug) {
376
+ this.model = new DocitPage({ slug: pageSlug });
377
+ await this.model.fetch({ graph: "html" });
378
+ } else {
379
+ const firstPage = app.docPages.at(0);
380
+ if (firstPage) {
381
+ this.model = new DocitPage({ id: firstPage.id });
382
+ await this.model.fetch({ graph: "html" });
383
+ } else {
384
+ this.model = null;
385
+ }
386
+ }
387
+ } else {
388
+ this.bookModel = null;
389
+ this.model = null;
390
+ }
391
+ this.canEdit = app.canEdit();
392
+ this.setupNavigation();
393
+ } catch (error) {
394
+ console.error("Failed to load page:", error);
395
+ this.showError("Failed to load documentation page");
396
+ this.model = null;
397
+ } finally {
398
+ this.loading = false;
399
+ await this.render();
400
+ app.events.emit("docit:page-rendered", {
401
+ book: this.bookModel,
402
+ page: this.model
403
+ });
404
+ }
405
+ }
406
+ setupNavigation() {
407
+ if (!this.model) {
408
+ this.prevPage = null;
409
+ this.nextPage = null;
410
+ return;
411
+ }
412
+ const app = this.getApp();
413
+ const pages = app.docPages.models;
414
+ const currentIndex = pages.findIndex((p) => p.id === this.model.id);
415
+ this.prevPage = currentIndex > 0 ? { slug: pages[currentIndex - 1].get("slug"), title: pages[currentIndex - 1].get("title") } : null;
416
+ this.nextPage = currentIndex < pages.length - 1 ? { slug: pages[currentIndex + 1].get("slug"), title: pages[currentIndex + 1].get("title") } : null;
417
+ }
418
+ async onActionNavigateToPage(event, element) {
419
+ event.preventDefault();
420
+ const pageSlug = element.dataset.pageSlug;
421
+ if (pageSlug) {
422
+ this.getApp().showPage("docs", {}, {
423
+ doc_book: this.bookModel.get("slug"),
424
+ doc_page: pageSlug
425
+ });
426
+ }
427
+ }
428
+ async onActionEditPage(event, element) {
429
+ event.preventDefault();
430
+ if (this.model) {
431
+ this.getApp().showPage("edit", {
432
+ id: this.model.id,
433
+ doc_book: this.bookModel.get("slug"),
434
+ doc_page: this.model.get("slug")
435
+ }, {});
436
+ }
437
+ }
438
+ async onActionViewHistory(event, element) {
439
+ this.showInfo("Revision history coming soon.");
440
+ }
441
+ async onActionEditPageInfo(event, element) {
442
+ if (this.model) {
443
+ this.getApp().showModelForm({
444
+ model: this.model,
445
+ fields: [
446
+ { label: "Title", name: "title", type: "text" },
447
+ { label: "Order Priority", name: "order_priority" },
448
+ { label: "Slug", name: "slug" }
449
+ ]
450
+ });
451
+ }
452
+ }
453
+ async onActionEditBook(event, element) {
454
+ if (this.model) {
455
+ const book = this.getApp().sidebar.currentBook;
456
+ if (!book) return;
457
+ this.getApp().showModelForm({
458
+ model: book,
459
+ fields: [
460
+ { label: "Title", name: "title", type: "text" },
461
+ { label: "Order Priority", name: "order_priority" },
462
+ { label: "Slug", name: "slug" },
463
+ { label: "Is Active", name: "is_active", type: "switch" }
464
+ ]
465
+ });
466
+ }
467
+ }
468
+ async onActionDeletePage(event, element) {
469
+ if (!this.model) return;
470
+ const confirmed = await this.getApp().showConfirm({
471
+ title: "Delete Page",
472
+ body: `Are you sure you want to delete "${this.model.get("title")}"?`,
473
+ confirmText: "Delete",
474
+ confirmClass: "btn-danger"
475
+ });
476
+ if (confirmed) {
477
+ await this.model.destroy();
478
+ this.showSuccess("Page deleted.");
479
+ await this.getApp().setActiveBook(this.bookModel);
480
+ const firstPage = this.getApp().docPages.at(0);
481
+ if (firstPage) {
482
+ this.getApp().showPage("docs", {}, { doc_book: this.bookModel.get("slug"), doc_page: firstPage.get("slug") });
483
+ } else {
484
+ this.getApp().showPage("docs", {}, { doc_book: this.bookModel.get("slug") });
485
+ }
486
+ }
487
+ }
488
+ async onAfterRender() {
489
+ await super.onAfterRender();
490
+ if (typeof Prism !== "undefined" && this.model) {
491
+ Prism.highlightAll();
492
+ }
493
+ }
494
+ }
495
+ class DocEditPage extends Page {
496
+ constructor(options = {}) {
497
+ super({
498
+ pageName: "edit",
499
+ title: "Edit Page",
500
+ className: "docit-edit-page",
501
+ ...options
502
+ });
503
+ this.model = null;
504
+ this.editor = null;
505
+ this.isDirty = false;
506
+ }
507
+ async getTemplate() {
508
+ return `
509
+ <div class="docit-edit-container vh-100">
510
+ {{#loading}}
511
+ <div class="docit-loading"><div class="spinner-border"></div></div>
512
+ {{/loading}}
513
+
514
+ {{^loading}}{{#model}}
515
+ <header class="docit-edit-header">
516
+ <div class="docit-edit-title-row">
517
+ <h2>Editing: {{model.title}}</h2>
518
+ <div class="docit-edit-actions">
519
+ <button class="btn btn-outline-secondary" data-action="cancel-edit">Cancel</button>
520
+ <button class="btn btn-success" data-action="save-page">
521
+ <i class="bi bi-check-lg"></i> Save Changes
522
+ </button>
523
+ </div>
524
+ </div>
525
+ </header>
526
+ <div class="docit-edit-body flex-grow-1">
527
+ <div id="editor"></div>
528
+ </div>
529
+ {{/model}}
530
+ {{^model}}
531
+ <div class="docit-error-state">
532
+ <h3>Page Not Found</h3>
533
+ <p>The page you're trying to edit could not be found.</p>
534
+ <button class="btn btn-primary" data-action="go-back">Go Back</button>
535
+ </div>
536
+ {{/model}}{{/loading}}
537
+ </div>
538
+ `;
539
+ }
540
+ async onParams(params = {}, query = {}) {
541
+ await super.onParams(params, query);
542
+ let model = null;
543
+ if (query.id) {
544
+ model = new DocitPage({ id: query.id });
545
+ } else if (query.doc_page) {
546
+ model = new DocitPage({ slug: query.doc_page });
547
+ }
548
+ this.model = model;
549
+ if (this.model) {
550
+ await this.model.fetch({ graph: "detail" });
551
+ }
552
+ }
553
+ initEditor() {
554
+ if (this.editor || !this.element.querySelector("#editor")) return;
555
+ this.editor = new toastui.Editor({
556
+ el: this.element.querySelector("#editor"),
557
+ height: "100%",
558
+ initialEditType: "markdown",
559
+ previewStyle: "tab",
560
+ initialValue: this.model.get("content") || ""
561
+ });
562
+ this.editor.on("change", () => {
563
+ this.isDirty = true;
564
+ });
565
+ }
566
+ async onActionSavePage() {
567
+ if (!this.model || !this.editor) return;
568
+ this.saving = true;
569
+ this.getApp().showLoading("saving...");
570
+ try {
571
+ const content = this.editor.getMarkdown();
572
+ await this.model.save({ content });
573
+ this.isDirty = false;
574
+ this.getApp().toast.success("Page saved successfully.");
575
+ this.getApp().showPage("docs", this.query);
576
+ } catch (error) {
577
+ console.error("Failed to save page:", error);
578
+ this.getApp().toast.error("Failed to save page.");
579
+ } finally {
580
+ this.saving = false;
581
+ this.getApp().hideLoading();
582
+ }
583
+ }
584
+ async onActionCancelEdit() {
585
+ this.getApp().showPage("docs", this.query);
586
+ }
587
+ onActionGoBack() {
588
+ this.getApp().showPage("docs");
589
+ }
590
+ async onBeforeRender() {
591
+ await super.onBeforeRender();
592
+ if (this.editor) {
593
+ this.editor.destroy();
594
+ this.editor = null;
595
+ }
596
+ }
597
+ async onAfterRender() {
598
+ await super.onAfterRender();
599
+ if (this.model) {
600
+ this.initEditor();
601
+ }
602
+ }
603
+ async onBeforeDestroy() {
604
+ this.editor?.destroy();
605
+ await super.onBeforeDestroy();
606
+ }
607
+ }
608
+ class DocItApp extends WebApp {
609
+ constructor(config = {}) {
610
+ const defaults = {
611
+ name: config.title || "DocIt Portal",
612
+ version: config.version || "1.0.0",
613
+ debug: config.debug || false,
614
+ container: config.container || "#app",
615
+ defaultRoute: "home",
616
+ basePath: config.basePath || "",
617
+ ...config
618
+ };
619
+ super(defaults);
620
+ this.bookSlug = config.bookSlug || null;
621
+ this.showBookNav = config.showBookNav !== void 0 ? config.showBookNav : !this.bookSlug;
622
+ this.theme = config.theme || "light";
623
+ this.editPermissions = config.permissions?.edit || ["manage_docit"];
624
+ this.sidebarConfig = {
625
+ showSearch: true,
626
+ defaultCollapsed: false,
627
+ ...config.sidebar
628
+ };
629
+ this.books = new DocitBookList();
630
+ this.books.params.sort = "-order_priority";
631
+ this.docPages = new DocitPageList();
632
+ this.docPages.params.sort = "-order_priority";
633
+ this.toast = new ToastService();
634
+ this.currentBook = null;
635
+ this.sidebar = null;
636
+ this.isDocItReady = false;
637
+ this.tokenManager = new TokenManager();
638
+ this.activeUser = null;
639
+ }
640
+ /**
641
+ * Start the DocIt application
642
+ */
643
+ async start() {
644
+ try {
645
+ console.log("Starting DocIt Portal...");
646
+ this.setupDocItLayout();
647
+ await this.setupTopNav();
648
+ await this.setupSidebar();
649
+ await this.checkAuthStatus();
650
+ this.registerDocItPages();
651
+ await super.start();
652
+ await this.loadInitialData();
653
+ this.isDocItReady = true;
654
+ this.events.emit("docit:ready", { app: this });
655
+ console.log("✅ DocIt Portal ready");
656
+ } catch (error) {
657
+ console.error("Failed to start DocIt:", error);
658
+ this.showError("Failed to initialize documentation portal");
659
+ throw error;
660
+ }
661
+ }
662
+ /**
663
+ * Setup DocIt specific layout
664
+ */
665
+ setupDocItLayout() {
666
+ const container = typeof this.container === "string" ? document.querySelector(this.container) : this.container;
667
+ if (!container) {
668
+ throw new Error(`Container not found: ${this.container}`);
669
+ }
670
+ container.classList.add("docit-app", `docit-theme-${this.theme}`);
671
+ container.innerHTML = `
672
+ <div class="docit-app-layout">
673
+ <div id="topnav-container"></div>
674
+ <div class="docit-body-layout">
675
+ <div class="docit-sidebar" id="docit-sidebar"></div>
676
+ <div class="docit-main">
677
+ <div class="docit-content" id="page-container"></div>
678
+ </div>
679
+ </div>
680
+ </div>
681
+ `;
682
+ this.pageContainer = "#page-container";
683
+ }
684
+ async setupTopNav() {
685
+ this.topnav = new TopNav({
686
+ containerId: "topnav-container",
687
+ brand: this.name,
688
+ theme: "navbar navbar-expand-lg bg-dark navbar-dark",
689
+ showSidebarToggle: false,
690
+ displayMode: "brand",
691
+ rightItems: [
692
+ {
693
+ id: "login",
694
+ icon: "bi-box-arrow-in-right",
695
+ href: "/examples/auth/",
696
+ label: "Login"
697
+ }
698
+ ],
699
+ userMenu: {
700
+ label: "User",
701
+ icon: "bi-person-circle",
702
+ items: [
703
+ {
704
+ label: "Profile",
705
+ icon: "bi-person",
706
+ action: "profile"
707
+ },
708
+ {
709
+ divider: true
710
+ },
711
+ {
712
+ label: "Logout",
713
+ icon: "bi-box-arrow-right",
714
+ action: "logout"
715
+ }
716
+ ]
717
+ }
718
+ });
719
+ await this.topnav.render();
720
+ }
721
+ onActionToggleSidebar() {
722
+ const layout = document.querySelector(".docit-layout");
723
+ layout.classList.toggle("sidebar-collapsed");
724
+ }
725
+ /**
726
+ * Setup sidebar navigation
727
+ */
728
+ async setupSidebar() {
729
+ this.sidebar = new DocNavSidebar({
730
+ containerId: "docit-sidebar",
731
+ app: this,
732
+ singleBookMode: !!this.bookSlug,
733
+ showBookNav: this.showBookNav,
734
+ books: this.books,
735
+ docPages: this.docPages,
736
+ activeUser: this.activeUser,
737
+ ...this.sidebarConfig
738
+ });
739
+ await this.sidebar.render();
740
+ }
741
+ /**
742
+ * Register DocIt pages
743
+ */
744
+ registerDocItPages() {
745
+ this.registerPage("home", DocHomePage, {
746
+ route: "/",
747
+ permissions: null
748
+ });
749
+ this.registerPage("docs", DocPage, {
750
+ route: "/docs",
751
+ permissions: null
752
+ // Public access
753
+ });
754
+ this.registerPage("edit", DocEditPage, {
755
+ route: "/edit",
756
+ permissions: this.editPermissions
757
+ });
758
+ }
759
+ /**
760
+ * Load initial data based on mode
761
+ */
762
+ async loadInitialData() {
763
+ try {
764
+ if (this.bookSlug) {
765
+ const book = new DocitBook({ slug: this.bookSlug });
766
+ await book.fetch();
767
+ if (book.id) {
768
+ this.books.add(book);
769
+ await this.setActiveBook(book);
770
+ } else {
771
+ throw new Error(`Book with slug '${this.bookSlug}' not found.`);
772
+ }
773
+ } else {
774
+ await this.books.fetch({ graph: "list" });
775
+ this.sidebar.render();
776
+ }
777
+ } catch (error) {
778
+ console.error("Failed to load initial data:", error);
779
+ this.showError("Failed to load documentation");
780
+ }
781
+ }
782
+ /**
783
+ * Set the active book and load its pages
784
+ * @param {DocitBook} book - The book to set as active
785
+ */
786
+ async setActiveBook(book) {
787
+ if (this.currentBook && book && this.currentBook.id === book.id) {
788
+ return;
789
+ }
790
+ this.currentBook = book;
791
+ this.docPages.reset();
792
+ if (book) {
793
+ await this.docPages.fetch({
794
+ book: book.get("id"),
795
+ graph: "list"
796
+ });
797
+ }
798
+ this.sidebar.setCurrentBook(book);
799
+ this.sidebar.setDocPages(this.docPages);
800
+ this.events.emit("docit:book-changed", { book });
801
+ }
802
+ /**
803
+ * Save doc page content using Model
804
+ */
805
+ async saveDocPageContent(docPageId, content) {
806
+ const docPage = this.docPages.get(docPageId) || new DocitPage({ id: docPageId });
807
+ docPage.set("content", content);
808
+ const response = await docPage.save();
809
+ if (!response.success || !response.data.status) {
810
+ throw new Error("Failed to save doc page");
811
+ }
812
+ return docPage;
813
+ }
814
+ /**
815
+ * Check if user can edit
816
+ */
817
+ canEdit() {
818
+ const user = this.activeUser;
819
+ if (!user) return false;
820
+ return this.editPermissions.some((perm) => user.hasPermission ? user.hasPermission(perm) : false);
821
+ }
822
+ /**
823
+ * Check authentication status and load user
824
+ */
825
+ async checkAuthStatus() {
826
+ try {
827
+ const token = this.tokenManager.getTokenInstance();
828
+ if (!token || !token.isValid()) {
829
+ this.events.emit("auth:unauthorized", { app: this });
830
+ return;
831
+ }
832
+ if (token.isExpired()) {
833
+ this.events.emit("auth:expired", { app: this });
834
+ return;
835
+ }
836
+ this.tokenManager.startAutoRefresh(this);
837
+ this.rest.setAuthToken(token.token);
838
+ const user = new User({ id: token.getUserId() });
839
+ await user.fetch();
840
+ this.setActiveUser(user);
841
+ } catch (error) {
842
+ console.error("Failed to check auth status:", error);
843
+ this.events.emit("auth:error", { error, app: this });
844
+ }
845
+ }
846
+ /**
847
+ * Set the active authenticated user
848
+ */
849
+ setActiveUser(user) {
850
+ this.activeUser = user;
851
+ if (this.sidebar) {
852
+ this.sidebar.setUser(user);
853
+ }
854
+ if (this.topnav) {
855
+ this.topnav.setUser(user);
856
+ this.tonnav.render();
857
+ }
858
+ this.events.emit("user:changed", { user, app: this });
859
+ return this;
860
+ }
861
+ /**
862
+ * Clear the active user (logout)
863
+ */
864
+ clearActiveUser() {
865
+ this.activeUser = null;
866
+ this.tokenManager.clearTokens();
867
+ this.rest.clearAuth();
868
+ if (this.sidebar) {
869
+ this.sidebar.setUser(null);
870
+ }
871
+ this.events.emit("user:cleared", { app: this });
872
+ return this;
873
+ }
874
+ /**
875
+ * Handle logout
876
+ */
877
+ async logout() {
878
+ this.clearActiveUser();
879
+ this.books.reset();
880
+ this.docPages.reset();
881
+ this.currentBook = null;
882
+ this.events.emit("auth:logout", { app: this });
883
+ window.location.reload();
884
+ }
885
+ async createNewBook() {
886
+ const result = await this.showModelForm({
887
+ title: "Create New Book",
888
+ model: new DocitBook(),
889
+ fields: [
890
+ { name: "title", label: "Title", required: true },
891
+ { name: "slug", label: "Slug", required: true, helpText: "A URL-friendly identifier." }
892
+ ]
893
+ });
894
+ if (result && result.success) {
895
+ this.books.add(result.data);
896
+ this.sidebar.render();
897
+ this.showSuccess("Book created successfully.");
898
+ }
899
+ }
900
+ async createNewPage(book) {
901
+ if (!book) return;
902
+ const data = await this.showForm({
903
+ title: "Create New Page",
904
+ fields: [
905
+ { name: "title", label: "Title", required: true },
906
+ { name: "slug", label: "Slug", required: true, helpText: "A URL-friendly identifier." }
907
+ ]
908
+ });
909
+ if (data && data.slug) {
910
+ data.book = book.id;
911
+ }
912
+ const newPage = new DocitPage();
913
+ const result = await newPage.save(data);
914
+ if (result && result.success) {
915
+ this.docPages.add(newPage);
916
+ this.sidebar.render();
917
+ this.showPage("edit", {
918
+ id: newPage.id,
919
+ doc_book: book.get("slug"),
920
+ doc_page: newPage.get("slug")
921
+ }, {});
922
+ }
923
+ }
924
+ /**
925
+ * Static factory method for quick setup
926
+ */
927
+ static create(config = {}) {
928
+ return new DocItApp(config);
929
+ }
930
+ /**
931
+ * Quick setup for single-book mode
932
+ */
933
+ static createForBook(bookSlug, config = {}) {
934
+ return new DocItApp({
935
+ ...config,
936
+ bookSlug,
937
+ showBookNav: false
938
+ });
939
+ }
940
+ }
941
+ export {
942
+ B as BUILD_TIME,
943
+ DocEditPage,
944
+ DocHomePage,
945
+ DocItApp,
946
+ DocNavSidebar,
947
+ DocPage,
948
+ DocitBook,
949
+ DocitBookList,
950
+ DocitPage,
951
+ DocitPageList,
952
+ b as VERSION,
953
+ a as VERSION_INFO,
954
+ c as VERSION_MAJOR,
955
+ e as VERSION_MINOR,
956
+ f as VERSION_REVISION,
957
+ WebApp
958
+ };
959
+ //# sourceMappingURL=docit.es.js.map