domma-cms 0.18.0 → 0.21.0
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.
- package/CLAUDE.md +37 -3
- package/admin/css/admin.css +1 -1
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +4 -4
- package/admin/js/config/sidebar-config.js +1 -1
- package/admin/js/lib/crud-tutorial.js +1 -0
- package/admin/js/lib/markdown-toolbar.js +5 -5
- package/admin/js/lib/project-context.js +1 -0
- package/admin/js/lib/sidebar-renderer.js +4 -0
- package/admin/js/templates/action-editor.html +7 -0
- package/admin/js/templates/block-editor.html +7 -0
- package/admin/js/templates/collection-editor.html +9 -0
- package/admin/js/templates/form-editor.html +9 -0
- package/admin/js/templates/menu-editor.html +98 -0
- package/admin/js/templates/menu-locations.html +14 -0
- package/admin/js/templates/menus.html +14 -0
- package/admin/js/templates/page-editor.html +9 -2
- package/admin/js/templates/project-detail.html +50 -0
- package/admin/js/templates/project-editor.html +45 -0
- package/admin/js/templates/project-settings.html +60 -0
- package/admin/js/templates/projects.html +13 -0
- package/admin/js/templates/role-editor.html +11 -0
- package/admin/js/templates/tutorials.html +335 -2
- package/admin/js/templates/view-editor.html +7 -0
- package/admin/js/views/action-editor.js +1 -1
- package/admin/js/views/actions-list.js +1 -1
- package/admin/js/views/block-editor.js +8 -8
- package/admin/js/views/blocks.js +2 -2
- package/admin/js/views/collection-editor.js +4 -4
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/form-editor.js +5 -5
- package/admin/js/views/forms.js +1 -1
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/menu-editor.js +19 -0
- package/admin/js/views/menu-locations.js +1 -0
- package/admin/js/views/menus.js +5 -0
- package/admin/js/views/page-editor.js +24 -24
- package/admin/js/views/pages.js +3 -3
- package/admin/js/views/project-detail.js +4 -0
- package/admin/js/views/project-editor.js +1 -0
- package/admin/js/views/project-settings.js +1 -0
- package/admin/js/views/projects.js +7 -0
- package/admin/js/views/role-editor.js +1 -1
- package/admin/js/views/roles.js +3 -3
- package/admin/js/views/tutorials.js +1 -1
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +3 -3
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/views-list.js +1 -1
- package/config/menu-locations.json +5 -0
- package/config/menus/admin-sidebar.json +185 -0
- package/config/menus/footer.json +33 -0
- package/config/menus/main.json +35 -0
- package/config/menus/sproj-1779696558011-menu.json +17 -0
- package/config/menus/sproj-1779696960337-menu.json +18 -0
- package/config/menus/sproj-1779696985353-menu.json +18 -0
- package/config/site.json +6 -22
- package/package.json +4 -3
- package/plugins/analytics/daily.json +3 -0
- package/plugins/analytics/journeys.json +8 -0
- package/plugins/analytics/lifetime.json +1 -1
- package/public/css/site.css +1 -1
- package/public/js/collection-browser.js +4 -0
- package/public/js/forms.js +1 -1
- package/public/js/site.js +1 -1
- package/server/middleware/auth.js +88 -22
- package/server/routes/api/actions.js +58 -5
- package/server/routes/api/auth.js +2 -2
- package/server/routes/api/blocks.js +18 -3
- package/server/routes/api/collections.js +201 -8
- package/server/routes/api/forms.js +266 -21
- package/server/routes/api/menu-locations.js +46 -0
- package/server/routes/api/menus.js +115 -0
- package/server/routes/api/pages.js +1 -1
- package/server/routes/api/projects.js +107 -0
- package/server/routes/api/scaffold.js +86 -0
- package/server/routes/api/sidebar.js +23 -0
- package/server/routes/api/users.js +32 -7
- package/server/routes/api/views.js +10 -2
- package/server/routes/public.js +79 -6
- package/server/server.js +38 -0
- package/server/services/actions.js +137 -8
- package/server/services/adapters/FileAdapter.js +23 -8
- package/server/services/adapters/MongoAdapter.js +36 -18
- package/server/services/blocks.js +20 -8
- package/server/services/collections.js +85 -8
- package/server/services/content.js +23 -9
- package/server/services/filterEngine.js +281 -0
- package/server/services/hooks.js +48 -0
- package/server/services/markdown.js +686 -109
- package/server/services/menus-migration.js +107 -0
- package/server/services/menus.js +422 -0
- package/server/services/permissionRegistry.js +26 -0
- package/server/services/plugins.js +9 -2
- package/server/services/presetCollections.js +22 -0
- package/server/services/projects.js +429 -0
- package/server/services/recipes/contact-list.json +78 -0
- package/server/services/recipes/onboarding.json +426 -0
- package/server/services/references.js +174 -0
- package/server/services/renderer.js +237 -40
- package/server/services/roles.js +6 -1
- package/server/services/rowAccess.js +86 -13
- package/server/services/scaffolder.js +465 -0
- package/server/services/sidebar-migration.js +117 -0
- package/server/services/sitemap.js +112 -0
- package/server/services/userRoles.js +86 -0
- package/server/services/users.js +23 -2
- package/server/services/views.js +15 -4
- package/server/templates/page.html +7 -2
- /package/config/{navigation.json → navigation.json.bak} +0 -0
package/CLAUDE.md
CHANGED
|
@@ -213,8 +213,9 @@ Each plugin directory has its own `CLAUDE.md` with routes, storage, and gotchas.
|
|
|
213
213
|
| `config/auth.json` | CMS (merge on update) | JWT expiry, bcrypt rounds |
|
|
214
214
|
| `config/content.json` | CMS (merge on update) | Content dirs, page defaults |
|
|
215
215
|
| `config/presets.json` | CMS (merge on update) | Preset collection schemas |
|
|
216
|
-
| `config/site.json` | Yours | Site title, theme, footer, SMTP
|
|
217
|
-
| `config/
|
|
216
|
+
| `config/site.json` | Yours | Site title, theme, brand, footer, SMTP |
|
|
217
|
+
| `config/menus/<slug>.json` | Yours | One file per menu (tree of items + style/behaviour overrides) |
|
|
218
|
+
| `config/menu-locations.json` | Yours | Slot → menu slug map (built-in slots: `navbar`, `footer-primary`, `footer-legal`) |
|
|
218
219
|
| `config/plugins.json` | Yours | Plugin enabled/disabled + settings |
|
|
219
220
|
| `config/connections.json` | Yours (Pro only) | Named MongoDB connections; absence = file mode |
|
|
220
221
|
| `config/effects.json` | Yours | Effects / celebrate shortcode config |
|
|
@@ -222,9 +223,17 @@ Each plugin directory has its own `CLAUDE.md` with routes, storage, and gotchas.
|
|
|
222
223
|
|
|
223
224
|
See `docs/configuration.md` for full schema reference.
|
|
224
225
|
|
|
226
|
+
## Multi-menu system
|
|
227
|
+
|
|
228
|
+
Menus live in `config/menus/<slug>.json`. The slot → menu map lives in `config/menu-locations.json`. Built-in slots: `navbar`, `footer-primary`, `footer-legal`, `admin-sidebar`. Plugins register additional slots via `hooks.registerMenuLocation()`. The public navbar's brand is on `site.json.brand` (parallel to `adminBrand`); items + variant + position come from the menu mapped to `navbar`. The `[menu]` shortcode renders any menu inline (`[menu slug="…" /]` or `[menu location="…" /]`). On first boot the CMS auto-migrates the legacy `config/navigation.json` and `site.json.footer.links` into this system; the originals are renamed `.bak` and a one-release read-fallback restores the navbar from `navigation.json.bak` if the navbar slot is unmapped. See `docs/configuration.md` for full schema.
|
|
229
|
+
|
|
230
|
+
## Admin sidebar (menu-data-driven)
|
|
231
|
+
|
|
232
|
+
The admin sidebar is now menu-data-driven — no more hardcoded `sidebar-config.js`. The menu lives at `config/menus/admin-sidebar.json` and is auto-seeded on first boot (idempotent, file-existence guarded) with the standard tree: Overview, Projects, Content, Data, System, Documentation. Renderer is at `admin/js/lib/sidebar-renderer.js`; it reads the menu, merges in plugin-registered items (via `hooks.registerSidebarItem({parent, item})`), filters by each item's optional `permission` field, then mounts. The **Menus** admin can edit the sidebar visually like any other menu — the item editor exposes the new `permission` field on each row (resource key from `permissionRegistry`). Plugin-injected items are render-time only and not shown in the Menus editor; admins hide them by adding a same-`text` item with `hidden: true` to the persisted menu.
|
|
233
|
+
|
|
225
234
|
## Key Gotchas
|
|
226
235
|
|
|
227
|
-
1. **
|
|
236
|
+
1. **Menu item sub-items**: use `items` key, NOT `children` — Domma navbar and the `[menu]` shortcode both read `items`.
|
|
228
237
|
2. **Domma collections**: use `.get(0)` NOT `[0]` to get native DOM elements.
|
|
229
238
|
3. **`.html()` strips interactive elements**: `E.modal().setContent(x)` also strips HTML — use light DOM slot
|
|
230
239
|
projection (`modal.element.appendChild(myDomElement)`) for buttons/inputs.
|
|
@@ -248,3 +257,28 @@ See `docs/configuration.md` for full schema reference.
|
|
|
248
257
|
- `docs/plugin-development.md` — Plugin API, lifecycle hooks, injection points
|
|
249
258
|
- `docs/theming.md` — Available themes, CSS variables, customisation
|
|
250
259
|
- `docs/api-reference.md` — REST API endpoints (for headless / external integrations)
|
|
260
|
+
|
|
261
|
+
## Projects
|
|
262
|
+
|
|
263
|
+
Projects group related artefacts under a named slug — sidebar navigation, per-user access scope, and a tag the scaffolder stamps on recipe-produced artefacts. Cross-cutting: touches nine services (`pages`, `collections`, `forms`, `actions`, `menus`, `blocks`, `views`, `roles`, `users`).
|
|
264
|
+
|
|
265
|
+
**Data model.** Project records live in the preset collection `content/collections/projects/` (same pattern as `user-types` and `roles`). Each record has `slug`, `name`, `description`, `icon`, `rootUrl`, `sortOrder`. The slug is immutable. Nine artefact types accept an optional `meta.project: '<slug>'` field — set it to file the artefact under a project; omit it to keep the artefact site-wide.
|
|
266
|
+
|
|
267
|
+
**Page URL inheritance.** Page frontmatter has three states:
|
|
268
|
+
|
|
269
|
+
- `project: null` → explicit opt-out; page stays untagged regardless of URL.
|
|
270
|
+
- `project: '<slug>'` → explicit override.
|
|
271
|
+
- Field missing → resolver picks the project whose `rootUrl` is the longest prefix match of the page's URL; no match means untagged.
|
|
272
|
+
|
|
273
|
+
The single source of truth is `getProjectForPage(urlPath, frontmatterProject)` in `server/services/projects.js`; every page-load path (renderer, sidebar, API list endpoints) calls it.
|
|
274
|
+
|
|
275
|
+
**User access scope.** Users gain `projects: ['<slug>', ...]` — the **access scope**, distinct from `user.meta.project` (the **administrative ownership** that decides where the user appears in the sidebar). Two fields, two jobs:
|
|
276
|
+
|
|
277
|
+
- `user.meta.project` — sidebar placement under a Project section.
|
|
278
|
+
- `user.projects: []` — what artefacts the user can see; **empty array = no restriction**; non-empty restricts to listed projects + untagged. Super-admins bypass entirely.
|
|
279
|
+
|
|
280
|
+
**Permissions.** New family `projects.{read, create, update, delete}` in `permissionRegistry`. Existing roles are not back-filled — admin grants `projects.read` via the role editor (same gotcha as Menus).
|
|
281
|
+
|
|
282
|
+
**Scaffolder integration.** A recipe with a `project: {...}` block creates the project record on apply (idempotent — re-apply doesn't clobber edits) using `tokens.namespace` as the slug, then stamps `meta.project: <namespace>` on every produced artefact. Seeded users also get `projects: ['<namespace>']` so they can log in and immediately see their project.
|
|
283
|
+
|
|
284
|
+
**Filtering on list endpoints.** Every artefact list path runs `list.filter(item => canSeeArtefact(user, item))`. Get/write endpoints return 403 when the artefact's project isn't in the user's scope. The Projects section in the admin sidebar only renders when `listProjectsForUser(user).length > 0`.
|
package/admin/css/admin.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.dashboard-layout{display:flex;flex-direction:column;min-height:100vh}.dashboard-wrapper{display:flex;flex:1;min-height:0}.dashboard-main{flex:1;min-width:0;overflow-y:auto}.view-container{padding:1.5rem 2rem;max-width:1200px}.view-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;gap:1rem}.view-header h1{font-size:1.5rem;font-weight:700;margin:0;display:flex;align-items:center;gap:.5rem}.view-header-actions{display:flex;gap:.5rem}.stat-card .card-body{display:flex;align-items:center;gap:1rem}.stat-icon{width:44px;height:44px;border-radius:8px;background:var(--primary-alpha, rgba(91,140,255,.15));color:var(--primary, #5b8cff);display:flex;align-items:center;justify-content:center;font-size:1.25rem;flex-shrink:0}.stat-icon--success{background:#34d39926;color:#34d399}.stat-icon--warning{background:#fbbf2426;color:#fbbf24}.stat-value{font-size:1.75rem;font-weight:700;line-height:1}.stat-label{font-size:.8rem;color:var(--text-muted, #888);margin-top:.25rem}.toolbar{display:flex;align-items:center;gap:.75rem}.form-check-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;font-size:.9rem}.form-hint{display:block;font-size:.8rem;color:var(--text-muted, #888);margin-top:.25rem}.dashboard-main:has(#editor-meta-tabs){overflow:hidden}.view-container:has(#editor-meta-tabs){display:flex;flex-direction:column;height:calc(100dvh - 60px);max-width:100%;padding-bottom:0}.view-container:has(#block-editor-body){max-width:100%}#editor-meta-tabs{flex:1;min-height:0;display:flex;flex-direction:column}#editor-meta-tabs .tab-content{flex:1;min-height:0;overflow:hidden}#editor-meta-tabs .tab-panel{height:100%;overflow-y:auto}#editor-meta-tabs .tab-panel:has(.editor-card),#live-preview-panel{overflow:hidden}.page-live-preview-frame{display:block;width:100%;height:calc(100vh - 180px);border:none;border-radius:6px}.editor-card{display:flex;flex-direction:column;height:100%}.editor-card .card-body{display:flex;flex-direction:column;flex:1;min-height:0;padding:0!important}.editor-toolbar{display:flex;align-items:center;gap:2px;padding:6px 10px;border-bottom:1px solid var(--border-color, rgba(255, 255, 255, .08));flex-wrap:wrap;flex-shrink:0}.editor-toolbar-btn{background:none;border:none;color:var(--text-muted, #888);padding:6px 8px;border-radius:4px;cursor:pointer;display:flex;align-items:center;transition:color .15s,background .15s}.editor-toolbar-btn:hover{color:var(--text, #eee);background:#ffffff14}.editor-toolbar-sep{width:1px;height:20px;background:var(--border-color, rgba(255, 255, 255, .08));margin:0 4px;flex-shrink:0}.editor-toolbar-right{margin-left:auto;display:flex;align-items:center;gap:2px}.editor-view-btn{background:none;border:none;color:var(--text-muted, #888);padding:6px 8px;border-radius:4px;cursor:pointer;display:flex;align-items:center;transition:color .15s,background .15s}.editor-view-btn:hover{color:var(--text, #eee);background:#ffffff14}.editor-view-btn.active{color:var(--primary, #5b8cff);background:var(--primary-alpha, rgba(91, 140, 255, .12))}.editor-body{display:flex;flex:1;min-height:0}.editor-pane{flex:1;display:flex;flex-direction:column;min-width:0}.editor-pane--write,.editor-pane--preview{overflow:hidden}.editor-pane--write{flex-direction:row}.editor-line-numbers{padding:1rem .6rem 1rem .75rem;background:var(--dm-background-alt, rgba(0, 0, 0, .15));border-right:1px solid var(--border-color, rgba(255, 255, 255, .08));color:var(--dm-text-muted, #666);font-family:Fira Code,Courier New,monospace;font-size:.9rem;line-height:1.6;text-align:right;user-select:none;white-space:pre;overflow:hidden;flex-shrink:0;min-width:2.5rem}.editor-line-numbers--foldable{padding:1rem 0;min-width:3rem;white-space:normal;text-align:left;display:flex;flex-direction:column}.editor-line-number-row{display:flex;align-items:center;justify-content:flex-end;padding-right:.6rem;gap:.15rem}.fold-toggle{display:inline-flex;align-items:center;justify-content:center;width:1em;font-size:.6rem;cursor:default;color:transparent}.fold-toggle--open,.fold-toggle--folded{cursor:pointer;color:var(--dm-text-muted, #666);transition:color .1s}.fold-toggle--open:hover,.fold-toggle--folded:hover{color:var(--dm-accent, #4a9eff)}.editor-line-num{min-width:1.5rem;text-align:right}.editor-mode-write .editor-pane--preview,.editor-mode-write .editor-divider,.editor-mode-preview .editor-pane--write,.editor-mode-preview .editor-divider{display:none}.editor-divider{width:1px;background:var(--border-color, rgba(255,255,255,.08));flex-shrink:0}.editor-textarea{flex:1;resize:none;border:none;border-radius:0;background:transparent;font-family:Fira Code,Courier New,monospace;font-size:.9rem;padding:1rem;outline:none;color:inherit;line-height:1.6}.editor-preview{flex:1;padding:1rem;overflow-y:auto;line-height:1.7}.editor-preview h1,.editor-preview h2,.editor-preview h3{margin-top:1.5rem}.editor-preview p{margin-bottom:.75rem}.editor-preview code{background:#ffffff0f;padding:.1em .3em;border-radius:3px;font-size:.9em}.editor-fullscreen{position:fixed;inset:0;z-index:1000;border-radius:0;margin:0}.editor-fullscreen .card-body{height:100vh}.media-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:.75rem;max-height:400px;overflow-y:auto;padding:.25rem}.media-picker-item{cursor:pointer;border:2px solid transparent;border-radius:6px;overflow:hidden;transition:border-color .15s}.media-picker-item:hover{border-color:var(--primary, #5b8cff)}.media-picker-item img{width:100%;height:80px;object-fit:cover;display:block}.media-picker-item span{display:block;font-size:.75rem;padding:4px 6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.docs-body h3{margin-top:1.25rem;margin-bottom:.5rem}.docs-body p{margin-bottom:.5rem;line-height:1.6}.docs-body ol,.docs-body ul{margin-bottom:.75rem;padding-left:1.25rem}.docs-body li{margin-bottom:.25rem}.docs-body hr{margin:1.25rem 0;border-color:var(--border-color, rgba(255, 255, 255, .08))}.docs-body .table{margin-bottom:.75rem}.nav-items-header,.nav-item-row{display:flex;gap:.5rem;align-items:center;padding:.35rem .75rem}.nav-items-header{border-bottom:1px solid var(--border-color, rgba(255,255,255,.08));padding-bottom:.4rem;margin-bottom:.25rem}.nav-item-row{border-bottom:1px solid var(--border-color, rgba(255,255,255,.04))}.nav-item-row:last-child{border-bottom:none}.nav-col-indent{width:18px;flex-shrink:0;color:var(--text-muted, #888);text-align:center;font-size:.85rem}.nav-col-main{flex:2;min-width:0}.nav-col-icon{flex:0 0 90px}.nav-col-parent{flex:2;min-width:0}.nav-col-action{flex-shrink:0}.nav-item-row--child{background:#ffffff05}.nav-col-action{display:flex;align-items:center;gap:.25rem}.nav-item-row--hidden{opacity:.45}.nav-item-row--hidden .item-text,.nav-item-row--hidden .footer-link-text{text-decoration:line-through}.btn-toggle-hidden{color:var(--text-muted, #888)}.btn-toggle-hidden.active{color:var(--color-warning, #f59e0b)}.fb-field-card.fb-drag-over{outline:2px dashed var(--primary, #6366f1);outline-offset:-2px}.toggle-label{display:flex;align-items:center;gap:.4rem;font-size:.9rem;cursor:pointer;margin-right:1rem}.preset-toggles{display:flex;flex-wrap:wrap;margin-top:.75rem}.presets-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}@media(max-width:900px){.presets-grid{grid-template-columns:repeat(2,1fr)}}@media(max-width:600px){.presets-grid{grid-template-columns:1fr}}.card-header{padding:.5rem 1rem}.card-header h2{margin:0;font-size:.9rem;font-weight:600;letter-spacing:.02em}.preset-card .card-header{display:flex;align-items:center;justify-content:space-between}.preset-card .card-header h3{margin:0;font-size:1rem}.media-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:1rem}.media-card{background:var(--card-bg, rgba(255,255,255,.04));border:1px solid var(--border-color, rgba(255,255,255,.08));border-radius:8px;overflow:hidden}.media-preview{height:130px;display:flex;align-items:center;justify-content:center;background:#0003;overflow:hidden}.media-thumb{width:100%;height:100%;object-fit:cover}.media-thumb--file{font-size:2rem;color:var(--text-muted, #888)}.media-info{padding:.5rem .75rem;display:flex;flex-direction:column;gap:.15rem}.media-name{font-size:.8rem;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:default}.media-rename-input{width:100%;font-size:.8rem;font-weight:500;padding:.1rem .25rem;border:1px solid var(--primary, #7c6af7);border-radius:3px;background:var(--input-bg, transparent);color:inherit;outline:none}.media-size{font-size:.75rem;color:var(--text-muted, #888)}.media-actions{padding:.5rem .75rem;display:flex;gap:.4rem;border-top:1px solid var(--border-color, rgba(255,255,255,.08))}#admin-topbar{height:60px;display:flex;align-items:center;gap:1rem;padding:0 1.25rem;background:var(--navbar-bg, rgba(0,0,0,.3));border-bottom:1px solid var(--border-color, rgba(255,255,255,.08));position:sticky;top:0;z-index:200;flex-shrink:0}.topbar-brand{display:flex;align-items:center;gap:.5rem;font-weight:700;font-size:.95rem;color:inherit;flex-shrink:0;margin-right:.5rem}.topbar-brand span[data-icon],.topbar-brand svg{color:var(--primary, #5b8cff);width:22px;height:22px}.topbar-brand-text{opacity:.85}.topbar-brand-logo{height:28px;width:auto;display:inline-block;vertical-align:middle;margin-right:.4em;object-fit:contain}.topbar-user{display:flex;align-items:center;gap:.6rem;flex:1}.topbar-user-name{font-size:.875rem;font-weight:500}.topbar-role-badge{font-size:.7rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;padding:.15rem .45rem;border-radius:4px}.topbar-role-badge--admin{background:#5b8cff26;color:#5b8cff}.topbar-role-badge--manager{background:#34d39926;color:#34d399}.topbar-role-badge--editor{background:#fbbf2426;color:#fbbf24}.topbar-role-badge--subscriber{background:#94a3b826;color:#94a3b8}.topbar-actions{display:flex;align-items:center;gap:.5rem;flex-shrink:0}.topbar-action-link{display:flex;align-items:center;gap:.35rem;font-size:.875rem;color:var(--text-muted, #888);text-decoration:none;padding:.4rem .65rem;border-radius:6px;transition:color .15s,background .15s}.topbar-action-link:hover{color:var(--text, #eee);background:#ffffff0f}.topbar-action-link span[data-icon],.topbar-action-link svg{width:16px;height:16px}.topbar-signout{display:flex;align-items:center;gap:.35rem;font-size:.875rem}.topbar-signout span[data-icon],.topbar-signout svg{width:16px;height:16px}.login-wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:1rem;background:var(--body-bg, #0f1117);z-index:100;overflow-y:auto}.login-card{width:100%;max-width:460px}.ob-done-icon{font-size:3rem;color:#34d399;margin-bottom:1rem;text-align:center}.btn-skip{display:block;text-align:center;margin-top:.75rem;font-size:.85rem;color:var(--text-muted, #888);text-decoration:none}.btn-skip:hover{text-decoration:underline}.theme-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.6rem}.theme-swatch{border:2px solid var(--border-color, rgba(255,255,255,.1));border-radius:8px;overflow:hidden;cursor:pointer;transition:border-color .15s,transform .1s}.theme-swatch:hover{border-color:var(--primary, #5b8cff);transform:translateY(-1px)}.theme-swatch.selected{border-color:var(--primary, #5b8cff);box-shadow:0 0 0 2px var(--primary, #5b8cff)}.theme-swatch-preview{height:52px;position:relative;display:flex;align-items:flex-end;padding:.35rem}.theme-swatch-accent{width:28px;height:6px;border-radius:3px}.theme-swatch-label{display:flex;justify-content:space-between;align-items:center;padding:.3rem .45rem;font-size:.7rem;background:var(--card-bg, rgba(255,255,255,.04))}.theme-swatch-mode{color:var(--text-muted, #888);font-size:.65rem}.login-logo{display:flex;align-items:center;gap:.75rem;margin-bottom:1.75rem;font-size:1.5rem}.login-logo span[data-icon]{font-size:2rem;color:var(--primary, #5b8cff)}.login-logo h1{margin:0;font-size:1.5rem;font-weight:700}.login-heading{font-size:1.1rem;font-weight:600;margin-bottom:1.25rem}.btn-block{width:100%}.plugins-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem}.plugin-card{display:flex;flex-direction:column}.plugin-card .card-body{flex:1}.plugin-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:.75rem}.plugin-icon{width:40px;height:40px;border-radius:8px;background:var(--primary-alpha, rgba(91,140,255,.15));color:var(--primary, #5b8cff);display:flex;align-items:center;justify-content:center;flex-shrink:0}.plugin-meta{flex:1;min-width:0}.plugin-name{font-weight:600;margin-bottom:.15rem}.plugin-version{font-size:.75rem;color:var(--text-muted, #888)}.plugin-desc{font-size:.875rem;color:var(--text-muted, #888);margin-bottom:1rem}.plugin-footer{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;border-top:1px solid var(--border-color, rgba(255,255,255,.08))}.plugin-footer-actions{display:flex;align-items:center;gap:.5rem}#admin-sidebar .sidebar-link{position:relative!important}#admin-sidebar .sidebar-badge{position:absolute!important;right:.75rem!important;top:50%!important;transform:translateY(-50%)!important;margin-left:0!important;padding-left:.45rem!important;padding-right:.45rem!important}#admin-sidebar .sidebar-text{padding-right:2rem!important}#admin-sidebar.sidebar-dark{background:var(--dm-surface);border-color:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-header{background:var(--dm-surface-raised);border-color:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-header-title{color:var(--dm-text)}#admin-sidebar.sidebar-dark .sidebar-link{color:var(--dm-text-muted)}#admin-sidebar.sidebar-dark .sidebar-link:hover{color:var(--dm-text);background:var(--dm-surface-overlay)}#admin-sidebar.sidebar-dark .sidebar-link.active{color:var(--dm-primary);background:var(--dm-primary-alpha, rgba(91,140,255,.12));border-left-color:var(--dm-primary)}#admin-sidebar.sidebar-dark .sidebar-heading{color:var(--dm-text-muted)}#admin-sidebar.sidebar-dark .sidebar-divider{background:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-footer{background:var(--dm-surface-raised);border-color:var(--dm-border)}.sidebar-heading--collapsible{display:flex!important;align-items:center;justify-content:space-between;cursor:pointer;user-select:none}.sidebar-heading-toggle{display:flex;align-items:center;opacity:.5;flex-shrink:0;transition:transform .2s ease}.sidebar-heading--collapsible.is-collapsed .sidebar-heading-toggle{transform:rotate(-90deg)}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.p-0{padding:0!important}.text-muted{color:var(--text-muted, #888)}@media(max-width:768px){.view-container{padding:1rem}.editor-body{flex-direction:column}.editor-divider{width:auto;height:1px}}.image-editor{display:flex;flex-direction:column;gap:0}.image-editor-toolbar{display:flex;flex-direction:row;align-items:center;gap:.25rem;padding:.5rem .75rem;border-bottom:1px solid var(--border, rgba(255, 255, 255, .1))}.image-editor-sep{display:inline-block;width:1px;height:1.5rem;background:var(--border, rgba(255, 255, 255, .15));margin:0 .25rem;flex-shrink:0}.editor-toolbar-btn.active{color:var(--primary, #7c6af7);background:color-mix(in srgb,var(--primary, #7c6af7) 15%,transparent)}.editor-toolbar-caret{font-size:.6rem;line-height:1;margin-left:2px;opacity:.6}.editor-toolbar-dropdown{position:absolute;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--border-color, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:4px;min-width:160px}.editor-toolbar-dropdown-item{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:none;border:none;color:var(--dm-text, #ddd);padding:7px 10px;border-radius:5px;cursor:pointer;font-size:.85rem;transition:background .12s,color .12s}.editor-toolbar-dropdown-item:hover{background:#ffffff14;color:var(--dm-text-bright, #fff)}.editor-toolbar-dropdown-item span[data-icon],.editor-toolbar-dropdown-item svg{width:15px;height:15px;flex-shrink:0;opacity:.75}.editor-toolbar{position:relative}.editor-effects-dropdown-menu{position:absolute;top:100%;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--border-color, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:6px;min-width:180px;max-height:340px;overflow-y:auto}.editor-effects-category{padding:6px 8px 3px;font-size:.7rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--dm-text-muted, #888)}.editor-effects-item{display:block;width:100%;text-align:left;background:none;border:none;color:var(--dm-text, #ddd);padding:6px 10px;border-radius:5px;cursor:pointer;font-size:.85rem;transition:background .12s,color .12s}.editor-effects-item:hover{background:#ffffff14;color:var(--dm-text-bright, #fff)}.editor-icon-picker{position:absolute;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--border-color, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:8px;width:320px}.editor-icon-picker-search{width:100%;margin-bottom:8px;font-size:.8rem}.editor-spacer-picker{position:absolute;z-index:9999;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, #333);border-radius:8px;padding:12px;width:240px;box-shadow:0 8px 24px #00000073}.editor-spacer-picker-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888);margin-bottom:10px}.editor-spacer-picker-row{display:flex;align-items:center;gap:10px;margin-bottom:12px}.editor-spacer-slider{flex:1;accent-color:var(--primary, #6366f1);cursor:pointer}.editor-spacer-slider-value{font-size:13px;font-weight:600;min-width:38px;text-align:right;color:var(--dm-text, #cdd6f4)}.editor-spacer-insert-btn{width:100%}.editor-icon-picker-size-row{display:flex;align-items:center;gap:8px;padding:0 8px 8px}.editor-icon-picker-size-row label{font-size:11px;color:var(--dm-text-muted, #888);white-space:nowrap}.editor-icon-picker-size{width:70px;font-size:12px;padding:3px 6px}.editor-icon-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(52px,1fr));gap:2px;max-height:280px;overflow-y:auto}.editor-icon-picker-item{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:6px 2px;border-radius:5px;cursor:pointer;background:none;border:none;color:var(--dm-text, #ddd);font-size:.58rem;text-align:center;gap:4px;overflow:hidden;transition:background .12s,color .12s}.editor-icon-picker-item:hover{background:#ffffff14;color:var(--dm-text-bright, #fff)}.editor-icon-picker-item span[data-icon],.editor-icon-picker-item svg{width:16px;height:16px;flex-shrink:0}.editor-icon-picker-item>span:last-child{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.2}.editor-icon-picker-empty{grid-column:1 / -1;text-align:center;color:var(--dm-text-muted, #888);font-size:.8rem;padding:1.5rem}.image-editor-canvas{min-height:320px;max-height:520px;background:#111;overflow:hidden;display:flex;align-items:center;justify-content:center}.image-editor-canvas img{display:block;max-width:100%;max-height:520px}.image-editor-resize{display:flex;flex-direction:row;align-items:center;gap:.5rem;padding:.6rem .75rem;border-top:1px solid var(--border, rgba(255, 255, 255, .1));font-size:.85rem}.image-editor-resize label{font-weight:600;color:var(--text-muted, #888);font-size:.8rem}.image-editor-resize .form-input{width:5rem;padding:.25rem .5rem;font-size:.85rem}.image-editor-footer{display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:.75rem;border-top:1px solid var(--border, rgba(255, 255, 255, .1));gap:.5rem}.image-editor-effects{border-top:1px solid var(--border, rgba(255, 255, 255, .1))}.image-editor-tab-bar{display:flex;flex-direction:row;overflow-x:auto;border-bottom:1px solid var(--border, rgba(255, 255, 255, .1));scrollbar-width:none}.image-editor-tab-bar::-webkit-scrollbar{display:none}.image-editor-tab-btn{display:inline-flex;align-items:center;gap:.35rem;padding:.5rem .85rem;font-size:.8rem;font-weight:500;border:none;border-bottom:2px solid transparent;background:transparent;cursor:pointer;color:var(--text-muted, #888);white-space:nowrap;transition:color .15s,border-color .15s;flex-shrink:0}.image-editor-tab-btn:hover{color:var(--text, #eee)}.image-editor-tab-btn.active{color:var(--primary, #7c6af7);border-bottom-color:var(--primary, #7c6af7)}.image-editor-tab-panel{padding:.75rem;max-height:200px;overflow-y:auto}.ie-presets-grid{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.5rem}.ie-preset-btn.active{color:var(--primary, #7c6af7);background:color-mix(in srgb,var(--primary, #7c6af7) 15%,transparent);border-color:var(--primary, #7c6af7)}.ie-server-note{font-size:.73rem;color:var(--text-muted, #888);font-style:italic;margin:.25rem 0 0;line-height:1.4}.ie-slider-row{display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem}.ie-slider-label{width:6rem;font-size:.8rem;color:var(--text-muted, #888);font-weight:600;flex-shrink:0}.ie-slider{flex:1;accent-color:var(--primary, #7c6af7);cursor:pointer}.ie-slider-val{width:3rem;font-size:.8rem;text-align:right;color:var(--text, #eee);flex-shrink:0}.ie-select{flex:1;font-size:.85rem;padding:.25rem .5rem}.ie-colour-input{width:40px;height:28px;padding:0;border:1px solid var(--border, rgba(255, 255, 255, .15));border-radius:4px;cursor:pointer;background:transparent;flex-shrink:0}.ie-wm-preview-row{display:flex;align-items:center;gap:.5rem;margin:.4rem 0}.ie-wm-preview-img{width:48px;height:48px;object-fit:contain;border-radius:4px;background:var(--surface-2, rgba(255, 255, 255, .06));flex-shrink:0}.ie-wm-preview-name{font-size:.8rem;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text, #eee)}.ie-media-picker{padding:.25rem 0}.ie-media-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:.5rem;max-height:300px;overflow-y:auto}.ie-media-thumb{display:flex;flex-direction:column;align-items:center;gap:.25rem;padding:.4rem;border-radius:6px;cursor:pointer;border:2px solid transparent;transition:border-color .15s,background .15s}.ie-media-thumb:hover{background:var(--surface-2, rgba(255, 255, 255, .08));border-color:var(--primary, #7c6af7)}.ie-media-thumb img{width:72px;height:72px;object-fit:cover;border-radius:4px}.ie-media-thumb-name{font-size:.7rem;color:var(--text-muted, #888);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:72px}.dm-slideover-left,.dm-slideover-right{width:min(40rem,95vw)!important;min-width:min(36rem,100vw)!important;max-width:100vw!important}.dm-slideover-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid var(--dm-border, #333);flex-shrink:0}.dm-slideover-title{margin:0;font-size:1rem;font-weight:600}.dm-slideover-close{padding:.2rem .35rem!important;line-height:1;font-size:.75rem}.dm-slideover-body{flex:1;overflow-y:auto;min-height:0;padding:1.5rem!important;box-sizing:border-box}.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-body):not(.dm-slideover-close){padding:1.5rem!important;box-sizing:border-box}.dm-slideover-body>div{padding-left:0!important;padding-right:0!important}.dm-slideover-body>button.btn:last-child{margin-top:.5rem}.dconfig-textarea{font-family:monospace;min-height:100px;resize:vertical}.dm-editor-line-numbers{white-space:pre}.dm-code-inline{font-family:Fira Code,Courier New,monospace;font-size:.82em;padding:.15em .35em;border-radius:3px;background:var(--dm-surface-subtle, rgba(0, 0, 0, .18));color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(255, 255, 255, .08))}.dm-code-block{font-family:Fira Code,Courier New,monospace;font-size:.82rem;line-height:1.6;padding:.65rem .9rem;border-radius:6px;background:var(--dm-surface-subtle, rgba(0, 0, 0, .18));color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(255, 255, 255, .08));white-space:pre;overflow-x:auto;display:block;margin:.4rem 0}.tabs-centered{text-align:center}.tabs-centered .tab-list{display:inline-flex}.tabs-centered .tab-content{text-align:left}@media(max-width:768px){.view-header{flex-direction:column;align-items:flex-start}.theme-grid{grid-template-columns:repeat(2,1fr)}.nav-item-row{flex-wrap:wrap}.nav-col-icon,.nav-col-parent{display:none}#admin-topbar{padding:0 .75rem}.topbar-brand-text{display:none}.image-editor-toolbar{flex-wrap:wrap}}#topbar-bell{position:relative}.topbar-bell-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;background:var(--dm-color-danger, #dc2626);color:#fff;font-size:10px;font-weight:700;line-height:16px;border-radius:9999px;text-align:center;pointer-events:none}
|
|
1
|
+
.dashboard-layout{display:flex;flex-direction:column;min-height:100vh}.dashboard-wrapper{display:flex;flex:1;min-height:0}.dashboard-main{flex:1;min-width:0;overflow-y:auto}.view-container{padding:1.5rem 2rem;max-width:1200px}.view-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1.5rem;gap:1rem}.view-header h1{font-size:1.5rem;font-weight:700;margin:0;display:flex;align-items:center;gap:.5rem}.view-header-actions{display:flex;gap:.5rem}.stat-card .card-body{display:flex;align-items:center;gap:1rem}.stat-icon{width:44px;height:44px;border-radius:8px;background:var(--primary-alpha, rgba(91,140,255,.15));color:var(--primary, #5b8cff);display:flex;align-items:center;justify-content:center;font-size:1.25rem;flex-shrink:0}.stat-icon--success{background:#34d39926;color:#34d399}.stat-icon--warning{background:#fbbf2426;color:#fbbf24}.stat-value{font-size:1.75rem;font-weight:700;line-height:1}.stat-label{font-size:.8rem;color:var(--text-muted, #888);margin-top:.25rem}.toolbar{display:flex;align-items:center;gap:.75rem}.form-check-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;font-size:.9rem}.form-hint{display:block;font-size:.8rem;color:var(--text-muted, #888);margin-top:.25rem}.dashboard-main:has(#editor-meta-tabs){overflow:hidden}.view-container:has(#editor-meta-tabs){display:flex;flex-direction:column;height:calc(100dvh - 60px);max-width:100%;padding-bottom:0}.view-container:has(#block-editor-body){max-width:100%}#editor-meta-tabs{flex:1;min-height:0;display:flex;flex-direction:column}#editor-meta-tabs .tab-content{flex:1;min-height:0;overflow:hidden}#editor-meta-tabs .tab-panel{height:100%;overflow-y:auto}#editor-meta-tabs .tab-panel:has(.editor-card),#live-preview-panel{overflow:hidden}.page-live-preview-frame{display:block;width:100%;height:calc(100vh - 180px);border:none;border-radius:6px}.editor-card{display:flex;flex-direction:column;height:100%}.editor-card .card-body{display:flex;flex-direction:column;flex:1;min-height:0;padding:0!important}.editor-toolbar{display:flex;align-items:center;gap:2px;padding:6px 10px;border-bottom:1px solid var(--border-color, rgba(255, 255, 255, .08));flex-wrap:wrap;flex-shrink:0}.editor-toolbar-btn{background:none;border:none;color:var(--text-muted, #888);padding:6px 8px;border-radius:4px;cursor:pointer;display:flex;align-items:center;transition:color .15s,background .15s}.editor-toolbar-btn:hover{color:var(--text, #eee);background:#ffffff14}.editor-toolbar-sep{width:1px;height:20px;background:var(--border-color, rgba(255, 255, 255, .08));margin:0 4px;flex-shrink:0}.editor-toolbar-right{margin-left:auto;display:flex;align-items:center;gap:2px}.editor-view-btn{background:none;border:none;color:var(--text-muted, #888);padding:6px 8px;border-radius:4px;cursor:pointer;display:flex;align-items:center;transition:color .15s,background .15s}.editor-view-btn:hover{color:var(--text, #eee);background:#ffffff14}.editor-view-btn.active{color:var(--primary, #5b8cff);background:var(--primary-alpha, rgba(91, 140, 255, .12))}.editor-body{display:flex;flex:1;min-height:0}.editor-pane{flex:1;display:flex;flex-direction:column;min-width:0}.editor-pane--write,.editor-pane--preview{overflow:hidden}.editor-pane--write{flex-direction:row}.editor-line-numbers{padding:1rem .6rem 1rem .75rem;background:var(--dm-background-alt, rgba(0, 0, 0, .15));border-right:1px solid var(--border-color, rgba(255, 255, 255, .08));color:var(--dm-text-muted, #666);font-family:Fira Code,Courier New,monospace;font-size:.9rem;line-height:1.6;text-align:right;user-select:none;white-space:pre;overflow:hidden;flex-shrink:0;min-width:2.5rem}.editor-line-numbers--foldable{padding:1rem 0;min-width:3rem;white-space:normal;text-align:left;display:flex;flex-direction:column}.editor-line-number-row{display:flex;align-items:center;justify-content:flex-end;padding-right:.6rem;gap:.15rem}.fold-toggle{display:inline-flex;align-items:center;justify-content:center;width:1em;font-size:.6rem;cursor:default;color:transparent}.fold-toggle--open,.fold-toggle--folded{cursor:pointer;color:var(--dm-text-muted, #666);transition:color .1s}.fold-toggle--open:hover,.fold-toggle--folded:hover{color:var(--dm-accent, #4a9eff)}.editor-line-num{min-width:1.5rem;text-align:right}.editor-mode-write .editor-pane--preview,.editor-mode-write .editor-divider,.editor-mode-preview .editor-pane--write,.editor-mode-preview .editor-divider{display:none}.editor-divider{width:1px;background:var(--border-color, rgba(255,255,255,.08));flex-shrink:0}.editor-textarea{flex:1;resize:none;border:none;border-radius:0;background:transparent;font-family:Fira Code,Courier New,monospace;font-size:.9rem;padding:1rem;outline:none;color:inherit;line-height:1.6}.editor-preview{flex:1;padding:1rem;overflow-y:auto;line-height:1.7}.editor-preview h1,.editor-preview h2,.editor-preview h3{margin-top:1.5rem}.editor-preview p{margin-bottom:.75rem}.editor-preview code{background:#ffffff0f;padding:.1em .3em;border-radius:3px;font-size:.9em}.editor-fullscreen{position:fixed;inset:0;z-index:1000;border-radius:0;margin:0}.editor-fullscreen .card-body{height:100vh}.media-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:.75rem;max-height:400px;overflow-y:auto;padding:.25rem}.media-picker-item{cursor:pointer;border:2px solid transparent;border-radius:6px;overflow:hidden;transition:border-color .15s}.media-picker-item:hover{border-color:var(--primary, #5b8cff)}.media-picker-item img{width:100%;height:80px;object-fit:cover;display:block}.media-picker-item span{display:block;font-size:.75rem;padding:4px 6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.docs-body h3{margin-top:1.25rem;margin-bottom:.5rem}.docs-body p{margin-bottom:.5rem;line-height:1.6}.docs-body ol,.docs-body ul{margin-bottom:.75rem;padding-left:1.25rem}.docs-body li{margin-bottom:.25rem}.docs-body hr{margin:1.25rem 0;border-color:var(--border-color, rgba(255, 255, 255, .08))}.docs-body .table{margin-bottom:.75rem}.nav-items-header,.nav-item-row{display:flex;gap:.5rem;align-items:center;padding:.35rem .75rem}.nav-items-header{border-bottom:1px solid var(--border-color, rgba(255,255,255,.08));padding-bottom:.4rem;margin-bottom:.25rem}.nav-item-row{border-bottom:1px solid var(--border-color, rgba(255,255,255,.04))}.nav-item-row:last-child{border-bottom:none}.nav-col-indent{width:18px;flex-shrink:0;color:var(--text-muted, #888);text-align:center;font-size:.85rem}.nav-col-main{flex:2;min-width:0}.nav-col-icon{flex:0 0 90px}.nav-col-parent{flex:2;min-width:0}.nav-col-action{flex-shrink:0}.nav-item-row--child{background:#ffffff05}.nav-col-action{display:flex;align-items:center;gap:.25rem}.nav-item-row--hidden{opacity:.45}.nav-item-row--hidden .item-text,.nav-item-row--hidden .footer-link-text{text-decoration:line-through}.btn-toggle-hidden{color:var(--text-muted, #888)}.btn-toggle-hidden.active{color:var(--color-warning, #f59e0b)}.fb-field-card.fb-drag-over{outline:2px dashed var(--primary, #6366f1);outline-offset:-2px}.toggle-label{display:flex;align-items:center;gap:.4rem;font-size:.9rem;cursor:pointer;margin-right:1rem}.preset-toggles{display:flex;flex-wrap:wrap;margin-top:.75rem}.presets-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}@media(max-width:900px){.presets-grid{grid-template-columns:repeat(2,1fr)}}@media(max-width:600px){.presets-grid{grid-template-columns:1fr}}.card-header{padding:.5rem 1rem}.card-header h2{margin:0;font-size:.9rem;font-weight:600;letter-spacing:.02em}.preset-card .card-header{display:flex;align-items:center;justify-content:space-between}.preset-card .card-header h3{margin:0;font-size:1rem}.media-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:1rem}.media-card{background:var(--card-bg, rgba(255,255,255,.04));border:1px solid var(--border-color, rgba(255,255,255,.08));border-radius:8px;overflow:hidden}.media-preview{height:130px;display:flex;align-items:center;justify-content:center;background:#0003;overflow:hidden}.media-thumb{width:100%;height:100%;object-fit:cover}.media-thumb--file{font-size:2rem;color:var(--text-muted, #888)}.media-info{padding:.5rem .75rem;display:flex;flex-direction:column;gap:.15rem}.media-name{font-size:.8rem;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:default}.media-rename-input{width:100%;font-size:.8rem;font-weight:500;padding:.1rem .25rem;border:1px solid var(--primary, #7c6af7);border-radius:3px;background:var(--input-bg, transparent);color:inherit;outline:none}.media-size{font-size:.75rem;color:var(--text-muted, #888)}.media-actions{padding:.5rem .75rem;display:flex;gap:.4rem;border-top:1px solid var(--border-color, rgba(255,255,255,.08))}#admin-topbar{height:60px;display:flex;align-items:center;gap:1rem;padding:0 1.25rem;background:var(--navbar-bg, rgba(0,0,0,.3));border-bottom:1px solid var(--border-color, rgba(255,255,255,.08));position:sticky;top:0;z-index:200;flex-shrink:0}.topbar-brand{display:flex;align-items:center;gap:.5rem;font-weight:700;font-size:.95rem;color:inherit;flex-shrink:0;margin-right:.5rem}.topbar-brand span[data-icon],.topbar-brand svg{color:var(--primary, #5b8cff);width:22px;height:22px}.topbar-brand-text{opacity:.85}.topbar-brand-logo{height:28px;width:auto;display:inline-block;vertical-align:middle;margin-right:.4em;object-fit:contain}.topbar-user{display:flex;align-items:center;gap:.6rem;flex:1}.topbar-user-name{font-size:.875rem;font-weight:500}.topbar-role-badge{font-size:.7rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;padding:.15rem .45rem;border-radius:4px}.topbar-role-badge--admin{background:#5b8cff26;color:#5b8cff}.topbar-role-badge--manager{background:#34d39926;color:#34d399}.topbar-role-badge--editor{background:#fbbf2426;color:#fbbf24}.topbar-role-badge--subscriber{background:#94a3b826;color:#94a3b8}.topbar-actions{display:flex;align-items:center;gap:.5rem;flex-shrink:0}.topbar-action-link{display:flex;align-items:center;gap:.35rem;font-size:.875rem;color:var(--text-muted, #888);text-decoration:none;padding:.4rem .65rem;border-radius:6px;transition:color .15s,background .15s}.topbar-action-link:hover{color:var(--text, #eee);background:#ffffff0f}.topbar-action-link span[data-icon],.topbar-action-link svg{width:16px;height:16px}.topbar-signout{display:flex;align-items:center;gap:.35rem;font-size:.875rem}.topbar-signout span[data-icon],.topbar-signout svg{width:16px;height:16px}.login-wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:1rem;background:var(--body-bg, #0f1117);z-index:100;overflow-y:auto}.login-card{width:100%;max-width:460px}.ob-done-icon{font-size:3rem;color:#34d399;margin-bottom:1rem;text-align:center}.btn-skip{display:block;text-align:center;margin-top:.75rem;font-size:.85rem;color:var(--text-muted, #888);text-decoration:none}.btn-skip:hover{text-decoration:underline}.theme-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.6rem}.theme-swatch{border:2px solid var(--border-color, rgba(255,255,255,.1));border-radius:8px;overflow:hidden;cursor:pointer;transition:border-color .15s,transform .1s}.theme-swatch:hover{border-color:var(--primary, #5b8cff);transform:translateY(-1px)}.theme-swatch.selected{border-color:var(--primary, #5b8cff);box-shadow:0 0 0 2px var(--primary, #5b8cff)}.theme-swatch-preview{height:52px;position:relative;display:flex;align-items:flex-end;padding:.35rem}.theme-swatch-accent{width:28px;height:6px;border-radius:3px}.theme-swatch-label{display:flex;justify-content:space-between;align-items:center;padding:.3rem .45rem;font-size:.7rem;background:var(--card-bg, rgba(255,255,255,.04))}.theme-swatch-mode{color:var(--text-muted, #888);font-size:.65rem}.login-logo{display:flex;align-items:center;gap:.75rem;margin-bottom:1.75rem;font-size:1.5rem}.login-logo span[data-icon]{font-size:2rem;color:var(--primary, #5b8cff)}.login-logo h1{margin:0;font-size:1.5rem;font-weight:700}.login-heading{font-size:1.1rem;font-weight:600;margin-bottom:1.25rem}.btn-block{width:100%}.plugins-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem}.plugin-card{display:flex;flex-direction:column}.plugin-card .card-body{flex:1}.plugin-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:.75rem}.plugin-icon{width:40px;height:40px;border-radius:8px;background:var(--primary-alpha, rgba(91,140,255,.15));color:var(--primary, #5b8cff);display:flex;align-items:center;justify-content:center;flex-shrink:0}.plugin-meta{flex:1;min-width:0}.plugin-name{font-weight:600;margin-bottom:.15rem}.plugin-version{font-size:.75rem;color:var(--text-muted, #888)}.plugin-desc{font-size:.875rem;color:var(--text-muted, #888);margin-bottom:1rem}.plugin-footer{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;border-top:1px solid var(--border-color, rgba(255,255,255,.08))}.plugin-footer-actions{display:flex;align-items:center;gap:.5rem}#admin-sidebar .sidebar-link{position:relative!important}#admin-sidebar .sidebar-badge{position:absolute!important;right:.75rem!important;top:50%!important;transform:translateY(-50%)!important;margin-left:0!important;padding-left:.45rem!important;padding-right:.45rem!important}#admin-sidebar .sidebar-text{padding-right:2rem!important}#admin-sidebar.sidebar-dark{background:var(--dm-surface);border-color:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-header{background:var(--dm-surface-raised);border-color:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-header-title{color:var(--dm-text)}#admin-sidebar.sidebar-dark .sidebar-link{color:var(--dm-text-muted)}#admin-sidebar.sidebar-dark .sidebar-link:hover{color:var(--dm-text);background:var(--dm-surface-overlay)}#admin-sidebar.sidebar-dark .sidebar-link.active{color:var(--dm-primary);background:var(--dm-primary-alpha, rgba(91,140,255,.12));border-left-color:var(--dm-primary)}#admin-sidebar.sidebar-dark .sidebar-heading{color:var(--dm-text-muted)}#admin-sidebar.sidebar-dark .sidebar-divider{background:var(--dm-border)}#admin-sidebar.sidebar-dark .sidebar-footer{background:var(--dm-surface-raised);border-color:var(--dm-border)}.sidebar-heading--collapsible{display:flex!important;align-items:center;justify-content:space-between;cursor:pointer;user-select:none}.sidebar-heading-toggle{display:flex;align-items:center;opacity:.5;flex-shrink:0;transition:transform .2s ease}.sidebar-heading--collapsible.is-collapsed .sidebar-heading-toggle{transform:rotate(-90deg)}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.p-0{padding:0!important}.text-muted{color:var(--text-muted, #888)}@media(max-width:768px){.view-container{padding:1rem}.editor-body{flex-direction:column}.editor-divider{width:auto;height:1px}}.image-editor{display:flex;flex-direction:column;gap:0}.image-editor-toolbar{display:flex;flex-direction:row;align-items:center;gap:.25rem;padding:.5rem .75rem;border-bottom:1px solid var(--border, rgba(255, 255, 255, .1))}.image-editor-sep{display:inline-block;width:1px;height:1.5rem;background:var(--border, rgba(255, 255, 255, .15));margin:0 .25rem;flex-shrink:0}.editor-toolbar-btn.active{color:var(--primary, #7c6af7);background:color-mix(in srgb,var(--primary, #7c6af7) 15%,transparent)}.editor-toolbar-caret{font-size:.6rem;line-height:1;margin-left:2px;opacity:.6}.editor-toolbar-dropdown{position:absolute;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--border-color, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:4px;min-width:160px}.editor-toolbar-dropdown-item{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:none;border:none;color:var(--dm-text, #ddd);padding:7px 10px;border-radius:5px;cursor:pointer;font-size:.85rem;transition:background .12s,color .12s}.editor-toolbar-dropdown-item:hover{background:#ffffff14;color:var(--dm-text-bright, #fff)}.editor-toolbar-dropdown-item span[data-icon],.editor-toolbar-dropdown-item svg{width:15px;height:15px;flex-shrink:0;opacity:.75}.editor-toolbar{position:relative}.editor-effects-dropdown-menu{position:absolute;top:100%;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--border-color, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:6px;min-width:180px;max-height:340px;overflow-y:auto}.editor-effects-category{padding:6px 8px 3px;font-size:.7rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--dm-text-muted, #888)}.editor-effects-item{display:block;width:100%;text-align:left;background:none;border:none;color:var(--dm-text, #ddd);padding:6px 10px;border-radius:5px;cursor:pointer;font-size:.85rem;transition:background .12s,color .12s}.editor-effects-item:hover{background:#ffffff14;color:var(--dm-text-bright, #fff)}.editor-icon-picker{position:absolute;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--border-color, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:8px;width:320px}.editor-icon-picker-search{width:100%;margin-bottom:8px;font-size:.8rem}.editor-spacer-picker{position:absolute;z-index:9999;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, #333);border-radius:8px;padding:12px;width:240px;box-shadow:0 8px 24px #00000073}.editor-spacer-picker-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888);margin-bottom:10px}.editor-spacer-picker-row{display:flex;align-items:center;gap:10px;margin-bottom:12px}.editor-spacer-slider{flex:1;accent-color:var(--primary, #6366f1);cursor:pointer}.editor-spacer-slider-value{font-size:13px;font-weight:600;min-width:38px;text-align:right;color:var(--dm-text, #cdd6f4)}.editor-spacer-insert-btn{width:100%}.editor-icon-picker-size-row{display:flex;align-items:center;gap:8px;padding:0 8px 8px}.editor-icon-picker-size-row label{font-size:11px;color:var(--dm-text-muted, #888);white-space:nowrap}.editor-icon-picker-size{width:70px;font-size:12px;padding:3px 6px}.editor-icon-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(52px,1fr));gap:2px;max-height:280px;overflow-y:auto}.editor-icon-picker-item{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:6px 2px;border-radius:5px;cursor:pointer;background:none;border:none;color:var(--dm-text, #ddd);font-size:.58rem;text-align:center;gap:4px;overflow:hidden;transition:background .12s,color .12s}.editor-icon-picker-item:hover{background:#ffffff14;color:var(--dm-text-bright, #fff)}.editor-icon-picker-item span[data-icon],.editor-icon-picker-item svg{width:16px;height:16px;flex-shrink:0}.editor-icon-picker-item>span:last-child{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.2}.editor-icon-picker-empty{grid-column:1 / -1;text-align:center;color:var(--dm-text-muted, #888);font-size:.8rem;padding:1.5rem}.image-editor-canvas{min-height:320px;max-height:520px;background:#111;overflow:hidden;display:flex;align-items:center;justify-content:center}.image-editor-canvas img{display:block;max-width:100%;max-height:520px}.image-editor-resize{display:flex;flex-direction:row;align-items:center;gap:.5rem;padding:.6rem .75rem;border-top:1px solid var(--border, rgba(255, 255, 255, .1));font-size:.85rem}.image-editor-resize label{font-weight:600;color:var(--text-muted, #888);font-size:.8rem}.image-editor-resize .form-input{width:5rem;padding:.25rem .5rem;font-size:.85rem}.image-editor-footer{display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:.75rem;border-top:1px solid var(--border, rgba(255, 255, 255, .1));gap:.5rem}.image-editor-effects{border-top:1px solid var(--border, rgba(255, 255, 255, .1))}.image-editor-tab-bar{display:flex;flex-direction:row;overflow-x:auto;border-bottom:1px solid var(--border, rgba(255, 255, 255, .1));scrollbar-width:none}.image-editor-tab-bar::-webkit-scrollbar{display:none}.image-editor-tab-btn{display:inline-flex;align-items:center;gap:.35rem;padding:.5rem .85rem;font-size:.8rem;font-weight:500;border:none;border-bottom:2px solid transparent;background:transparent;cursor:pointer;color:var(--text-muted, #888);white-space:nowrap;transition:color .15s,border-color .15s;flex-shrink:0}.image-editor-tab-btn:hover{color:var(--text, #eee)}.image-editor-tab-btn.active{color:var(--primary, #7c6af7);border-bottom-color:var(--primary, #7c6af7)}.image-editor-tab-panel{padding:.75rem;max-height:200px;overflow-y:auto}.ie-presets-grid{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.5rem}.ie-preset-btn.active{color:var(--primary, #7c6af7);background:color-mix(in srgb,var(--primary, #7c6af7) 15%,transparent);border-color:var(--primary, #7c6af7)}.ie-server-note{font-size:.73rem;color:var(--text-muted, #888);font-style:italic;margin:.25rem 0 0;line-height:1.4}.ie-slider-row{display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem}.ie-slider-label{width:6rem;font-size:.8rem;color:var(--text-muted, #888);font-weight:600;flex-shrink:0}.ie-slider{flex:1;accent-color:var(--primary, #7c6af7);cursor:pointer}.ie-slider-val{width:3rem;font-size:.8rem;text-align:right;color:var(--text, #eee);flex-shrink:0}.ie-select{flex:1;font-size:.85rem;padding:.25rem .5rem}.ie-colour-input{width:40px;height:28px;padding:0;border:1px solid var(--border, rgba(255, 255, 255, .15));border-radius:4px;cursor:pointer;background:transparent;flex-shrink:0}.ie-wm-preview-row{display:flex;align-items:center;gap:.5rem;margin:.4rem 0}.ie-wm-preview-img{width:48px;height:48px;object-fit:contain;border-radius:4px;background:var(--surface-2, rgba(255, 255, 255, .06));flex-shrink:0}.ie-wm-preview-name{font-size:.8rem;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text, #eee)}.ie-media-picker{padding:.25rem 0}.ie-media-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:.5rem;max-height:300px;overflow-y:auto}.ie-media-thumb{display:flex;flex-direction:column;align-items:center;gap:.25rem;padding:.4rem;border-radius:6px;cursor:pointer;border:2px solid transparent;transition:border-color .15s,background .15s}.ie-media-thumb:hover{background:var(--surface-2, rgba(255, 255, 255, .08));border-color:var(--primary, #7c6af7)}.ie-media-thumb img{width:72px;height:72px;object-fit:cover;border-radius:4px}.ie-media-thumb-name{font-size:.7rem;color:var(--text-muted, #888);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:72px}.dm-slideover-left,.dm-slideover-right{width:min(40rem,95vw)!important;min-width:min(36rem,100vw)!important;max-width:100vw!important}.dm-slideover-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid var(--dm-border, #333);flex-shrink:0}.dm-slideover-title{margin:0;font-size:1rem;font-weight:600}.dm-slideover-close{padding:.2rem .35rem!important;line-height:1;font-size:.75rem}.dm-slideover-body{flex:1;overflow-y:auto;min-height:0;padding:1.5rem!important;box-sizing:border-box}.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-body):not(.dm-slideover-close){padding:1.5rem!important;box-sizing:border-box}.dm-slideover-body>div{padding-left:0!important;padding-right:0!important}.dm-slideover-body>button.btn:last-child{margin-top:.5rem}.dconfig-textarea{font-family:monospace;min-height:100px;resize:vertical}.dm-editor-line-numbers{white-space:pre}.dm-code-inline{font-family:Fira Code,Courier New,monospace;font-size:.82em;padding:.15em .35em;border-radius:3px;background:var(--dm-surface-subtle, rgba(0, 0, 0, .18));color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(255, 255, 255, .08))}.dm-code-block{font-family:Fira Code,Courier New,monospace;font-size:.82rem;line-height:1.6;padding:.65rem .9rem;border-radius:6px;background:var(--dm-surface-subtle, rgba(0, 0, 0, .18));color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(255, 255, 255, .08));white-space:pre;overflow-x:auto;display:block;margin:.4rem 0}.tabs-centered{text-align:center}.tabs-centered .tab-list{display:inline-flex}.tabs-centered .tab-content{text-align:left}@media(max-width:768px){.view-header{flex-direction:column;align-items:flex-start}.theme-grid{grid-template-columns:repeat(2,1fr)}.nav-item-row{flex-wrap:wrap}.nav-col-icon,.nav-col-parent{display:none}#admin-topbar{padding:0 .75rem}.topbar-brand-text{display:none}.image-editor-toolbar{flex-wrap:wrap}}#topbar-bell{position:relative}.topbar-bell-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;background:var(--dm-color-danger, #dc2626);color:#fff;font-size:10px;font-weight:700;line-height:16px;border-radius:9999px;text-align:center;pointer-events:none}.cb-scaffolder{padding:1rem 1.1rem;background:var(--dm-surface-subtle);border:1px solid var(--dm-accent);border-radius:8px;color:var(--dm-text)}.cb-scaffolder-head{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.cb-scaffolder-head .cb-icon{flex-shrink:0;color:var(--dm-accent)}.cb-scaffolder-head h2{margin:0;font-size:1.05rem;font-weight:700;color:var(--dm-text)}.cb-scaffolder-blurb{margin:0 0 .75rem;font-size:.88rem;line-height:1.5;color:var(--dm-text-muted)}.cb-recipe-list{display:flex;flex-direction:column;gap:.5rem}.cb-recipe-card{display:flex;align-items:flex-start;gap:.75rem;text-align:left;padding:.75rem 1rem;background:var(--dm-surface);border:1px solid var(--dm-border);border-radius:6px;cursor:pointer;transition:border-color .12s;width:100%;color:var(--dm-text)}.cb-recipe-card:hover{border-color:var(--dm-accent)}.cb-recipe-card .cb-icon{flex-shrink:0;font-size:1.5rem;color:var(--dm-accent);margin-top:.1rem}.cb-recipe-meta{flex:1}.cb-recipe-name{font-weight:600;margin-bottom:.15rem;font-size:.95rem;color:var(--dm-text)}.cb-recipe-desc{font-size:.83rem;line-height:1.45;color:var(--dm-text-muted)}.cb-apply-panel{padding:1rem 1.1rem;background:var(--dm-surface);border:1px solid var(--dm-accent);border-radius:8px;color:var(--dm-text)}.cb-apply-head{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.cb-apply-head .cb-icon{color:var(--dm-accent);font-size:1.4rem}.cb-apply-head h3{margin:0;font-size:1rem;font-weight:600;color:var(--dm-text)}.cb-apply-desc,.cb-apply-hint{margin:.25rem 0 .75rem;font-size:.85rem;line-height:1.5;color:var(--dm-text-muted)}.cb-apply-hint{font-size:.75rem;margin-top:.2rem}.cb-apply-field{margin-bottom:.6rem}.cb-apply-field label{display:block;font-size:.82rem;font-weight:600;margin-bottom:.2rem;color:var(--dm-text)}.cb-apply-field input{width:100%;padding:.4rem .55rem;border:1px solid var(--dm-border);border-radius:4px;font-size:.88rem;background:var(--dm-surface);color:var(--dm-text)}.cb-apply-status{margin:.5rem 0;font-size:.85rem;min-height:1.2rem;color:var(--dm-text)}.cb-apply-status.cb-error{color:var(--dm-danger, #dc2626)}.cb-apply-buttons{display:flex;gap:.5rem;justify-content:flex-end;margin-top:.5rem}.cb-success-panel{padding:1rem 1.1rem;background:var(--dm-surface);border:1px solid var(--dm-success, #16a34a);border-radius:8px;color:var(--dm-text)}.cb-success-panel .cb-icon{color:var(--dm-success, #16a34a);font-size:1.4rem}.cb-success-summary{margin:.25rem 0 .5rem;padding-left:1.2rem;font-size:.86rem;line-height:1.6;color:var(--dm-text)}.cb-success-warning{margin:.5rem 0;padding:.5rem .75rem;background:var(--dm-surface-subtle);border-left:3px solid var(--dm-warning, #eab308);border-radius:0 4px 4px 0;font-size:.82rem;color:var(--dm-text-muted)}.cb-success-snippet-label{margin:0 0 .25rem;font-size:.82rem;font-weight:600;color:var(--dm-text)}.cb-success-actions{display:flex;gap:.5rem;margin-top:.5rem;flex-wrap:wrap}.cb-code{position:relative}.cb-code pre{background:var(--dm-surface-subtle);color:var(--dm-text);display:block;padding:.6rem 2.4rem .6rem .75rem;white-space:pre;overflow-x:auto;margin:0;border-radius:6px;font-size:.82em;font-family:monospace;line-height:1.45}.cb-code .cb-copy{position:absolute;top:.35rem;right:.35rem;background:none;border:none;cursor:pointer;opacity:.45;padding:.2rem;line-height:1;color:var(--dm-text)}.cb-code .cb-copy:hover{opacity:1}.cb-intro{padding:.75rem 1rem;border-left:3px solid var(--dm-accent);background:var(--dm-surface-subtle);border-radius:0 6px 6px 0}.cb-intro p{margin:0;font-size:.92rem;line-height:1.5;color:var(--dm-text)}.cb-tutorial-link{display:flex;align-items:center;gap:.6rem;padding:.65rem .85rem;border:1px solid var(--dm-border);border-radius:6px;text-decoration:none;color:var(--dm-text);font-size:.88rem;transition:border-color .12s}.cb-tutorial-link:hover{border-color:var(--dm-accent)}.cb-tutorial-link .cb-icon{flex-shrink:0;color:var(--dm-accent)}.cb-tutorial-link .cb-arrow{color:var(--dm-text-muted);font-size:1.1rem;margin-left:auto}.menu-tree-row{display:grid;grid-template-columns:16px 1.4fr 1.4fr .9fr .7fr .9fr repeat(6,auto);gap:6px;align-items:center;padding:4px 0;transition:opacity .15s ease}.menu-tree-row--hidden{opacity:.5}.menu-tree-grip{cursor:grab;color:var(--dm-text-muted)}.menu-tree{padding:8px 0}.menu-tree-row .me-icon-wrap{display:flex;gap:2px}.menu-tree-row .me-icon-wrap .me-icon{flex:1;min-width:0}.menu-tree-row .me-icon-wrap .me-icon-pick{flex-shrink:0;padding:.2rem .45rem;font-size:.85em}.dm-admin-sidebar{display:flex;flex-direction:column;gap:2px;padding:8px 0}.dm-admin-sidebar details{margin:0}.dm-admin-sidebar details>summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:.5rem;padding:.4rem .75rem;border-radius:4px;user-select:none}.dm-admin-sidebar details>summary::-webkit-details-marker{display:none}.dm-admin-sidebar details>summary:after{content:"\25b8";margin-left:auto;transition:transform .15s ease;color:var(--dm-text-muted, #999);font-size:.85em}.dm-admin-sidebar details[open]>summary:after{transform:rotate(90deg)}.dm-admin-sidebar details>div{padding-left:1rem;border-left:1px solid var(--dm-border-subtle, rgba(0, 0, 0, .08));margin-left:.95rem}.dm-admin-sidebar .sidebar-link{display:flex;align-items:center;gap:.5rem;padding:.35rem .75rem;text-decoration:none;color:var(--dm-text, inherit);border-radius:4px}.dm-admin-sidebar .sidebar-link:hover,.dm-admin-sidebar details>summary:hover{background:var(--dm-bg-subtle, rgba(0, 0, 0, .05))}.dm-admin-sidebar .sidebar-link.active{background:var(--dm-accent-bg, rgba(0, 0, 0, .08));font-weight:600}.dm-admin-sidebar .sidebar-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dm-admin-sidebar [data-icon]{flex-shrink:0}.dm-admin-sidebar-header{display:flex;align-items:center;gap:.5rem;padding:.65rem .85rem;margin:0 0 .5rem;font-weight:600;font-size:.95rem;letter-spacing:.01em;color:var(--dm-text, inherit);border-bottom:1px solid var(--dm-border-subtle, rgba(0, 0, 0, .08))}.dm-admin-sidebar-header [data-icon]{color:var(--dm-text-muted, #999)}.dm-admin-sidebar .sidebar-badge{margin-left:auto;padding:0 .4rem;min-width:1.25rem;height:1.1rem;line-height:1.1rem;text-align:center;font-size:.7rem;font-weight:600;border-radius:999px;background:var(--dm-accent-bg, rgba(0, 0, 0, .12));color:var(--dm-text-muted, #888)}.dm-admin-sidebar .sidebar-link.active .sidebar-badge{background:var(--dm-accent, #4a86e8);color:#fff}.dm-admin-sidebar .sidebar-children{overflow:hidden;transition:max-height .22s ease,opacity .18s ease}.dm-admin-sidebar details:not([open])>.sidebar-children{opacity:0}.dm-admin-sidebar{position:relative}.dm-admin-sidebar-handle{position:absolute;top:0;right:-3px;width:6px;height:100%;cursor:col-resize;background:transparent;z-index:10;transition:background .15s}.dm-admin-sidebar-handle:hover,.dm-admin-sidebar-handle:active{background:var(--dm-accent, #4a86e8);opacity:.35}.dm-admin-sidebar--dark{background:#1a1d24;color:#e3e6eb}.dm-admin-sidebar--dark .sidebar-link,.dm-admin-sidebar--dark summary{color:#e3e6eb}.dm-admin-sidebar--dark .sidebar-link:hover,.dm-admin-sidebar--dark details>summary:hover{background:#ffffff0f}.dm-admin-sidebar--dark .sidebar-link.active{background:#4a86e840}.dm-admin-sidebar--dark .sidebar-badge{background:#ffffff1f;color:#cbd0d8}.dm-admin-sidebar--light{background:#fff;color:#1a1d24;border-right:1px solid rgba(0,0,0,.08)}.dm-admin-sidebar--light .sidebar-link,.dm-admin-sidebar--light summary{color:#1a1d24}.dm-admin-sidebar--light .sidebar-link:hover,.dm-admin-sidebar--light details>summary:hover{background:#0000000a}.dm-admin-sidebar--light .sidebar-link.active{background:#4a86e826}.dm-admin-sidebar--transparent{background:transparent}.page-header{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin:0 0 1.25rem;padding-bottom:.75rem;border-bottom:1px solid var(--dm-border-subtle, rgba(0, 0, 0, .08))}.page-header h2{margin:0;display:flex;align-items:center;gap:.5rem;font-size:1.4rem;line-height:1.2}.page-header h2 [data-icon]{color:var(--dm-text-muted, #888)}.header-actions{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.card+.card,.card+p,p+.card,.card+.toolbar,.toolbar+.card,.card+.grid{margin-top:1rem}.toolbar{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin:0 0 1rem}.btn+.btn,.btn+a.btn,a.btn+.btn,a.btn+a.btn{margin-left:.35rem}[style*=inline-flex] .btn+.btn,[style*=inline-flex] .btn+a.btn,[style*=inline-flex] a.btn+.btn{margin-left:0}.card-body>[id$=-form],.card-body>form{margin:.25rem 0}.card-header{display:flex;align-items:center;gap:.5rem}.card-header h3{margin:0;font-size:1.05rem}.card-header [data-icon]{color:var(--dm-text-muted, #888)}.page-header+p.text-muted{margin:-.5rem 0 1rem}.dm-pe-grid{gap:.75rem 1rem}.dm-pe-grid .form-group{display:flex;flex-direction:column;gap:.25rem;margin:0}.dm-pe-grid .form-group.col-span-2{grid-column:1 / -1}.dm-pe-grid label{font-size:.85rem;font-weight:600;color:var(--dm-text, inherit)}.dm-pe-grid label .required{color:var(--dm-danger, #c33);margin-left:2px}.dm-pe-grid .form-hint{font-size:.75rem;color:var(--dm-text-muted, #888);margin-top:.1rem}.dm-pe-grid .form-input{width:100%;padding:.4rem .6rem}.dm-pe-grid textarea.form-input{resize:vertical;min-height:2.5rem}
|
package/admin/js/api.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const d="/api";function a(){return S.get("auth_token")}function m(){return S.get("auth_refresh_token")}function l(e){S.set("auth_token",e)}function h(){S.remove("auth_token"),S.remove("auth_refresh_token"),S.remove("auth_user")}async function u(){const e=m();if(!e)throw new Error("No refresh token");const o=await fetch(`${d}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(!o.ok)throw h(),R.navigate("/login"),new Error("Token refresh failed");const{token:s}=await o.json();return l(s),s}async function t(e,o={}){let s=a();const r=i=>({...o.body!==void 0?{"Content-Type":"application/json"}:{},...o.headers,...i?{Authorization:`Bearer ${i}`}:{}});let n=await fetch(`${d}${e}`,{...o,headers:r(s)});if(n.status===401&&m())try{s=await u(),n=await fetch(`${d}${e}`,{...o,headers:r(s)})}catch{return}if(!n.ok){const i=await n.json().catch(()=>({error:"Request failed"}));throw new Error(i.error||i.message||`HTTP ${n.status}`)}return n.status===204?null:n.json()}async function y(e,o){const s=a(),r=s?{Authorization:`Bearer ${s}`}:{},n=await fetch(`${d}${e}`,{method:"POST",headers:r,body:o});if(!n.ok){const i=await n.json().catch(()=>({error:"Upload failed"}));throw new Error(i.error||i.message||`HTTP ${n.status}`)}return n.json()}export const api={auth:{setupStatus:()=>t("/auth/setup-status",{method:"GET"}),setup:e=>t("/auth/setup",{method:"POST",body:JSON.stringify(e)}),login:e=>t("/auth/login",{method:"POST",body:JSON.stringify(e)}),me:()=>t("/auth/me",{method:"GET"}),updateMe:e=>t("/auth/me",{method:"PUT",body:JSON.stringify(e)}),refresh:e=>t("/auth/refresh",{method:"POST",body:JSON.stringify({refreshToken:e})}),forgotPassword:e=>t("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e})}),resetPassword:(e,o)=>t("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e,password:o})})},pages:{list:()=>t("/pages",{method:"GET"}),get:e=>t(`/pages${e}`,{method:"GET"}),create:e=>t("/pages",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/pages${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/pages${e}`,{method:"DELETE"}),preview:e=>t("/pages/preview",{method:"POST",body:JSON.stringify({markdown:e})}),tags:()=>t("/pages/tags",{method:"GET"}).then(e=>e.tags||[])},settings:{get:()=>t("/settings",{method:"GET"}),save:e=>t("/settings",{method:"PUT",body:JSON.stringify(e)}),getCustomCss:()=>t("/settings/custom-css",{method:"GET"}),saveCustomCss:e=>t("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:e})})},navigation:{get:()=>t("/navigation",{method:"GET"}),save:e=>t("/navigation",{method:"PUT",body:JSON.stringify(e)})},layouts:{get:()=>t("/layouts",{method:"GET"}),save:e=>t("/layouts",{method:"PUT",body:JSON.stringify(e)}),create:e=>t("/layouts",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/layouts/${e}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/layouts/${e}`,{method:"DELETE"}),getOptions:()=>t("/layouts/options",{method:"GET"}),saveOptions:e=>t("/layouts/options",{method:"PUT",body:JSON.stringify(e)})},media:{list:()=>t("/media",{method:"GET"}),upload:e=>y("/media",e),delete:e=>t(`/media/${encodeURIComponent(e)}`,{method:"DELETE"}),rename:(e,o)=>t(`/media/${encodeURIComponent(e)}`,{method:"PATCH",body:JSON.stringify({newName:o})}),info:e=>t(`/media/${encodeURIComponent(e)}/info`,{method:"GET"}),transform:(e,o)=>t(`/media/${encodeURIComponent(e)}/transform`,{method:"POST",body:JSON.stringify(o)})},users:{list:()=>t("/users",{method:"GET"}),get:e=>t(`/users/${e}`,{method:"GET"}),create:e=>t("/users",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/users/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/users/${e}`,{method:"DELETE"})},plugins:{list:()=>t("/plugins",{method:"GET"}),update:(e,o)=>t(`/plugins/${e}`,{method:"PUT",body:JSON.stringify(o)}),adminConfig:()=>t("/plugins/admin-config",{method:"GET"})},marketplace:{catalogue:()=>t("/plugins/marketplace",{method:"GET"}),install:(e,o)=>t("/plugins/marketplace/install",{method:"POST",body:JSON.stringify({slug:e,version:o})}),uninstall:e=>t(`/plugins/marketplace/${encodeURIComponent(e)}`,{method:"DELETE"})},collections:{list:()=>t("/collections",{method:"GET"}),proStatus:()=>t("/collections/pro-status",{method:"GET"}),get:e=>t(`/collections/${e}`,{method:"GET"}),create:e=>t("/collections",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/collections/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/collections/${e}`,{method:"DELETE"}),listEntries:(e,o={})=>{const s=new URLSearchParams(o).toString();return t(`/collections/${e}/entries${s?"?"+s:""}`,{method:"GET"})},getEntry:(e,o)=>t(`/collections/${e}/entries/${o}`,{method:"GET"}),createEntry:(e,o)=>t(`/collections/${e}/entries`,{method:"POST",body:JSON.stringify({data:o})}),updateEntry:(e,o,s)=>t(`/collections/${e}/entries/${o}`,{method:"PUT",body:JSON.stringify({data:s})}),deleteEntry:(e,o)=>t(`/collections/${e}/entries/${o}`,{method:"DELETE"}),clearEntries:e=>t(`/collections/${e}/entries`,{method:"DELETE"}),import:(e,o)=>t(`/collections/${e}/import`,{method:"POST",body:JSON.stringify({entries:o})}),publicList:(e,o={})=>{const s=new URLSearchParams(o).toString();return t(`/collections/${e}/public${s?"?"+s:""}`,{method:"GET"})},getConnections:()=>t("/collections/connections",{method:"GET"}),saveConnections:e=>t("/collections/connections",{method:"PUT",body:JSON.stringify(e)}),migrateStorage:(e,o)=>t(`/collections/${e}/migrate-storage`,{method:"POST",body:JSON.stringify({storage:o})})},forms:{list:()=>t("/forms",{method:"GET"}),create:e=>t("/forms",{method:"POST",body:JSON.stringify(e)}),get:e=>t(`/forms/${e}`,{method:"GET"}),update:(e,o)=>t(`/forms/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/forms/${e}`,{method:"DELETE"}),listSubmissions:e=>t(`/forms/${e}/submissions`,{method:"GET"}),clearSubmissions:e=>t(`/forms/${e}/submissions`,{method:"DELETE"}),deleteSubmission:(e,o)=>t(`/forms/${e}/submissions/${o}`,{method:"DELETE"}),testEmail:e=>t("/forms/test-email",{method:"POST",body:JSON.stringify({to:e})})},views:{list:()=>t("/views",{method:"GET"}),get:e=>t(`/views/${e}`,{method:"GET"}),create:e=>t("/views",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/views/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/views/${e}`,{method:"DELETE"}),execute:(e,o={})=>{const s=new URLSearchParams(o).toString();return t(`/views/${e}/execute${s?"?"+s:""}`,{method:"GET"})},forCollection:e=>t(`/views/collection/${e}`,{method:"GET"})},actions:{list:()=>t("/actions",{method:"GET"}),get:e=>t(`/actions/${e}`,{method:"GET"}),create:e=>t("/actions",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/actions/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/actions/${e}`,{method:"DELETE"}),execute:(e,o)=>t(`/actions/${e}/execute`,{method:"POST",body:JSON.stringify({entryId:o})}),forCollection:e=>t(`/actions/collection/${e}`,{method:"GET"}),checkAccess:(e,o)=>t(`/actions/${e}/check-access`,{method:"POST",body:JSON.stringify({entryIds:o})})},versions:{list:e=>t(`/versions/list${e}`),get:(e,o)=>t(`/versions/get/${encodeURIComponent(o)}${e}`),create:(e,o)=>t(`/versions/create${e}`,{method:"POST",body:JSON.stringify({label:o})}),restore:(e,o)=>t(`/versions/restore/${encodeURIComponent(o)}${e}`,{method:"POST"}),delete:(e,o)=>t(`/versions/delete/${encodeURIComponent(o)}${e}`,{method:"DELETE"}),bulkDelete:(e,o)=>t(`/versions/bulk-delete${e}`,{method:"POST",body:JSON.stringify({filenames:o})}),prune:(e,o)=>t(`/versions/prune${e}`,{method:"POST",body:JSON.stringify({keep:o})})},blocks:{list:()=>t("/blocks",{method:"GET"}),get:e=>t(`/blocks/${encodeURIComponent(e)}`,{method:"GET"}),put:(e,o)=>t(`/blocks/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/blocks/${encodeURIComponent(e)}`,{method:"DELETE"}),async exportBundle(e){const o=a(),s=await fetch(`${d}/blocks/${encodeURIComponent(e)}/export`,{headers:o?{Authorization:`Bearer ${o}`}:{}});if(!s.ok){const c=await s.json().catch(()=>({}));throw new Error(c.error||`Export failed (${s.status})`)}const r=await s.blob(),n=URL.createObjectURL(r),i=document.createElement("a");i.href=n,i.download=`${e}.dmblock.json`,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(n)},async importBundle(e,{overwrite:o=!1}={}){const s=await fetch(`${d}/blocks/import`,{method:"POST",headers:{"Content-Type":"application/json",...a()?{Authorization:`Bearer ${a()}`}:{}},body:JSON.stringify({...e,overwrite:o})});if(s.status===409){const r=await s.json().catch(()=>({})),n=new Error(r.error||"Block already exists");throw n.code="CONFLICT",n.name=r.name,n}if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||`Import failed (${s.status})`)}return s.json()}},components:{list:()=>t("/components",{method:"GET"}),get:e=>t(`/components/${encodeURIComponent(e)}`,{method:"GET"}),compile:(e,o)=>t("/components/compile",{method:"POST",body:JSON.stringify({name:e,source:o})}),put:(e,o)=>t(`/components/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/components/${encodeURIComponent(e)}`,{method:"DELETE"}),async exportBundle(e){const o=a(),s=await fetch(`${d}/components/${encodeURIComponent(e)}/export`,{headers:o?{Authorization:`Bearer ${o}`}:{}});if(!s.ok){const c=await s.json().catch(()=>({}));throw new Error(c.error||`Export failed (${s.status})`)}const r=await s.blob(),n=URL.createObjectURL(r),i=document.createElement("a");i.href=n,i.download=`${e}.dmcomponent.json`,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(n)},async importBundle(e,{overwrite:o=!1}={}){const s=await fetch(`${d}/components/import`,{method:"POST",headers:{"Content-Type":"application/json",...a()?{Authorization:`Bearer ${a()}`}:{}},body:JSON.stringify({...e,overwrite:o})});if(s.status===409){const r=await s.json().catch(()=>({})),n=new Error(r.error||"Component already exists");throw n.code="CONFLICT",n.name=r.name,n}if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||`Import failed (${s.status})`)}return s.json()}},get:e=>t(e,{method:"GET"}),post:(e,o)=>t(e,{method:"POST",body:JSON.stringify(o)}),put:(e,o)=>t(e,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(e,{method:"DELETE"}),settingsExt:{testEmail:e=>t("/settings/test-email",{method:"POST",body:JSON.stringify({to:e})})},system:{notifications:{list:()=>t("/system/notifications",{method:"GET"}),unreadCount:()=>t("/system/notifications/unread-count",{method:"GET"}),markRead:e=>t(`/system/notifications/${e}/read`,{method:"POST"}),dismiss:e=>t(`/system/notifications/${e}/dismiss`,{method:"POST"}),remove:e=>t(`/system/notifications/${e}`,{method:"DELETE"})}},dashboard:{summary:()=>t("/dashboard/summary",{method:"GET"}),summaryLite:()=>t("/dashboard/summary?lite=1",{method:"GET"})}};export function isAuthenticated(){return!!a()}export function getUser(){return S.get("auth_user")}export function setAuthData({token:e,refreshToken:o,user:s}){e&&l(e),o&&S.set("auth_refresh_token",o),s&&S.set("auth_user",s)}export function logout(){const e=m();e&&fetch(`${d}/auth/logout`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})}).catch(()=>{}),h(),R.navigate("/login")}export{t as apiRequest,u as refreshAccessToken,a as getToken,h as clearAuth};
|
|
1
|
+
const d="/api";function a(){return S.get("auth_token")}function m(){return S.get("auth_refresh_token")}function l(e){S.set("auth_token",e)}function h(){S.remove("auth_token"),S.remove("auth_refresh_token"),S.remove("auth_user")}async function u(){const e=m();if(!e)throw new Error("No refresh token");const o=await fetch(`${d}/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(!o.ok)throw h(),R.navigate("/login"),new Error("Token refresh failed");const{token:s}=await o.json();return l(s),s}async function t(e,o={}){let s=a();const r=i=>({...o.body!==void 0?{"Content-Type":"application/json"}:{},...o.headers,...i?{Authorization:`Bearer ${i}`}:{}});let n=await fetch(`${d}${e}`,{...o,headers:r(s)});if(n.status===401&&m())try{s=await u(),n=await fetch(`${d}${e}`,{...o,headers:r(s)})}catch{return}if(!n.ok){const i=await n.json().catch(()=>({error:"Request failed"}));throw new Error(i.error||i.message||`HTTP ${n.status}`)}return n.status===204?null:n.json()}async function y(e,o){const s=a(),r=s?{Authorization:`Bearer ${s}`}:{},n=await fetch(`${d}${e}`,{method:"POST",headers:r,body:o});if(!n.ok){const i=await n.json().catch(()=>({error:"Upload failed"}));throw new Error(i.error||i.message||`HTTP ${n.status}`)}return n.json()}export const api={auth:{setupStatus:()=>t("/auth/setup-status",{method:"GET"}),setup:e=>t("/auth/setup",{method:"POST",body:JSON.stringify(e)}),login:e=>t("/auth/login",{method:"POST",body:JSON.stringify(e)}),me:()=>t("/auth/me",{method:"GET"}),updateMe:e=>t("/auth/me",{method:"PUT",body:JSON.stringify(e)}),refresh:e=>t("/auth/refresh",{method:"POST",body:JSON.stringify({refreshToken:e})}),forgotPassword:e=>t("/auth/forgot-password",{method:"POST",body:JSON.stringify({email:e})}),resetPassword:(e,o)=>t("/auth/reset-password",{method:"POST",body:JSON.stringify({token:e,password:o})})},pages:{list:()=>t("/pages",{method:"GET"}),get:e=>t(`/pages${e}`,{method:"GET"}),create:e=>t("/pages",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/pages${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/pages${e}`,{method:"DELETE"}),preview:e=>t("/pages/preview",{method:"POST",body:JSON.stringify({markdown:e})}),tags:()=>t("/pages/tags",{method:"GET"}).then(e=>e.tags||[])},settings:{get:()=>t("/settings",{method:"GET"}),save:e=>t("/settings",{method:"PUT",body:JSON.stringify(e)}),getCustomCss:()=>t("/settings/custom-css",{method:"GET"}),saveCustomCss:e=>t("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:e})})},navigation:{get:()=>t("/navigation",{method:"GET"}),save:e=>t("/navigation",{method:"PUT",body:JSON.stringify(e)})},menus:{list:()=>t("/menus",{method:"GET"}),get:e=>t(`/menus/${encodeURIComponent(e)}`,{method:"GET"}),create:e=>t("/menus",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/menus/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/menus/${encodeURIComponent(e)}`,{method:"DELETE"}),duplicate:e=>t(`/menus/${encodeURIComponent(e)}/duplicate`,{method:"POST"})},menuLocations:{get:()=>t("/menu-locations",{method:"GET"}),save:e=>t("/menu-locations",{method:"PUT",body:JSON.stringify(e)}),registry:()=>t("/menu-locations/registry",{method:"GET"})},projects:{list:()=>t("/projects",{method:"GET"}),get:e=>t(`/projects/${encodeURIComponent(e)}`,{method:"GET"}),create:e=>t("/projects",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/projects/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/projects/${encodeURIComponent(e)}`,{method:"DELETE"}),artefacts:e=>t(`/projects/${encodeURIComponent(e)}/artefacts`,{method:"GET"}),untagAll:e=>t(`/projects/${encodeURIComponent(e)}/untag-all`,{method:"POST"})},layouts:{get:()=>t("/layouts",{method:"GET"}),save:e=>t("/layouts",{method:"PUT",body:JSON.stringify(e)}),create:e=>t("/layouts",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/layouts/${e}`,{method:"PUT",body:JSON.stringify(o)}),remove:e=>t(`/layouts/${e}`,{method:"DELETE"}),getOptions:()=>t("/layouts/options",{method:"GET"}),saveOptions:e=>t("/layouts/options",{method:"PUT",body:JSON.stringify(e)})},media:{list:()=>t("/media",{method:"GET"}),upload:e=>y("/media",e),delete:e=>t(`/media/${encodeURIComponent(e)}`,{method:"DELETE"}),rename:(e,o)=>t(`/media/${encodeURIComponent(e)}`,{method:"PATCH",body:JSON.stringify({newName:o})}),info:e=>t(`/media/${encodeURIComponent(e)}/info`,{method:"GET"}),transform:(e,o)=>t(`/media/${encodeURIComponent(e)}/transform`,{method:"POST",body:JSON.stringify(o)})},users:{list:()=>t("/users",{method:"GET"}),get:e=>t(`/users/${e}`,{method:"GET"}),create:e=>t("/users",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/users/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/users/${e}`,{method:"DELETE"})},plugins:{list:()=>t("/plugins",{method:"GET"}),update:(e,o)=>t(`/plugins/${e}`,{method:"PUT",body:JSON.stringify(o)}),adminConfig:()=>t("/plugins/admin-config",{method:"GET"})},marketplace:{catalogue:()=>t("/plugins/marketplace",{method:"GET"}),install:(e,o)=>t("/plugins/marketplace/install",{method:"POST",body:JSON.stringify({slug:e,version:o})}),uninstall:e=>t(`/plugins/marketplace/${encodeURIComponent(e)}`,{method:"DELETE"})},collections:{list:()=>t("/collections",{method:"GET"}),proStatus:()=>t("/collections/pro-status",{method:"GET"}),get:e=>t(`/collections/${e}`,{method:"GET"}),create:e=>t("/collections",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/collections/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/collections/${e}`,{method:"DELETE"}),listEntries:(e,o={})=>{const s=new URLSearchParams(o).toString();return t(`/collections/${e}/entries${s?"?"+s:""}`,{method:"GET"})},getEntry:(e,o)=>t(`/collections/${e}/entries/${o}`,{method:"GET"}),createEntry:(e,o)=>t(`/collections/${e}/entries`,{method:"POST",body:JSON.stringify({data:o})}),updateEntry:(e,o,s)=>t(`/collections/${e}/entries/${o}`,{method:"PUT",body:JSON.stringify({data:s})}),deleteEntry:(e,o)=>t(`/collections/${e}/entries/${o}`,{method:"DELETE"}),clearEntries:e=>t(`/collections/${e}/entries`,{method:"DELETE"}),import:(e,o)=>t(`/collections/${e}/import`,{method:"POST",body:JSON.stringify({entries:o})}),publicList:(e,o={})=>{const s=new URLSearchParams(o).toString();return t(`/collections/${e}/public${s?"?"+s:""}`,{method:"GET"})},getConnections:()=>t("/collections/connections",{method:"GET"}),saveConnections:e=>t("/collections/connections",{method:"PUT",body:JSON.stringify(e)}),migrateStorage:(e,o)=>t(`/collections/${e}/migrate-storage`,{method:"POST",body:JSON.stringify({storage:o})})},forms:{list:()=>t("/forms",{method:"GET"}),create:e=>t("/forms",{method:"POST",body:JSON.stringify(e)}),get:e=>t(`/forms/${e}`,{method:"GET"}),update:(e,o)=>t(`/forms/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/forms/${e}`,{method:"DELETE"}),listSubmissions:e=>t(`/forms/${e}/submissions`,{method:"GET"}),clearSubmissions:e=>t(`/forms/${e}/submissions`,{method:"DELETE"}),deleteSubmission:(e,o)=>t(`/forms/${e}/submissions/${o}`,{method:"DELETE"}),testEmail:e=>t("/forms/test-email",{method:"POST",body:JSON.stringify({to:e})})},views:{list:()=>t("/views",{method:"GET"}),get:e=>t(`/views/${e}`,{method:"GET"}),create:e=>t("/views",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/views/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/views/${e}`,{method:"DELETE"}),execute:(e,o={})=>{const s=new URLSearchParams(o).toString();return t(`/views/${e}/execute${s?"?"+s:""}`,{method:"GET"})},forCollection:e=>t(`/views/collection/${e}`,{method:"GET"})},actions:{list:()=>t("/actions",{method:"GET"}),get:e=>t(`/actions/${e}`,{method:"GET"}),create:e=>t("/actions",{method:"POST",body:JSON.stringify(e)}),update:(e,o)=>t(`/actions/${e}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/actions/${e}`,{method:"DELETE"}),execute:(e,o)=>t(`/actions/${e}/execute`,{method:"POST",body:JSON.stringify({entryId:o})}),forCollection:e=>t(`/actions/collection/${e}`,{method:"GET"}),checkAccess:(e,o)=>t(`/actions/${e}/check-access`,{method:"POST",body:JSON.stringify({entryIds:o})})},versions:{list:e=>t(`/versions/list${e}`),get:(e,o)=>t(`/versions/get/${encodeURIComponent(o)}${e}`),create:(e,o)=>t(`/versions/create${e}`,{method:"POST",body:JSON.stringify({label:o})}),restore:(e,o)=>t(`/versions/restore/${encodeURIComponent(o)}${e}`,{method:"POST"}),delete:(e,o)=>t(`/versions/delete/${encodeURIComponent(o)}${e}`,{method:"DELETE"}),bulkDelete:(e,o)=>t(`/versions/bulk-delete${e}`,{method:"POST",body:JSON.stringify({filenames:o})}),prune:(e,o)=>t(`/versions/prune${e}`,{method:"POST",body:JSON.stringify({keep:o})})},blocks:{list:()=>t("/blocks",{method:"GET"}),get:e=>t(`/blocks/${encodeURIComponent(e)}`,{method:"GET"}),put:(e,o)=>t(`/blocks/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/blocks/${encodeURIComponent(e)}`,{method:"DELETE"}),async exportBundle(e){const o=a(),s=await fetch(`${d}/blocks/${encodeURIComponent(e)}/export`,{headers:o?{Authorization:`Bearer ${o}`}:{}});if(!s.ok){const c=await s.json().catch(()=>({}));throw new Error(c.error||`Export failed (${s.status})`)}const r=await s.blob(),n=URL.createObjectURL(r),i=document.createElement("a");i.href=n,i.download=`${e}.dmblock.json`,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(n)},async importBundle(e,{overwrite:o=!1}={}){const s=await fetch(`${d}/blocks/import`,{method:"POST",headers:{"Content-Type":"application/json",...a()?{Authorization:`Bearer ${a()}`}:{}},body:JSON.stringify({...e,overwrite:o})});if(s.status===409){const r=await s.json().catch(()=>({})),n=new Error(r.error||"Block already exists");throw n.code="CONFLICT",n.name=r.name,n}if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||`Import failed (${s.status})`)}return s.json()}},components:{list:()=>t("/components",{method:"GET"}),get:e=>t(`/components/${encodeURIComponent(e)}`,{method:"GET"}),compile:(e,o)=>t("/components/compile",{method:"POST",body:JSON.stringify({name:e,source:o})}),put:(e,o)=>t(`/components/${encodeURIComponent(e)}`,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(`/components/${encodeURIComponent(e)}`,{method:"DELETE"}),async exportBundle(e){const o=a(),s=await fetch(`${d}/components/${encodeURIComponent(e)}/export`,{headers:o?{Authorization:`Bearer ${o}`}:{}});if(!s.ok){const c=await s.json().catch(()=>({}));throw new Error(c.error||`Export failed (${s.status})`)}const r=await s.blob(),n=URL.createObjectURL(r),i=document.createElement("a");i.href=n,i.download=`${e}.dmcomponent.json`,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(n)},async importBundle(e,{overwrite:o=!1}={}){const s=await fetch(`${d}/components/import`,{method:"POST",headers:{"Content-Type":"application/json",...a()?{Authorization:`Bearer ${a()}`}:{}},body:JSON.stringify({...e,overwrite:o})});if(s.status===409){const r=await s.json().catch(()=>({})),n=new Error(r.error||"Component already exists");throw n.code="CONFLICT",n.name=r.name,n}if(!s.ok){const r=await s.json().catch(()=>({}));throw new Error(r.error||`Import failed (${s.status})`)}return s.json()}},get:e=>t(e,{method:"GET"}),post:(e,o)=>t(e,{method:"POST",body:JSON.stringify(o)}),put:(e,o)=>t(e,{method:"PUT",body:JSON.stringify(o)}),delete:e=>t(e,{method:"DELETE"}),settingsExt:{testEmail:e=>t("/settings/test-email",{method:"POST",body:JSON.stringify({to:e})})},system:{notifications:{list:()=>t("/system/notifications",{method:"GET"}),unreadCount:()=>t("/system/notifications/unread-count",{method:"GET"}),markRead:e=>t(`/system/notifications/${e}/read`,{method:"POST"}),dismiss:e=>t(`/system/notifications/${e}/dismiss`,{method:"POST"}),remove:e=>t(`/system/notifications/${e}`,{method:"DELETE"})}},dashboard:{summary:()=>t("/dashboard/summary",{method:"GET"}),summaryLite:()=>t("/dashboard/summary?lite=1",{method:"GET"})}};export function isAuthenticated(){return!!a()}export function getUser(){return S.get("auth_user")}export function setAuthData({token:e,refreshToken:o,user:s}){e&&l(e),o&&S.set("auth_refresh_token",o),s&&S.set("auth_user",s)}export function logout(){const e=m();e&&fetch(`${d}/auth/logout`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})}).catch(()=>{}),h(),R.navigate("/login")}export{t as apiRequest,u as refreshAccessToken,a as getToken,h as clearAuth};
|
package/admin/js/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{getSidebarConfig as G}from"./config/sidebar-config.js";import{views as Q}from"./views/index.js";import{api as s,getUser as u,isAuthenticated as c,logout as X}from"./api.js";import{installHttpInterceptor as Z}from"./http-interceptor.js";$(()=>{Z(),(async()=>{try{const t=c()?await s.settings.get():null;Domma.theme.init({theme:t?.adminTheme||"charcoal-dark",persist:!0})}catch{Domma.theme.init({theme:"charcoal-dark",persist:!0})}})();const W=["jb-company","jb-agent","jb-candidate"],U=["/job-board","/my-profile"];function f(t){return t&&W.includes(t.role)}function w(t){return U.some(e=>t===e||t.startsWith(e+"/"))}R.use(async(t,e,i)=>{if(t.path==="/login"||t.path==="/reset-password")return i();if(!c()){R.navigate("/login");return}if(f(u())&&!w(t.path)){R.navigate("/job-board");return}i()});let g=null;async function V(){if(!c())return{};try{const[t,e,i,a,o,n,d,p,b,D,r,h,J,q]=await Promise.all([s.pages.list().catch(()=>[]),s.media.list().catch(()=>[]),s.users.list().catch(()=>[]),s.plugins.list().catch(()=>[]),s.collections.list().catch(()=>[]),s.forms.list().catch(()=>[]),s.views.list().catch(()=>[]),s.actions.list().catch(()=>[]),s.blocks.list().catch(()=>[]),s.components.list().catch(()=>[]),s.navigation.get().catch(()=>({})),s.layouts.get().catch(()=>({})),s.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]})),s.system.notifications.unreadCount().catch(()=>({count:0}))]),T=r.items||[],Y=T.filter(y=>!y.hidden).length,L=T.length,z=a.filter(y=>y.enabled).length,j=a.length;return{pages:t.length,media:e.length,users:i.length,plugins:j>0?`${z}/${j}`:null,collections:o.length,forms:n.length,views:d.length,actions:p.length,blocks:b.length,components:D.length,navigation:L>0?`${Y}/${L}`:null,layouts:Object.keys(h).length,roles:(J.entries||[]).length,unreadNotifications:q.count||0}}catch{return{}}}async function k(){try{return(await s.get("/auth/permissions")).permissions||[]}catch{return[]}}async function K(){try{return await s.get("/settings/db-status")}catch{return{configured:!1}}}async function x(t){g&&$("#admin-sidebar").empty();const[e,i,a]=await Promise.all([V(),K(),c()?s.settings.get().catch(()=>({})):Promise.resolve({})]),o=a.adminBrand||{},n=o.title||"CMS Admin",d=o.icon||"layout",p=o.logo||"",b=$("#admin-topbar .topbar-brand");p?b.html(`<img src="${l(p)}" class="topbar-brand-logo" alt="${l(n)}"><span class="topbar-brand-text">${l(n)}</span>`):b.html(`<span data-icon="${l(d)}"></span><span class="topbar-brand-text">${l(n)}</span>`),Domma.icons.scan("#admin-topbar .topbar-brand");const D=N.map(r=>{if(!r.countKey)return r;const h=e[r.countKey];return{...r,badge:h!=null&&h>0?String(h):null}});if(g=Domma.elements.sidebar("#admin-sidebar",{header:{title:n,icon:d},items:G(t,e,D),collapsible:!0,collapseAt:992,push:!0,contentSelector:".dashboard-main",top:"60px"}),O(),F(),i?.configured){const r=$("#admin-sidebar").find(".sidebar-header-title").first();r.length&&!r.find(".db-badge").length&&r.append('<span class="db-badge" title="MongoDB connected" style="display:inline-flex;align-items:center;background:#16a34a;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:9999px;letter-spacing:.5px;margin-left:6px;vertical-align:middle;line-height:1;">DB</span>')}}function O(){$("#admin-sidebar .sidebar-link").each(function(){const t=$(this).find(".sidebar-text").text().trim();t&&E.tooltip(this,{content:t,position:"right"})})}function F(){const t="sidebar_collapsed_sections",e=new Set(S.get(t)||[]);$("#admin-sidebar .sidebar-heading").each(function(){const i=$(this);i.addClass("sidebar-heading--collapsible"),i.append('<span class="sidebar-heading-toggle"><span data-icon="chevron-down"></span></span>');const a=i.text().trim();if(e.has(a)){i.addClass("is-collapsed");let o=i.next();for(;o.length&&!o.hasClass("sidebar-heading")&&!o.hasClass("sidebar-divider");)o.hide(),o=o.next()}i.on("click",function(){const o=!i.hasClass("is-collapsed");i.toggleClass("is-collapsed",o);let n=i.next();for(;n.length&&!n.hasClass("sidebar-heading")&&!n.hasClass("sidebar-divider");)n.toggle(!o),n=n.next();o?e.add(a):e.delete(a),S.set(t,[...e])})}),Domma.icons.scan("#admin-sidebar")}M.subscribe("router:afterChange",({to:t})=>{(t.path==="/login"||t.path==="/reset-password")&&m&&(clearInterval(m),m=null),g&&t.path!=="/login"&&t.path!=="/reset-password"&&g.setActive("#"+t.path)});const H=[{path:"/",view:"dashboard",title:"Dashboard - Domma CMS"},{path:"/pages",view:"pages",title:"Pages - Domma CMS"},{path:"/pages/new",view:"pageEditor",title:"New Page - Domma CMS"},{path:"/pages/edit/*",view:"pageEditor",title:"Edit Page - Domma CMS"},{path:"/media",view:"media",title:"Media - Domma CMS"},{path:"/navigation",view:"navigation",title:"Navigation - Domma CMS"},{path:"/layouts",view:"layouts",title:"Layouts - Domma CMS"},{path:"/settings",view:"settings",title:"Settings - Domma CMS"},{path:"/users",view:"users",title:"Users - Domma CMS"},{path:"/users/new",view:"userEditor",title:"New User - Domma CMS"},{path:"/users/edit/:id",view:"userEditor",title:"Edit User - Domma CMS"},{path:"/plugins",view:"plugins",title:"Plugins - Domma CMS"},{path:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - Domma CMS"},{path:"/api-reference",view:"apiReference",title:"API Reference - Domma CMS"},{path:"/collections",view:"collections",title:"Collections - Domma CMS"},{path:"/collections/new",view:"collectionEditor",title:"New Collection - Domma CMS"},{path:"/collections/edit/:slug",view:"collectionEditor",title:"Edit Collection - Domma CMS"},{path:"/collections/:slug/entries",view:"collectionEntries",title:"Entries - Domma CMS"},{path:"/forms",view:"forms",title:"Forms - Domma CMS"},{path:"/forms/new",view:"formEditor",title:"New Form - Domma CMS"},{path:"/forms/edit/:slug",view:"formEditor",title:"Edit Form - Domma CMS"},{path:"/forms/:slug/submissions",view:"formSubmissions",title:"Submissions - Domma CMS"},{path:"/views",view:"viewsList",title:"Views - Domma CMS"},{path:"/views/new",view:"viewEditor",title:"New View - Domma CMS"},{path:"/views/edit/:slug",view:"viewEditor",title:"Edit View - Domma CMS"},{path:"/views/:slug/preview",view:"viewPreview",title:"View Preview - Domma CMS"},{path:"/actions",view:"actionsList",title:"Actions - Domma CMS"},{path:"/actions/new",view:"actionEditor",title:"New Action - Domma CMS"},{path:"/actions/edit/:slug",view:"actionEditor",title:"Edit Action - Domma CMS"},{path:"/pro/docs",view:"proDocs",title:"Pro Documentation - Domma CMS"},{path:"/blocks",view:"blocks",title:"Blocks - Domma CMS"},{path:"/blocks/new",view:"blockEditor",title:"New Block - Domma CMS"},{path:"/blocks/edit/:name",view:"blockEditor",title:"Edit Block - Domma CMS"},{path:"/components",view:"components",title:"Components - Domma CMS"},{path:"/components/new",view:"componentEditor",title:"New Component - Domma CMS"},{path:"/components/edit/:name",view:"componentEditor",title:"Edit Component - Domma CMS"},{path:"/my-profile",view:"myProfile",title:"My Profile - Domma CMS"},{path:"/roles",view:"roles",title:"Roles & Permissions - Domma CMS"},{path:"/roles/edit/:id",view:"roleEditor",title:"Edit Role - Domma CMS"},{path:"/effects",view:"effects",title:"Effects - Domma CMS"},{path:"/system/notifications",view:"notifications",title:"Notifications - Domma CMS"},{path:"/login",view:"login",title:"Sign in - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}},{path:"/reset-password",view:"login",title:"Reset Password - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}}];M.subscribe("router:afterChange",async({to:t,from:e})=>{if(!(t.path==="/login"||t.path==="/reset-password")){if(f(u())&&!w(t.path)){R.navigate("/job-board");return}if($("#admin-sidebar").show(),$("#admin-topbar").show(),e?.path==="/login"||e?.path==="/reset-password"){$("#topbar-user-name").remove(),await _();const i=await k();x(i);try{const a=await s.system.notifications.list().catch(()=>[]),o=(Array.isArray(a)?a:[]).filter(n=>n.unread&&["warning","critical"].includes(n.data?.severity)).slice(0,3);for(const n of o){const d=l(n.data?.title||""),p=l((n.data?.body||"").slice(0,120));E.toast(`${d} \u2014 ${p}`,{type:n.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}B()}}),M.subscribe("router:afterChange",()=>{setTimeout(()=>{$(".btn-primary, .btn-danger").length&&Domma.effects.reveal(".btn-primary, .btn-danger",{animation:"fade",stagger:40,duration:300})},50)});const P="cms_card_states",v=S.get(P)||{};M.subscribe("router:afterChange",()=>{setTimeout(()=>{$("#view-container .card-collapsible").each(function(){const t=$(this).find(".card-header h2, .card-header h3").first().text().trim();t&&v[t]==="collapsed"&&$(this).addClass("card-collapsed")})},200)}),$("#view-container").on("click",".card-collapsible .card-header",function(t){if($(t.target).closest("button, a").length)return;const e=$(this).closest(".card");e.toggleClass("card-collapsed");const i=$(this).find("h2, h3").first().text().trim();i&&(v[i]=e.hasClass("card-collapsed")?"collapsed":"open",S.set(P,v))}),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash==="#/login"||window.location.hash.startsWith("#/reset-password"))return;const e=document.querySelector("#view-container .view-header button.btn-primary");e&&(t.preventDefault(),e.click())});const A={...Q},C=[...H];let N=[];async function _(){if(c())try{const t=await s.plugins.adminConfig();N=t.sidebar||[],t.routes?.length&&C.push(...t.routes);for(const[e,i]of Object.entries(t.views||{}))try{const a=await import(`/plugins/${i.entry}`);A[e]=a[i.exportName]}catch{}for(const{id:e,href:i}of t.css||[])if(!document.getElementById(e)){const a=document.createElement("link");a.id=e,a.rel="stylesheet",a.href=i,document.head.appendChild(a)}}catch{}}function B(){const t=u();if(!t||$("#topbar-user-name").length)return;const i={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role;$("#topbar-user").html(`
|
|
2
|
-
<span id="topbar-user-name" class="topbar-user-name">${
|
|
3
|
-
<span class="topbar-role-badge topbar-role-badge--${
|
|
1
|
+
import{views as W}from"./views/index.js";import{api as r,getUser as l,isAuthenticated as c,logout as B}from"./api.js";import{installHttpInterceptor as I}from"./http-interceptor.js";$(()=>{I(),(async()=>{try{const t=c()?await r.settings.get():null;Domma.theme.init({theme:t?.adminTheme||"charcoal-dark",persist:!0})}catch{Domma.theme.init({theme:"charcoal-dark",persist:!0})}})();const k=["jb-company","jb-agent","jb-candidate"],A=["/job-board","/my-profile"];function u(t){return t&&k.includes(t.role)}function h(t){return A.some(e=>t===e||t.startsWith(e+"/"))}R.use(async(t,e,o)=>{if(t.path==="/login"||t.path==="/reset-password")return o();if(!c()){R.navigate("/login");return}if(u(l())&&!h(t.path)){R.navigate("/job-board");return}o()});async function v(){try{return(await r.get("/auth/permissions")).permissions||[]}catch{return[]}}async function b(t){const{renderAdminSidebar:e}=await import("./lib/sidebar-renderer.js");await e({mount:"#admin-sidebar",permissions:t})}M.subscribe("router:afterChange",({to:t})=>{(t.path==="/login"||t.path==="/reset-password")&&n&&(clearInterval(n),n=null)});const L=[{path:"/",view:"dashboard",title:"Dashboard - Domma CMS"},{path:"/pages",view:"pages",title:"Pages - Domma CMS"},{path:"/pages/new",view:"pageEditor",title:"New Page - Domma CMS"},{path:"/pages/edit/*",view:"pageEditor",title:"Edit Page - Domma CMS"},{path:"/media",view:"media",title:"Media - Domma CMS"},{path:"/menus",view:"menus",title:"Menus - Domma CMS"},{path:"/menus/new",view:"menuEditor",title:"New Menu - Domma CMS"},{path:"/menus/edit/:slug",view:"menuEditor",title:"Edit Menu - Domma CMS"},{path:"/menu-locations",view:"menuLocations",title:"Menu Locations - Domma CMS"},{path:"/navigation",view:"menusRedirect",title:"Menus - Domma CMS"},{path:"/layouts",view:"layouts",title:"Layouts - Domma CMS"},{path:"/settings",view:"settings",title:"Settings - Domma CMS"},{path:"/users",view:"users",title:"Users - Domma CMS"},{path:"/users/new",view:"userEditor",title:"New User - Domma CMS"},{path:"/users/edit/:id",view:"userEditor",title:"Edit User - Domma CMS"},{path:"/plugins",view:"plugins",title:"Plugins - Domma CMS"},{path:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - Domma CMS"},{path:"/api-reference",view:"apiReference",title:"API Reference - Domma CMS"},{path:"/collections",view:"collections",title:"Collections - Domma CMS"},{path:"/collections/new",view:"collectionEditor",title:"New Collection - Domma CMS"},{path:"/collections/edit/:slug",view:"collectionEditor",title:"Edit Collection - Domma CMS"},{path:"/collections/:slug/entries",view:"collectionEntries",title:"Entries - Domma CMS"},{path:"/forms",view:"forms",title:"Forms - Domma CMS"},{path:"/forms/new",view:"formEditor",title:"New Form - Domma CMS"},{path:"/forms/edit/:slug",view:"formEditor",title:"Edit Form - Domma CMS"},{path:"/forms/:slug/submissions",view:"formSubmissions",title:"Submissions - Domma CMS"},{path:"/views",view:"viewsList",title:"Views - Domma CMS"},{path:"/views/new",view:"viewEditor",title:"New View - Domma CMS"},{path:"/views/edit/:slug",view:"viewEditor",title:"Edit View - Domma CMS"},{path:"/views/:slug/preview",view:"viewPreview",title:"View Preview - Domma CMS"},{path:"/actions",view:"actionsList",title:"Actions - Domma CMS"},{path:"/actions/new",view:"actionEditor",title:"New Action - Domma CMS"},{path:"/actions/edit/:slug",view:"actionEditor",title:"Edit Action - Domma CMS"},{path:"/pro/docs",view:"proDocs",title:"Pro Documentation - Domma CMS"},{path:"/blocks",view:"blocks",title:"Blocks - Domma CMS"},{path:"/blocks/new",view:"blockEditor",title:"New Block - Domma CMS"},{path:"/blocks/edit/:name",view:"blockEditor",title:"Edit Block - Domma CMS"},{path:"/components",view:"components",title:"Components - Domma CMS"},{path:"/components/new",view:"componentEditor",title:"New Component - Domma CMS"},{path:"/components/edit/:name",view:"componentEditor",title:"Edit Component - Domma CMS"},{path:"/my-profile",view:"myProfile",title:"My Profile - Domma CMS"},{path:"/roles",view:"roles",title:"Roles & Permissions - Domma CMS"},{path:"/roles/edit/:id",view:"roleEditor",title:"Edit Role - Domma CMS"},{path:"/effects",view:"effects",title:"Effects - Domma CMS"},{path:"/system/notifications",view:"notifications",title:"Notifications - Domma CMS"},{path:"/projects",view:"projects",title:"Projects - Domma CMS"},{path:"/projects/new",view:"projectEditor",title:"New Project - Domma CMS"},{path:"/projects/edit/:slug",view:"projectEditor",title:"Edit Project - Domma CMS"},{path:"/projects/:slug/settings",view:"projectSettings",title:"Project settings - Domma CMS"},{path:"/projects/:slug/pages",view:"pages",title:"Project pages - Domma CMS"},{path:"/projects/:slug/collections",view:"collections",title:"Project collections - Domma CMS"},{path:"/projects/:slug/forms",view:"forms",title:"Project forms - Domma CMS"},{path:"/projects/:slug/actions",view:"actionsList",title:"Project actions - Domma CMS"},{path:"/projects/:slug/menus",view:"menus",title:"Project menus - Domma CMS"},{path:"/projects/:slug/blocks",view:"blocks",title:"Project blocks - Domma CMS"},{path:"/projects/:slug/views",view:"viewsList",title:"Project views - Domma CMS"},{path:"/projects/:slug/roles",view:"roles",title:"Project roles - Domma CMS"},{path:"/projects/:slug/users",view:"users",title:"Project users - Domma CMS"},{path:"/projects/:slug",view:"projectDetail",title:"Project - Domma CMS"},{path:"/login",view:"login",title:"Sign in - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}},{path:"/reset-password",view:"login",title:"Reset Password - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}}];M.subscribe("router:afterChange",async({to:t,from:e})=>{if(!(t.path==="/login"||t.path==="/reset-password")){if(u(l())&&!h(t.path)){R.navigate("/job-board");return}if($("#admin-sidebar").show(),$("#admin-topbar").show(),e?.path==="/login"||e?.path==="/reset-password"){$("#topbar-user-name").remove(),await j();const o=await v();b(o);try{const i=await r.system.notifications.list().catch(()=>[]),s=(Array.isArray(i)?i:[]).filter(a=>a.unread&&["warning","critical"].includes(a.data?.severity)).slice(0,3);for(const a of s){const p=m(a.data?.title||""),g=m((a.data?.body||"").slice(0,120));E.toast(`${p} \u2014 ${g}`,{type:a.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}y()}}),M.subscribe("router:afterChange",()=>{setTimeout(()=>{$(".btn-primary, .btn-danger").length&&Domma.effects.reveal(".btn-primary, .btn-danger",{animation:"fade",stagger:40,duration:300})},50)});const N=["#field-project","#menu-project","#collection-project","#action-project","#view-project","#block-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function _(t){return/^\/(pages|menus|collections|forms|actions|views|blocks|users|roles)\/(new|edit\/)/.test(t)}M.subscribe("router:afterChange",({to:t})=>{if(!t||!_(t.path))return;const o=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!o||!/\/new$/.test(t.path))return;let i=0;const s=60,a=1500,p=()=>{i+=s;for(const g of N){const d=document.querySelector(g);if(!d)continue;if(Array.from(d.options||[]).find(T=>T.value===o)){d.value=o,d.dispatchEvent(new Event("change",{bubbles:!0}));return}}i<a&&setTimeout(p,s)};setTimeout(p,s)});const C="cms_card_states",w=S.get(C)||{};M.subscribe("router:afterChange",()=>{setTimeout(()=>{$("#view-container .card-collapsible").each(function(){const t=$(this).find(".card-header h2, .card-header h3").first().text().trim();t&&w[t]==="collapsed"&&$(this).addClass("card-collapsed")})},200)}),$("#view-container").on("click",".card-collapsible .card-header",function(t){if($(t.target).closest("button, a").length)return;const e=$(this).closest(".card");e.toggleClass("card-collapsed");const o=$(this).find("h2, h3").first().text().trim();o&&(w[o]=e.hasClass("card-collapsed")?"collapsed":"open",S.set(C,w))}),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash==="#/login"||window.location.hash.startsWith("#/reset-password"))return;const e=document.querySelector("#view-container .view-header button.btn-primary");e&&(t.preventDefault(),e.click())});const D={...W},f=[...L];async function j(){if(c())try{const t=await r.plugins.adminConfig();t.routes?.length&&f.push(...t.routes);for(const[e,o]of Object.entries(t.views||{}))try{const i=await import(`/plugins/${o.entry}`);D[e]=i[o.exportName]}catch{}for(const{id:e,href:o}of t.css||[])if(!document.getElementById(e)){const i=document.createElement("link");i.id=e,i.rel="stylesheet",i.href=o,document.head.appendChild(i)}}catch{}}function y(){const t=l();if(!t||$("#topbar-user-name").length)return;const o={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role;$("#topbar-user").html(`
|
|
2
|
+
<span id="topbar-user-name" class="topbar-user-name">${m(t.name)}</span>
|
|
3
|
+
<span class="topbar-role-badge topbar-role-badge--${m(t.role)}">${o}</span>
|
|
4
4
|
`),$("#topbar-actions").html(`
|
|
5
5
|
<a href="#/system/notifications" id="topbar-bell" class="topbar-action-link" data-tooltip="Notifications" data-tooltip-placement="bottom" hidden>
|
|
6
6
|
<span data-icon="bell"></span>
|
|
@@ -18,4 +18,4 @@ import{getSidebarConfig as G}from"./config/sidebar-config.js";import{views as Q}
|
|
|
18
18
|
<span data-icon="log-out"></span>
|
|
19
19
|
<span>Sign out</span>
|
|
20
20
|
</a>
|
|
21
|
-
`),$("#topbar-logout-btn").on("click",
|
|
21
|
+
`),$("#topbar-logout-btn").on("click",i=>{i.preventDefault(),B()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),P(),n||(n=setInterval(P,6e4))}function m(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}let n=null;async function P(){if(c())try{const{count:t}=await r.system.notifications.unreadCount();$("#topbar-bell").removeAttr("hidden");const e=$("#topbar-bell-badge");t>0?e.text(t>99?"99+":String(t)).removeAttr("hidden"):e.attr("hidden","").text("")}catch{}}(async()=>{if(!c()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash="#/login";else{const i=(window.location.hash||"#/").slice(1)||"/";if(u(l())&&!h(i)&&(window.location.hash="#/job-board"),await j(),l()){const a=await v();await b(a),y()}}R.init({container:"#view-container",routes:f,views:D,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}});const t=R._extractParams.bind(R);R._extractParams=function(i,s){if(i.endsWith("/*")){const a=i.slice(0,-2);return s.startsWith(a+"/")?{}:null}return t(i,s)};const e=(window.location.hash||"#/").slice(1)||"/";f.filter(i=>i.path.endsWith("/*")).some(i=>e.startsWith(i.path.slice(0,-2)+"/"))&&R._handleRouteChange()})()});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function getSidebarConfig(
|
|
1
|
+
export function getSidebarConfig(){return console.warn("[sidebar-config] getSidebarConfig() is deprecated. Use renderAdminSidebar() from lib/sidebar-renderer.js."),[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function openCrudTutorial(t=null){const e=E.slideover({title:"CRUD shortcut",size:"md",position:"right"}),n=document.createElement("div");n.className="crud-tutorial",n.style.cssText="padding:1rem;display:flex;flex-direction:column;gap:1.25rem;",n.appendChild(g()),n.appendChild(v(e)),n.appendChild(y(e,t)),e.setContent(n),e.open(),window.Domma?.icons?.scan&&Domma.icons.scan(n)}function v(t){const e=document.createElement("a");e.href="#/tutorials",e.className="cb-tutorial-link",e.addEventListener("click",()=>{try{t.close()}catch{}});const n=document.createElement("span");n.setAttribute("data-icon","book-open"),n.className="cb-icon",e.appendChild(n);const o=document.createElement("span"),a=document.createElement("strong");a.textContent="Open the full tutorial \u2192 ",o.appendChild(a),o.appendChild(document.createTextNode('"Building a CRUD App" in Tutorials')),e.appendChild(o);const c=document.createElement("span");return c.className="cb-arrow",c.textContent="\u203A",e.appendChild(c),e}function g(){const t=document.createElement("div");t.className="cb-intro";const e=document.createElement("p");return e.textContent="Drop a working Collection + Form + Actions onto this page in one click. Pick a starter template below \u2014 the slugs are editable, and we'll insert the matching shortcode at your cursor when it's done.",t.appendChild(e),t}function k(t){const e=document.createElement("div");e.className="cb-code";const n=document.createElement("pre");n.textContent=t,e.appendChild(n);const o=document.createElement("button");o.type="button",o.title="Copy",o.className="cb-copy";const a=document.createElement("span");return a.setAttribute("data-icon","copy"),o.appendChild(a),o.addEventListener("click",()=>{const c=document.createElement("textarea");c.value=t,c.style.cssText="position:fixed;opacity:0;",document.body.appendChild(c),c.select(),document.execCommand("copy"),c.remove(),E.toast("Copied!",{type:"success",duration:1200})}),e.appendChild(o),e}export function mountScaffolder(t,e=null,n=null){t.appendChild(y(e,n)),window.Domma?.icons?.scan&&Domma.icons.scan(t)}function y(t,e){const n=document.createElement("section");n.className="cb-scaffolder";const o=document.createElement("div");o.className="cb-scaffolder-head";const a=document.createElement("span");a.setAttribute("data-icon","sparkles"),a.className="cb-icon",o.appendChild(a);const c=document.createElement("h2");c.textContent="Or skip ahead \u2014 scaffold a working system in one click",o.appendChild(c),n.appendChild(o);const l=document.createElement("p");l.className="cb-scaffolder-blurb",l.textContent="Pick a starter template; we'll create the Collection, Form, and Actions for you. Rename the slugs if you want, then hit Apply. You can also paste the ready-made shortcode into this page.",n.appendChild(l);const i=document.createElement("div");return i.className="cb-recipe-list",i.textContent="Loading templates\u2026",n.appendChild(i),fetch("/api/scaffold/recipes",{headers:w()}).then(r=>r.json()).then(({recipes:r})=>{if(i.textContent="",!r?.length){i.textContent="No bundled templates available.";return}for(const h of r)i.appendChild(A(h,n,t,e))}).catch(()=>{i.textContent="Couldn't load templates \u2014 admin permissions required."}),n}function A(t,e,n,o){const a=document.createElement("button");a.type="button",a.className="cb-recipe-card";const c=document.createElement("span");c.setAttribute("data-icon",t.icon||"box"),c.className="cb-icon",a.appendChild(c);const l=document.createElement("div");l.className="cb-recipe-meta";const i=document.createElement("div");i.className="cb-recipe-name",i.textContent=t.name;const r=document.createElement("div");return r.className="cb-recipe-desc",r.textContent=t.description||"",l.appendChild(i),l.appendChild(r),a.appendChild(l),a.addEventListener("click",()=>D(t,e,n,o)),a}function D(t,e,n,o){const a=document.createElement("div");a.className="cb-apply-panel";const c=document.createElement("div");c.className="cb-apply-head";const l=document.createElement("span");l.setAttribute("data-icon",t.icon||"box"),l.className="cb-icon",c.appendChild(l);const i=document.createElement("h3");i.textContent=t.name,c.appendChild(i),a.appendChild(c);const r=document.createElement("p");r.className="cb-apply-desc",r.textContent=t.description||"",a.appendChild(r);const h={};for(const s of t.options||[]){const u=document.createElement("div");u.className="cb-apply-field";const C=document.createElement("label");C.textContent=s.label||s.name;const f=document.createElement("input");if(f.type="text",f.value=s.default||"",u.appendChild(C),u.appendChild(f),s.hint){const N=document.createElement("div");N.className="cb-apply-hint",N.textContent=s.hint,u.appendChild(N)}a.appendChild(u),h[s.name]=f}const d=document.createElement("div");d.className="cb-apply-status",a.appendChild(d);const b=document.createElement("div");b.className="cb-apply-buttons";const p=document.createElement("button");p.type="button",p.className="btn btn-outline",p.textContent="Cancel",p.addEventListener("click",()=>{const s=y(n,o);a.replaceWith(s),window.Domma?.icons?.scan&&Domma.icons.scan(s)});const m=document.createElement("button");m.type="button",m.className="btn btn-primary",m.textContent="Apply template",m.addEventListener("click",async()=>{m.disabled=!0,p.disabled=!0,d.textContent="Creating collection, form, and actions\u2026",d.classList.remove("cb-error");const s={};for(const[u,C]of Object.entries(h))s[u]=C.value.trim();try{const u=await fetch("/api/scaffold/apply",{method:"POST",headers:{"Content-Type":"application/json",...w()},body:JSON.stringify({recipe:t.slug,options:s})}),C=await u.json();if(!u.ok){d.classList.add("cb-error"),Array.isArray(C.conflicts)?d.textContent=`Conflicts: ${C.conflicts.join("; ")}`:d.textContent=C.error||"Apply failed.",m.disabled=!1,p.disabled=!1;return}const f=L(t,C,n,o,e);a.replaceWith(f),window.Domma?.icons?.scan&&Domma.icons.scan(f)}catch(u){d.classList.add("cb-error"),d.textContent=u.message||"Network error.",m.disabled=!1,p.disabled=!1}}),b.appendChild(p),b.appendChild(m),a.appendChild(b),e.replaceWith(a),window.Domma?.icons?.scan&&Domma.icons.scan(a)}function L(t,e,n,o,a){const c=document.createElement("div");c.className="cb-success-panel";const l=document.createElement("div");l.className="cb-apply-head";const i=document.createElement("span");i.setAttribute("data-icon","check-circle"),i.className="cb-icon",l.appendChild(i);const r=document.createElement("h3");r.textContent=`${t.name} \u2014 scaffolded`,l.appendChild(r),c.appendChild(l);const h=document.createElement("ul");if(h.className="cb-success-summary",e.created.collection&&h.appendChild(x(`Collection: ${e.created.collection}`)),e.created.form&&h.appendChild(x(`Form: ${e.created.form}`)),e.created.actions?.length&&h.appendChild(x(`Actions: ${e.created.actions.join(", ")}`)),c.appendChild(h),e.warnings?.length){const p=document.createElement("div");p.className="cb-success-warning",p.textContent=e.warnings.join(" \xB7 "),c.appendChild(p)}if(e.snippet){const p=document.createElement("p");p.className="cb-success-snippet-label",p.textContent="Ready-made shortcode:",c.appendChild(p),c.appendChild(k(e.snippet));const m=document.createElement("div");if(m.className="cb-success-actions",o&&n){const s=document.createElement("button");s.type="button",s.className="btn btn-primary",s.textContent="Insert into this page",s.addEventListener("click",()=>{T(o,e.snippet),E.toast("Inserted at cursor",{type:"success",duration:1500}),n.close()}),m.appendChild(s)}else{const s=document.createElement("a");s.className="btn btn-primary",s.textContent="Open a new page to paste this",s.href="#/pages/new",m.appendChild(s)}c.appendChild(m)}const d=document.createElement("button");d.type="button",d.className="btn btn-outline",d.textContent="Scaffold another",d.addEventListener("click",()=>{c.replaceWith(y(n,o)),window.Domma?.icons?.scan&&Domma.icons.scan(a.parentElement||document.body)});const b=c.querySelector(".cb-success-actions");return b?b.appendChild(d):c.appendChild(d),c}function x(t){const e=document.createElement("li");return e.textContent=t,e}function T(t,e){const n=t.selectionStart||0,o=t.selectionEnd||0,a=t.value||"";t.value=a.slice(0,n)+e+a.slice(o);const c=n+e.length;t.selectionStart=t.selectionEnd=c,t.focus(),t.dispatchEvent(new Event("input",{bubbles:!0}))}function w(){try{const t=typeof S<"u"&&S.get?S.get("auth_token"):null;return t?{Authorization:`Bearer ${t}`}:{}}catch{return{}}}
|