webspresso 0.0.90 → 0.0.91

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 (94) hide show
  1. package/README.md +358 -8
  2. package/bin/commands/service.js +193 -0
  3. package/bin/webspresso.js +2 -0
  4. package/core/auth/middleware.js +9 -0
  5. package/core/compression/compressible.js +51 -0
  6. package/core/compression/compression-stream.js +59 -0
  7. package/core/compression/index.js +25 -0
  8. package/core/compression/middleware.js +301 -0
  9. package/core/compression/negotiate.js +153 -0
  10. package/core/errors/base.js +31 -0
  11. package/core/errors/domain.js +193 -0
  12. package/core/errors/http.js +133 -0
  13. package/core/errors/index.js +77 -0
  14. package/core/errors/middleware.js +162 -0
  15. package/core/errors/normalize.js +171 -0
  16. package/core/orm/complexity.js +120 -0
  17. package/core/orm/events.js +64 -14
  18. package/core/orm/index.js +45 -10
  19. package/core/orm/model.js +2 -0
  20. package/core/orm/query-builder.js +30 -4
  21. package/core/orm/repository.js +57 -25
  22. package/core/orm/transaction.js +39 -5
  23. package/core/orm/utils.js +6 -3
  24. package/core/queue/adapters/database.js +284 -0
  25. package/core/queue/adapters/memory.js +155 -0
  26. package/core/queue/adapters/redis.js +336 -0
  27. package/core/queue/index.js +21 -0
  28. package/core/queue/job.js +103 -0
  29. package/core/queue/manager.js +387 -0
  30. package/core/realtime/adapters/redis.js +431 -0
  31. package/core/realtime/adapters/socket-io.js +203 -0
  32. package/core/realtime/adapters/sse.js +210 -0
  33. package/core/realtime/adapters/websocket.js +337 -0
  34. package/core/realtime/auth-manager.js +140 -0
  35. package/core/realtime/core.js +359 -0
  36. package/core/realtime/emitter.js +98 -0
  37. package/core/realtime/errors.js +71 -0
  38. package/core/realtime/index.js +79 -0
  39. package/core/realtime/reconnect-manager.js +118 -0
  40. package/core/realtime/registry.js +243 -0
  41. package/core/shutdown/http-adapter.js +134 -0
  42. package/core/shutdown/index.js +12 -0
  43. package/core/shutdown/shutdown-manager.js +312 -0
  44. package/core/ssr/index.js +13 -0
  45. package/core/ssr/stream.js +174 -0
  46. package/index.d.ts +829 -52
  47. package/index.js +103 -0
  48. package/package.json +22 -2
  49. package/plugins/admin-panel/api.js +37 -4
  50. package/plugins/admin-panel/app.js +57 -17
  51. package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +4 -1
  52. package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
  53. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +37 -23
  54. package/plugins/admin-panel/client/parts/08-record-list.js +17 -24
  55. package/plugins/admin-panel/client/parts/09-record-form.js +168 -19
  56. package/plugins/admin-panel/core/admin-module.js +2 -2
  57. package/plugins/admin-panel/core/api-extensions.js +149 -0
  58. package/plugins/admin-panel/field-renderers/rich-text.js +3 -2
  59. package/plugins/admin-panel/index.js +90 -17
  60. package/plugins/admin-panel/modules/custom-pages.js +144 -45
  61. package/plugins/admin-panel/modules/menu.js +85 -0
  62. package/plugins/admin-panel/modules/user-management.js +5 -2
  63. package/plugins/admin-panel/vendor/mithril.min.js +1 -0
  64. package/plugins/admin-panel/vendor/quill.min.js +7 -0
  65. package/plugins/admin-panel/vendor/quill.snow.min.css +945 -0
  66. package/plugins/admin-panel/vendor/tailwind.min.js +65 -0
  67. package/plugins/basic-auth/index.js +293 -0
  68. package/plugins/data-exchange/export-xlsx.js +5 -1
  69. package/plugins/data-exchange/import.js +16 -2
  70. package/plugins/data-exchange/index.js +14 -0
  71. package/plugins/email/index.js +14 -1
  72. package/plugins/index.js +29 -0
  73. package/plugins/queue/index.js +108 -0
  74. package/plugins/realtime/index.js +150 -0
  75. package/plugins/recaptcha/index.js +11 -0
  76. package/plugins/redirect/index.js +9 -1
  77. package/plugins/rest-resources/index.js +280 -11
  78. package/plugins/upload/index.js +16 -0
  79. package/src/app-context.js +46 -6
  80. package/src/file-router.js +196 -21
  81. package/src/helpers.js +20 -2
  82. package/src/plugin-manager.js +124 -4
  83. package/src/server.js +304 -44
  84. package/src/services/builtins/auth.js +273 -0
  85. package/src/services/builtins/exchange.js +229 -0
  86. package/src/services/builtins/mail.js +134 -0
  87. package/src/services/builtins/media.js +196 -0
  88. package/src/services/builtins/system.js +221 -0
  89. package/src/services/discovery.js +140 -0
  90. package/src/services/executor.js +292 -0
  91. package/src/services/index.js +70 -0
  92. package/src/services/memoize.js +256 -0
  93. package/src/services/registry.js +288 -0
  94. package/src/services/validator.js +281 -0
@@ -4,6 +4,8 @@
4
4
  * @module plugins/admin-panel
5
5
  */
6
6
 
7
+ const path = require('path');
8
+ const fs = require('fs');
7
9
  const { createAdminUserModel } = require('./admin-user-model');
8
10
  const { generateAdminUsersMigration } = require('./migration-template');
9
11
  const { createApiHandlers } = require('./api');
@@ -75,22 +77,26 @@ function adminPanelPlugin(options = {}) {
75
77
  version: '2.0.0',
76
78
  description: 'Modular admin panel for Webspresso with extensions support',
77
79
 
78
- // CSP requirements for admin panel scripts, styles, and custom page iframe sources
79
- // Note: cdn.quilljs.com 301-redirects to cdn.jsdelivr.net; CSP is enforced on the final URL.
80
+ // CSP requirements for admin panel scripts, styles, and custom page iframe sources (Offline Zero-CDN)
80
81
  csp: {
81
82
  styleSrc: [
83
+ "'self'",
84
+ "'unsafe-inline'",
82
85
  'https://cdn.quilljs.com',
83
86
  'https://cdn.jsdelivr.net',
84
87
  'https://unpkg.com',
85
88
  'https://cdn.tailwindcss.com',
86
89
  ],
87
90
  scriptSrc: [
91
+ "'self'",
92
+ "'unsafe-inline'",
93
+ "'unsafe-eval'",
88
94
  'https://cdn.quilljs.com',
89
95
  'https://cdn.jsdelivr.net',
90
96
  'https://unpkg.com',
91
97
  'https://cdn.tailwindcss.com',
92
98
  ],
93
- connectSrc: ['https://unpkg.com', 'https://cdn.tailwindcss.com', 'https://cdn.jsdelivr.net'],
99
+ connectSrc: ["'self'", 'https://unpkg.com', 'https://cdn.tailwindcss.com', 'https://cdn.jsdelivr.net'],
94
100
  frameSrc: ["'self'", 'data:', 'blob:', 'https:'],
95
101
  },
96
102
  enabled,
@@ -162,6 +168,15 @@ function adminPanelPlugin(options = {}) {
162
168
  /^https:/i.test(String(process.env.BASE_URL || '').trim())
163
169
  ? true
164
170
  : 'auto';
171
+ if (process.env.NODE_ENV === 'production') {
172
+ const warnMsg = '[webspresso:admin] Warning: Admin panel is using default MemoryStore in production. Please configure a persistent session store to prevent memory leaks.';
173
+ if (typeof process !== 'undefined' && typeof process.emitWarning === 'function') {
174
+ process.emitWarning(warnMsg, 'SecurityWarning');
175
+ } else {
176
+ console.warn(warnMsg);
177
+ }
178
+ }
179
+
165
180
  ctx.app.use(session({
166
181
  secret,
167
182
  resave: false,
@@ -189,22 +204,24 @@ function adminPanelPlugin(options = {}) {
189
204
  const { app } = ctx;
190
205
 
191
206
  const uploadUrlResolved =
192
- uploadUrlOption || app.get('webspresso.uploadPath') || null;
207
+ uploadUrlOption || (app && typeof app.get === 'function' ? app.get('webspresso.uploadPath') : null);
193
208
  if (uploadUrlResolved) {
194
209
  registry.configure({ uploadUrl: uploadUrlResolved });
195
210
  }
196
211
 
197
212
  // Check if admin_users table exists (migration run)
198
- db.knex.schema.hasTable('admin_users').then((hasAdminTable) => {
199
- if (!hasAdminTable) {
200
- console.warn(
201
- '\n⚠️ [admin-panel] admin_users table not found. Run: webspresso admin:setup\n' +
202
- ' Or create the table manually via migration. See: AdminUser model / getMigrationTemplate()\n'
203
- );
204
- }
205
- }).catch((err) => {
206
- console.warn('[admin-panel] Could not check admin_users table:', err.message);
207
- });
213
+ if (db?.knex?.schema?.hasTable) {
214
+ db.knex.schema.hasTable('admin_users').then((hasAdminTable) => {
215
+ if (!hasAdminTable) {
216
+ console.warn(
217
+ '\n⚠️ [admin-panel] admin_users table not found. Run: webspresso admin:setup\n' +
218
+ ' Or create the table manually via migration. See: AdminUser model / getMigrationTemplate()\n'
219
+ );
220
+ }
221
+ }).catch((err) => {
222
+ console.warn('[admin-panel] Could not check admin_users table:', err.message);
223
+ });
224
+ }
208
225
 
209
226
  // Create and register AdminUser model
210
227
  const { hasModel: hasGlobalModel, getModel: getGlobalModel } = require('../../core/orm/model');
@@ -313,6 +330,8 @@ function adminPanelPlugin(options = {}) {
313
330
  // Settings API routes
314
331
  ctx.addRoute('get', `${adminPath}/api/extensions/settings`, requireAuth, extensionHandlers.settingsGetHandler);
315
332
  ctx.addRoute('post', `${adminPath}/api/extensions/settings`, requireAuth, extensionHandlers.settingsUpdateHandler);
333
+ ctx.addRoute('get', `${adminPath}/api/extensions/system-info`, requireAuth, extensionHandlers.systemInfoHandler);
334
+ ctx.addRoute('get', `${adminPath}/api/system-info`, requireAuth, extensionHandlers.systemInfoHandler);
316
335
 
317
336
  // Custom pages API routes
318
337
  ctx.addRoute('get', `${adminPath}/api/extensions/pages/:pageId/data`, requireAuth, pageHandlers.getPageData);
@@ -360,6 +379,30 @@ function adminPanelPlugin(options = {}) {
360
379
  // Query API routes (auth required)
361
380
  ctx.addRoute('get', `${adminPath}/api/models/:model/queries/:query`, requireAuth, apiHandlers.queryHandler);
362
381
 
382
+ // ==========================================
383
+ // Static Vendor Assets (Offline Zero-CDN)
384
+ // ==========================================
385
+ const vendorDir = path.join(__dirname, 'vendor');
386
+ ctx.addRoute('get', `${adminPath}/vendor/:file`, (req, res) => {
387
+ const fileName = path.basename(req.params.file);
388
+ const filePath = path.join(vendorDir, fileName);
389
+
390
+ if (!fs.existsSync(filePath)) {
391
+ return res.status(404).send('Vendor asset not found');
392
+ }
393
+
394
+ if (fileName.endsWith('.js')) {
395
+ res.type('application/javascript');
396
+ } else if (fileName.endsWith('.css')) {
397
+ res.type('text/css');
398
+ } else {
399
+ res.type('application/octet-stream');
400
+ }
401
+
402
+ res.setHeader('Cache-Control', 'public, max-age=86400');
403
+ res.sendFile(filePath);
404
+ });
405
+
363
406
  // ==========================================
364
407
  // HTML Endpoints
365
408
  // ==========================================
@@ -465,9 +508,9 @@ function generateAdminPanelHtml(adminPath, registry) {
465
508
  });
466
509
  } catch (e) {}
467
510
  })();
468
- </script>
469
- <script src="https://unpkg.com/mithril/mithril.js"></script>
470
- <script src="https://cdn.tailwindcss.com"></script>
511
+ <script>window.__ADMIN_PATH__ = ${JSON.stringify(adminPath)};</script>
512
+ <script src="${adminPath}/vendor/mithril.min.js"></script>
513
+ <script src="${adminPath}/vendor/tailwind.min.js"></script>
471
514
  <script>tailwind.config = { darkMode: 'class' };</script>
472
515
  <style>
473
516
  body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }
@@ -484,6 +527,35 @@ function generateAdminPanelHtml(adminPath, registry) {
484
527
  .dark .ql-snow .ql-stroke { stroke: #94a3b8; }
485
528
  .dark .ql-snow .ql-fill { fill: #94a3b8; }
486
529
  .dark .ql-picker { color: #cbd5e1; }
530
+ @media (min-width: 768px) {
531
+ .admin-table-sticky-check {
532
+ position: sticky;
533
+ left: 0;
534
+ z-index: 5;
535
+ box-shadow: 4px 0 8px -4px rgba(0, 0, 0, 0.08);
536
+ }
537
+ th.admin-table-sticky-check {
538
+ z-index: 15;
539
+ }
540
+ .admin-table-sticky-first {
541
+ position: sticky;
542
+ left: 40px;
543
+ z-index: 5;
544
+ box-shadow: 4px 0 8px -4px rgba(0, 0, 0, 0.08);
545
+ }
546
+ th.admin-table-sticky-first {
547
+ z-index: 15;
548
+ }
549
+ .admin-table-sticky-actions {
550
+ position: sticky;
551
+ right: 0;
552
+ z-index: 5;
553
+ box-shadow: -4px 0 8px -4px rgba(0, 0, 0, 0.08);
554
+ }
555
+ th.admin-table-sticky-actions {
556
+ z-index: 15;
557
+ }
558
+ }
487
559
  </style>
488
560
  ${registry.getStylesHtml()}
489
561
  ${registry.getScriptsHtml('head')}
@@ -530,5 +602,6 @@ function generateAdminPanelHtml(adminPath, registry) {
530
602
 
531
603
  // Export registry and utilities for external use
532
604
  module.exports = adminPanelPlugin;
605
+ module.exports.adminPanelPlugin = adminPanelPlugin;
533
606
  module.exports.AdminRegistry = AdminRegistry;
534
607
  module.exports.defaultRegistry = defaultRegistry;
@@ -315,45 +315,41 @@ function createCustomPage(pageConfig) {
315
315
  const SettingsPage = {
316
316
  oninit(vnode) {
317
317
  vnode.state.config = null;
318
+ vnode.state.systemInfo = null;
318
319
  vnode.state.loading = true;
319
320
  vnode.state.saving = false;
321
+ vnode.state.checkingUpdates = false;
320
322
  vnode.state.formData = {};
321
323
  vnode.state.reloadBusy = false;
324
+ vnode.state.error = null;
322
325
 
323
- vnode.state.reloadFromServer = () => {
326
+ vnode.state.loadAll = (forceUpdateCheck = false) => {
324
327
  vnode.state.reloadBusy = true;
325
328
  vnode.state.error = null;
329
+ if (forceUpdateCheck) vnode.state.checkingUpdates = true;
326
330
  m.redraw();
327
- return api.get('/extensions/config')
328
- .then(result => {
329
- vnode.state.config = result;
330
- vnode.state.formData = { ...result.settings };
331
- })
332
- .catch(err => {
333
- vnode.state.error = err.message;
334
- })
335
- .finally(() => {
336
- vnode.state.reloadBusy = false;
337
- m.redraw();
338
- });
339
- };
340
-
341
- api.get('/extensions/config')
342
- .then(result => {
343
- vnode.state.config = result;
344
- vnode.state.formData = { ...result.settings };
345
- vnode.state.loading = false;
346
- m.redraw();
347
- })
348
- .catch(err => {
331
+ return Promise.all([
332
+ api.get('/extensions/config'),
333
+ api.get('/extensions/system-info' + (forceUpdateCheck ? '?force=true' : '')).catch(() => null)
334
+ ]).then(([configRes, sysRes]) => {
335
+ vnode.state.config = configRes;
336
+ vnode.state.formData = { ...configRes.settings };
337
+ if (sysRes) vnode.state.systemInfo = sysRes;
338
+ }).catch(err => {
349
339
  vnode.state.error = err.message;
340
+ }).finally(() => {
350
341
  vnode.state.loading = false;
342
+ vnode.state.reloadBusy = false;
343
+ vnode.state.checkingUpdates = false;
351
344
  m.redraw();
352
345
  });
346
+ };
347
+
348
+ vnode.state.loadAll();
353
349
  },
354
350
 
355
351
  view(vnode) {
356
- const { config, loading, saving, formData, error } = vnode.state;
352
+ const { config, systemInfo, loading, saving, checkingUpdates, formData, error, reloadBusy } = vnode.state;
357
353
 
358
354
  if (loading) {
359
355
  return m(Layout, m('div.flex.justify-center.py-12', m(Spinner)));
@@ -365,43 +361,147 @@ const SettingsPage = {
365
361
  m('div.mb-6.flex.items-start.justify-between.gap-4', [
366
362
  m('div', [
367
363
  m('h1.text-2xl.font-bold.text-gray-900.dark:text-slate-100', 'Admin Settings'),
368
- m('p.text-xs.text-gray-500.dark:text-slate-400.mt-1', 'Reload discards unsaved changes'),
364
+ m('p.text-xs.text-gray-500.dark:text-slate-400.mt-1', 'System overview and admin panel configuration'),
369
365
  ]),
370
366
  m(RefreshIconButton, {
371
367
  title: 'Reload settings from server',
372
- disabled: saving,
373
- spinning: vnode.state.reloadBusy,
368
+ disabled: saving || reloadBusy,
369
+ spinning: reloadBusy,
374
370
  onclick: () => {
375
- vnode.state.reloadFromServer();
371
+ vnode.state.loadAll();
376
372
  },
377
373
  }),
378
374
  ]),
379
375
 
380
- error && m('div.bg-red-50.border.border-red-200.rounded.p-4.text-red-700.mb-4', error),
376
+ error && m('div.bg-red-50.dark:bg-red-950/40.border.border-red-200.dark:border-red-800.rounded-lg.p-4.text-red-700.dark:text-red-300.mb-6', error),
377
+
378
+ // System Information Card
379
+ systemInfo ? m('div.bg-white.dark:bg-slate-800.rounded-xl.shadow-sm.border.border-gray-200.dark:border-slate-700.mb-6.overflow-hidden', [
380
+ m('div.px-6.py-4.border-b.border-gray-100.dark:border-slate-700.flex.items-center.justify-between.bg-gray-50/50.dark:bg-slate-800/80', [
381
+ m('div.flex.items-center.gap-2.5', [
382
+ m('div.w-8.h-8.rounded-lg.bg-indigo-50.dark:bg-indigo-950/60.text-indigo-600.dark:text-indigo-400.flex.items-center.justify-center', [
383
+ m('svg.w-4.h-4', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
384
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z' })
385
+ )
386
+ ]),
387
+ m('div', [
388
+ m('h2.text-base.font-semibold.text-gray-900.dark:text-slate-100', 'System Information'),
389
+ m('p.text-xs.text-gray-500.dark:text-slate-400', 'Runtime environment and version status')
390
+ ])
391
+ ]),
392
+ m('button.inline-flex.items-center.gap-1.5.px-3.py-1.5.text-xs.font-medium.text-indigo-600.dark:text-indigo-400.bg-indigo-50.dark:bg-indigo-950/50.hover:bg-indigo-100.dark:hover:bg-indigo-900/50.rounded-lg.transition-colors.disabled:opacity-50', {
393
+ disabled: checkingUpdates,
394
+ onclick: () => {
395
+ vnode.state.loadAll(true);
396
+ }
397
+ }, [
398
+ checkingUpdates ? m(Spinner) : m('svg.w-3.5.h-3.5', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
399
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15' })
400
+ ),
401
+ checkingUpdates ? 'Checking...' : 'Check for Updates'
402
+ ])
403
+ ]),
404
+ m('div.p-6.grid.grid-cols-1.md:grid-cols-2.lg:grid-cols-3.gap-5', [
405
+ // Webspresso Version
406
+ m('div.p-3.5.rounded-lg.bg-gray-50.dark:bg-slate-900/50.border.border-gray-100.dark:border-slate-800', [
407
+ m('div.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'Webspresso Framework'),
408
+ m('div.mt-1.5.flex.items-center.gap-2.flex-wrap', [
409
+ m('span.text-sm.font-semibold.text-gray-900.dark:text-slate-100', 'v' + (systemInfo.webspressoVersion || '0.0.1')),
410
+ systemInfo.hasUpdate
411
+ ? m('span.inline-flex.items-center.px-2.py-0.5.rounded-full.text-[11px].font-semibold.bg-amber-100.text-amber-800.dark:bg-amber-950/60.dark:text-amber-300.border.border-amber-200.dark:border-amber-800', '⚡ Update: v' + systemInfo.latestVersion)
412
+ : m('span.inline-flex.items-center.px-2.py-0.5.rounded-full.text-[11px].font-semibold.bg-emerald-100.text-emerald-800.dark:bg-emerald-950/60.dark:text-emerald-300.border.border-emerald-200.dark:border-emerald-800', '✓ Up to date')
413
+ ]),
414
+ systemInfo.hasUpdate && m('div.mt-2', [
415
+ m('code.text-[11px].bg-white.dark:bg-slate-800.text-indigo-600.dark:text-indigo-400.px-2.py-0.5.rounded.border.border-gray-200.dark:border-slate-700.select-all', 'npm i webspresso@latest')
416
+ ])
417
+ ]),
418
+
419
+ // Node.js Version
420
+ m('div.p-3.5.rounded-lg.bg-gray-50.dark:bg-slate-900/50.border.border-gray-100.dark:border-slate-800', [
421
+ m('div.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'Node.js Runtime'),
422
+ m('div.mt-1.5.text-sm.font-semibold.text-gray-900.dark:text-slate-100', systemInfo.nodeVersion || process.version)
423
+ ]),
381
424
 
382
- m('div.bg-white dark:bg-slate-800.rounded-lg.shadow', [
383
- m('div.p-6.space-y-4', [
425
+ // Environment
426
+ m('div.p-3.5.rounded-lg.bg-gray-50.dark:bg-slate-900/50.border.border-gray-100.dark:border-slate-800', [
427
+ m('div.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'Environment'),
428
+ m('div.mt-1.5', [
429
+ m('span.inline-flex.items-center.px-2.5.py-0.5.rounded-full.text-xs.font-semibold', {
430
+ class: systemInfo.environment === 'production'
431
+ ? 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950/60 dark:text-emerald-300'
432
+ : 'bg-blue-100 text-blue-800 dark:bg-blue-950/60 dark:text-blue-300'
433
+ }, systemInfo.environment || 'development')
434
+ ])
435
+ ]),
436
+
437
+ // Operating System
438
+ m('div.p-3.5.rounded-lg.bg-gray-50.dark:bg-slate-900/50.border.border-gray-100.dark:border-slate-800', [
439
+ m('div.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'OS & Architecture'),
440
+ m('div.mt-1.5.text-sm.font-semibold.text-gray-900.dark:text-slate-100.truncate', { title: systemInfo.platform }, systemInfo.platform || 'Server')
441
+ ]),
442
+
443
+ // Server Uptime
444
+ m('div.p-3.5.rounded-lg.bg-gray-50.dark:bg-slate-900/50.border.border-gray-100.dark:border-slate-800', [
445
+ m('div.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'Server Uptime'),
446
+ m('div.mt-1.5.text-sm.font-semibold.text-gray-900.dark:text-slate-100', systemInfo.uptimeFormatted || '0s')
447
+ ]),
448
+
449
+ // Memory Usage
450
+ m('div.p-3.5.rounded-lg.bg-gray-50.dark:bg-slate-900/50.border.border-gray-100.dark:border-slate-800', [
451
+ m('div.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'Memory (Heap / RSS)'),
452
+ m('div.mt-1.5.text-sm.font-semibold.text-gray-900.dark:text-slate-100',
453
+ (systemInfo.memory?.heapUsed || '0 MB') + ' / ' + (systemInfo.memory?.heapTotal || '0 MB') + ' (' + (systemInfo.memory?.rss || '0 MB') + ')'
454
+ )
455
+ ]),
456
+
457
+ // Database Engine
458
+ m('div.p-3.5.rounded-lg.bg-gray-50.dark:bg-slate-900/50.border.border-gray-100.dark:border-slate-800', [
459
+ m('div.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'Database Client'),
460
+ m('div.mt-1.5.text-sm.font-semibold.text-gray-900.dark:text-slate-100', systemInfo.database?.client || 'knex')
461
+ ])
462
+ ])
463
+ ]) : null,
464
+
465
+ // Admin Preferences Card
466
+ m('div.bg-white.dark:bg-slate-800.rounded-xl.shadow-sm.border.border-gray-200.dark:border-slate-700.overflow-hidden', [
467
+ m('div.px-6.py-4.border-b.border-gray-100.dark:border-slate-700.flex.items-center.gap-2.5.bg-gray-50/50.dark:bg-slate-800/80', [
468
+ m('div.w-8.h-8.rounded-lg.bg-blue-50.dark:bg-blue-950/60.text-blue-600.dark:text-blue-400.flex.items-center.justify-center', [
469
+ m('svg.w-4.h-4', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
470
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4' })
471
+ )
472
+ ]),
384
473
  m('div', [
385
- m('label.block.text-sm.font-medium.text-gray-700', 'Panel Title'),
386
- m('input.mt-1.block.w-full.rounded.border-gray-300 dark:border-slate-600.shadow-sm.focus:border-blue-500.focus:ring-blue-500', {
474
+ m('h2.text-base.font-semibold.text-gray-900.dark:text-slate-100', 'Panel Preferences'),
475
+ m('p.text-xs.text-gray-500.dark:text-slate-400', 'Customize admin title, theme colors, and table pagination')
476
+ ])
477
+ ]),
478
+
479
+ m('div.p-6.space-y-5', [
480
+ m('div', [
481
+ m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300', 'Panel Title'),
482
+ m('input.mt-1.block.w-full.rounded-lg.border-gray-300.dark:border-slate-600.dark:bg-slate-900.dark:text-slate-100.shadow-sm.focus:border-blue-500.focus:ring-blue-500.text-sm.px-3.py-2', {
387
483
  type: 'text',
388
484
  value: formData.title || '',
485
+ placeholder: 'Admin Panel',
389
486
  oninput: (e) => { formData.title = e.target.value; },
390
487
  }),
391
488
  ]),
392
489
 
393
490
  m('div', [
394
- m('label.block.text-sm.font-medium.text-gray-700', 'Primary Color'),
395
- m('input.mt-1.block.w-24.h-10.rounded.border-gray-300 dark:border-slate-600.shadow-sm', {
396
- type: 'color',
397
- value: formData.primaryColor || '#3B82F6',
398
- oninput: (e) => { formData.primaryColor = e.target.value; },
399
- }),
491
+ m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300', 'Primary Accent Color'),
492
+ m('div.mt-1.5.flex.items-center.gap-3', [
493
+ m('input.w-10.h-10.rounded-lg.border.border-gray-300.dark:border-slate-600.cursor-pointer.p-0.5.bg-transparent', {
494
+ type: 'color',
495
+ value: formData.primaryColor || '#3B82F6',
496
+ oninput: (e) => { formData.primaryColor = e.target.value; },
497
+ }),
498
+ m('span.text-xs.font-mono.text-gray-600.dark:text-slate-400', formData.primaryColor || '#3B82F6'),
499
+ ]),
400
500
  ]),
401
501
 
402
502
  m('div', [
403
- m('label.block.text-sm.font-medium.text-gray-700', 'Records Per Page'),
404
- m('input.mt-1.block.w-32.rounded.border-gray-300 dark:border-slate-600.shadow-sm.focus:border-blue-500.focus:ring-blue-500', {
503
+ m('label.block.text-sm.font-medium.text-gray-700.dark:text-slate-300', 'Records Per Page'),
504
+ m('input.mt-1.block.w-32.rounded-lg.border-gray-300.dark:border-slate-600.dark:bg-slate-900.dark:text-slate-100.shadow-sm.focus:border-blue-500.focus:ring-blue-500.text-sm.px-3.py-2', {
405
505
  type: 'number',
406
506
  min: 5,
407
507
  max: 100,
@@ -411,15 +511,14 @@ const SettingsPage = {
411
511
  ]),
412
512
  ]),
413
513
 
414
- m('div.bg-gray-50 dark:bg-slate-900.px-6.py-4.flex.justify-end.gap-3.rounded-b-lg', [
415
- m('button.px-4.py-2.text-sm.font-medium.text-white.bg-blue-600.rounded.hover:bg-blue-700.disabled:opacity-50', {
514
+ m('div.bg-gray-50.dark:bg-slate-900/70.px-6.py-4.flex.justify-end.gap-3.border-t.border-gray-100.dark:border-slate-700', [
515
+ m('button.inline-flex.items-center.gap-2.px-4.py-2.text-sm.font-medium.text-white.bg-blue-600.hover:bg-blue-700.rounded-lg.shadow-sm.transition-colors.disabled:opacity-50', {
416
516
  disabled: saving,
417
517
  onclick: async () => {
418
518
  vnode.state.saving = true;
419
519
  m.redraw();
420
520
  try {
421
521
  await api.post('/extensions/settings', formData);
422
- // Reload to apply changes
423
522
  window.location.reload();
424
523
  } catch (err) {
425
524
  vnode.state.error = err.message;
@@ -427,7 +526,7 @@ const SettingsPage = {
427
526
  m.redraw();
428
527
  }
429
528
  },
430
- }, saving ? 'Saving...' : 'Save Settings'),
529
+ }, saving ? [m(Spinner), 'Saving...'] : 'Save Settings'),
431
530
  ]),
432
531
  ]),
433
532
  ]);
@@ -389,6 +389,90 @@ const MobileHeader = {
389
389
  },
390
390
  };
391
391
 
392
+ // Webspresso Version Check State
393
+ var versionCheckState = {
394
+ checked: false,
395
+ loading: false,
396
+ data: null,
397
+ dismissed: false,
398
+ };
399
+
400
+ async function checkWebspressoVersion(force) {
401
+ if (versionCheckState.loading) return;
402
+ if (versionCheckState.checked && !force) return;
403
+ versionCheckState.loading = true;
404
+ try {
405
+ const res = await api.get('/extensions/system-info' + (force ? '?force=true' : ''));
406
+ versionCheckState.data = res;
407
+ versionCheckState.checked = true;
408
+ if (res && res.latestVersion) {
409
+ try {
410
+ const dismissedVer = sessionStorage.getItem('webspresso_dismiss_update_' + res.latestVersion);
411
+ if (dismissedVer === 'true') {
412
+ versionCheckState.dismissed = true;
413
+ }
414
+ } catch (e) {}
415
+ }
416
+ } catch (err) {
417
+ // Silent fail for background check
418
+ } finally {
419
+ versionCheckState.loading = false;
420
+ m.redraw();
421
+ }
422
+ }
423
+
424
+ // Version Update Toast Notification
425
+ const VersionUpdateToast = {
426
+ oninit() {
427
+ if (!versionCheckState.checked && !versionCheckState.loading) {
428
+ checkWebspressoVersion();
429
+ }
430
+ },
431
+ view() {
432
+ const data = versionCheckState.data;
433
+ if (!data || !data.hasUpdate || versionCheckState.dismissed) return null;
434
+
435
+ return m('div.fixed.bottom-5.right-5.z-50.max-w-sm.bg-white.dark:bg-slate-900.border.border-indigo-200.dark:border-indigo-800.rounded-xl.shadow-2xl.p-4.transition-all', [
436
+ m('div.flex.items-start.gap-3', [
437
+ m('div.flex-shrink-0.w-9.h-9.rounded-lg.bg-indigo-100.dark:bg-indigo-950/80.text-indigo-600.dark:text-indigo-400.flex.items-center.justify-center.font-bold', [
438
+ m('svg.w-5.h-5', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
439
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M13 10V3L4 14h7v7l9-11h-7z' })
440
+ )
441
+ ]),
442
+ m('div.flex-1.min-w-0', [
443
+ m('div.flex.items-center.justify-between.gap-2', [
444
+ m('h4.text-sm.font-semibold.text-gray-900.dark:text-slate-100', 'Update Available'),
445
+ m('button.text-gray-400.hover:text-gray-600.dark:hover:text-slate-300.-mr-1.-mt-1.p-1.rounded-md.hover:bg-gray-100.dark:hover:bg-slate-800.transition-colors', {
446
+ title: 'Dismiss notification',
447
+ onclick: () => {
448
+ versionCheckState.dismissed = true;
449
+ if (data.latestVersion) {
450
+ try { sessionStorage.setItem('webspresso_dismiss_update_' + data.latestVersion, 'true'); } catch (e) {}
451
+ }
452
+ }
453
+ }, m('svg.w-4.h-4', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
454
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M6 18L18 6M6 6l12 12' })
455
+ ))
456
+ ]),
457
+ m('p.text-xs.text-gray-600.dark:text-slate-300.mt-1', [
458
+ 'Webspresso ',
459
+ m('span.font-bold.text-indigo-600.dark:text-indigo-400', 'v' + data.latestVersion),
460
+ ' is available (current: v' + data.webspressoVersion + ').'
461
+ ]),
462
+ m('div.mt-2.5.flex.items-center.gap-2', [
463
+ m('code.text-[11px].bg-gray-100.dark:bg-slate-800.text-gray-800.dark:text-slate-200.px-2.py-1.rounded.font-mono.border.border-gray-200.dark:border-slate-700.select-all', 'npm i webspresso@latest'),
464
+ m('a.text-xs.font-medium.text-indigo-600.dark:text-indigo-400.hover:underline.ml-auto', {
465
+ href: 'https://github.com/webspresso/webspresso/releases',
466
+ target: '_blank',
467
+ rel: 'noopener noreferrer',
468
+ }, 'Changelog')
469
+ ])
470
+ ])
471
+ ])
472
+ ]);
473
+ }
474
+ };
475
+
392
476
  // Layout Component (with sidebar)
393
477
  const Layout = {
394
478
  view(vnode) {
@@ -396,6 +480,7 @@ const Layout = {
396
480
  m(MobileHeader),
397
481
  m(Sidebar),
398
482
  m('main.lg:ml-64.p-6.pt-20.lg:pt-6', vnode.children),
483
+ m(VersionUpdateToast),
399
484
  ]);
400
485
  },
401
486
  };
@@ -508,9 +508,12 @@ function createUserManagementApiHandlers(options) {
508
508
  return res.json({ data: [] });
509
509
  }
510
510
 
511
+ const repo = db.getRepository(modelName);
512
+ const tableName = repo?.model?.table || modelName;
513
+
511
514
  const sessions = await db.knex('remember_tokens')
512
- .select('remember_tokens.*', `${modelName}.${fieldMap.email} as user_email`, `${modelName}.${fieldMap.name} as user_name`)
513
- .leftJoin(modelName, 'remember_tokens.user_id', `${modelName}.id`)
515
+ .select('remember_tokens.*', `${tableName}.${fieldMap.email} as user_email`, `${tableName}.${fieldMap.name} as user_name`)
516
+ .leftJoin(tableName, 'remember_tokens.user_id', `${tableName}.id`)
514
517
  .orderBy('remember_tokens.created_at', 'desc');
515
518
 
516
519
  res.json({ data: sessions });
@@ -0,0 +1 @@
1
+ !function(){"use strict";function e(e,t,n,r,o,i){return{tag:e,key:t,attrs:n,children:r,text:o,dom:i,domSize:void 0,state:void 0,events:void 0,instance:void 0}}e.normalize=function(t){return Array.isArray(t)?e("[",void 0,void 0,e.normalizeChildren(t),void 0,void 0):null==t||"boolean"==typeof t?null:"object"==typeof t?t:e("#",void 0,void 0,String(t),void 0,void 0)},e.normalizeChildren=function(t){var n=[];if(t.length){for(var r=null!=t[0]&&null!=t[0].key,o=1;o<t.length;o++)if((null!=t[o]&&null!=t[o].key)!==r)throw new TypeError(!r||null==t[o]&&"boolean"!=typeof t[o]?"In fragments, vnodes must either all have keys or none have keys.":"In fragments, vnodes must either all have keys or none have keys. You may wish to consider using an explicit keyed empty fragment, m.fragment({key: ...}), instead of a hole.");for(o=0;o<t.length;o++)n[o]=e.normalize(t[o])}return n};var t=function(){var t,n=arguments[this],r=this+1;if(null==n?n={}:("object"!=typeof n||null!=n.tag||Array.isArray(n))&&(n={},r=this),arguments.length===r+1)t=arguments[r],Array.isArray(t)||(t=[t]);else for(t=[];r<arguments.length;)t.push(arguments[r++]);return e("",n.key,n,t)},n={}.hasOwnProperty,r=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,o={};function i(e){for(var t in e)if(n.call(e,t))return!1;return!0}function l(l){if(null==l||"string"!=typeof l&&"function"!=typeof l&&"function"!=typeof l.view)throw Error("The selector must be either a string or a component.");var a=t.apply(1,arguments);return"string"==typeof l&&(a.children=e.normalizeChildren(a.children),"["!==l)?function(e,t){var r=t.attrs,o=n.call(r,"class"),l=o?r.class:r.className;if(t.tag=e.tag,t.attrs={},!i(e.attrs)&&!i(r)){var a={};for(var u in r)n.call(r,u)&&(a[u]=r[u]);r=a}for(var u in e.attrs)n.call(e.attrs,u)&&"className"!==u&&!n.call(r,u)&&(r[u]=e.attrs[u]);for(var u in null==l&&null==e.attrs.className||(r.className=null!=l?null!=e.attrs.className?String(e.attrs.className)+" "+String(l):l:null!=e.attrs.className?e.attrs.className:null),o&&(r.class=null),r)if(n.call(r,u)&&"key"!==u){t.attrs=r;break}return t}(o[l]||function(e){for(var t,n="div",i=[],l={};t=r.exec(e);){var a=t[1],u=t[2];if(""===a&&""!==u)n=u;else if("#"===a)l.id=u;else if("."===a)i.push(u);else if("["===t[3][0]){var s=t[6];s&&(s=s.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===t[4]?i.push(s):l[t[4]]=""===s?s:s||!0}}return i.length>0&&(l.className=i.join(" ")),o[e]={tag:n,attrs:l}}(l),a):(a.tag=l,a)}if(l.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},l.fragment=function(){var n=t.apply(0,arguments);return n.tag="[",n.children=e.normalizeChildren(n.children),n},(a=function(e){if(!(this instanceof a))throw new Error("Promise must be called with 'new'.");if("function"!=typeof e)throw new TypeError("executor must be a function.");var t=this,n=[],r=[],o=s(n,!0),i=s(r,!1),l=t._instance={resolvers:n,rejectors:r},u="function"==typeof setImmediate?setImmediate:setTimeout;function s(e,o){return function a(s){var f;try{if(!o||null==s||"object"!=typeof s&&"function"!=typeof s||"function"!=typeof(f=s.then))u((function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",s);for(var t=0;t<e.length;t++)e[t](s);n.length=0,r.length=0,l.state=o,l.retry=function(){a(s)}}));else{if(s===t)throw new TypeError("Promise can't be resolved with itself.");c(f.bind(s))}}catch(e){i(e)}}}function c(e){var t=0;function n(e){return function(n){t++>0||e(n)}}var r=n(i);try{e(n(o),r)}catch(e){r(e)}}c(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function i(e,t,i,l){t.push((function(t){if("function"!=typeof e)i(t);else try{n(e(t))}catch(e){r&&r(e)}})),"function"==typeof o.retry&&l===o.state&&o.retry()}var l=new a((function(e,t){n=e,r=t}));return i(e,o.resolvers,n,!0),i(t,o.rejectors,r,!1),l},a.prototype.catch=function(e){return this.then(null,e)},a.prototype.finally=function(e){return this.then((function(t){return a.resolve(e()).then((function(){return t}))}),(function(t){return a.resolve(e()).then((function(){return a.reject(t)}))}))},a.resolve=function(e){return e instanceof a?e:new a((function(t){t(e)}))},a.reject=function(e){return new a((function(t,n){n(e)}))},a.all=function(e){return new a((function(t,n){var r=e.length,o=0,i=[];if(0===e.length)t([]);else for(var l=0;l<e.length;l++)!function(l){function a(e){o++,i[l]=e,o===r&&t(i)}null==e[l]||"object"!=typeof e[l]&&"function"!=typeof e[l]||"function"!=typeof e[l].then?a(e[l]):e[l].then(a,n)}(l)}))},a.race=function(e){return new a((function(t,n){for(var r=0;r<e.length;r++)e[r].then(t,n)}))},"undefined"!=typeof window){void 0===window.Promise?window.Promise=a:window.Promise.prototype.finally||(window.Promise.prototype.finally=a.prototype.finally);var a=window.Promise}else if("undefined"!=typeof global){void 0===global.Promise?global.Promise=a:global.Promise.prototype.finally||(global.Promise.prototype.finally=a.prototype.finally);a=global.Promise}var u=function(t){var n,r=t&&t.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function l(e,t){if(e.state!==t)throw new Error("'vnode.state' must not be modified.")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{l(e,t)}}function u(){try{return r.activeElement}catch(e){return null}}function s(e,t,n,r,o,i,l){for(var a=n;a<r;a++){var u=t[a];null!=u&&c(e,u,o,l,i)}}function c(t,n,o,l,u){var f=n.tag;if("string"==typeof f)switch(n.state={},null!=n.attrs&&_(n.attrs,n,o),f){case"#":!function(e,t,n){t.dom=r.createTextNode(t.children),w(e,t.dom,n)}(t,n,u);break;case"<":d(t,n,l,u);break;case"[":!function(e,t,n,o,i){var l=r.createDocumentFragment();if(null!=t.children){var a=t.children;s(l,a,0,a.length,n,null,o)}t.dom=l.firstChild,t.domSize=l.childNodes.length,w(e,l,i)}(t,n,o,l,u);break;default:!function(e,t,n,o,l){var a=t.tag,u=t.attrs,c=u&&u.is,f=(o=i(t)||o)?c?r.createElementNS(o,a,{is:c}):r.createElementNS(o,a):c?r.createElement(a,{is:c}):r.createElement(a);t.dom=f,null!=u&&function(e,t,n){"input"===e.tag&&null!=t.type&&e.dom.setAttribute("type",t.type);var r=null!=t&&"input"===e.tag&&"file"===t.type;for(var o in t)C(e,o,null,t[o],n,r)}(t,u,o);if(w(e,f,l),!b(t)&&null!=t.children){var d=t.children;s(f,d,0,d.length,n,null,o),"select"===t.tag&&null!=u&&function(e,t){if("value"in t)if(null===t.value)-1!==e.dom.selectedIndex&&(e.dom.value=null);else{var n=""+t.value;e.dom.value===n&&-1!==e.dom.selectedIndex||(e.dom.value=n)}"selectedIndex"in t&&C(e,"selectedIndex",null,t.selectedIndex,void 0)}(t,u)}}(t,n,o,l,u)}else!function(t,n,r,o,i){(function(t,n){var r;if("function"==typeof t.tag.view){if(t.state=Object.create(t.tag),null!=(r=t.state.view).$$reentrantLock$$)return;r.$$reentrantLock$$=!0}else{if(t.state=void 0,null!=(r=t.tag).$$reentrantLock$$)return;r.$$reentrantLock$$=!0,t.state=null!=t.tag.prototype&&"function"==typeof t.tag.prototype.view?new t.tag(t):t.tag(t)}_(t.state,t,n),null!=t.attrs&&_(t.attrs,t,n);if(t.instance=e.normalize(a.call(t.state.view,t)),t.instance===t)throw Error("A view cannot return the vnode it received as argument");r.$$reentrantLock$$=null})(n,r),null!=n.instance?(c(t,n.instance,r,o,i),n.dom=n.instance.dom,n.domSize=null!=n.dom?n.instance.domSize:0):n.domSize=0}(t,n,o,l,u)}var f={caption:"table",thead:"table",tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"};function d(e,t,n,o){var i=t.children.match(/^\s*?<(\w+)/im)||[],l=r.createElement(f[i[1]]||"div");"http://www.w3.org/2000/svg"===n?(l.innerHTML='<svg xmlns="http://www.w3.org/2000/svg">'+t.children+"</svg>",l=l.firstChild):l.innerHTML=t.children,t.dom=l.firstChild,t.domSize=l.childNodes.length,t.instance=[];for(var a,u=r.createDocumentFragment();a=l.firstChild;)t.instance.push(a),u.appendChild(a);w(e,u,o)}function p(e,t,n,r,o,i){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)s(e,n,0,n.length,r,o,i);else if(null==n||0===n.length)k(e,t,0,t.length);else{var l=null!=t[0]&&null!=t[0].key,a=null!=n[0]&&null!=n[0].key,u=0,f=0;if(!l)for(;f<t.length&&null==t[f];)f++;if(!a)for(;u<n.length&&null==n[u];)u++;if(l!==a)k(e,t,f,t.length),s(e,n,u,n.length,r,o,i);else if(a){for(var d,p,w,b,S,E=t.length-1,j=n.length-1;E>=f&&j>=u&&(w=t[E],b=n[j],w.key===b.key);)w!==b&&h(e,w,b,r,o,i),null!=b.dom&&(o=b.dom),E--,j--;for(;E>=f&&j>=u&&(d=t[f],p=n[u],d.key===p.key);)f++,u++,d!==p&&h(e,d,p,r,y(t,f,o),i);for(;E>=f&&j>=u&&u!==j&&d.key===b.key&&w.key===p.key;)g(e,w,S=y(t,f,o)),w!==p&&h(e,w,p,r,S,i),++u<=--j&&g(e,d,o),d!==b&&h(e,d,b,r,o,i),null!=b.dom&&(o=b.dom),f++,w=t[--E],b=n[j],d=t[f],p=n[u];for(;E>=f&&j>=u&&w.key===b.key;)w!==b&&h(e,w,b,r,o,i),null!=b.dom&&(o=b.dom),j--,w=t[--E],b=n[j];if(u>j)k(e,t,f,E+1);else if(f>E)s(e,n,u,j+1,r,o,i);else{var C,z,A=o,T=j-u+1,N=new Array(T),O=0,P=0,I=2147483647,$=0;for(P=0;P<T;P++)N[P]=-1;for(P=j;P>=u;P--){null==C&&(C=v(t,f,E+1));var L=C[(b=n[P]).key];null!=L&&(I=L<I?L:-1,N[P-u]=L,w=t[L],t[L]=null,w!==b&&h(e,w,b,r,o,i),null!=b.dom&&(o=b.dom),$++)}if(o=A,$!==E-f+1&&k(e,t,f,E+1),0===$)s(e,n,u,j+1,r,o,i);else if(-1===I)for(O=(z=function(e){var t=[0],n=0,r=0,o=0,i=m.length=e.length;for(o=0;o<i;o++)m[o]=e[o];for(o=0;o<i;++o)if(-1!==e[o]){var l=t[t.length-1];if(e[l]<e[o])m[o]=l,t.push(o);else{for(n=0,r=t.length-1;n<r;){var a=(n>>>1)+(r>>>1)+(n&r&1);e[t[a]]<e[o]?n=a+1:r=a}e[o]<e[t[n]]&&(n>0&&(m[o]=t[n-1]),t[n]=o)}}n=t.length,r=t[n-1];for(;n-- >0;)t[n]=r,r=m[r];return m.length=0,t}(N)).length-1,P=j;P>=u;P--)p=n[P],-1===N[P-u]?c(e,p,r,i,o):z[O]===P-u?O--:g(e,p,o),null!=p.dom&&(o=n[P].dom);else for(P=j;P>=u;P--)p=n[P],-1===N[P-u]&&c(e,p,r,i,o),null!=p.dom&&(o=n[P].dom)}}else{var R=t.length<n.length?t.length:n.length;for(u=u<f?u:f;u<R;u++)(d=t[u])===(p=n[u])||null==d&&null==p||(null==d?c(e,p,r,i,y(t,u+1,o)):null==p?x(e,d):h(e,d,p,r,y(t,u+1,o),i));t.length>R&&k(e,t,u,t.length),n.length>R&&s(e,n,u,n.length,r,o,i)}}}function h(t,n,r,o,l,u){var s=n.tag;if(s===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{var n;if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate)if(void 0!==(n=a.call(e.attrs.onbeforeupdate,e,t))&&!n)break;if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate)if(void 0!==(n=a.call(e.state.onbeforeupdate,e,t))&&!n)break;return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,e.attrs=t.attrs,e.children=t.children,e.text=t.text,!0}(r,n))return;if("string"==typeof s)switch(null!=r.attrs&&D(r.attrs,r,o),s){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(S(e,t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize,n.instance=t.instance)}(t,n,r,u,l);break;case"[":!function(e,t,n,r,o,i){p(e,t.children,n.children,r,o,i);var l=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u<a.length;u++){var s=a[u];null!=s&&null!=s.dom&&(null==n.dom&&(n.dom=s.dom),l+=s.domSize||1)}1!==l&&(n.domSize=l)}}(t,n,r,o,l,u);break;default:!function(e,t,n,r){var o=t.dom=e.dom;r=i(t)||r,"textarea"===t.tag&&null==t.attrs&&(t.attrs={});(function(e,t,n,r){t&&t===n&&console.warn("Don't reuse attrs object, use new object for every redraw, this will throw in next major");if(null!=n){"input"===e.tag&&null!=n.type&&e.dom.setAttribute("type",n.type);var o="input"===e.tag&&"file"===n.type;for(var i in n)C(e,i,t&&t[i],n[i],r,o)}var l;if(null!=t)for(var i in t)null==(l=t[i])||null!=n&&null!=n[i]||z(e,i,l,r)})(t,e.attrs,t.attrs,r),b(t)||p(o,e.children,t.children,n,null,r)}(n,r,o,u)}else!function(t,n,r,o,i,l){if(r.instance=e.normalize(a.call(r.state.view,r)),r.instance===r)throw Error("A view cannot return the vnode it received as argument");D(r.state,r,o),null!=r.attrs&&D(r.attrs,r,o);null!=r.instance?(null==n.instance?c(t,r.instance,o,l,i):h(t,n.instance,r.instance,o,i,l),r.dom=r.instance.dom,r.domSize=r.instance.domSize):null!=n.instance?(x(t,n.instance),r.dom=void 0,r.domSize=0):(r.dom=n.dom,r.domSize=n.domSize)}(t,n,r,o,l,u)}else x(t,n),c(t,r,o,u,l)}function v(e,t,n){for(var r=Object.create(null);t<n;t++){var o=e[t];if(null!=o){var i=o.key;null!=i&&(r[i]=t)}}return r}var m=[];function y(e,t,n){for(;t<e.length;t++)if(null!=e[t]&&null!=e[t].dom)return e[t].dom;return n}function g(e,t,n){var o=r.createDocumentFragment();!function e(t,n,r){for(;null!=r.dom&&r.dom.parentNode===t;){if("string"!=typeof r.tag){if(null!=(r=r.instance))continue}else if("<"===r.tag)for(var o=0;o<r.instance.length;o++)n.appendChild(r.instance[o]);else if("["!==r.tag)n.appendChild(r.dom);else if(1===r.children.length){if(null!=(r=r.children[0]))continue}else for(o=0;o<r.children.length;o++){var i=r.children[o];null!=i&&e(t,n,i)}break}}(e,o,t),w(e,o,n)}function w(e,t,n){null!=n?e.insertBefore(t,n):e.appendChild(t)}function b(e){if(null==e.attrs||null==e.attrs.contenteditable&&null==e.attrs.contentEditable)return!1;var t=e.children;if(null!=t&&1===t.length&&"<"===t[0].tag){var n=t[0].children;e.dom.innerHTML!==n&&(e.dom.innerHTML=n)}else if(null!=t&&0!==t.length)throw new Error("Child node of a contenteditable must be trusted.");return!0}function k(e,t,n,r){for(var o=n;o<r;o++){var i=t[o];null!=i&&x(e,i)}}function x(e,t){var n,r,o,i=0,u=t.state;"string"!=typeof t.tag&&"function"==typeof t.state.onbeforeremove&&(null!=(o=a.call(t.state.onbeforeremove,t))&&"function"==typeof o.then&&(i=1,n=o));t.attrs&&"function"==typeof t.attrs.onbeforeremove&&(null!=(o=a.call(t.attrs.onbeforeremove,t))&&"function"==typeof o.then&&(i|=2,r=o));if(l(t,u),i){if(null!=n){var s=function(){1&i&&((i&=2)||c())};n.then(s,s)}if(null!=r){s=function(){2&i&&((i&=1)||c())};r.then(s,s)}}else j(t),E(e,t);function c(){l(t,u),j(t),E(e,t)}}function S(e,t){for(var n=0;n<t.instance.length;n++)e.removeChild(t.instance[n])}function E(e,t){for(;null!=t.dom&&t.dom.parentNode===e;){if("string"!=typeof t.tag){if(null!=(t=t.instance))continue}else if("<"===t.tag)S(e,t);else{if("["!==t.tag&&(e.removeChild(t.dom),!Array.isArray(t.children)))break;if(1===t.children.length){if(null!=(t=t.children[0]))continue}else for(var n=0;n<t.children.length;n++){var r=t.children[n];null!=r&&E(e,r)}}break}}function j(e){if("string"!=typeof e.tag&&"function"==typeof e.state.onremove&&a.call(e.state.onremove,e),e.attrs&&"function"==typeof e.attrs.onremove&&a.call(e.attrs.onremove,e),"string"!=typeof e.tag)null!=e.instance&&j(e.instance);else{var t=e.children;if(Array.isArray(t))for(var n=0;n<t.length;n++){var r=t[n];null!=r&&j(r)}}}function C(e,t,n,o,i,l){if(!("key"===t||"is"===t||null==o||A(t)||n===o&&!function(e,t){return"value"===t||"checked"===t||"selectedIndex"===t||"selected"===t&&e.dom===u()||"option"===e.tag&&e.dom.parentNode===r.activeElement}(e,t)&&"object"!=typeof o||"type"===t&&"input"===e.tag)){if("o"===t[0]&&"n"===t[1])return R(e,t,o);if("xlink:"===t.slice(0,6))e.dom.setAttributeNS("http://www.w3.org/1999/xlink",t.slice(6),o);else if("style"===t)$(e.dom,n,o);else if(T(e,t,i)){if("value"===t){if(("input"===e.tag||"textarea"===e.tag)&&e.dom.value===""+o&&(l||e.dom===u()))return;if("select"===e.tag&&null!==n&&e.dom.value===""+o)return;if("option"===e.tag&&null!==n&&e.dom.value===""+o)return;if(l&&""+o!="")return void console.error("`value` is read-only on file inputs!")}e.dom[t]=o}else"boolean"==typeof o?o?e.dom.setAttribute(t,""):e.dom.removeAttribute(t):e.dom.setAttribute("className"===t?"class":t,o)}}function z(e,t,n,r){if("key"!==t&&"is"!==t&&null!=n&&!A(t))if("o"===t[0]&&"n"===t[1])R(e,t,void 0);else if("style"===t)$(e.dom,n,null);else if(!T(e,t,r)||"className"===t||"title"===t||"value"===t&&("option"===e.tag||"select"===e.tag&&-1===e.dom.selectedIndex&&e.dom===u())||"input"===e.tag&&"type"===t){var o=t.indexOf(":");-1!==o&&(t=t.slice(o+1)),!1!==n&&e.dom.removeAttribute("className"===t?"class":t)}else e.dom[t]=null}function A(e){return"oninit"===e||"oncreate"===e||"onupdate"===e||"onremove"===e||"onbeforeremove"===e||"onbeforeupdate"===e}function T(e,t,n){return void 0===n&&(e.tag.indexOf("-")>-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var N,O=/[A-Z]/g;function P(e){return"-"+e.toLowerCase()}function I(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(O,P)}function $(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(I(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(I(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(I(r))}}function L(){this._=n}function R(e,t,r){if(null!=e.events){if(e.events._=n,e.events[t]===r)return;null==r||"function"!=typeof r&&"object"!=typeof r?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=r)}else null==r||"function"!=typeof r&&"object"!=typeof r||(e.events=new L,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=r)}function _(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function D(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return L.prototype=Object.create(null),L.prototype.handleEvent=function(e){var t,n=this["on"+e.type];"function"==typeof n?t=n.call(e.currentTarget,e):"function"==typeof n.handleEvent&&n.handleEvent(e),this._&&!1!==e.redraw&&(0,this._)(),!1===t&&(e.preventDefault(),e.stopPropagation())},function(t,r,o){if(!t)throw new TypeError("DOM element being rendered to does not exist.");if(null!=N&&t.contains(N))throw new TypeError("Node is currently being rendered to and thus is locked.");var i=n,l=N,a=[],s=u(),c=t.namespaceURI;N=t,n="function"==typeof o?o:void 0;try{null==t.vnodes&&(t.textContent=""),r=e.normalizeChildren(Array.isArray(r)?r:[r]),p(t,t.vnodes,r,a,null,"http://www.w3.org/1999/xhtml"===c?void 0:c),t.vnodes=r,null!=s&&u()!==s&&"function"==typeof s.focus&&s.focus();for(var f=0;f<a.length;f++)a[f]()}finally{n=i,N=l}}}("undefined"!=typeof window?window:null),s=function(t,n,r){var o=[],i=!1,l=-1;function a(){for(l=0;l<o.length;l+=2)try{t(o[l],e(o[l+1]),u)}catch(e){r.error(e)}l=-1}function u(){i||(i=!0,n((function(){i=!1,a()})))}return u.sync=a,{mount:function(n,r){if(null!=r&&null==r.view&&"function"!=typeof r)throw new TypeError("m.mount expects a component, not a vnode.");var i=o.indexOf(n);i>=0&&(o.splice(i,2),i<=l&&(l-=2),t(n,[])),null!=r&&(o.push(n,r),t(n,e(r),u))},redraw:u}}(u,"undefined"!=typeof requestAnimationFrame?requestAnimationFrame:null,"undefined"!=typeof console?console:null),c=function(e){if("[object Object]"!==Object.prototype.toString.call(e))return"";var t=[];for(var n in e)r(n,e[n]);return t.join("&");function r(e,n){if(Array.isArray(n))for(var o=0;o<n.length;o++)r(e+"["+o+"]",n[o]);else if("[object Object]"===Object.prototype.toString.call(n))for(var o in n)r(e+"["+o+"]",n[o]);else t.push(encodeURIComponent(e)+(null!=n&&""!==n?"="+encodeURIComponent(n):""))}},f=Object.assign||function(e,t){for(var r in t)n.call(t,r)&&(e[r]=t[r])},d=function(e,t){if(/:([^\/\.-]+)(\.{3})?:/.test(e))throw new SyntaxError("Template parameter names must be separated by either a '/', '-', or '.'.");if(null==t)return e;var n=e.indexOf("?"),r=e.indexOf("#"),o=r<0?e.length:r,i=n<0?o:n,l=e.slice(0,i),a={};f(a,t);var u=l.replace(/:([^\/\.-]+)(\.{3})?/g,(function(e,n,r){return delete a[n],null==t[n]?e:r?t[n]:encodeURIComponent(String(t[n]))})),s=u.indexOf("?"),d=u.indexOf("#"),p=d<0?u.length:d,h=s<0?p:s,v=u.slice(0,h);n>=0&&(v+=e.slice(n,o)),s>=0&&(v+=(n<0?"?":"&")+u.slice(s,p));var m=c(a);return m&&(v+=(n<0&&s<0?"?":"&")+m),r>=0&&(v+=e.slice(r)),d>=0&&(v+=(r<0?"":"&")+u.slice(d)),v},p=function(e,t,r){var o=0;function i(e){return new t(e)}function l(e){return function(n,o){"string"!=typeof n?(o=n,n=n.url):null==o&&(o={});var l=new t((function(t,r){e(d(n,o.params),o,(function(e){if("function"==typeof o.type)if(Array.isArray(e))for(var n=0;n<e.length;n++)e[n]=new o.type(e[n]);else e=new o.type(e);t(e)}),r)}));if(!0===o.background)return l;var a=0;function u(){0==--a&&"function"==typeof r&&r()}return function e(t){var n=t.then;t.constructor=i;t.then=function(){a++;var r=n.apply(t,arguments);return r.then(u,(function(e){if(u(),0===a)throw e})),e(r)};return t}(l)}}function a(e,t){for(var r in e.headers)if(n.call(e.headers,r)&&r.toLowerCase()===t)return!0;return!1}return i.prototype=t.prototype,i.__proto__=t,{request:l((function(t,r,o,i){var l,u=null!=r.method?r.method.toUpperCase():"GET",s=r.body,c=(null==r.serialize||r.serialize===JSON.serialize)&&!(s instanceof e.FormData||s instanceof e.URLSearchParams),f=r.responseType||("function"==typeof r.extract?"":"json"),d=new e.XMLHttpRequest,p=!1,h=!1,v=d,m=d.abort;for(var y in d.abort=function(){p=!0,m.call(this)},d.open(u,t,!1!==r.async,"string"==typeof r.user?r.user:void 0,"string"==typeof r.password?r.password:void 0),c&&null!=s&&!a(r,"content-type")&&d.setRequestHeader("Content-Type","application/json; charset=utf-8"),"function"==typeof r.deserialize||a(r,"accept")||d.setRequestHeader("Accept","application/json, text/*"),r.withCredentials&&(d.withCredentials=r.withCredentials),r.timeout&&(d.timeout=r.timeout),d.responseType=f,r.headers)n.call(r.headers,y)&&d.setRequestHeader(y,r.headers[y]);d.onreadystatechange=function(e){if(!p&&4===e.target.readyState)try{var n,l=e.target.status>=200&&e.target.status<300||304===e.target.status||/^file:\/\//i.test(t),a=e.target.response;if("json"===f){if(!e.target.responseType&&"function"!=typeof r.extract)try{a=JSON.parse(e.target.responseText)}catch(e){a=null}}else f&&"text"!==f||null==a&&(a=e.target.responseText);if("function"==typeof r.extract?(a=r.extract(e.target,r),l=!0):"function"==typeof r.deserialize&&(a=r.deserialize(a)),l)o(a);else{var u=function(){try{n=e.target.responseText}catch(e){n=a}var t=new Error(n);t.code=e.target.status,t.response=a,i(t)};0===d.status?setTimeout((function(){h||u()})):u()}}catch(e){i(e)}},d.ontimeout=function(e){h=!0;var t=new Error("Request timed out");t.code=e.target.status,i(t)},"function"==typeof r.config&&(d=r.config(d,r,t)||d)!==v&&(l=d.abort,d.abort=function(){p=!0,l.call(this)}),null==s?d.send():"function"==typeof r.serialize?d.send(r.serialize(s)):s instanceof e.FormData||s instanceof e.URLSearchParams?d.send(s):d.send(JSON.stringify(s))})),jsonp:l((function(t,n,r,i){var l=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+o++,a=e.document.createElement("script");e[l]=function(t){delete e[l],a.parentNode.removeChild(a),r(t)},a.onerror=function(){delete e[l],a.parentNode.removeChild(a),i(new Error("JSONP request failed"))},a.src=t+(t.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(l),e.document.documentElement.appendChild(a)}))}}("undefined"!=typeof window?window:null,a,s.redraw),h=s,v=function(){return l.apply(this,arguments)};v.m=l,v.trust=l.trust,v.fragment=l.fragment,v.Fragment="[",v.mount=h.mount;var m=l,y=a;function g(e){try{return decodeURIComponent(e)}catch(t){return e}}var w=function(e){if(""===e||null==e)return{};"?"===e.charAt(0)&&(e=e.slice(1));for(var t=e.split("&"),n={},r={},o=0;o<t.length;o++){var i=t[o].split("="),l=g(i[0]),a=2===i.length?g(i[1]):"";"true"===a?a=!0:"false"===a&&(a=!1);var u=l.split(/\]\[?|\[/),s=r;l.indexOf("[")>-1&&u.pop();for(var c=0;c<u.length;c++){var f=u[c],d=u[c+1],p=""==d||!isNaN(parseInt(d,10));if(""===f)null==n[l=u.slice(0,c).join()]&&(n[l]=Array.isArray(s)?s.length:0),f=n[l]++;else if("__proto__"===f)break;if(c===u.length-1)s[f]=a;else{var h=Object.getOwnPropertyDescriptor(s,f);null!=h&&(h=h.value),null==h&&(s[f]=h=p?[]:{}),s=h}}}return r},b=function(e){var t=e.indexOf("?"),n=e.indexOf("#"),r=n<0?e.length:n,o=t<0?r:t,i=e.slice(0,o).replace(/\/{2,}/g,"/");return i?("/"!==i[0]&&(i="/"+i),i.length>1&&"/"===i[i.length-1]&&(i=i.slice(0,-1))):i="/",{path:i,params:t<0?{}:w(e.slice(t+1,r))}},k=function(e){var t=b(e),n=Object.keys(t.params),r=[],o=new RegExp("^"+t.path.replace(/:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g,(function(e,t,n){return null==t?"\\"+e:(r.push({k:t,r:"..."===n}),"..."===n?"(.*)":"."===n?"([^/]+)\\.":"([^/]+)"+(n||""))}))+"$");return function(e){for(var i=0;i<n.length;i++)if(t.params[n[i]]!==e.params[n[i]])return!1;if(!r.length)return o.test(e.path);var l=o.exec(e.path);if(null==l)return!1;for(i=0;i<r.length;i++)e.params[r[i].k]=r[i].r?l[i+1]:decodeURIComponent(l[i+1]);return!0}},x=new RegExp("^(?:key|oninit|oncreate|onbeforeupdate|onupdate|onbeforeremove|onremove)$"),S=function(e,t){var r={};if(null!=t)for(var o in e)n.call(e,o)&&!x.test(o)&&t.indexOf(o)<0&&(r[o]=e[o]);else for(var o in e)n.call(e,o)&&!x.test(o)&&(r[o]=e[o]);return r},E={};function j(e){try{return decodeURIComponent(e)}catch(t){return e}}v.route=function(t,n){var r,o,i,l,a,u,s=null==t?null:"function"==typeof t.setImmediate?t.setImmediate:t.setTimeout,c=y.resolve(),p=!1,h=!1,v=0,g=E,w={onbeforeupdate:function(){return!(!(v=v?2:1)||E===g)},onremove:function(){t.removeEventListener("popstate",z,!1),t.removeEventListener("hashchange",C,!1)},view:function(){if(v&&E!==g){var t=[e(i,l.key,l)];return g&&(t=g.render(t[0])),t}}},x=T.SKIP={};function C(){p=!1;var e=t.location.hash;"#"!==T.prefix[0]&&(e=t.location.search+e,"?"!==T.prefix[0]&&"/"!==(e=t.location.pathname+e)[0]&&(e="/"+e));var s=e.concat().replace(/(?:%[a-f89][a-f0-9])+/gim,j).slice(T.prefix.length),d=b(s);function h(e){console.error(e),A(o,null,{replace:!0})}f(d.params,t.history.state),function e(t){for(;t<r.length;t++)if(r[t].check(d)){var f=r[t].component,p=r[t].route,m=f,y=u=function(r){if(y===u){if(r===x)return e(t+1);i=null==r||"function"!=typeof r.view&&"function"!=typeof r?"div":r,l=d.params,a=s,u=null,g=f.render?f:null,2===v?n.redraw():(v=2,n.redraw.sync())}};return void(f.view||"function"==typeof f?(f={},y(m)):f.onmatch?c.then((function(){return f.onmatch(d.params,s,p)})).then(y,s===o?null:h):y("div"))}if(s===o)throw new Error("Could not resolve default route "+o+".");A(o,null,{replace:!0})}(0)}function z(){p||(p=!0,s(C))}function A(e,n,r){if(e=d(e,n),h){z();var o=r?r.state:null,i=r?r.title:null;r&&r.replace?t.history.replaceState(o,i,T.prefix+e):t.history.pushState(o,i,T.prefix+e)}else t.location.href=T.prefix+e}function T(e,i,l){if(!e)throw new TypeError("DOM element being rendered to does not exist.");if(r=Object.keys(l).map((function(e){if("/"!==e[0])throw new SyntaxError("Routes must start with a '/'.");if(/:([^\/\.-]+)(\.{3})?:/.test(e))throw new SyntaxError("Route parameter names must be separated with either '/', '.', or '-'.");return{route:e,component:l[e],check:k(e)}})),o=i,null!=i){var a=b(i);if(!r.some((function(e){return e.check(a)})))throw new ReferenceError("Default route doesn't match any known routes.")}"function"==typeof t.history.pushState?t.addEventListener("popstate",z,!1):"#"===T.prefix[0]&&t.addEventListener("hashchange",C,!1),h=!0,n.mount(e,w),C()}return T.set=function(e,t,n){null!=u&&((n=n||{}).replace=!0),u=null,A(e,t,n)},T.get=function(){return a},T.prefix="#!",T.Link={view:function(e){var t,n,r,o=m(e.attrs.selector||"a",S(e.attrs,["options","params","selector","onclick"]),e.children);return(o.attrs.disabled=Boolean(o.attrs.disabled))?(o.attrs.href=null,o.attrs["aria-disabled"]="true"):(t=e.attrs.options,n=e.attrs.onclick,r=d(o.attrs.href,e.attrs.params),o.attrs.href=T.prefix+r,o.attrs.onclick=function(e){var o;"function"==typeof n?o=n.call(e.currentTarget,e):null==n||"object"!=typeof n||"function"==typeof n.handleEvent&&n.handleEvent(e),!1===o||e.defaultPrevented||0!==e.button&&0!==e.which&&1!==e.which||e.currentTarget.target&&"_self"!==e.currentTarget.target||e.ctrlKey||e.metaKey||e.shiftKey||e.altKey||(e.preventDefault(),e.redraw=!1,T.set(r,null,t))}),o}},T.param=function(e){return l&&null!=e?l[e]:l},T}("undefined"!=typeof window?window:null,h),v.render=u,v.redraw=h.redraw,v.request=p.request,v.jsonp=p.jsonp,v.parseQueryString=w,v.buildQueryString=c,v.parsePathname=b,v.buildPathname=d,v.vnode=e,v.PromisePolyfill=a,v.censor=S,"undefined"!=typeof module?module.exports=v:window.m=v}();