domma-cms 1.2.0 → 1.3.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/admin/css/admin.css +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/templates/docs/usage-dashboard.html +82 -0
- package/admin/js/templates/documentation.html +4 -0
- package/admin/js/views/dashboard-board.js +49 -16
- package/admin/js/views/doc-pages.js +1 -1
- package/package.json +1 -1
- package/server/routes/api/auth.js +5 -1
- package/server/routes/api/sidebar.js +9 -0
- package/server/routes/api/widgets.js +82 -7
- package/server/server.js +1 -0
- package/server/services/dashboardSettings.js +111 -0
- package/server/services/docs.js +1 -0
- package/server/services/siteGitignore.js +1 -0
- package/server/services/widgets.js +56 -0
package/admin/css/admin.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.dashboard-layout{display:flex;flex-direction:column;height:100vh;min-height:100vh;overflow:hidden}.dashboard-wrapper{display:flex;flex:1;min-height:0;overflow:hidden}.dashboard-main{flex:1;min-width:0;overflow-y:auto}#admin-sidebar.dashboard-sidebar{display:flex;flex-direction:column;flex-shrink:0;overflow:hidden}#admin-sidebar .dm-admin-sidebar{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--dm-border) transparent}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar{width:8px}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-track{background:transparent}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-thumb{background:var(--dm-border);border-radius:4px}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-thumb:hover{background:var(--dm-text-muted)}.view-container{padding:1.5rem 2rem;max-width:1200px}.view-container:has(>.dm-plugin-shell){max-width:100%}.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:.75rem;flex-wrap:wrap}.view-header-actions .btn-primary,.view-header-actions .btn-success,.view-header-actions .btn-danger{margin-left:.5rem}.stat-card .card-body{display:flex;align-items:center;gap:1rem}.stat-icon{width:44px;height:44px;border-radius:8px;background:color-mix(in srgb,var(--dm-primary) 14%,transparent);color:var(--dm-primary, #5b8cff);display:flex;align-items:center;justify-content:center;font-size:1.25rem;flex-shrink:0}.stat-icon--success{background:color-mix(in srgb,var(--dm-success) 15%,transparent);color:var(--dm-success)}.stat-icon--warning{background:color-mix(in srgb,var(--dm-warning) 15%,transparent);color:var(--dm-warning)}.stat-value{font-size:1.75rem;font-weight:700;line-height:1}.stat-label{font-size:.8rem;color:var(--dm-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(--dm-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(#editor-meta-tabs)>.dm-plugin-shell{flex:1;min-height:0;display:flex;flex-direction:column}.view-container:has(#editor-meta-tabs)>.dm-plugin-shell>.dm-plugin-banner,.view-container:has(#editor-meta-tabs) .pex-bar{flex:none}.view-container:has(#editor-meta-tabs)>.dm-plugin-shell>.dm-plugin-body{flex:1;min-height:0;display:flex;flex-direction:column}.view-container:has(#block-editor-body){max-width:100%}.view-container:has(#ep-editor-tabs){max-width:100%}.ep-editor-grid{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,1fr) minmax(0,1.3fr);gap:1rem;align-items:start}.ep-editor-grid>.ep-tryit-col,.ep-editor-grid>.ep-results-col{position:sticky;top:1rem;display:flex;flex-direction:column;gap:1rem;min-width:0}@media(max-width:1500px){.ep-editor-grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.ep-editor-grid>:first-child{grid-column:1 / -1}.ep-editor-grid>.ep-tryit-col,.ep-editor-grid>.ep-results-col{position:static}}@media(max-width:1024px){.ep-editor-grid{grid-template-columns:minmax(0,1fr)}}#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}.live-preview-bar{display:flex;align-items:center;gap:var(--dm-space-4);flex-wrap:wrap;padding:var(--dm-space-2) 0;border-bottom:1px solid var(--dm-border);margin-bottom:var(--dm-space-2)}.live-preview-widths{display:inline-flex;border:1px solid var(--dm-border);border-radius:var(--dm-radius-sm);overflow:hidden}.live-preview-width{display:inline-flex;align-items:center;justify-content:center;padding:.3rem .55rem;border:none;background:transparent;color:inherit;cursor:pointer;line-height:1}.live-preview-width+.live-preview-width{border-left:1px solid var(--dm-border)}.live-preview-width:hover{background:var(--dm-hover-bg)}.live-preview-width.active{background:var(--dm-active-bg)}.live-preview-status{flex:1 1 auto;font-size:.8rem;opacity:.75}.live-preview-actions{display:flex;gap:var(--dm-space-1)}.live-preview-wrap{display:flex;flex-direction:column;height:100%;min-height:0}.live-preview-wrap>.live-preview-bar{flex:none}.live-preview-stage{display:flex;justify-content:center;flex:1;min-height:0;overflow:auto;background:var(--dm-background-alt);border-radius:var(--dm-radius-md)}.page-live-preview-frame{display:block;width:100%;max-width:100%;height:100%;border:none;border-radius:6px;background:var(--dm-background);transition:max-width .18s ease}.page-live-preview-frame[data-width="1024"]{max-width:1024px}.page-live-preview-frame[data-width="390"]{max-width:390px}.page-live-preview-frame[data-width="1024"],.page-live-preview-frame[data-width="390"]{border-left:1px solid var(--dm-border);border-right:1px solid var(--dm-border)}.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(--dm-border, rgba(255, 255, 255, .08));flex-wrap:wrap;flex-shrink:0}.editor-toolbar-btn{background:none;border:none;color:var(--dm-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(--dm-text, #eee);background:var(--dm-hover-bg)}.editor-toolbar-sep{width:1px;height:20px;background:var(--dm-border, 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(--dm-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(--dm-text, #eee);background:var(--dm-hover-bg)}.editor-view-btn.active{color:var(--dm-primary, #5b8cff);background:color-mix(in srgb,var(--dm-primary) 14%,transparent)}.editor-body{display:flex;flex:1;min-height:0;--dm-editor-split: 50%}@media(min-width:769px){.editor-body:not(.editor-mode-write):not(.editor-mode-preview) .editor-pane--write{flex:0 0 var(--dm-editor-split, 50%)}.editor-body:not(.editor-mode-write):not(.editor-mode-preview) .editor-pane--preview{flex:1 1 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(--dm-border, 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-primary, #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:7px;background:linear-gradient(to right,transparent 0,transparent 3px,var(--dm-border, rgba(255,255,255,.08)) 3px,var(--dm-border, rgba(255,255,255,.08)) 4px,transparent 4px,transparent 100%);cursor:col-resize;flex-shrink:0;touch-action:none;transition:background .12s ease}.editor-divider:hover,.editor-divider:focus-visible,.editor-divider.is-dragging{background:linear-gradient(to right,transparent 0,transparent 2px,var(--dm-primary, #5b8cff) 2px,var(--dm-primary, #5b8cff) 5px,transparent 5px,transparent 100%);outline:none}.editor-body.is-resizing .editor-pane{pointer-events:none}.editor-body.is-resizing{cursor:col-resize;user-select:none}.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-body .editor-textarea,#editor-body .editor-line-numbers{font-family:var(--pe-font, "Fira Code", "Courier New", monospace);font-size:var(--pe-size, .9rem);line-height:var(--pe-line, 1.6)}#editor-body.pe-nowrap .editor-textarea{white-space:pre;overflow-x:auto}.editor-preview{flex:1;display:block;width:100%;border:none;background:var(--dm-background)}.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(--dm-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(--dm-border, rgba(255, 255, 255, .08))}.docs-body .table{margin-bottom:.75rem}.docs-body h2{margin-top:1.75rem;margin-bottom:.6rem;padding-bottom:.3rem;border-bottom:1px solid var(--dm-border)}.docs-body h2:first-child{margin-top:0}.docs-body blockquote{margin:.75rem 0;padding:.5rem .9rem;border-left:3px solid var(--dm-primary);background:var(--dm-surface);border-radius:var(--dm-radius, 6px)}.docs-body pre{margin-bottom:.75rem;overflow-x:auto}.pg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:1rem;margin-top:1.5rem}.pg-grid>.card.pg-card{display:block;margin:0;text-decoration:none}.pg-card h3{margin-top:0}.pg-card .text-muted,.pg-card-pages{margin-bottom:0}.pg-card-pages{margin-top:.5rem;font-size:.8rem;color:var(--dm-text-muted)}.pg-actions{display:flex;gap:.5rem;flex-wrap:wrap}.pg-meta{margin-bottom:.75rem}.pg-pages{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:1rem}.pg-body{max-width:80ch}.nav-items-header,.nav-item-row{display:flex;gap:.5rem;align-items:center;padding:.35rem .75rem}.nav-items-header{border-bottom:1px solid var(--dm-border, rgba(255,255,255,.08));padding-bottom:.4rem;margin-bottom:.25rem}.nav-item-row{border-bottom:1px solid var(--dm-border, rgba(255,255,255,.04))}.nav-item-row:last-child{border-bottom:none}.nav-col-indent{width:18px;flex-shrink:0;color:var(--dm-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:color-mix(in srgb,var(--dm-text) 3%,transparent)}.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(--dm-text-muted, #888)}.btn-toggle-hidden.active{color:var(--dm-warning, #f59e0b)}.fb-drag-grip{flex-shrink:0;display:inline-flex;align-items:center;color:var(--dm-text-muted);cursor:grab}.fb-drag-grip:hover{color:var(--dm-text)}.btn-ghost span[data-icon=trash]{color:var(--dm-danger, #dc2626)}.view-container:has(.fb-fields-layout){max-width:none}.fb-fields-layout{display:grid;grid-template-columns:minmax(0,6fr) minmax(320px,4fr);gap:1.25rem;align-items:start}.fb-preview-pane{position:sticky;top:1rem;min-width:0}@media(max-width:1100px){.fb-fields-layout{grid-template-columns:1fr}.fb-preview-pane{position:static}}.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(--dm-card-bg, rgba(255,255,255,.04));border:1px solid var(--dm-border, 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(--dm-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(--dm-primary, #7c6af7);border-radius:3px;background:var(--dm-input-bg, transparent);color:inherit;outline:none}.media-size{font-size:.75rem;color:var(--dm-text-muted, #888)}.media-actions{padding:.5rem .75rem;display:flex;gap:.4rem;border-top:1px solid var(--dm-border, rgba(255,255,255,.08))}#admin-topbar{height:60px;display:flex;align-items:center;gap:1rem;padding:0 1.25rem;background:var(--dm-navbar-bg, rgba(0,0,0,.3));border-bottom:1px solid var(--dm-border, 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(--dm-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-avatar{width:1.75rem;height:1.75rem;border-radius:50%;object-fit:cover;flex-shrink:0}.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:color-mix(in srgb,var(--dm-primary) 15%,transparent);color:var(--dm-primary)}.topbar-role-badge--manager{background:color-mix(in srgb,var(--dm-success) 15%,transparent);color:var(--dm-success)}.topbar-role-badge--editor{background:color-mix(in srgb,var(--dm-warning) 15%,transparent);color:var(--dm-warning)}.topbar-role-badge--subscriber{background:color-mix(in srgb,var(--dm-text-muted) 15%,transparent);color:var(--dm-text-muted)}.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(--dm-text-muted, #888);text-decoration:none;padding:.4rem .65rem;border-radius:6px;transition:color .15s,background .15s}.topbar-action-link:hover{color:var(--dm-text, #eee);background:var(--dm-hover-bg)}.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(--dm-background, #0f1117);z-index:100;overflow-y:auto}.login-card{width:100%;max-width:460px}.ob-done-icon{font-size:3rem;color:var(--dm-success);margin-bottom:1rem;text-align:center}.btn-skip{display:block;text-align:center;margin-top:.75rem;font-size:.85rem;color:var(--dm-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(--dm-border, rgba(255,255,255,.1));border-radius:8px;overflow:hidden;cursor:pointer;transition:border-color .15s,transform .1s}.theme-swatch:hover{border-color:var(--dm-primary, #5b8cff);transform:translateY(-1px)}.theme-swatch.selected{border-color:var(--dm-primary, #5b8cff);box-shadow:0 0 0 2px var(--dm-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(--dm-card-bg, rgba(255,255,255,.04))}.theme-swatch-mode{color:var(--dm-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(--dm-primary, #5b8cff)}.login-logo h1{margin:0;font-size:1.5rem;font-weight:700}.login-card [hidden]{display:none!important}.login-heading{font-size:1.1rem;font-weight:600;margin-bottom:1.25rem}.login-rules{margin:.25rem 0 .75rem;padding-left:1.1rem;color:var(--dm-danger);font-size:.85rem}.login-rules li+li{margin-top:.2rem}.login-small{font-size:.8rem;margin-top:.75rem}.login-icon-warn{color:var(--dm-warning)}.pwf{position:relative;display:block}.pwf>input{width:100%;padding-right:2.5rem}.pwf-toggle{position:absolute;top:50%;right:.35rem;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;padding:0;border:0;border-radius:var(--dm-radius-sm);background:transparent;color:var(--dm-text-muted);cursor:pointer}.pwf-toggle:hover,.pwf-toggle:focus-visible{color:var(--dm-text);background:var(--dm-hover-bg)}.pwf-toggle svg{width:16px;height:16px}.pwf-toggle [data-icon=eye-off],.pwf.is-shown .pwf-toggle [data-icon=eye]{display:none}.pwf.is-shown .pwf-toggle [data-icon=eye-off]{display:inline-block}.pwf-caps{display:block;margin-top:.25rem;color:var(--dm-warning);font-size:.8rem}.pwf-caps[hidden]{display:none}.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-accent: var(--dm-primary, #5b8cff);position:relative;overflow:hidden;background-image:radial-gradient(120% 90% at 100% 0%,color-mix(in srgb,var(--plugin-accent) 13%,transparent) 0%,color-mix(in srgb,var(--plugin-accent) 4%,transparent) 42%,transparent 72%)}.plugin-card .plugin-icon{background:color-mix(in srgb,var(--plugin-accent) 16%,transparent);color:var(--plugin-accent)}.plugin-watermark{position:absolute;top:-34px;right:-30px;width:148px;height:148px;display:block;color:var(--plugin-accent);opacity:.09;pointer-events:none;z-index:0}.plugin-card>.card-body,.plugin-card>.plugin-footer{position:relative;z-index:1}.plugin-card:has(.badge-secondary) .plugin-watermark{opacity:.05}@media(prefers-reduced-motion:no-preference){.plugin-card{transition:background-image .2s ease}}.plugin-card .card-body{flex:1}.plugin-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:.75rem;flex-wrap:wrap}.plugin-icon{width:40px;height:40px;border-radius:8px;background:color-mix(in srgb,var(--dm-primary) 14%,transparent);color:var(--dm-primary, #5b8cff);display:flex;align-items:center;justify-content:center;flex-shrink:0}.plugin-meta{flex:1 1 140px;min-width:0}.plugin-name{font-weight:600;margin-bottom:.15rem}.plugin-version{font-size:.75rem;color:var(--dm-text-muted, #888)}.plugin-desc{font-size:.875rem;color:var(--dm-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(--dm-border, 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:color-mix(in srgb,var(--dm-primary) 14%,transparent);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(--dm-text-muted, #888)}@media(max-width:768px){.view-container{padding:1rem}.editor-body{flex-direction:column}.editor-divider{width:auto;height:1px;cursor:default;background:var(--dm-border, rgba(255,255,255,.08))}}.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(--dm-border, rgba(255, 255, 255, .1))}.image-editor-sep{display:inline-block;width:1px;height:1.5rem;background:var(--dm-border, rgba(255, 255, 255, .15));margin:0 .25rem;flex-shrink:0}.editor-toolbar-btn.active{color:var(--dm-primary, #7c6af7);background:color-mix(in srgb,var(--dm-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(--dm-border, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:4px;min-width:160px;max-height:70vh;overflow-y:auto}.editor-toolbar-dropdown-heading{padding:6px 10px 3px;color:var(--dm-text-muted, #8a8a9a);font-size:.66rem;text-transform:uppercase;letter-spacing:.06em}.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:var(--dm-hover-bg);color:var(--dm-text, #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(--dm-border, 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:var(--dm-hover-bg);color:var(--dm-text, #fff)}.editor-icon-picker{position:absolute;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, 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(--dm-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:var(--dm-hover-bg);color:var(--dm-text, #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(--dm-border, rgba(255, 255, 255, .1));font-size:.85rem}.image-editor-resize label{font-weight:600;color:var(--dm-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(--dm-border, rgba(255, 255, 255, .1));gap:.5rem}.image-editor-effects{border-top:1px solid var(--dm-border, rgba(255, 255, 255, .1))}.image-editor-tab-bar{display:flex;flex-direction:row;overflow-x:auto;border-bottom:1px solid var(--dm-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(--dm-text-muted, #888);white-space:nowrap;transition:color .15s,border-color .15s;flex-shrink:0}.image-editor-tab-btn:hover{color:var(--dm-text, #eee)}.image-editor-tab-btn.active{color:var(--dm-primary, #7c6af7);border-bottom-color:var(--dm-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(--dm-primary, #7c6af7);background:color-mix(in srgb,var(--dm-primary, #7c6af7) 15%,transparent);border-color:var(--dm-primary, #7c6af7)}.ie-server-note{font-size:.73rem;color:var(--dm-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(--dm-text-muted, #888);font-weight:600;flex-shrink:0}.ie-slider{flex:1;accent-color:var(--dm-primary, #7c6af7);cursor:pointer}.ie-slider-val{width:3rem;font-size:.8rem;text-align:right;color:var(--dm-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(--dm-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(--dm-surface-raised, 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(--dm-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(--dm-surface-raised, rgba(255, 255, 255, .08));border-color:var(--dm-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(--dm-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-resizer{position:absolute;top:0;bottom:0;width:6px;cursor:col-resize;z-index:2;background:transparent}.dm-slideover-right>.dm-slideover-resizer{left:0}.dm-slideover-left>.dm-slideover-resizer{right:0}.dm-slideover-resizer:hover,.is-resizing>.dm-slideover-resizer{background:var(--dm-primary);opacity:.7}.dm-slideover.is-resizing{user-select:none;transition:none!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:auto;overscroll-behavior:contain;min-height:0;padding:1.5rem!important;box-sizing:border-box}.dm-slideover-body:empty{flex:0 0 auto;padding:0!important}.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-body):not(.dm-slideover-close){padding:1.5rem!important;box-sizing:border-box;flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain}.dm-slideover-body,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close){scrollbar-width:thin;scrollbar-color:var(--dm-border) transparent}.dm-slideover-body::-webkit-scrollbar,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar{width:8px;height:8px}.dm-slideover-body::-webkit-scrollbar-track,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-track{background:transparent}.dm-slideover-body::-webkit-scrollbar-thumb,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-thumb{background:var(--dm-border);border-radius:4px}.dm-slideover-body::-webkit-scrollbar-thumb:hover,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-thumb:hover{background:var(--dm-text-muted)}.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-background-alt, 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-background-alt, 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-action-link[hidden],.topbar-bell-badge[hidden]{display:none}.topbar-bell-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;background:var(--dm-danger, #dc2626);color:var(--dm-danger-text, #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-background-alt);border:1px solid var(--dm-primary);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-primary)}.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-primary)}.cb-recipe-card .cb-icon{flex-shrink:0;font-size:1.5rem;color:var(--dm-primary);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-primary);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-primary);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-background-alt);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-background-alt);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-primary);background:var(--dm-background-alt);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-primary)}.cb-tutorial-link .cb-icon{flex-shrink:0;color:var(--dm-primary)}.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:26px 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-row--tool .me-tool-badge{margin-left:.4rem;font-size:.7rem;vertical-align:middle}.menu-tree-row--default .me-row-label{font-style:italic;opacity:.8}.menu-tree-grip{cursor:grab;color:var(--dm-text-muted)}.menu-tree-grip:hover{color:var(--dm-text)}.menu-tree{padding:8px 0}.menu-tree-row--separator{display:grid;grid-template-columns:26px minmax(80px,1fr) repeat(7,auto);gap:6px;align-items:center;padding:2px 0;opacity:.85}.menu-tree-row--separator:hover{opacity:1}.menu-tree-row--separator .me-sep-line{display:flex;align-items:center;gap:10px;width:100%}.menu-tree-row--separator .me-sep-line:before,.menu-tree-row--separator .me-sep-line:after{content:"";flex:1;border-top:1px dashed var(--dm-border, rgba(128, 128, 128, .55))}.menu-tree-row--separator .me-sep-label{font-size:.66rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--dm-text-muted, #8a8a8a);white-space:nowrap}.dm-admin-sidebar .sidebar-divider{border:0;border-top:1px solid var(--dm-border, rgba(128, 128, 128, .5));margin:.5rem .75rem;opacity:.8}.dm-admin-sidebar .sidebar-spacer--xs{height:var(--dm-space-1, .25rem)}.dm-admin-sidebar .sidebar-spacer--sm{height:var(--dm-space-2, .5rem)}.dm-admin-sidebar .sidebar-spacer--md{height:var(--dm-space-4, 1rem)}.dm-admin-sidebar .sidebar-spacer--lg{height:var(--dm-space-6, 1.5rem)}.dm-admin-sidebar .sidebar-spacer--xl{height:var(--dm-space-10, 2.5rem)}.dm-admin-sidebar .sidebar-spacer--flex{flex:1 1 auto;min-height:var(--dm-space-4, 1rem)}.dm-admin-sidebar--has-flex-spacer{display:flex;flex-direction:column;min-height:100%}.menu-tree-row--spacer{display:grid;grid-template-columns:26px minmax(80px,1fr) repeat(7,auto);gap:6px;align-items:center;padding:2px 0;opacity:.85}.menu-tree-row--spacer:hover{opacity:1}.menu-tree-row--spacer .me-spacer-line{display:flex;align-items:center;gap:8px;width:100%}.menu-tree-row--spacer .me-spacer-line:before,.menu-tree-row--spacer .me-spacer-line:after{content:"";flex:1;border-top:1px dotted var(--dm-border, rgba(128, 128, 128, .55))}.menu-tree-row--spacer .me-spacer-label{font-size:.66rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--dm-text-muted, #8a8a8a);white-space:nowrap}.menu-tree-row--spacer .me-spacer-size{width:auto;min-width:5.5rem;height:26px;padding:0 .35rem;font-size:.75rem}.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;font-size:.875rem}.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;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888)}.dm-admin-sidebar details details>summary{font-size:.875rem;font-weight:500;text-transform:none;letter-spacing:0;color:inherit}.dm-admin-sidebar .dm-menu-badge{font-size:.7rem;font-weight:600}.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-light, 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-hover-bg, rgba(0, 0, 0, .05))}.dm-admin-sidebar .sidebar-link.active{background:var(--dm-active-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{position:sticky;top:0;z-index:2;background:var(--dm-surface);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-light, rgba(0, 0, 0, .08))}.dm-admin-sidebar--dark .dm-admin-sidebar-header{background:#1a1d24}.dm-admin-sidebar--light .dm-admin-sidebar-header{background:#fff}.dm-admin-sidebar-header [data-icon]{color:var(--dm-text-muted, #999)}.dm-admin-sidebar-header .dm-admin-sidebar-arrange{margin-left:auto;padding:.2rem .35rem;color:var(--dm-text-muted, #999);line-height:1}.dm-admin-sidebar-header .dm-admin-sidebar-arrange:hover,.dm-admin-sidebar-header .dm-admin-sidebar-arrange:focus-visible{color:var(--dm-text)}.dm-admin-sidebar.is-arranging>:not(.dm-admin-sidebar-header):not(.sb-arrange):not(style){display:none!important}.dm-admin-sidebar.is-arranging .dm-admin-sidebar-arrange,.dm-admin-sidebar.is-arranging .dm-admin-sidebar-collapse{display:none}.sb-arrange{box-sizing:border-box;max-width:100vw;padding:0 .5rem 1rem;animation:sb-arrange-in .18s cubic-bezier(.2,.8,.2,1)}@keyframes sb-arrange-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}.sb-arrange-bar{position:sticky;top:2.6rem;z-index:2;display:flex;align-items:center;gap:.4rem;padding:.4rem .25rem;background:inherit}.sb-arrange-title{font-weight:600;margin-right:auto}.sb-arrange-hint{margin:0 .25rem .5rem;font-size:.78rem;line-height:1.35;color:var(--dm-text-muted)}.sb-arr-row{display:flex;align-items:center;gap:.45rem;padding:.35rem .4rem .35rem calc(.4rem + var(--sb-arr-depth, 0) * 1rem);border-radius:var(--dm-radius-sm, 4px);cursor:grab;user-select:none;font-size:.9rem}.sb-arr-row:hover{background:color-mix(in srgb,var(--dm-text) 6%,transparent)}.sb-arr-row:focus-visible{outline:2px solid var(--dm-primary);outline-offset:-2px}.sb-arr-row--folder .sb-arr-text{font-weight:600}.sb-arr-row--hidden .sb-arr-text,.sb-arr-row--hidden .sb-arr-icon{opacity:.45;text-decoration:line-through}.sb-arr-row--structural .sb-arr-text{font-size:.75rem;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted)}.sb-arr-grip{color:var(--dm-text-muted);flex:none}.sb-arr-icon{width:16px;flex:none}.sb-arr-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sb-arr-hide{flex:none;display:inline-flex;padding:.15rem;border:0;border-radius:var(--dm-radius-sm, 4px);background:transparent;color:var(--dm-text-muted);cursor:pointer}.sb-arr-hide:hover,.sb-arr-hide:focus-visible{color:var(--dm-text);background:color-mix(in srgb,var(--dm-text) 8%,transparent)}.sb-arr-lock{flex:none;color:var(--dm-text-muted);opacity:.6}.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-active-bg, rgba(0, 0, 0, .12));color:var(--dm-text-muted, #888)}.dm-admin-sidebar .sidebar-link.active .sidebar-badge{background:var(--dm-primary, #4a86e8);color:var(--dm-primary-text, #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}#admin-sidebar{position:relative;transition:width .18s ease}#admin-sidebar.is-resizing{transition:none}@media(prefers-reduced-motion:reduce){#admin-sidebar{transition:none}}.dm-admin-sidebar-handle{position:absolute;top:0;right:0;width:8px;height:100%;cursor:col-resize;z-index:10;touch-action:none;outline:none}.dm-admin-sidebar-handle:before,.dm-admin-sidebar-handle:after{content:"";position:absolute;right:2px;transition:background .15s,opacity .15s}.dm-admin-sidebar-handle:before{top:0;bottom:0;width:2px;background:transparent}.dm-admin-sidebar-handle:after{top:50%;width:3px;height:28px;margin-top:-14px;border-radius:2px;background:var(--dm-border, rgba(0, 0, 0, .15));opacity:.8}.dm-admin-sidebar-handle:hover:before,.dm-admin-sidebar-handle:focus-visible:before,.is-resizing>.dm-admin-sidebar-handle:before{background:var(--dm-primary, #4a86e8);opacity:.5}.dm-admin-sidebar-handle:hover:after,.dm-admin-sidebar-handle:focus-visible:after,.is-resizing>.dm-admin-sidebar-handle:after{background:var(--dm-primary, #4a86e8);opacity:1}.dm-admin-sidebar-handle:focus-visible:before{opacity:1}#admin-sidebar:has(.is-arranging)>.dm-admin-sidebar-handle{display:none}body.dm-sidebar-resizing{cursor:col-resize;user-select:none}body.dm-sidebar-resizing iframe{pointer-events:none}.dm-admin-sidebar-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dm-admin-sidebar-header .dm-admin-sidebar-collapse{padding:.2rem .35rem;color:var(--dm-text-muted, #999);line-height:1}.dm-admin-sidebar-header .dm-admin-sidebar-collapse:hover,.dm-admin-sidebar-header .dm-admin-sidebar-collapse:focus-visible{color:var(--dm-text)}.dm-admin-sidebar-collapse [data-icon]{transition:transform .18s ease}.dm-admin-sidebar .sidebar-initial{display:none}#admin-sidebar.is-collapsed{width:56px!important}#admin-sidebar.is-collapsed .dm-admin-sidebar{overflow-x:hidden}#admin-sidebar.is-collapsed .dm-admin-sidebar-header{justify-content:center;padding-left:0;padding-right:0}#admin-sidebar.is-collapsed .dm-admin-sidebar-header>[data-icon],#admin-sidebar.is-collapsed .dm-admin-sidebar-title,#admin-sidebar.is-collapsed .dm-admin-sidebar-arrange{display:none}#admin-sidebar.is-collapsed .dm-admin-sidebar-collapse [data-icon]{transform:rotate(180deg)}#admin-sidebar.is-collapsed .sidebar-text{position:absolute!important;width:1px;height:1px;padding:0!important;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}#admin-sidebar.is-collapsed .sidebar-link,#admin-sidebar.is-collapsed details>summary{justify-content:center;padding:.5rem 0;margin:0 6px;width:auto!important}#admin-sidebar.is-collapsed details>summary:after,#admin-sidebar.is-collapsed .dm-menu-badge,#admin-sidebar.is-collapsed .sidebar-children{display:none!important}#admin-sidebar.is-collapsed .sidebar-initial{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:4px;font-size:.75rem;font-weight:600;background:var(--dm-hover-bg, rgba(0, 0, 0, .06))}#admin-sidebar.is-collapsed details>summary[aria-expanded=true]{background:var(--dm-active-bg, rgba(0, 0, 0, .08))}#admin-sidebar.is-collapsed .sidebar-badge{top:4px!important;right:8px!important;transform:none!important;width:8px;height:8px;min-width:0;padding:0!important;border-radius:50%;font-size:0;line-height:0;overflow:hidden}#admin-sidebar.is-collapsed .sidebar-badge--live{padding:0!important;background:var(--dm-success, #16a34a)}#admin-sidebar.is-collapsed .sidebar-badge--live:before{display:none}#admin-sidebar.is-collapsed summary>.sidebar-badge--sum{position:absolute!important;margin:0!important}#admin-sidebar.is-collapsed details[open]>summary>.sidebar-badge--sum{display:block}#admin-sidebar.is-collapsed .sidebar-divider{margin-left:8px;margin-right:8px}#admin-sidebar.is-collapsed .sidebar-spacer--flex{min-height:var(--dm-space-2, .5rem)}#admin-sidebar.is-collapsed details>summary{position:relative}.dm-admin-sidebar-flyout{position:fixed;z-index:1000;min-width:200px;max-width:320px;overflow-y:auto;background:var(--dm-surface, #fff);color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(0, 0, 0, .1));border-radius:var(--dm-radius-md, 8px);box-shadow:var(--dm-shadow-lg, 0 8px 24px rgba(0, 0, 0, .15));animation:dm-sidebar-flyout-in .12s ease-out}.dm-admin-sidebar-flyout>.dm-admin-sidebar{padding:4px 6px 6px}.dm-admin-sidebar-flyout-title{padding:.45rem .75rem .35rem;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888)}.dm-admin-sidebar-flyout .sidebar-badge{margin-left:auto}@keyframes dm-sidebar-flyout-in{0%{opacity:0;transform:translate(-4px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.dm-admin-sidebar-flyout{animation:none}}.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-light, 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}.dm-menu-badge{display:inline-block;margin-left:6px;padding:1px 7px;font-size:.7em;font-weight:700;line-height:1.4;border-radius:999px;background:var(--dm-text-muted);color:var(--dm-text-inverse, #fff);vertical-align:middle}.menu-tree-row--slim{display:grid;grid-template-columns:26px 1fr auto repeat(9,auto);gap:6px;align-items:center}.menu-tree-row--slim .me-row-label{font-weight:600}.menu-tree-row--slim .me-row-url{color:var(--dm-text-muted);font-size:.85em}.me-pages-picker{display:flex;gap:6px;align-items:center;margin-bottom:6px}.me-pages-picker .form-input{flex:1;min-width:0}.me-colour-control{display:flex;gap:6px;align-items:center}.me-colour-control .me-colour-hex{width:48px;height:32px;padding:2px;flex:none}.me-bind-summary{padding:.6rem .8rem;border:1px solid var(--dm-border, rgba(128, 128, 128, .35));border-radius:var(--dm-radius-md, 6px);background:color-mix(in srgb,var(--dm-text) 4%,transparent);font-size:.9em}.me-bind-summary code{font-size:.9em}.me-item-form{display:flex;flex-direction:column;gap:10px;padding:4px 2px}.me-item-form .form-field{display:flex;flex-direction:column;gap:4px}.me-item-form .form-label{font-size:.85em;color:var(--dm-text-muted)}.me-form-section{margin:12px 0 2px;padding-top:10px;border-top:1px solid var(--dm-border-light, rgba(0,0,0,.15));font-size:.78em;text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.me-item-form .me-form-section:first-child{margin-top:0;padding-top:0;border-top:0}.me-item-form .me-colour-hex{width:48px;height:32px;padding:2px}.me-item-form .me-field-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.me-item-form .me-field-grid .form-field{margin:0}.me-item-form .me-icon-field{display:flex;align-items:center;gap:8px}.me-item-form .me-icon-field .form-input{flex:1}.me-item-form .me-icon-preview{width:24px;height:24px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;color:var(--dm-text-muted)}#admin-sidebar.dm-menu-floating{border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 8px);box-shadow:var(--dm-shadow-lg);overflow:hidden}@media(max-width:768px){#admin-sidebar.dm-menu-floating{position:static!important;inset:auto!important;max-height:none!important;transform:none!important}.dashboard-wrapper{padding-left:0!important;padding-right:0!important}}[data-logic-section]{border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 6px);padding:.9rem 1rem 1rem;margin-bottom:1rem;background:color-mix(in srgb,var(--dm-text) 3%,transparent)}[data-logic-section] .fb-logic-cond-row,[data-logic-section] .fb-logic-val-rule{padding:.55rem .65rem;border-radius:var(--dm-radius-sm, 4px);border:1px solid var(--dm-border);background:var(--dm-surface, transparent)}[data-logic-section] .fb-logic-cond-row:last-of-type,[data-logic-section] .fb-logic-val-rule:last-of-type{margin-bottom:.75rem}[data-logic-section]>.btn{margin-top:.15rem}[data-logic-section] textarea{min-height:5rem}.project-filter-notice{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap;margin:0 0 1rem;padding:.6rem .85rem;border:1px solid var(--dm-border);border-left:3px solid var(--dm-info, var(--dm-primary));border-radius:var(--dm-radius-md, 6px);background:color-mix(in srgb,var(--dm-info, var(--dm-primary)) 6%,transparent);font-size:.85rem;color:var(--dm-text-muted)}.project-filter-notice a{color:var(--dm-primary);font-weight:600}.ctx-help{display:inline-flex;align-items:center;justify-content:center;width:1.05rem;height:1.05rem;margin-left:.35rem;padding:0;border:1px solid var(--dm-border-dark, var(--dm-border));border-radius:var(--dm-radius-full, 9999px);background:transparent;color:var(--dm-text-muted);font-size:.68rem;font-weight:700;line-height:1;cursor:pointer;vertical-align:middle;flex-shrink:0}.ctx-help:hover,.ctx-help:focus-visible{border-color:var(--dm-primary);color:var(--dm-primary);outline:none}.card-header h3 .ctx-help{align-self:center}.ctx-preview-card{position:sticky;top:1rem}@media(max-width:1100px){.ctx-workspace>.col-8,.ctx-workspace>.col-4{flex:0 0 auto;width:100%}.ctx-preview-card{position:static}}.dm-plugin-shell{border:1px solid var(--dm-border);border-radius:var(--dm-radius-lg, 12px);background:var(--dm-surface);overflow:hidden}.dm-plugin-banner{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;padding:.7rem 1rem;background:var(--dm-background-alt);border-bottom:1px solid var(--dm-border)}.dm-plugin-banner-identity{display:flex;align-items:center;gap:.55rem;min-width:0;margin-right:auto}.dm-plugin-banner-actions{display:flex;align-items:center;gap:.25rem;flex:0 0 auto;margin-right:.15rem;padding-right:.4rem;border-right:1px solid var(--dm-border)}.dm-plugin-banner-action{padding:.25rem .4rem;line-height:1;color:var(--dm-text-muted)}.dm-plugin-banner-action:hover{color:var(--dm-primary)}.dm-plugin-banner-icon{display:inline-flex;align-items:center;color:var(--dm-primary);flex:0 0 auto}.dm-plugin-banner-name{margin:0;font-size:1.05rem;font-weight:600;letter-spacing:.01em;color:var(--dm-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dm-activity{display:inline-flex;align-items:center;flex:0 0 auto;margin-left:.35rem}.dm-activity domma-loader{display:inline-block;line-height:0}.dm-activity[hidden]{display:none!important}#admin-topbar>.dm-activity{margin:0 .75rem 0 0}.dm-plugin-banner-parent{font-weight:500;color:var(--dm-text-muted)}.dm-plugin-banner-sep{margin:0 .4rem;color:var(--dm-text-muted)}.dm-plugin-banner-facts{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap}.dm-plugin-fact{font-size:.72rem;line-height:1.6;padding:1px 8px;border-radius:999px;border:1px solid var(--dm-border);color:var(--dm-text-muted);background:var(--dm-surface);white-space:nowrap}.dm-plugin-fact--version{font-variant-numeric:tabular-nums;color:var(--dm-text)}.dm-plugin-fact--licence.is-included{color:var(--dm-info);border-color:var(--dm-info)}.dm-plugin-fact--licence.is-licensed{color:var(--dm-success);border-color:var(--dm-success)}.dm-plugin-fact--expiry{font-variant-numeric:tabular-nums}.dm-plugin-fact--expiry.is-warning{color:var(--dm-warning);border-color:var(--dm-warning)}.dm-plugin-fact--expiry.is-danger{color:var(--dm-danger);border-color:var(--dm-danger)}.dm-plugin-body{padding:1rem}.dm-plugin-body .card.dm-plugin-unwrap{border:0;background:none;box-shadow:none;margin:0}.dm-plugin-body .card.dm-plugin-unwrap>.card-body{padding:0}.dm-plugin-body>.p-4{padding:0}@media(max-width:640px){.dm-plugin-banner{align-items:flex-start;flex-direction:column;gap:.5rem}.dm-plugin-body{padding:.75rem}}.analytics-filter{max-width:240px}.dm-help-btn{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;margin-left:.3rem;padding:0;border:0;background:none;line-height:1;color:var(--dm-text-muted);opacity:.75;cursor:pointer}.dm-help-btn:hover,.dm-help-btn:focus-visible{color:var(--dm-primary);opacity:1}.dm-popover.dm-help-popover{--dm-popover-max-width: 300px;font-size:.85rem;line-height:1.45;font-weight:400}.dm-help-pop{position:fixed;z-index:10050;max-width:300px;padding:.6rem .75rem;background:var(--dm-surface, var(--dm-background));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);box-shadow:0 8px 24px #00000040;font-size:.85rem;line-height:1.45;text-align:left;font-weight:400}.dm-help-pop strong{display:block;margin-bottom:.2rem;font-weight:600}.dm-help-pop p{margin:0}.dm-admin-sidebar .sidebar-badge[data-badge-label]{cursor:help}.dm-admin-sidebar .sidebar-badge--danger,.dm-admin-sidebar .sidebar-link.active .sidebar-badge--danger{background:var(--dm-danger, #dc2626);color:#fff}.dm-admin-sidebar .sidebar-badge--live{background:color-mix(in srgb,var(--dm-success, #16a34a) 20%,transparent);color:var(--dm-success, #16a34a)}#admin-sidebar .sidebar-badge--live{padding-left:1.05rem!important}#admin-sidebar summary>.sidebar-badge--sum{position:static!important;transform:none!important;margin-left:auto!important;margin-right:.45rem;text-transform:none;letter-spacing:0}#admin-sidebar summary:has(>.sidebar-badge--sum):after{margin-left:0}#admin-sidebar details[open]>summary>.sidebar-badge--sum{display:none}.dm-admin-sidebar .sidebar-badge--live:before{content:"";position:absolute;left:.4rem;top:50%;width:.4rem;height:.4rem;margin-top:-.2rem;border-radius:50%;background:currentColor;animation:dm-badge-pulse 2s ease-in-out infinite}@keyframes dm-badge-pulse{50%{opacity:.35}}@media(prefers-reduced-motion:reduce){.dm-admin-sidebar .sidebar-badge--live:before{animation:none}}.dm-badge-pop{width:290px;max-width:calc(100vw - 16px);font-size:.8rem;pointer-events:none}.dm-badge-pop-title{display:block;font-size:.85rem}.dm-badge-pop-sub{margin:.1rem 0 .5rem;color:var(--dm-text-muted)}.dm-badge-pop-rows{display:grid;grid-template-columns:1fr auto;gap:.2rem .75rem;margin:0 0 .5rem;padding:.45rem 0;border-top:1px solid var(--dm-border);border-bottom:1px solid var(--dm-border)}.dm-badge-pop-rows dt{color:var(--dm-text-muted)}.dm-badge-pop-rows dd{margin:0;text-align:right;font-weight:600;font-variant-numeric:tabular-nums}.dm-badge-pop-items{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.4rem}.dm-badge-pop-items li{display:flex;flex-direction:column;min-width:0}.dm-badge-pop-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dm-badge-pop-meta{font-size:.72rem;color:var(--dm-text-muted)}.dm-badge-pop .is-danger,.dm-badge-pop li.is-danger .dm-badge-pop-meta{color:var(--dm-danger, #dc2626)}.dm-badge-pop-empty{margin:0;color:var(--dm-text-muted)}.dm-plugin-body,.dm-slideover{font-family:var(--dm-font-sans, inherit)}.dm-plugin-body :is(button,input,select,textarea,.btn,.form-input,.form-select,.form-textarea),.dm-slideover :is(button,input,select,textarea,.btn,.form-input,.form-select,.form-textarea){font-family:inherit}.dm-plugin-body :is(input,select,textarea,.form-input,.form-select,.form-textarea):not(.form-input-lg),.dm-slideover :is(input,select,textarea,.form-input,.form-select,.form-textarea):not(.form-input-lg){font-size:var(--dm-text-sm)}.dm-plugin-body .form-input-lg{font-size:var(--dm-text-base)}.dm-plugin-body :is(.font-mono,code,pre,kbd),.dm-slideover :is(.font-mono,code,pre,kbd){font-family:var(--dm-font-mono, monospace)}.dm-plugin-body .form-label,.dm-slideover .form-label{font-size:var(--dm-text-sm)}.related-box{display:flex;flex-direction:column;gap:.6rem;border-top:1px solid var(--dm-border);padding-top:.75rem}.related-box[hidden]{display:none}.related-head{display:flex;align-items:center;gap:.4rem}.related-head strong{flex:1}.related-list{list-style:none;margin:.3rem 0 0;padding:0;display:flex;flex-direction:column;gap:.25rem}.related-list li{display:flex;flex-direction:column}.related-list li.is-past{opacity:.7}.related-meta,.related-note,.related-loading{color:var(--dm-text-muted);font-size:var(--dm-text-xs);margin:0}.login-challenge{display:flex;flex-direction:column;gap:.6rem;margin-bottom:.75rem}.login-challenge #challenge-code{font-size:1.25rem;letter-spacing:.2em;text-align:center}.login-trust{display:flex;align-items:center;gap:.45rem;font-size:.875rem}.login-enrol{display:flex;flex-direction:column;align-items:center;gap:.6rem;margin-bottom:1rem}.login-enrol img{background:#fff;padding:.5rem;border-radius:.5rem}.login-secret{display:flex;flex-direction:column;align-items:center;gap:.25rem;text-align:center}.login-secret code{font-size:.95rem;letter-spacing:.08em;word-break:break-all}.usx-secret{display:flex;flex-direction:column;gap:.75rem;padding:1rem}.usx-secret-link{display:block;padding:.6rem .75rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-sm);background:var(--dm-background-alt);word-break:break-all;font-size:.8rem;user-select:all}.usx-secret-actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem}.usx-secret-actions .btn{display:inline-flex;align-items:center;gap:.35rem}.usx-secret-note{margin-right:auto;color:var(--dm-success)}.usx-secret-foot{color:var(--dm-text-muted);font-size:.8rem;margin:0}.uex-reset{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .5rem;margin-top:.5rem}.uex-reset>small:first-child{flex-basis:100%;color:var(--dm-text-muted)}.uex-reset .btn{display:inline-flex;align-items:center;gap:.35rem}.uex-reset-open{flex-basis:100%;color:var(--dm-info)}.dm-loading-slot>.skeleton-list,.dm-loading-slot>.skeleton-table{border-color:transparent;background:transparent}.dm-loading-slot>.skeleton-list .skeleton-list-item{padding-left:.35rem;padding-right:.35rem}.dm-skeleton-figure{width:3.5rem;height:1em;margin:.1em 0}.view-container:has(.dbp){max-width:none}.dbp-head{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:.9rem}.dbp-hint{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-sm);display:flex;align-items:center;gap:.35rem}.dbp-source{color:var(--dm-text-muted);font-size:var(--dm-text-xs);white-space:nowrap}.dbp-board{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));column-gap:1rem;row-gap:0;grid-auto-rows:4px;grid-auto-flow:row dense;align-items:start}.dbp-board>.dbp-card{align-self:start;margin-bottom:1rem}.dbp-card{grid-column:span 2;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);background:var(--dm-surface);display:flex;flex-direction:column;min-width:0;min-height:9rem;outline:none}.dbp-card:focus-visible{border-color:var(--dm-primary)}.dbp-size-s{grid-column:span 1}.dbp-size-m{grid-column:span 2}.dbp-size-l{grid-column:span 3}.dbp-size-xl{grid-column:1 / -1}@media(max-width:1100px){.dbp-board{grid-template-columns:repeat(2,minmax(0,1fr))}.dbp-size-l{grid-column:1 / -1}}@media(max-width:640px){.dbp-board{grid-template-columns:minmax(0,1fr)}.dbp-card{grid-column:1 / -1}}.dbp-card-head{display:flex;align-items:center;gap:.5rem;padding:.55rem .7rem;border-bottom:1px solid var(--dm-border);min-width:0}.dbp-grip{cursor:grab;color:var(--dm-text-muted);display:inline-flex;touch-action:none}.dbp-grip:active{cursor:grabbing}.dbp-icon{display:inline-flex;align-items:center;justify-content:center;width:1.8rem;height:1.8rem;flex:0 0 auto;border-radius:var(--dm-radius-md);color:var(--dm-primary);background:color-mix(in srgb,var(--dm-primary) 12%,transparent)}.dbp-title{font-weight:600;color:var(--dm-text);text-decoration:none;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}a.dbp-title:hover,a.dbp-title:focus-visible{color:var(--dm-primary);text-decoration:underline}.dbp-from{margin-left:auto;color:var(--dm-text-muted);font-size:var(--dm-text-xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dbp-kebab{border:0;background:none;color:var(--dm-text-muted);cursor:pointer;padding:.1rem .35rem;border-radius:var(--dm-radius-md);line-height:1}.dbp-kebab:hover,.dbp-kebab:focus-visible{background:color-mix(in srgb,var(--dm-text) 8%,transparent);color:var(--dm-text)}.dbp-body{padding:.75rem .9rem;display:flex;flex-direction:column;gap:.6rem;min-width:0;flex:1 1 auto}.dbp-card.is-loading .dbp-body{opacity:.75}.dbp-skeleton{height:3.5rem;border-radius:var(--dm-radius-md);background:color-mix(in srgb,var(--dm-text) 7%,transparent)}.dbp-figure{display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap}.dbp-figure-value{font-size:var(--dm-text-2xl, 1.5rem);line-height:1.15;font-weight:600;font-variant-numeric:tabular-nums;color:var(--dm-text)}.dbp-figure-label{color:var(--dm-text-muted);font-size:var(--dm-text-sm)}.dbp-figure.is-danger .dbp-figure-value{color:var(--dm-danger)}.dbp-figure.is-attention .dbp-figure-value{color:var(--dm-warning)}.dbp-sub{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-facts{display:grid;grid-template-columns:auto 1fr;gap:.2rem .8rem;margin:0;font-size:var(--dm-text-sm)}.dbp-facts dt{color:var(--dm-text-muted)}.dbp-facts dd{margin:0;text-align:right;font-variant-numeric:tabular-nums}.dbp-rows{display:flex;flex-direction:column;min-width:0}.dbp-row{display:flex;align-items:center;gap:.6rem;padding:.4rem .3rem;border-bottom:1px solid var(--dm-border);color:var(--dm-text);text-decoration:none;min-width:0}.dbp-row:last-child{border-bottom:0}a.dbp-row:hover,a.dbp-row:focus-visible{background:color-mix(in srgb,var(--dm-text) 6%,transparent);outline:none}.dbp-row-main{display:flex;flex-direction:column;min-width:0;flex:1 1 auto}.dbp-row-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dbp-row-meta{color:var(--dm-text-muted);font-size:var(--dm-text-xs);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dbp-row-value{font-variant-numeric:tabular-nums;color:var(--dm-text-muted);font-size:var(--dm-text-sm);white-space:nowrap}.dbp-row.is-danger{box-shadow:inset 3px 0 0 var(--dm-danger)}.dbp-row.is-attention{box-shadow:inset 3px 0 0 var(--dm-warning)}.dbp-row.is-ok .dbp-row-value{color:var(--dm-success)}.dbp-chart{position:relative;height:240px}.dbp-empty{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-sm)}.dbp-empty.is-error{color:var(--dm-danger)}.dbp-empty-board{text-align:center;padding:3rem 1rem;border:1px dashed var(--dm-border);border-radius:var(--dm-radius-md);color:var(--dm-text-muted)}.dbp-gallery{display:flex;flex-direction:column;gap:.6rem;padding:.25rem}.dbp-gallery-hint{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-gallery-group{margin:.6rem 0 .1rem;font-size:var(--dm-text-xs);text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.dbp-gallery-item{display:flex;align-items:center;gap:.6rem;padding:.5rem .3rem;border-bottom:1px solid var(--dm-border)}.dbp-gallery-main{display:flex;flex-direction:column;min-width:0;flex:1 1 auto}.dbp-gallery-main small{color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-gallery-added{color:var(--dm-success);font-size:var(--dm-text-xs);white-space:nowrap}.dbp-fold{border:0;background:none;color:var(--dm-text-muted);cursor:pointer;padding:.15rem;border-radius:var(--dm-radius-md);display:inline-flex;line-height:1}.dbp-fold:hover,.dbp-fold:focus-visible{background:color-mix(in srgb,var(--dm-text) 8%,transparent);color:var(--dm-text)}.dbp-fold [data-icon]{transition:transform .15s ease}.dbp-card.is-collapsed{min-height:0}.dbp-card.is-collapsed .dbp-card-head{border-bottom:0}.dbp-card.is-collapsed .dbp-fold [data-icon]{transform:rotate(-90deg)}.dbp-from+.dbp-fold{margin-left:0}@media(prefers-reduced-motion:reduce){.dbp-fold [data-icon]{transition:none}}.dbp-body[hidden]{display:none}
|
|
1
|
+
.dashboard-layout{display:flex;flex-direction:column;height:100vh;min-height:100vh;overflow:hidden}.dashboard-wrapper{display:flex;flex:1;min-height:0;overflow:hidden}.dashboard-main{flex:1;min-width:0;overflow-y:auto}#admin-sidebar.dashboard-sidebar{display:flex;flex-direction:column;flex-shrink:0;overflow:hidden}#admin-sidebar .dm-admin-sidebar{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--dm-border) transparent}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar{width:8px}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-track{background:transparent}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-thumb{background:var(--dm-border);border-radius:4px}#admin-sidebar .dm-admin-sidebar::-webkit-scrollbar-thumb:hover{background:var(--dm-text-muted)}.view-container{padding:1.5rem 2rem;max-width:1200px}.view-container:has(>.dm-plugin-shell){max-width:100%}.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:.75rem;flex-wrap:wrap}.view-header-actions .btn-primary,.view-header-actions .btn-success,.view-header-actions .btn-danger{margin-left:.5rem}.stat-card .card-body{display:flex;align-items:center;gap:1rem}.stat-icon{width:44px;height:44px;border-radius:8px;background:color-mix(in srgb,var(--dm-primary) 14%,transparent);color:var(--dm-primary, #5b8cff);display:flex;align-items:center;justify-content:center;font-size:1.25rem;flex-shrink:0}.stat-icon--success{background:color-mix(in srgb,var(--dm-success) 15%,transparent);color:var(--dm-success)}.stat-icon--warning{background:color-mix(in srgb,var(--dm-warning) 15%,transparent);color:var(--dm-warning)}.stat-value{font-size:1.75rem;font-weight:700;line-height:1}.stat-label{font-size:.8rem;color:var(--dm-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(--dm-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(#editor-meta-tabs)>.dm-plugin-shell{flex:1;min-height:0;display:flex;flex-direction:column}.view-container:has(#editor-meta-tabs)>.dm-plugin-shell>.dm-plugin-banner,.view-container:has(#editor-meta-tabs) .pex-bar{flex:none}.view-container:has(#editor-meta-tabs)>.dm-plugin-shell>.dm-plugin-body{flex:1;min-height:0;display:flex;flex-direction:column}.view-container:has(#block-editor-body){max-width:100%}.view-container:has(#ep-editor-tabs){max-width:100%}.ep-editor-grid{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,1fr) minmax(0,1.3fr);gap:1rem;align-items:start}.ep-editor-grid>.ep-tryit-col,.ep-editor-grid>.ep-results-col{position:sticky;top:1rem;display:flex;flex-direction:column;gap:1rem;min-width:0}@media(max-width:1500px){.ep-editor-grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.ep-editor-grid>:first-child{grid-column:1 / -1}.ep-editor-grid>.ep-tryit-col,.ep-editor-grid>.ep-results-col{position:static}}@media(max-width:1024px){.ep-editor-grid{grid-template-columns:minmax(0,1fr)}}#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}.live-preview-bar{display:flex;align-items:center;gap:var(--dm-space-4);flex-wrap:wrap;padding:var(--dm-space-2) 0;border-bottom:1px solid var(--dm-border);margin-bottom:var(--dm-space-2)}.live-preview-widths{display:inline-flex;border:1px solid var(--dm-border);border-radius:var(--dm-radius-sm);overflow:hidden}.live-preview-width{display:inline-flex;align-items:center;justify-content:center;padding:.3rem .55rem;border:none;background:transparent;color:inherit;cursor:pointer;line-height:1}.live-preview-width+.live-preview-width{border-left:1px solid var(--dm-border)}.live-preview-width:hover{background:var(--dm-hover-bg)}.live-preview-width.active{background:var(--dm-active-bg)}.live-preview-status{flex:1 1 auto;font-size:.8rem;opacity:.75}.live-preview-actions{display:flex;gap:var(--dm-space-1)}.live-preview-wrap{display:flex;flex-direction:column;height:100%;min-height:0}.live-preview-wrap>.live-preview-bar{flex:none}.live-preview-stage{display:flex;justify-content:center;flex:1;min-height:0;overflow:auto;background:var(--dm-background-alt);border-radius:var(--dm-radius-md)}.page-live-preview-frame{display:block;width:100%;max-width:100%;height:100%;border:none;border-radius:6px;background:var(--dm-background);transition:max-width .18s ease}.page-live-preview-frame[data-width="1024"]{max-width:1024px}.page-live-preview-frame[data-width="390"]{max-width:390px}.page-live-preview-frame[data-width="1024"],.page-live-preview-frame[data-width="390"]{border-left:1px solid var(--dm-border);border-right:1px solid var(--dm-border)}.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(--dm-border, rgba(255, 255, 255, .08));flex-wrap:wrap;flex-shrink:0}.editor-toolbar-btn{background:none;border:none;color:var(--dm-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(--dm-text, #eee);background:var(--dm-hover-bg)}.editor-toolbar-sep{width:1px;height:20px;background:var(--dm-border, 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(--dm-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(--dm-text, #eee);background:var(--dm-hover-bg)}.editor-view-btn.active{color:var(--dm-primary, #5b8cff);background:color-mix(in srgb,var(--dm-primary) 14%,transparent)}.editor-body{display:flex;flex:1;min-height:0;--dm-editor-split: 50%}@media(min-width:769px){.editor-body:not(.editor-mode-write):not(.editor-mode-preview) .editor-pane--write{flex:0 0 var(--dm-editor-split, 50%)}.editor-body:not(.editor-mode-write):not(.editor-mode-preview) .editor-pane--preview{flex:1 1 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(--dm-border, 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-primary, #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:7px;background:linear-gradient(to right,transparent 0,transparent 3px,var(--dm-border, rgba(255,255,255,.08)) 3px,var(--dm-border, rgba(255,255,255,.08)) 4px,transparent 4px,transparent 100%);cursor:col-resize;flex-shrink:0;touch-action:none;transition:background .12s ease}.editor-divider:hover,.editor-divider:focus-visible,.editor-divider.is-dragging{background:linear-gradient(to right,transparent 0,transparent 2px,var(--dm-primary, #5b8cff) 2px,var(--dm-primary, #5b8cff) 5px,transparent 5px,transparent 100%);outline:none}.editor-body.is-resizing .editor-pane{pointer-events:none}.editor-body.is-resizing{cursor:col-resize;user-select:none}.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-body .editor-textarea,#editor-body .editor-line-numbers{font-family:var(--pe-font, "Fira Code", "Courier New", monospace);font-size:var(--pe-size, .9rem);line-height:var(--pe-line, 1.6)}#editor-body.pe-nowrap .editor-textarea{white-space:pre;overflow-x:auto}.editor-preview{flex:1;display:block;width:100%;border:none;background:var(--dm-background)}.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(--dm-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(--dm-border, rgba(255, 255, 255, .08))}.docs-body .table{margin-bottom:.75rem}.docs-body h2{margin-top:1.75rem;margin-bottom:.6rem;padding-bottom:.3rem;border-bottom:1px solid var(--dm-border)}.docs-body h2:first-child{margin-top:0}.docs-body blockquote{margin:.75rem 0;padding:.5rem .9rem;border-left:3px solid var(--dm-primary);background:var(--dm-surface);border-radius:var(--dm-radius, 6px)}.docs-body pre{margin-bottom:.75rem;overflow-x:auto}.pg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:1rem;margin-top:1.5rem}.pg-grid>.card.pg-card{display:block;margin:0;text-decoration:none}.pg-card h3{margin-top:0}.pg-card .text-muted,.pg-card-pages{margin-bottom:0}.pg-card-pages{margin-top:.5rem;font-size:.8rem;color:var(--dm-text-muted)}.pg-actions{display:flex;gap:.5rem;flex-wrap:wrap}.pg-meta{margin-bottom:.75rem}.pg-pages{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:1rem}.pg-body{max-width:80ch}.nav-items-header,.nav-item-row{display:flex;gap:.5rem;align-items:center;padding:.35rem .75rem}.nav-items-header{border-bottom:1px solid var(--dm-border, rgba(255,255,255,.08));padding-bottom:.4rem;margin-bottom:.25rem}.nav-item-row{border-bottom:1px solid var(--dm-border, rgba(255,255,255,.04))}.nav-item-row:last-child{border-bottom:none}.nav-col-indent{width:18px;flex-shrink:0;color:var(--dm-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:color-mix(in srgb,var(--dm-text) 3%,transparent)}.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(--dm-text-muted, #888)}.btn-toggle-hidden.active{color:var(--dm-warning, #f59e0b)}.fb-drag-grip{flex-shrink:0;display:inline-flex;align-items:center;color:var(--dm-text-muted);cursor:grab}.fb-drag-grip:hover{color:var(--dm-text)}.btn-ghost span[data-icon=trash]{color:var(--dm-danger, #dc2626)}.view-container:has(.fb-fields-layout){max-width:none}.fb-fields-layout{display:grid;grid-template-columns:minmax(0,6fr) minmax(320px,4fr);gap:1.25rem;align-items:start}.fb-preview-pane{position:sticky;top:1rem;min-width:0}@media(max-width:1100px){.fb-fields-layout{grid-template-columns:1fr}.fb-preview-pane{position:static}}.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(--dm-card-bg, rgba(255,255,255,.04));border:1px solid var(--dm-border, 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(--dm-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(--dm-primary, #7c6af7);border-radius:3px;background:var(--dm-input-bg, transparent);color:inherit;outline:none}.media-size{font-size:.75rem;color:var(--dm-text-muted, #888)}.media-actions{padding:.5rem .75rem;display:flex;gap:.4rem;border-top:1px solid var(--dm-border, rgba(255,255,255,.08))}#admin-topbar{height:60px;display:flex;align-items:center;gap:1rem;padding:0 1.25rem;background:var(--dm-navbar-bg, rgba(0,0,0,.3));border-bottom:1px solid var(--dm-border, 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(--dm-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-avatar{width:1.75rem;height:1.75rem;border-radius:50%;object-fit:cover;flex-shrink:0}.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:color-mix(in srgb,var(--dm-primary) 15%,transparent);color:var(--dm-primary)}.topbar-role-badge--manager{background:color-mix(in srgb,var(--dm-success) 15%,transparent);color:var(--dm-success)}.topbar-role-badge--editor{background:color-mix(in srgb,var(--dm-warning) 15%,transparent);color:var(--dm-warning)}.topbar-role-badge--subscriber{background:color-mix(in srgb,var(--dm-text-muted) 15%,transparent);color:var(--dm-text-muted)}.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(--dm-text-muted, #888);text-decoration:none;padding:.4rem .65rem;border-radius:6px;transition:color .15s,background .15s}.topbar-action-link:hover{color:var(--dm-text, #eee);background:var(--dm-hover-bg)}.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(--dm-background, #0f1117);z-index:100;overflow-y:auto}.login-card{width:100%;max-width:460px}.ob-done-icon{font-size:3rem;color:var(--dm-success);margin-bottom:1rem;text-align:center}.btn-skip{display:block;text-align:center;margin-top:.75rem;font-size:.85rem;color:var(--dm-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(--dm-border, rgba(255,255,255,.1));border-radius:8px;overflow:hidden;cursor:pointer;transition:border-color .15s,transform .1s}.theme-swatch:hover{border-color:var(--dm-primary, #5b8cff);transform:translateY(-1px)}.theme-swatch.selected{border-color:var(--dm-primary, #5b8cff);box-shadow:0 0 0 2px var(--dm-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(--dm-card-bg, rgba(255,255,255,.04))}.theme-swatch-mode{color:var(--dm-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(--dm-primary, #5b8cff)}.login-logo h1{margin:0;font-size:1.5rem;font-weight:700}.login-card [hidden]{display:none!important}.login-heading{font-size:1.1rem;font-weight:600;margin-bottom:1.25rem}.login-rules{margin:.25rem 0 .75rem;padding-left:1.1rem;color:var(--dm-danger);font-size:.85rem}.login-rules li+li{margin-top:.2rem}.login-small{font-size:.8rem;margin-top:.75rem}.login-icon-warn{color:var(--dm-warning)}.pwf{position:relative;display:block}.pwf>input{width:100%;padding-right:2.5rem}.pwf-toggle{position:absolute;top:50%;right:.35rem;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;padding:0;border:0;border-radius:var(--dm-radius-sm);background:transparent;color:var(--dm-text-muted);cursor:pointer}.pwf-toggle:hover,.pwf-toggle:focus-visible{color:var(--dm-text);background:var(--dm-hover-bg)}.pwf-toggle svg{width:16px;height:16px}.pwf-toggle [data-icon=eye-off],.pwf.is-shown .pwf-toggle [data-icon=eye]{display:none}.pwf.is-shown .pwf-toggle [data-icon=eye-off]{display:inline-block}.pwf-caps{display:block;margin-top:.25rem;color:var(--dm-warning);font-size:.8rem}.pwf-caps[hidden]{display:none}.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-accent: var(--dm-primary, #5b8cff);position:relative;overflow:hidden;background-image:radial-gradient(120% 90% at 100% 0%,color-mix(in srgb,var(--plugin-accent) 13%,transparent) 0%,color-mix(in srgb,var(--plugin-accent) 4%,transparent) 42%,transparent 72%)}.plugin-card .plugin-icon{background:color-mix(in srgb,var(--plugin-accent) 16%,transparent);color:var(--plugin-accent)}.plugin-watermark{position:absolute;top:-34px;right:-30px;width:148px;height:148px;display:block;color:var(--plugin-accent);opacity:.09;pointer-events:none;z-index:0}.plugin-card>.card-body,.plugin-card>.plugin-footer{position:relative;z-index:1}.plugin-card:has(.badge-secondary) .plugin-watermark{opacity:.05}@media(prefers-reduced-motion:no-preference){.plugin-card{transition:background-image .2s ease}}.plugin-card .card-body{flex:1}.plugin-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:.75rem;flex-wrap:wrap}.plugin-icon{width:40px;height:40px;border-radius:8px;background:color-mix(in srgb,var(--dm-primary) 14%,transparent);color:var(--dm-primary, #5b8cff);display:flex;align-items:center;justify-content:center;flex-shrink:0}.plugin-meta{flex:1 1 140px;min-width:0}.plugin-name{font-weight:600;margin-bottom:.15rem}.plugin-version{font-size:.75rem;color:var(--dm-text-muted, #888)}.plugin-desc{font-size:.875rem;color:var(--dm-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(--dm-border, 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:color-mix(in srgb,var(--dm-primary) 14%,transparent);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(--dm-text-muted, #888)}@media(max-width:768px){.view-container{padding:1rem}.editor-body{flex-direction:column}.editor-divider{width:auto;height:1px;cursor:default;background:var(--dm-border, rgba(255,255,255,.08))}}.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(--dm-border, rgba(255, 255, 255, .1))}.image-editor-sep{display:inline-block;width:1px;height:1.5rem;background:var(--dm-border, rgba(255, 255, 255, .15));margin:0 .25rem;flex-shrink:0}.editor-toolbar-btn.active{color:var(--dm-primary, #7c6af7);background:color-mix(in srgb,var(--dm-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(--dm-border, rgba(255, 255, 255, .1));border-radius:8px;box-shadow:0 8px 24px #0006;padding:4px;min-width:160px;max-height:70vh;overflow-y:auto}.editor-toolbar-dropdown-heading{padding:6px 10px 3px;color:var(--dm-text-muted, #8a8a9a);font-size:.66rem;text-transform:uppercase;letter-spacing:.06em}.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:var(--dm-hover-bg);color:var(--dm-text, #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(--dm-border, 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:var(--dm-hover-bg);color:var(--dm-text, #fff)}.editor-icon-picker{position:absolute;z-index:1000;background:var(--dm-surface, #1e1e2e);border:1px solid var(--dm-border, 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(--dm-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:var(--dm-hover-bg);color:var(--dm-text, #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(--dm-border, rgba(255, 255, 255, .1));font-size:.85rem}.image-editor-resize label{font-weight:600;color:var(--dm-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(--dm-border, rgba(255, 255, 255, .1));gap:.5rem}.image-editor-effects{border-top:1px solid var(--dm-border, rgba(255, 255, 255, .1))}.image-editor-tab-bar{display:flex;flex-direction:row;overflow-x:auto;border-bottom:1px solid var(--dm-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(--dm-text-muted, #888);white-space:nowrap;transition:color .15s,border-color .15s;flex-shrink:0}.image-editor-tab-btn:hover{color:var(--dm-text, #eee)}.image-editor-tab-btn.active{color:var(--dm-primary, #7c6af7);border-bottom-color:var(--dm-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(--dm-primary, #7c6af7);background:color-mix(in srgb,var(--dm-primary, #7c6af7) 15%,transparent);border-color:var(--dm-primary, #7c6af7)}.ie-server-note{font-size:.73rem;color:var(--dm-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(--dm-text-muted, #888);font-weight:600;flex-shrink:0}.ie-slider{flex:1;accent-color:var(--dm-primary, #7c6af7);cursor:pointer}.ie-slider-val{width:3rem;font-size:.8rem;text-align:right;color:var(--dm-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(--dm-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(--dm-surface-raised, 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(--dm-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(--dm-surface-raised, rgba(255, 255, 255, .08));border-color:var(--dm-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(--dm-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-resizer{position:absolute;top:0;bottom:0;width:6px;cursor:col-resize;z-index:2;background:transparent}.dm-slideover-right>.dm-slideover-resizer{left:0}.dm-slideover-left>.dm-slideover-resizer{right:0}.dm-slideover-resizer:hover,.is-resizing>.dm-slideover-resizer{background:var(--dm-primary);opacity:.7}.dm-slideover.is-resizing{user-select:none;transition:none!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:auto;overscroll-behavior:contain;min-height:0;padding:1.5rem!important;box-sizing:border-box}.dm-slideover-body:empty{flex:0 0 auto;padding:0!important}.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-body):not(.dm-slideover-close){padding:1.5rem!important;box-sizing:border-box;flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain}.dm-slideover-body,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close){scrollbar-width:thin;scrollbar-color:var(--dm-border) transparent}.dm-slideover-body::-webkit-scrollbar,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar{width:8px;height:8px}.dm-slideover-body::-webkit-scrollbar-track,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-track{background:transparent}.dm-slideover-body::-webkit-scrollbar-thumb,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-thumb{background:var(--dm-border);border-radius:4px}.dm-slideover-body::-webkit-scrollbar-thumb:hover,.dm-slideover>div:not(.dm-slideover-header):not(.dm-slideover-close)::-webkit-scrollbar-thumb:hover{background:var(--dm-text-muted)}.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-background-alt, 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-background-alt, 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-action-link[hidden],.topbar-bell-badge[hidden]{display:none}.topbar-bell-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;background:var(--dm-danger, #dc2626);color:var(--dm-danger-text, #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-background-alt);border:1px solid var(--dm-primary);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-primary)}.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-primary)}.cb-recipe-card .cb-icon{flex-shrink:0;font-size:1.5rem;color:var(--dm-primary);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-primary);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-primary);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-background-alt);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-background-alt);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-primary);background:var(--dm-background-alt);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-primary)}.cb-tutorial-link .cb-icon{flex-shrink:0;color:var(--dm-primary)}.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:26px 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-row--tool .me-tool-badge{margin-left:.4rem;font-size:.7rem;vertical-align:middle}.menu-tree-row--default .me-row-label{font-style:italic;opacity:.8}.menu-tree-grip{cursor:grab;color:var(--dm-text-muted)}.menu-tree-grip:hover{color:var(--dm-text)}.menu-tree{padding:8px 0}.menu-tree-row--separator{display:grid;grid-template-columns:26px minmax(80px,1fr) repeat(7,auto);gap:6px;align-items:center;padding:2px 0;opacity:.85}.menu-tree-row--separator:hover{opacity:1}.menu-tree-row--separator .me-sep-line{display:flex;align-items:center;gap:10px;width:100%}.menu-tree-row--separator .me-sep-line:before,.menu-tree-row--separator .me-sep-line:after{content:"";flex:1;border-top:1px dashed var(--dm-border, rgba(128, 128, 128, .55))}.menu-tree-row--separator .me-sep-label{font-size:.66rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--dm-text-muted, #8a8a8a);white-space:nowrap}.dm-admin-sidebar .sidebar-divider{border:0;border-top:1px solid var(--dm-border, rgba(128, 128, 128, .5));margin:.5rem .75rem;opacity:.8}.dm-admin-sidebar .sidebar-spacer--xs{height:var(--dm-space-1, .25rem)}.dm-admin-sidebar .sidebar-spacer--sm{height:var(--dm-space-2, .5rem)}.dm-admin-sidebar .sidebar-spacer--md{height:var(--dm-space-4, 1rem)}.dm-admin-sidebar .sidebar-spacer--lg{height:var(--dm-space-6, 1.5rem)}.dm-admin-sidebar .sidebar-spacer--xl{height:var(--dm-space-10, 2.5rem)}.dm-admin-sidebar .sidebar-spacer--flex{flex:1 1 auto;min-height:var(--dm-space-4, 1rem)}.dm-admin-sidebar--has-flex-spacer{display:flex;flex-direction:column;min-height:100%}.menu-tree-row--spacer{display:grid;grid-template-columns:26px minmax(80px,1fr) repeat(7,auto);gap:6px;align-items:center;padding:2px 0;opacity:.85}.menu-tree-row--spacer:hover{opacity:1}.menu-tree-row--spacer .me-spacer-line{display:flex;align-items:center;gap:8px;width:100%}.menu-tree-row--spacer .me-spacer-line:before,.menu-tree-row--spacer .me-spacer-line:after{content:"";flex:1;border-top:1px dotted var(--dm-border, rgba(128, 128, 128, .55))}.menu-tree-row--spacer .me-spacer-label{font-size:.66rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--dm-text-muted, #8a8a8a);white-space:nowrap}.menu-tree-row--spacer .me-spacer-size{width:auto;min-width:5.5rem;height:26px;padding:0 .35rem;font-size:.75rem}.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;font-size:.875rem}.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;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888)}.dm-admin-sidebar details details>summary{font-size:.875rem;font-weight:500;text-transform:none;letter-spacing:0;color:inherit}.dm-admin-sidebar .dm-menu-badge{font-size:.7rem;font-weight:600}.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-light, 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-hover-bg, rgba(0, 0, 0, .05))}.dm-admin-sidebar .sidebar-link.active{background:var(--dm-active-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{position:sticky;top:0;z-index:2;background:var(--dm-surface);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-light, rgba(0, 0, 0, .08))}.dm-admin-sidebar--dark .dm-admin-sidebar-header{background:#1a1d24}.dm-admin-sidebar--light .dm-admin-sidebar-header{background:#fff}.dm-admin-sidebar-header [data-icon]{color:var(--dm-text-muted, #999)}.dm-admin-sidebar-header .dm-admin-sidebar-arrange{margin-left:auto;padding:.2rem .35rem;color:var(--dm-text-muted, #999);line-height:1}.dm-admin-sidebar-header .dm-admin-sidebar-arrange:hover,.dm-admin-sidebar-header .dm-admin-sidebar-arrange:focus-visible{color:var(--dm-text)}.dm-admin-sidebar.is-arranging>:not(.dm-admin-sidebar-header):not(.sb-arrange):not(style){display:none!important}.dm-admin-sidebar.is-arranging .dm-admin-sidebar-arrange,.dm-admin-sidebar.is-arranging .dm-admin-sidebar-collapse{display:none}.sb-arrange{box-sizing:border-box;max-width:100vw;padding:0 .5rem 1rem;animation:sb-arrange-in .18s cubic-bezier(.2,.8,.2,1)}@keyframes sb-arrange-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}.sb-arrange-bar{position:sticky;top:2.6rem;z-index:2;display:flex;align-items:center;gap:.4rem;padding:.4rem .25rem;background:inherit}.sb-arrange-title{font-weight:600;margin-right:auto}.sb-arrange-hint{margin:0 .25rem .5rem;font-size:.78rem;line-height:1.35;color:var(--dm-text-muted)}.sb-arr-row{display:flex;align-items:center;gap:.45rem;padding:.35rem .4rem .35rem calc(.4rem + var(--sb-arr-depth, 0) * 1rem);border-radius:var(--dm-radius-sm, 4px);cursor:grab;user-select:none;font-size:.9rem}.sb-arr-row:hover{background:color-mix(in srgb,var(--dm-text) 6%,transparent)}.sb-arr-row:focus-visible{outline:2px solid var(--dm-primary);outline-offset:-2px}.sb-arr-row--folder .sb-arr-text{font-weight:600}.sb-arr-row--hidden .sb-arr-text,.sb-arr-row--hidden .sb-arr-icon{opacity:.45;text-decoration:line-through}.sb-arr-row--structural .sb-arr-text{font-size:.75rem;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted)}.sb-arr-grip{color:var(--dm-text-muted);flex:none}.sb-arr-icon{width:16px;flex:none}.sb-arr-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sb-arr-hide{flex:none;display:inline-flex;padding:.15rem;border:0;border-radius:var(--dm-radius-sm, 4px);background:transparent;color:var(--dm-text-muted);cursor:pointer}.sb-arr-hide:hover,.sb-arr-hide:focus-visible{color:var(--dm-text);background:color-mix(in srgb,var(--dm-text) 8%,transparent)}.sb-arr-lock{flex:none;color:var(--dm-text-muted);opacity:.6}.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-active-bg, rgba(0, 0, 0, .12));color:var(--dm-text-muted, #888)}.dm-admin-sidebar .sidebar-link.active .sidebar-badge{background:var(--dm-primary, #4a86e8);color:var(--dm-primary-text, #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}#admin-sidebar{position:relative;transition:width .18s ease}#admin-sidebar.is-resizing{transition:none}@media(prefers-reduced-motion:reduce){#admin-sidebar{transition:none}}.dm-admin-sidebar-handle{position:absolute;top:0;right:0;width:8px;height:100%;cursor:col-resize;z-index:10;touch-action:none;outline:none}.dm-admin-sidebar-handle:before,.dm-admin-sidebar-handle:after{content:"";position:absolute;right:2px;transition:background .15s,opacity .15s}.dm-admin-sidebar-handle:before{top:0;bottom:0;width:2px;background:transparent}.dm-admin-sidebar-handle:after{top:50%;width:3px;height:28px;margin-top:-14px;border-radius:2px;background:var(--dm-border, rgba(0, 0, 0, .15));opacity:.8}.dm-admin-sidebar-handle:hover:before,.dm-admin-sidebar-handle:focus-visible:before,.is-resizing>.dm-admin-sidebar-handle:before{background:var(--dm-primary, #4a86e8);opacity:.5}.dm-admin-sidebar-handle:hover:after,.dm-admin-sidebar-handle:focus-visible:after,.is-resizing>.dm-admin-sidebar-handle:after{background:var(--dm-primary, #4a86e8);opacity:1}.dm-admin-sidebar-handle:focus-visible:before{opacity:1}#admin-sidebar:has(.is-arranging)>.dm-admin-sidebar-handle{display:none}body.dm-sidebar-resizing{cursor:col-resize;user-select:none}body.dm-sidebar-resizing iframe{pointer-events:none}.dm-admin-sidebar-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dm-admin-sidebar-header .dm-admin-sidebar-collapse{padding:.2rem .35rem;color:var(--dm-text-muted, #999);line-height:1}.dm-admin-sidebar-header .dm-admin-sidebar-collapse:hover,.dm-admin-sidebar-header .dm-admin-sidebar-collapse:focus-visible{color:var(--dm-text)}.dm-admin-sidebar-collapse [data-icon]{transition:transform .18s ease}.dm-admin-sidebar .sidebar-initial{display:none}#admin-sidebar.is-collapsed{width:56px!important}#admin-sidebar.is-collapsed .dm-admin-sidebar{overflow-x:hidden}#admin-sidebar.is-collapsed .dm-admin-sidebar-header{justify-content:center;padding-left:0;padding-right:0}#admin-sidebar.is-collapsed .dm-admin-sidebar-header>[data-icon],#admin-sidebar.is-collapsed .dm-admin-sidebar-title,#admin-sidebar.is-collapsed .dm-admin-sidebar-arrange{display:none}#admin-sidebar.is-collapsed .dm-admin-sidebar-collapse [data-icon]{transform:rotate(180deg)}#admin-sidebar.is-collapsed .sidebar-text{position:absolute!important;width:1px;height:1px;padding:0!important;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}#admin-sidebar.is-collapsed .sidebar-link,#admin-sidebar.is-collapsed details>summary{justify-content:center;padding:.5rem 0;margin:0 6px;width:auto!important}#admin-sidebar.is-collapsed details>summary:after,#admin-sidebar.is-collapsed .dm-menu-badge,#admin-sidebar.is-collapsed .sidebar-children{display:none!important}#admin-sidebar.is-collapsed .sidebar-initial{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:4px;font-size:.75rem;font-weight:600;background:var(--dm-hover-bg, rgba(0, 0, 0, .06))}#admin-sidebar.is-collapsed details>summary[aria-expanded=true]{background:var(--dm-active-bg, rgba(0, 0, 0, .08))}#admin-sidebar.is-collapsed .sidebar-badge{top:4px!important;right:8px!important;transform:none!important;width:8px;height:8px;min-width:0;padding:0!important;border-radius:50%;font-size:0;line-height:0;overflow:hidden}#admin-sidebar.is-collapsed .sidebar-badge--live{padding:0!important;background:var(--dm-success, #16a34a)}#admin-sidebar.is-collapsed .sidebar-badge--live:before{display:none}#admin-sidebar.is-collapsed summary>.sidebar-badge--sum{position:absolute!important;margin:0!important}#admin-sidebar.is-collapsed details[open]>summary>.sidebar-badge--sum{display:block}#admin-sidebar.is-collapsed .sidebar-divider{margin-left:8px;margin-right:8px}#admin-sidebar.is-collapsed .sidebar-spacer--flex{min-height:var(--dm-space-2, .5rem)}#admin-sidebar.is-collapsed details>summary{position:relative}.dm-admin-sidebar-flyout{position:fixed;z-index:1000;min-width:200px;max-width:320px;overflow-y:auto;background:var(--dm-surface, #fff);color:var(--dm-text, inherit);border:1px solid var(--dm-border, rgba(0, 0, 0, .1));border-radius:var(--dm-radius-md, 8px);box-shadow:var(--dm-shadow-lg, 0 8px 24px rgba(0, 0, 0, .15));animation:dm-sidebar-flyout-in .12s ease-out}.dm-admin-sidebar-flyout>.dm-admin-sidebar{padding:4px 6px 6px}.dm-admin-sidebar-flyout-title{padding:.45rem .75rem .35rem;font-size:.78rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--dm-text-muted, #888)}.dm-admin-sidebar-flyout .sidebar-badge{margin-left:auto}@keyframes dm-sidebar-flyout-in{0%{opacity:0;transform:translate(-4px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.dm-admin-sidebar-flyout{animation:none}}.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-light, 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}.dm-menu-badge{display:inline-block;margin-left:6px;padding:1px 7px;font-size:.7em;font-weight:700;line-height:1.4;border-radius:999px;background:var(--dm-text-muted);color:var(--dm-text-inverse, #fff);vertical-align:middle}.menu-tree-row--slim{display:grid;grid-template-columns:26px 1fr auto repeat(9,auto);gap:6px;align-items:center}.menu-tree-row--slim .me-row-label{font-weight:600}.menu-tree-row--slim .me-row-url{color:var(--dm-text-muted);font-size:.85em}.me-pages-picker{display:flex;gap:6px;align-items:center;margin-bottom:6px}.me-pages-picker .form-input{flex:1;min-width:0}.me-colour-control{display:flex;gap:6px;align-items:center}.me-colour-control .me-colour-hex{width:48px;height:32px;padding:2px;flex:none}.me-bind-summary{padding:.6rem .8rem;border:1px solid var(--dm-border, rgba(128, 128, 128, .35));border-radius:var(--dm-radius-md, 6px);background:color-mix(in srgb,var(--dm-text) 4%,transparent);font-size:.9em}.me-bind-summary code{font-size:.9em}.me-item-form{display:flex;flex-direction:column;gap:10px;padding:4px 2px}.me-item-form .form-field{display:flex;flex-direction:column;gap:4px}.me-item-form .form-label{font-size:.85em;color:var(--dm-text-muted)}.me-form-section{margin:12px 0 2px;padding-top:10px;border-top:1px solid var(--dm-border-light, rgba(0,0,0,.15));font-size:.78em;text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.me-item-form .me-form-section:first-child{margin-top:0;padding-top:0;border-top:0}.me-item-form .me-colour-hex{width:48px;height:32px;padding:2px}.me-item-form .me-field-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.me-item-form .me-field-grid .form-field{margin:0}.me-item-form .me-icon-field{display:flex;align-items:center;gap:8px}.me-item-form .me-icon-field .form-input{flex:1}.me-item-form .me-icon-preview{width:24px;height:24px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;color:var(--dm-text-muted)}#admin-sidebar.dm-menu-floating{border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 8px);box-shadow:var(--dm-shadow-lg);overflow:hidden}@media(max-width:768px){#admin-sidebar.dm-menu-floating{position:static!important;inset:auto!important;max-height:none!important;transform:none!important}.dashboard-wrapper{padding-left:0!important;padding-right:0!important}}[data-logic-section]{border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 6px);padding:.9rem 1rem 1rem;margin-bottom:1rem;background:color-mix(in srgb,var(--dm-text) 3%,transparent)}[data-logic-section] .fb-logic-cond-row,[data-logic-section] .fb-logic-val-rule{padding:.55rem .65rem;border-radius:var(--dm-radius-sm, 4px);border:1px solid var(--dm-border);background:var(--dm-surface, transparent)}[data-logic-section] .fb-logic-cond-row:last-of-type,[data-logic-section] .fb-logic-val-rule:last-of-type{margin-bottom:.75rem}[data-logic-section]>.btn{margin-top:.15rem}[data-logic-section] textarea{min-height:5rem}.project-filter-notice{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap;margin:0 0 1rem;padding:.6rem .85rem;border:1px solid var(--dm-border);border-left:3px solid var(--dm-info, var(--dm-primary));border-radius:var(--dm-radius-md, 6px);background:color-mix(in srgb,var(--dm-info, var(--dm-primary)) 6%,transparent);font-size:.85rem;color:var(--dm-text-muted)}.project-filter-notice a{color:var(--dm-primary);font-weight:600}.ctx-help{display:inline-flex;align-items:center;justify-content:center;width:1.05rem;height:1.05rem;margin-left:.35rem;padding:0;border:1px solid var(--dm-border-dark, var(--dm-border));border-radius:var(--dm-radius-full, 9999px);background:transparent;color:var(--dm-text-muted);font-size:.68rem;font-weight:700;line-height:1;cursor:pointer;vertical-align:middle;flex-shrink:0}.ctx-help:hover,.ctx-help:focus-visible{border-color:var(--dm-primary);color:var(--dm-primary);outline:none}.card-header h3 .ctx-help{align-self:center}.ctx-preview-card{position:sticky;top:1rem}@media(max-width:1100px){.ctx-workspace>.col-8,.ctx-workspace>.col-4{flex:0 0 auto;width:100%}.ctx-preview-card{position:static}}.dm-plugin-shell{border:1px solid var(--dm-border);border-radius:var(--dm-radius-lg, 12px);background:var(--dm-surface);overflow:hidden}.dm-plugin-banner{display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;padding:.7rem 1rem;background:var(--dm-background-alt);border-bottom:1px solid var(--dm-border)}.dm-plugin-banner-identity{display:flex;align-items:center;gap:.55rem;min-width:0;margin-right:auto}.dm-plugin-banner-actions{display:flex;align-items:center;gap:.25rem;flex:0 0 auto;margin-right:.15rem;padding-right:.4rem;border-right:1px solid var(--dm-border)}.dm-plugin-banner-action{padding:.25rem .4rem;line-height:1;color:var(--dm-text-muted)}.dm-plugin-banner-action:hover{color:var(--dm-primary)}.dm-plugin-banner-icon{display:inline-flex;align-items:center;color:var(--dm-primary);flex:0 0 auto}.dm-plugin-banner-name{margin:0;font-size:1.05rem;font-weight:600;letter-spacing:.01em;color:var(--dm-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dm-activity{display:inline-flex;align-items:center;flex:0 0 auto;margin-left:.35rem}.dm-activity domma-loader{display:inline-block;line-height:0}.dm-activity[hidden]{display:none!important}#admin-topbar>.dm-activity{margin:0 .75rem 0 0}.dm-plugin-banner-parent{font-weight:500;color:var(--dm-text-muted)}.dm-plugin-banner-sep{margin:0 .4rem;color:var(--dm-text-muted)}.dm-plugin-banner-facts{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap}.dm-plugin-fact{font-size:.72rem;line-height:1.6;padding:1px 8px;border-radius:999px;border:1px solid var(--dm-border);color:var(--dm-text-muted);background:var(--dm-surface);white-space:nowrap}.dm-plugin-fact--version{font-variant-numeric:tabular-nums;color:var(--dm-text)}.dm-plugin-fact--licence.is-included{color:var(--dm-info);border-color:var(--dm-info)}.dm-plugin-fact--licence.is-licensed{color:var(--dm-success);border-color:var(--dm-success)}.dm-plugin-fact--expiry{font-variant-numeric:tabular-nums}.dm-plugin-fact--expiry.is-warning{color:var(--dm-warning);border-color:var(--dm-warning)}.dm-plugin-fact--expiry.is-danger{color:var(--dm-danger);border-color:var(--dm-danger)}.dm-plugin-body{padding:1rem}.dm-plugin-body .card.dm-plugin-unwrap{border:0;background:none;box-shadow:none;margin:0}.dm-plugin-body .card.dm-plugin-unwrap>.card-body{padding:0}.dm-plugin-body>.p-4{padding:0}@media(max-width:640px){.dm-plugin-banner{align-items:flex-start;flex-direction:column;gap:.5rem}.dm-plugin-body{padding:.75rem}}.analytics-filter{max-width:240px}.dm-help-btn{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;margin-left:.3rem;padding:0;border:0;background:none;line-height:1;color:var(--dm-text-muted);opacity:.75;cursor:pointer}.dm-help-btn:hover,.dm-help-btn:focus-visible{color:var(--dm-primary);opacity:1}.dm-popover.dm-help-popover{--dm-popover-max-width: 300px;font-size:.85rem;line-height:1.45;font-weight:400}.dm-help-pop{position:fixed;z-index:10050;max-width:300px;padding:.6rem .75rem;background:var(--dm-surface, var(--dm-background));color:var(--dm-text);border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);box-shadow:0 8px 24px #00000040;font-size:.85rem;line-height:1.45;text-align:left;font-weight:400}.dm-help-pop strong{display:block;margin-bottom:.2rem;font-weight:600}.dm-help-pop p{margin:0}.dm-admin-sidebar .sidebar-badge[data-badge-label]{cursor:help}.dm-admin-sidebar .sidebar-badge--danger,.dm-admin-sidebar .sidebar-link.active .sidebar-badge--danger{background:var(--dm-danger, #dc2626);color:#fff}.dm-admin-sidebar .sidebar-badge--live{background:color-mix(in srgb,var(--dm-success, #16a34a) 20%,transparent);color:var(--dm-success, #16a34a)}#admin-sidebar .sidebar-badge--live{padding-left:1.05rem!important}#admin-sidebar summary>.sidebar-badge--sum{position:static!important;transform:none!important;margin-left:auto!important;margin-right:.45rem;text-transform:none;letter-spacing:0}#admin-sidebar summary:has(>.sidebar-badge--sum):after{margin-left:0}#admin-sidebar details[open]>summary>.sidebar-badge--sum{display:none}.dm-admin-sidebar .sidebar-badge--live:before{content:"";position:absolute;left:.4rem;top:50%;width:.4rem;height:.4rem;margin-top:-.2rem;border-radius:50%;background:currentColor;animation:dm-badge-pulse 2s ease-in-out infinite}@keyframes dm-badge-pulse{50%{opacity:.35}}@media(prefers-reduced-motion:reduce){.dm-admin-sidebar .sidebar-badge--live:before{animation:none}}.dm-badge-pop{width:290px;max-width:calc(100vw - 16px);font-size:.8rem;pointer-events:none}.dm-badge-pop-title{display:block;font-size:.85rem}.dm-badge-pop-sub{margin:.1rem 0 .5rem;color:var(--dm-text-muted)}.dm-badge-pop-rows{display:grid;grid-template-columns:1fr auto;gap:.2rem .75rem;margin:0 0 .5rem;padding:.45rem 0;border-top:1px solid var(--dm-border);border-bottom:1px solid var(--dm-border)}.dm-badge-pop-rows dt{color:var(--dm-text-muted)}.dm-badge-pop-rows dd{margin:0;text-align:right;font-weight:600;font-variant-numeric:tabular-nums}.dm-badge-pop-items{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.4rem}.dm-badge-pop-items li{display:flex;flex-direction:column;min-width:0}.dm-badge-pop-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dm-badge-pop-meta{font-size:.72rem;color:var(--dm-text-muted)}.dm-badge-pop .is-danger,.dm-badge-pop li.is-danger .dm-badge-pop-meta{color:var(--dm-danger, #dc2626)}.dm-badge-pop-empty{margin:0;color:var(--dm-text-muted)}.dm-plugin-body,.dm-slideover{font-family:var(--dm-font-sans, inherit)}.dm-plugin-body :is(button,input,select,textarea,.btn,.form-input,.form-select,.form-textarea),.dm-slideover :is(button,input,select,textarea,.btn,.form-input,.form-select,.form-textarea){font-family:inherit}.dm-plugin-body :is(input,select,textarea,.form-input,.form-select,.form-textarea):not(.form-input-lg),.dm-slideover :is(input,select,textarea,.form-input,.form-select,.form-textarea):not(.form-input-lg){font-size:var(--dm-text-sm)}.dm-plugin-body .form-input-lg{font-size:var(--dm-text-base)}.dm-plugin-body :is(.font-mono,code,pre,kbd),.dm-slideover :is(.font-mono,code,pre,kbd){font-family:var(--dm-font-mono, monospace)}.dm-plugin-body .form-label,.dm-slideover .form-label{font-size:var(--dm-text-sm)}.related-box{display:flex;flex-direction:column;gap:.6rem;border-top:1px solid var(--dm-border);padding-top:.75rem}.related-box[hidden]{display:none}.related-head{display:flex;align-items:center;gap:.4rem}.related-head strong{flex:1}.related-list{list-style:none;margin:.3rem 0 0;padding:0;display:flex;flex-direction:column;gap:.25rem}.related-list li{display:flex;flex-direction:column}.related-list li.is-past{opacity:.7}.related-meta,.related-note,.related-loading{color:var(--dm-text-muted);font-size:var(--dm-text-xs);margin:0}.login-challenge{display:flex;flex-direction:column;gap:.6rem;margin-bottom:.75rem}.login-challenge #challenge-code{font-size:1.25rem;letter-spacing:.2em;text-align:center}.login-trust{display:flex;align-items:center;gap:.45rem;font-size:.875rem}.login-enrol{display:flex;flex-direction:column;align-items:center;gap:.6rem;margin-bottom:1rem}.login-enrol img{background:#fff;padding:.5rem;border-radius:.5rem}.login-secret{display:flex;flex-direction:column;align-items:center;gap:.25rem;text-align:center}.login-secret code{font-size:.95rem;letter-spacing:.08em;word-break:break-all}.usx-secret{display:flex;flex-direction:column;gap:.75rem;padding:1rem}.usx-secret-link{display:block;padding:.6rem .75rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-sm);background:var(--dm-background-alt);word-break:break-all;font-size:.8rem;user-select:all}.usx-secret-actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem}.usx-secret-actions .btn{display:inline-flex;align-items:center;gap:.35rem}.usx-secret-note{margin-right:auto;color:var(--dm-success)}.usx-secret-foot{color:var(--dm-text-muted);font-size:.8rem;margin:0}.uex-reset{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .5rem;margin-top:.5rem}.uex-reset>small:first-child{flex-basis:100%;color:var(--dm-text-muted)}.uex-reset .btn{display:inline-flex;align-items:center;gap:.35rem}.uex-reset-open{flex-basis:100%;color:var(--dm-info)}.dm-loading-slot>.skeleton-list,.dm-loading-slot>.skeleton-table{border-color:transparent;background:transparent}.dm-loading-slot>.skeleton-list .skeleton-list-item{padding-left:.35rem;padding-right:.35rem}.dm-skeleton-figure{width:3.5rem;height:1em;margin:.1em 0}.view-container:has(.dbp){max-width:none}.dbp-head{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:.9rem}.dbp-hint{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-sm);display:flex;align-items:center;gap:.35rem}.dbp-source{color:var(--dm-text-muted);font-size:var(--dm-text-xs);white-space:nowrap}.dbp-board{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));column-gap:1rem;row-gap:0;grid-auto-rows:4px;grid-auto-flow:row dense;align-items:start}.dbp-board>.dbp-card{align-self:start;margin-bottom:1rem}.dbp-card{grid-column:span 2;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);background:var(--dm-surface);display:flex;flex-direction:column;min-width:0;min-height:9rem;outline:none}.dbp-card:focus-visible{border-color:var(--dm-primary)}.dbp-size-s{grid-column:span 1}.dbp-size-m{grid-column:span 2}.dbp-size-l{grid-column:span 3}.dbp-size-xl{grid-column:1 / -1}@media(max-width:1100px){.dbp-board{grid-template-columns:repeat(2,minmax(0,1fr))}.dbp-size-l{grid-column:1 / -1}}@media(max-width:640px){.dbp-board{grid-template-columns:minmax(0,1fr)}.dbp-card{grid-column:1 / -1}}.dbp-card-head{display:flex;align-items:center;gap:.5rem;padding:.55rem .7rem;border-bottom:1px solid var(--dm-border);min-width:0}.dbp-grip{cursor:grab;color:var(--dm-text-muted);display:inline-flex;touch-action:none}.dbp-grip:active{cursor:grabbing}.dbp-icon{display:inline-flex;align-items:center;justify-content:center;width:1.8rem;height:1.8rem;flex:0 0 auto;border-radius:var(--dm-radius-md);color:var(--dm-primary);background:color-mix(in srgb,var(--dm-primary) 12%,transparent)}.dbp-title{font-weight:600;color:var(--dm-text);text-decoration:none;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}a.dbp-title:hover,a.dbp-title:focus-visible{color:var(--dm-primary);text-decoration:underline}.dbp-from{margin-left:auto;color:var(--dm-text-muted);font-size:var(--dm-text-xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dbp-kebab{border:0;background:none;color:var(--dm-text-muted);cursor:pointer;padding:.1rem .35rem;border-radius:var(--dm-radius-md);line-height:1}.dbp-kebab:hover,.dbp-kebab:focus-visible{background:color-mix(in srgb,var(--dm-text) 8%,transparent);color:var(--dm-text)}.dbp-body{padding:.75rem .9rem;display:flex;flex-direction:column;gap:.6rem;min-width:0;flex:1 1 auto}.dbp-card.is-loading .dbp-body{opacity:.75}.dbp-skeleton{height:3.5rem;border-radius:var(--dm-radius-md);background:color-mix(in srgb,var(--dm-text) 7%,transparent)}.dbp-figure{display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap}.dbp-figure-value{font-size:var(--dm-text-2xl, 1.5rem);line-height:1.15;font-weight:600;font-variant-numeric:tabular-nums;color:var(--dm-text)}.dbp-figure-label{color:var(--dm-text-muted);font-size:var(--dm-text-sm)}.dbp-figure.is-danger .dbp-figure-value{color:var(--dm-danger)}.dbp-figure.is-attention .dbp-figure-value{color:var(--dm-warning)}.dbp-sub{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-facts{display:grid;grid-template-columns:auto 1fr;gap:.2rem .8rem;margin:0;font-size:var(--dm-text-sm)}.dbp-facts dt{color:var(--dm-text-muted)}.dbp-facts dd{margin:0;text-align:right;font-variant-numeric:tabular-nums}.dbp-rows{display:flex;flex-direction:column;min-width:0}.dbp-row{display:flex;align-items:center;gap:.6rem;padding:.4rem .3rem;border-bottom:1px solid var(--dm-border);color:var(--dm-text);text-decoration:none;min-width:0}.dbp-row:last-child{border-bottom:0}a.dbp-row:hover,a.dbp-row:focus-visible{background:color-mix(in srgb,var(--dm-text) 6%,transparent);outline:none}.dbp-row-main{display:flex;flex-direction:column;min-width:0;flex:1 1 auto}.dbp-row-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dbp-row-meta{color:var(--dm-text-muted);font-size:var(--dm-text-xs);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dbp-row-value{font-variant-numeric:tabular-nums;color:var(--dm-text-muted);font-size:var(--dm-text-sm);white-space:nowrap}.dbp-row.is-danger{box-shadow:inset 3px 0 0 var(--dm-danger)}.dbp-row.is-attention{box-shadow:inset 3px 0 0 var(--dm-warning)}.dbp-row.is-ok .dbp-row-value{color:var(--dm-success)}.dbp-chart{position:relative;height:240px}.dbp-empty{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-sm)}.dbp-empty.is-error{color:var(--dm-danger)}.dbp-empty-board{text-align:center;padding:3rem 1rem;border:1px dashed var(--dm-border);border-radius:var(--dm-radius-md);color:var(--dm-text-muted)}.dbp-gallery{display:flex;flex-direction:column;gap:.6rem;padding:.25rem}.dbp-gallery-hint{margin:0;color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-gallery-group{margin:.6rem 0 .1rem;font-size:var(--dm-text-xs);text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.dbp-gallery-item{display:flex;align-items:center;gap:.6rem;padding:.5rem .3rem;border-bottom:1px solid var(--dm-border)}.dbp-gallery-main{display:flex;flex-direction:column;min-width:0;flex:1 1 auto}.dbp-gallery-main small{color:var(--dm-text-muted);font-size:var(--dm-text-xs)}.dbp-gallery-added{color:var(--dm-success);font-size:var(--dm-text-xs);white-space:nowrap}.dbp-fold{border:0;background:none;color:var(--dm-text-muted);cursor:pointer;padding:.15rem;border-radius:var(--dm-radius-md);display:inline-flex;line-height:1}.dbp-fold:hover,.dbp-fold:focus-visible{background:color-mix(in srgb,var(--dm-text) 8%,transparent);color:var(--dm-text)}.dbp-fold [data-icon]{transition:transform .15s ease}.dbp-card.is-collapsed{min-height:0}.dbp-card.is-collapsed .dbp-card-head{border-bottom:0}.dbp-card.is-collapsed .dbp-fold [data-icon]{transform:rotate(-90deg)}.dbp-from+.dbp-fold{margin-left:0}@media(prefers-reduced-motion:reduce){.dbp-fold [data-icon]{transition:none}}.dbp-body[hidden]{display:none}@media(min-width:1101px){.dbp.dbp-cols-3 .dbp-board{grid-template-columns:repeat(3,minmax(0,1fr))}.dbp.dbp-cols-6 .dbp-board{grid-template-columns:repeat(6,minmax(0,1fr))}}.dbp.is-compact .dbp-card-head{padding:.35rem .55rem}.dbp.is-compact .dbp-body{padding:.5rem .65rem;gap:.4rem}.dbp.is-compact .dbp-figure-value{font-size:var(--dm-text-xl, 1.25rem)}.dbp.is-compact .dbp-row{padding:.25rem .2rem}.dbp.is-compact .dbp-card{min-height:0}.dbp.is-locked .dbp-grip{display:none}.dbp-greeting{margin:0 0 .6rem;font-size:var(--dm-text-lg, 1.1rem);color:var(--dm-text);font-weight:500}.dbp-settings{display:flex;flex-direction:column;gap:.85rem;padding:.25rem}.dbp-settings-section{margin:.4rem 0 0;font-size:var(--dm-text-xs);text-transform:uppercase;letter-spacing:.06em;color:var(--dm-text-muted)}.dbp-field{display:flex;flex-direction:column;gap:.3rem}.dbp-field .form-label{display:flex;align-items:center;gap:.35rem}.dbp-field-row{display:grid;grid-template-columns:1fr 1fr;gap:.8rem}.dbp-switch{flex-direction:row;align-items:center;gap:.6rem;cursor:pointer}.dbp-switch>span{display:flex;align-items:center;gap:.35rem}.dbp-settings-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:.4rem}.dbp-card{container-type:inline-size}@container (max-width: 240px){.dbp-card-head .dbp-icon,.dbp-card-head .dbp-from{display:none}.dbp-card-head .dbp-title{white-space:normal;line-height:1.2;overflow-wrap:anywhere;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.dbp-card-head{gap:.35rem}}.dbp-tabs{display:flex;flex-wrap:wrap;gap:.25rem;margin:0 0 .9rem;border-bottom:1px solid var(--dm-border)}.dbp-tab{border:1px solid transparent;border-bottom:0;background:none;color:var(--dm-text-muted);cursor:pointer;padding:.45rem .85rem;border-radius:var(--dm-radius-md) var(--dm-radius-md) 0 0;font-weight:500;margin-bottom:-1px;max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dbp-tab:hover,.dbp-tab:focus-visible{color:var(--dm-text);background:color-mix(in srgb,var(--dm-text) 6%,transparent);outline:none}.dbp-tab.is-active{color:var(--dm-text);background:var(--dm-surface);border-color:var(--dm-border)}.dbp-tab-new{display:inline-flex;align-items:center;padding:.45rem .6rem}
|
package/admin/js/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{views as q}from"./views/index.js";import{withPluginChrome as B}from"./lib/plugin-chrome.js";import{api as r,getUser as F,isAuthenticated as c,logout as G,sendToLogin as z}from"./api.js";import{isAuthScreen as C,loginHashFor as K}from"./lib/login-arrange.js";import{installHttpInterceptor as J}from"./http-interceptor.js";import{installResizableSlideovers as Y}from"./lib/slideover-resizable.js";import{installActivityThrobber as Q}from"./lib/activity-throbber.js";import{isPathAllowed as X,redirectFor as Z}from"./lib/admin-scope.js";import{deliverDesktop as tt,desktopActive as et,notificationSettings as it}from"./lib/desktop-notify.js";import{avatarUrl as ot}from"./lib/avatar.js";import{installCalculator as at}from"./lib/calculator.js";$(()=>{J(),at(),Q(),Y(),(async()=>{const{applyAdminTheme:t,refreshAdminTheme:i}=await import("./lib/admin-theme.js");c()?await i(r,{init:!0}):t({dommaTheme:"charcoal-dark"},{init:!0})})();let m=null,l="/",h=!1,w=null;function D(t){if(y[t])return E.toast(`${y[t]} is switched off on this site.`,{type:"info"}),"/";const i=b[t]||Z(m,t);return i||((t.split("?")[0]||"/")==="/"&&l&&l!=="/"?b[l]||l:null)}const b={},y={};R.use(async(t,i,e)=>{if(C(t.path))return e();if(!c()){z();return}const o=D(t.path);if(o){R.navigate(o);return}e()});async function g(){try{const t=await r.get("/auth/permissions");m=t.adminScope||null,l=t.adminHome||"/";const i=t.permissions||[];return h=X(m,"/settings")&&i.some(e=>e==="settings"||e.startsWith("settings.")),w=i.some(e=>e==="notifications"||e.startsWith("notifications.")),i}catch{return m=null,l="/",h=!1,w=null,[]}}document.addEventListener("dm:admin-home-changed",()=>{g()});let N=null,P=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(N){P=!0;return}N=(async()=>{do{P=!1;try{await A(await g())}catch{}}while(P)})().finally(()=>{N=null})});async function A(t){const{renderAdminSidebar:i}=await import("./lib/sidebar-renderer.js");await i({mount:"#admin-sidebar",permissions:t,adminScope:m})}M.subscribe("router:afterChange",({to:t})=>{C(t.path)&&(u&&(clearInterval(u),u=null),m=null,l="/",h=!1,w=null)});const O=[{path:"/",view:"dashboard",title:"Dashboard - Domma CMS"},{path:"/my-dashboard",view:"myDashboard",title:"My 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:"/context-menus",view:"contextMenus",title:"Context Menus - Domma CMS"},{path:"/context-menus/new",view:"contextMenuEditor",title:"New Context Menu - Domma CMS"},{path:"/context-menus/edit/:slug",view:"contextMenuEditor",title:"Edit Context Menu - 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:"/theme",view:"theme",title:"Theme - 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:"/plugins/code/:name",view:"pluginCode",title:"Plugin Code - Domma CMS"},{path:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - Domma CMS"},{path:"/tutorials/crud",view:"tutorialCrud",title:"Building a CRUD App - Domma CMS"},{path:"/tutorials/plugin",view:"tutorialPlugin",title:"Writing a Plugin - Domma CMS"},{path:"/tutorials/forms",view:"tutorialForms",title:"Form Follow-Up - Domma CMS"},{path:"/docs/components",view:"componentsReference",title:"Components Reference - Domma CMS"},{path:"/docs/components-howto",view:"componentsHowto",title:"Components How-To - Domma CMS"},{path:"/docs/components-walkthrough",view:"componentsWalkthrough",title:"Components Walkthrough - Domma CMS"},{path:"/docs/components-rules",view:"componentsRules",title:"Components Rules - Domma CMS"},{path:"/docs/usage/pages",view:"usagePages",title:"Pages - Usage - Domma CMS"},{path:"/docs/usage/media",view:"usageMedia",title:"Media - Usage - Domma CMS"},{path:"/docs/usage/navigation",view:"usageNavigation",title:"Navigation - Usage - Domma CMS"},{path:"/docs/usage/dconfig",view:"usageDconfig",title:"DConfig - Usage - Domma CMS"},{path:"/docs/usage/shortcodes",view:"usageShortcodes",title:"Shortcodes - Usage - Domma CMS"},{path:"/docs/usage/site-settings",view:"usageSiteSettings",title:"Site Settings - Usage - Domma CMS"},{path:"/docs/usage/plugins",view:"usagePlugins",title:"Plugins - Usage - Domma CMS"},{path:"/docs/usage/users-roles",view:"usageUsersRoles",title:"Users & Roles - Usage - Domma CMS"},{path:"/docs/usage/views",view:"usageViews",title:"Views - Usage - Domma CMS"},{path:"/docs/usage/actions",view:"usageActions",title:"Actions - Usage - Domma CMS"},{path:"/docs/usage/cta-shortcode",view:"usageCtaShortcode",title:"CTA Shortcode - Usage - Domma CMS"},{path:"/docs/usage/editions",view:"usageEditions",title:"Editions & Licences - Usage - Domma CMS"},{path:"/docs/usage/collections",view:"usageCollections",title:"Collections & Forms - Usage - Domma CMS"},{path:"/docs/usage/tools",view:"usageTools",title:"Built-in Tools - Usage - Domma CMS"},{path:"/docs/plugins",view:"pluginGuides",title:"Plugin Guides - Domma CMS"},{path:"/docs/plugins/*",view:"pluginGuide",title:"Plugin Guide - Domma CMS"},{path:"/docs/api/authentication",view:"apiRefAuthentication",title:"Authentication - API - Domma CMS"},{path:"/docs/api/pages",view:"apiRefPages",title:"Pages - API - Domma CMS"},{path:"/docs/api/settings",view:"apiRefSettings",title:"Settings - API - Domma CMS"},{path:"/docs/api/layouts",view:"apiRefLayouts",title:"Layouts - API - Domma CMS"},{path:"/docs/api/navigation",view:"apiRefNavigation",title:"Navigation - API - Domma CMS"},{path:"/docs/api/media",view:"apiRefMedia",title:"Media - API - Domma CMS"},{path:"/docs/api/users",view:"apiRefUsers",title:"Users - API - Domma CMS"},{path:"/docs/api/plugins",view:"apiRefPlugins",title:"Plugins - API - Domma CMS"},{path:"/docs/api/collections",view:"apiRefCollections",title:"Collections - API - Domma CMS"},{path:"/docs/api/views",view:"apiRefViews",title:"Views API - Domma CMS"},{path:"/docs/api/actions",view:"apiRefActions",title:"Actions API - Domma CMS"},{path:"/docs/api/forms",view:"apiRefForms",title:"Forms API - Domma CMS"},{path:"/docs/api/scaffold",view:"apiRefScaffold",title:"Scaffold API - Domma CMS"},{path:"/docs/api/external",view:"apiRefExternal",title:"External API & Tokens - Domma CMS"},{path:"/docs/api/content",view:"apiRefContent",title:"Content API - Domma CMS"},{path:"/docs/api/builder",view:"apiRefBuilder",title:"API Builder - 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:"/search",view:"search",title:"Search - Domma CMS"},{path:"/content-api",view:"contentApi",title:"Content API - Domma CMS"},{path:"/notes",view:"notes",title:"Notes - Domma CMS"},{path:"/todo",view:"todo",title:"Todo - Domma CMS"},{path:"/calculator",view:"calculator",title:"Calculator - Domma CMS"},{path:"/analytics",view:"analytics",title:"Analytics - Domma CMS"},{path:"/seo",view:"seo",title:"SEO - Domma CMS"},{path:"/seo/:tab",view:"seo",title:"SEO - Domma CMS"},{path:"/contacts",view:"contacts",title:"Contacts - Domma CMS"},{path:"/calls/new",view:"callNew",title:"Call now - Domma CMS"},{path:"/system/notifications",view:"notifications",title:"Notifications - Domma CMS"},{path:"/api-tokens",view:"apiTokens",title:"API Tokens - Domma CMS"},{path:"/api-endpoints",view:"apiEndpoints",title:"API Builder - Domma CMS"},{path:"/api-endpoints/new",view:"apiEndpointEditor",title:"New API Endpoint - Domma CMS"},{path:"/api-endpoints/edit/:id",view:"apiEndpointEditor",title:"Edit API Endpoint - Domma CMS"},{path:"/projects",view:"projects",title:"Projects - Domma CMS"},{path:"/projects/new",view:"projects",title:"New Project - Domma CMS"},{path:"/projects/edit/:slug",view:"projects",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/components",view:"components",title:"Project components - 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/apis",view:"apiEndpoints",title:"Project APIs - 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:i})=>{if(!C(t.path)){if($("#admin-sidebar").show(),$("#admin-topbar").show(),C(i?.path)){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(r)),await I();const e=await g(),o=D(t.path);if(o&&R.navigate(o),A(e),it.read().signInToasts)try{const a=await r.system.notifications.list().catch(()=>[]),n=(Array.isArray(a)?a:[]).filter(s=>s.unread&&["warning","critical"].includes(s.data?.severity)).slice(0,3);for(const s of n){const f=d(s.data?.title||""),x=d((s.data?.body||"").slice(0,120));E.toast(`${f} - ${x}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}U()}}),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 _=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function V(t){return/^\/(pages|menus|context-menus|collections|forms|actions|views|blocks|components|users|roles|api-endpoints)\/(new|edit\/)/.test(t)}M.subscribe("router:afterChange",({to:t})=>{const i=t?String(t.path).split("?")[0]:"";if(!t||!V(i))return;const o=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!o||!/\/new$/.test(i))return;let a=0;const n=60,s=1500,f=()=>{a+=n;for(const x of _){const v=document.querySelector(x);if(!v)continue;if(Array.from(v.options||[]).find(H=>H.value===o)){v.value=o,v.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(f,n)};setTimeout(f,n)});const T="cms_card_states",j=S.get(T)||{};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&&j[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 i=$(this).closest(".card");i.toggleClass("card-collapsed");const e=$(this).find("h2, h3").first().text().trim();e&&(j[e]=i.hasClass("card-collapsed")?"collapsed":"open",S.set(T,j))}),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash.startsWith("#/login")||window.location.hash.startsWith("#/reset-password"))return;const i=document.querySelector("#view-container .view-header button.btn-primary");i&&(t.preventDefault(),i.click())});const p={...q},W={notes:{displayName:"Notes",icon:"edit-3"},todo:{displayName:"Todo",icon:"check-square"},calculator:{displayName:"Calculator",icon:"calculator"},analytics:{displayName:"Analytics",icon:"chart-bar"},seo:{displayName:"SEO",icon:"search"},contacts:{displayName:"Contacts",icon:"users"},projects:{displayName:"Projects",icon:"folder"},pages:{displayName:"Pages",icon:"file-text"},media:{displayName:"Media",icon:"image"},menus:{displayName:"Menus",icon:"menu"},contextMenus:{displayName:"Context Menus",icon:"menu"},theme:{displayName:"Theme",icon:"droplet"},collections:{displayName:"Collections",icon:"database"},forms:{displayName:"Forms",icon:"layout"},viewsList:{displayName:"Views",icon:"eye"},actionsList:{displayName:"Actions",icon:"zap"},apiEndpoints:{displayName:"API Builder",icon:"code"},blocks:{displayName:"Blocks",icon:"box"},components:{displayName:"Components",icon:"component"},notifications:{displayName:"Notifications",icon:"bell"},roles:{displayName:"Roles",icon:"shield"},users:{displayName:"Users",icon:"users"},settings:{displayName:"Site Settings",icon:"settings"},effects:{displayName:"Effects",icon:"sparkles"},layouts:{displayName:"Layouts",icon:"layout"},plugins:{displayName:"Marketplace",icon:"store"},myProfile:{displayName:"My Profile",icon:"user"},apiTokens:{displayName:"API Tokens",icon:"key"},search:{displayName:"Search",icon:"search"},contentApi:{displayName:"Content API",icon:"file-text"},dashboard:{displayName:"Dashboard",icon:"home"},myDashboard:{displayName:"My Dashboard",icon:"layout"},pageEditor:{displayName:"Pages",section:"Edit",icon:"file-text"},menuEditor:{displayName:"Menus",section:"Edit",icon:"menu"},menuLocations:{displayName:"Menus",section:"Locations",icon:"map-pin"},contextMenuEditor:{displayName:"Context Menus",section:"Edit",icon:"menu"},collectionEditor:{displayName:"Collections",section:"Edit",icon:"database"},collectionEntries:{displayName:"Collections",section:"Entries",icon:"database"},formEditor:{displayName:"Forms",section:"Edit",icon:"layout"},formSubmissions:{displayName:"Forms",section:"Submissions",icon:"inbox"},viewEditor:{displayName:"Views",section:"Edit",icon:"eye"},viewPreview:{displayName:"Views",section:"Preview",icon:"eye"},actionEditor:{displayName:"Actions",section:"Edit",icon:"zap"},apiEndpointEditor:{displayName:"API Builder",section:"Edit",icon:"code"},blockEditor:{displayName:"Blocks",section:"Edit",icon:"box"},componentEditor:{displayName:"Components",section:"Edit",icon:"component"},roleEditor:{displayName:"Roles",section:"Edit",icon:"shield"},userEditor:{displayName:"Users",section:"Edit",icon:"users"},projectDetail:{displayName:"Projects",section:"Overview",icon:"folder"},projectSettings:{displayName:"Projects",section:"Settings",icon:"folder"},pluginCode:{displayName:"Marketplace",section:"Code",icon:"code"}};for(const[t,i]of Object.entries(W))p[t]&&(p[t]=B(p[t],{plugin:t,licence:"Built in",...i}));const k=[...O];async function I(){if(c())try{const t=await r.plugins.adminConfig(),i=new Set(k.map(e=>e.path));for(const e of t.routes||[])i.has(e.path)||k.push(e);for(const[e,o]of Object.entries(t.views||{}))try{const a=await import(`/plugins/${o.entry}`);p[e]=B(a[o.exportName],(t.meta||{})[e])}catch{}for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(b[`/${e}`]=o);for(const[e,{enabled:o,displayName:a}]of Object.entries(t.tools||{}))o===!1&&(y[`/${e}`]=a);for(const{id:e,href:o}of t.css||[])if(!document.getElementById(e)){const a=document.createElement("link");a.id=e,a.rel="stylesheet",a.href=o,document.head.appendChild(a)}}catch{}}function U(){const t=F();if(!t||$("#topbar-user-name").length)return;const e={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role,o=ot(t);$("#topbar-user").html(`
|
|
1
|
+
import{views as q}from"./views/index.js";import{withPluginChrome as B}from"./lib/plugin-chrome.js";import{api as r,getUser as F,isAuthenticated as c,logout as G,sendToLogin as z}from"./api.js";import{isAuthScreen as C,loginHashFor as K}from"./lib/login-arrange.js";import{installHttpInterceptor as J}from"./http-interceptor.js";import{installResizableSlideovers as Y}from"./lib/slideover-resizable.js";import{installActivityThrobber as Q}from"./lib/activity-throbber.js";import{isPathAllowed as X,redirectFor as Z}from"./lib/admin-scope.js";import{deliverDesktop as tt,desktopActive as et,notificationSettings as it}from"./lib/desktop-notify.js";import{avatarUrl as ot}from"./lib/avatar.js";import{installCalculator as at}from"./lib/calculator.js";$(()=>{J(),at(),Q(),Y(),(async()=>{const{applyAdminTheme:t,refreshAdminTheme:i}=await import("./lib/admin-theme.js");c()?await i(r,{init:!0}):t({dommaTheme:"charcoal-dark"},{init:!0})})();let m=null,l="/",h=!1,w=null;function D(t){if(y[t])return E.toast(`${y[t]} is switched off on this site.`,{type:"info"}),"/";const i=b[t]||Z(m,t);return i||((t.split("?")[0]||"/")==="/"&&l&&l!=="/"?b[l]||l:null)}const b={},y={};R.use(async(t,i,e)=>{if(C(t.path))return e();if(!c()){z();return}const o=D(t.path);if(o){R.navigate(o);return}e()});async function g(){try{const t=await r.get("/auth/permissions");m=t.adminScope||null,l=t.adminHome||"/";const i=t.permissions||[];return h=X(m,"/settings")&&i.some(e=>e==="settings"||e.startsWith("settings.")),w=i.some(e=>e==="notifications"||e.startsWith("notifications.")),i}catch{return m=null,l="/",h=!1,w=null,[]}}document.addEventListener("dm:admin-home-changed",()=>{g()});let N=null,P=!1;document.addEventListener("dm:sidebar-rebuild",()=>{if(N){P=!0;return}N=(async()=>{do{P=!1;try{await A(await g())}catch{}}while(P)})().finally(()=>{N=null})});async function A(t){const{renderAdminSidebar:i}=await import("./lib/sidebar-renderer.js");await i({mount:"#admin-sidebar",permissions:t,adminScope:m})}M.subscribe("router:afterChange",({to:t})=>{C(t.path)&&(u&&(clearInterval(u),u=null),m=null,l="/",h=!1,w=null)});const O=[{path:"/",view:"dashboard",title:"Dashboard - Domma CMS"},{path:"/my-dashboard",view:"myDashboard",title:"My 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:"/context-menus",view:"contextMenus",title:"Context Menus - Domma CMS"},{path:"/context-menus/new",view:"contextMenuEditor",title:"New Context Menu - Domma CMS"},{path:"/context-menus/edit/:slug",view:"contextMenuEditor",title:"Edit Context Menu - 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:"/theme",view:"theme",title:"Theme - 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:"/plugins/code/:name",view:"pluginCode",title:"Plugin Code - Domma CMS"},{path:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - Domma CMS"},{path:"/tutorials/crud",view:"tutorialCrud",title:"Building a CRUD App - Domma CMS"},{path:"/tutorials/plugin",view:"tutorialPlugin",title:"Writing a Plugin - Domma CMS"},{path:"/tutorials/forms",view:"tutorialForms",title:"Form Follow-Up - Domma CMS"},{path:"/docs/components",view:"componentsReference",title:"Components Reference - Domma CMS"},{path:"/docs/components-howto",view:"componentsHowto",title:"Components How-To - Domma CMS"},{path:"/docs/components-walkthrough",view:"componentsWalkthrough",title:"Components Walkthrough - Domma CMS"},{path:"/docs/components-rules",view:"componentsRules",title:"Components Rules - Domma CMS"},{path:"/docs/usage/pages",view:"usagePages",title:"Pages - Usage - Domma CMS"},{path:"/docs/usage/media",view:"usageMedia",title:"Media - Usage - Domma CMS"},{path:"/docs/usage/navigation",view:"usageNavigation",title:"Navigation - Usage - Domma CMS"},{path:"/docs/usage/dconfig",view:"usageDconfig",title:"DConfig - Usage - Domma CMS"},{path:"/docs/usage/shortcodes",view:"usageShortcodes",title:"Shortcodes - Usage - Domma CMS"},{path:"/docs/usage/site-settings",view:"usageSiteSettings",title:"Site Settings - Usage - Domma CMS"},{path:"/docs/usage/plugins",view:"usagePlugins",title:"Plugins - Usage - Domma CMS"},{path:"/docs/usage/users-roles",view:"usageUsersRoles",title:"Users & Roles - Usage - Domma CMS"},{path:"/docs/usage/views",view:"usageViews",title:"Views - Usage - Domma CMS"},{path:"/docs/usage/actions",view:"usageActions",title:"Actions - Usage - Domma CMS"},{path:"/docs/usage/cta-shortcode",view:"usageCtaShortcode",title:"CTA Shortcode - Usage - Domma CMS"},{path:"/docs/usage/editions",view:"usageEditions",title:"Editions & Licences - Usage - Domma CMS"},{path:"/docs/usage/collections",view:"usageCollections",title:"Collections & Forms - Usage - Domma CMS"},{path:"/docs/usage/tools",view:"usageTools",title:"Built-in Tools - Usage - Domma CMS"},{path:"/docs/usage/dashboard",view:"usageDashboard",title:"Dashboard - Usage - Domma CMS"},{path:"/docs/plugins",view:"pluginGuides",title:"Plugin Guides - Domma CMS"},{path:"/docs/plugins/*",view:"pluginGuide",title:"Plugin Guide - Domma CMS"},{path:"/docs/api/authentication",view:"apiRefAuthentication",title:"Authentication - API - Domma CMS"},{path:"/docs/api/pages",view:"apiRefPages",title:"Pages - API - Domma CMS"},{path:"/docs/api/settings",view:"apiRefSettings",title:"Settings - API - Domma CMS"},{path:"/docs/api/layouts",view:"apiRefLayouts",title:"Layouts - API - Domma CMS"},{path:"/docs/api/navigation",view:"apiRefNavigation",title:"Navigation - API - Domma CMS"},{path:"/docs/api/media",view:"apiRefMedia",title:"Media - API - Domma CMS"},{path:"/docs/api/users",view:"apiRefUsers",title:"Users - API - Domma CMS"},{path:"/docs/api/plugins",view:"apiRefPlugins",title:"Plugins - API - Domma CMS"},{path:"/docs/api/collections",view:"apiRefCollections",title:"Collections - API - Domma CMS"},{path:"/docs/api/views",view:"apiRefViews",title:"Views API - Domma CMS"},{path:"/docs/api/actions",view:"apiRefActions",title:"Actions API - Domma CMS"},{path:"/docs/api/forms",view:"apiRefForms",title:"Forms API - Domma CMS"},{path:"/docs/api/scaffold",view:"apiRefScaffold",title:"Scaffold API - Domma CMS"},{path:"/docs/api/external",view:"apiRefExternal",title:"External API & Tokens - Domma CMS"},{path:"/docs/api/content",view:"apiRefContent",title:"Content API - Domma CMS"},{path:"/docs/api/builder",view:"apiRefBuilder",title:"API Builder - 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:"/search",view:"search",title:"Search - Domma CMS"},{path:"/content-api",view:"contentApi",title:"Content API - Domma CMS"},{path:"/notes",view:"notes",title:"Notes - Domma CMS"},{path:"/todo",view:"todo",title:"Todo - Domma CMS"},{path:"/calculator",view:"calculator",title:"Calculator - Domma CMS"},{path:"/analytics",view:"analytics",title:"Analytics - Domma CMS"},{path:"/seo",view:"seo",title:"SEO - Domma CMS"},{path:"/seo/:tab",view:"seo",title:"SEO - Domma CMS"},{path:"/contacts",view:"contacts",title:"Contacts - Domma CMS"},{path:"/calls/new",view:"callNew",title:"Call now - Domma CMS"},{path:"/system/notifications",view:"notifications",title:"Notifications - Domma CMS"},{path:"/api-tokens",view:"apiTokens",title:"API Tokens - Domma CMS"},{path:"/api-endpoints",view:"apiEndpoints",title:"API Builder - Domma CMS"},{path:"/api-endpoints/new",view:"apiEndpointEditor",title:"New API Endpoint - Domma CMS"},{path:"/api-endpoints/edit/:id",view:"apiEndpointEditor",title:"Edit API Endpoint - Domma CMS"},{path:"/projects",view:"projects",title:"Projects - Domma CMS"},{path:"/projects/new",view:"projects",title:"New Project - Domma CMS"},{path:"/projects/edit/:slug",view:"projects",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/components",view:"components",title:"Project components - 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/apis",view:"apiEndpoints",title:"Project APIs - 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:i})=>{if(!C(t.path)){if($("#admin-sidebar").show(),$("#admin-topbar").show(),C(i?.path)){$("#topbar-user-name").remove(),import("./lib/admin-theme.js").then(a=>a.refreshAdminTheme(r)),await I();const e=await g(),o=D(t.path);if(o&&R.navigate(o),A(e),it.read().signInToasts)try{const a=await r.system.notifications.list().catch(()=>[]),n=(Array.isArray(a)?a:[]).filter(s=>s.unread&&["warning","critical"].includes(s.data?.severity)).slice(0,3);for(const s of n){const f=d(s.data?.title||""),U=d((s.data?.body||"").slice(0,120));E.toast(`${f} - ${U}`,{type:s.data?.severity==="critical"?"error":"warning",duration:0})}}catch{}}T()}}),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 _=["#field-project","#menu-project","#collection-project","#endpoint-project","#action-project","#view-project","#block-project","#component-project","#role-project",'[name="project"]','[name="ownedByProject"]'];function V(t){return/^\/(pages|menus|context-menus|collections|forms|actions|views|blocks|components|users|roles|api-endpoints)\/(new|edit\/)/.test(t)}M.subscribe("router:afterChange",({to:t})=>{const i=t?String(t.path).split("?")[0]:"";if(!t||!V(i))return;const o=new URLSearchParams(location.hash.split("?")[1]||"").get("project");if(!o||!/\/new$/.test(i))return;let a=0;const n=60,s=1500,f=()=>{a+=n;for(const U of _){const v=document.querySelector(U);if(!v)continue;if(Array.from(v.options||[]).find(H=>H.value===o)){v.value=o,v.dispatchEvent(new Event("change",{bubbles:!0}));return}}a<s&&setTimeout(f,n)};setTimeout(f,n)});const x="cms_card_states",j=S.get(x)||{};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&&j[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 i=$(this).closest(".card");i.toggleClass("card-collapsed");const e=$(this).find("h2, h3").first().text().trim();e&&(j[e]=i.hasClass("card-collapsed")?"collapsed":"open",S.set(x,j))}),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash.startsWith("#/login")||window.location.hash.startsWith("#/reset-password"))return;const i=document.querySelector("#view-container .view-header button.btn-primary");i&&(t.preventDefault(),i.click())});const p={...q},W={notes:{displayName:"Notes",icon:"edit-3"},todo:{displayName:"Todo",icon:"check-square"},calculator:{displayName:"Calculator",icon:"calculator"},analytics:{displayName:"Analytics",icon:"chart-bar"},seo:{displayName:"SEO",icon:"search"},contacts:{displayName:"Contacts",icon:"users"},projects:{displayName:"Projects",icon:"folder"},pages:{displayName:"Pages",icon:"file-text"},media:{displayName:"Media",icon:"image"},menus:{displayName:"Menus",icon:"menu"},contextMenus:{displayName:"Context Menus",icon:"menu"},theme:{displayName:"Theme",icon:"droplet"},collections:{displayName:"Collections",icon:"database"},forms:{displayName:"Forms",icon:"layout"},viewsList:{displayName:"Views",icon:"eye"},actionsList:{displayName:"Actions",icon:"zap"},apiEndpoints:{displayName:"API Builder",icon:"code"},blocks:{displayName:"Blocks",icon:"box"},components:{displayName:"Components",icon:"component"},notifications:{displayName:"Notifications",icon:"bell"},roles:{displayName:"Roles",icon:"shield"},users:{displayName:"Users",icon:"users"},settings:{displayName:"Site Settings",icon:"settings"},effects:{displayName:"Effects",icon:"sparkles"},layouts:{displayName:"Layouts",icon:"layout"},plugins:{displayName:"Marketplace",icon:"store"},myProfile:{displayName:"My Profile",icon:"user"},apiTokens:{displayName:"API Tokens",icon:"key"},search:{displayName:"Search",icon:"search"},contentApi:{displayName:"Content API",icon:"file-text"},dashboard:{displayName:"Dashboard",icon:"home"},myDashboard:{displayName:"My Dashboard",icon:"layout"},pageEditor:{displayName:"Pages",section:"Edit",icon:"file-text"},menuEditor:{displayName:"Menus",section:"Edit",icon:"menu"},menuLocations:{displayName:"Menus",section:"Locations",icon:"map-pin"},contextMenuEditor:{displayName:"Context Menus",section:"Edit",icon:"menu"},collectionEditor:{displayName:"Collections",section:"Edit",icon:"database"},collectionEntries:{displayName:"Collections",section:"Entries",icon:"database"},formEditor:{displayName:"Forms",section:"Edit",icon:"layout"},formSubmissions:{displayName:"Forms",section:"Submissions",icon:"inbox"},viewEditor:{displayName:"Views",section:"Edit",icon:"eye"},viewPreview:{displayName:"Views",section:"Preview",icon:"eye"},actionEditor:{displayName:"Actions",section:"Edit",icon:"zap"},apiEndpointEditor:{displayName:"API Builder",section:"Edit",icon:"code"},blockEditor:{displayName:"Blocks",section:"Edit",icon:"box"},componentEditor:{displayName:"Components",section:"Edit",icon:"component"},roleEditor:{displayName:"Roles",section:"Edit",icon:"shield"},userEditor:{displayName:"Users",section:"Edit",icon:"users"},projectDetail:{displayName:"Projects",section:"Overview",icon:"folder"},projectSettings:{displayName:"Projects",section:"Settings",icon:"folder"},pluginCode:{displayName:"Marketplace",section:"Code",icon:"code"}};for(const[t,i]of Object.entries(W))p[t]&&(p[t]=B(p[t],{plugin:t,licence:"Built in",...i}));const k=[...O];async function I(){if(c())try{const t=await r.plugins.adminConfig(),i=new Set(k.map(e=>e.path));for(const e of t.routes||[])i.has(e.path)||k.push(e);for(const[e,o]of Object.entries(t.views||{}))try{const a=await import(`/plugins/${o.entry}`);p[e]=B(a[o.exportName],(t.meta||{})[e])}catch{}for(const[e,{route:o}]of Object.entries(t.takeovers||{}))o&&(b[`/${e}`]=o);for(const[e,{enabled:o,displayName:a}]of Object.entries(t.tools||{}))o===!1&&(y[`/${e}`]=a);for(const{id:e,href:o}of t.css||[])if(!document.getElementById(e)){const a=document.createElement("link");a.id=e,a.rel="stylesheet",a.href=o,document.head.appendChild(a)}}catch{}}function T(){const t=F();if(!t||$("#topbar-user-name").length)return;const e={"super-admin":"Super Admin",admin:"Admin",user:"User"}[t.role]||t.role,o=ot(t);$("#topbar-user").html(`
|
|
2
2
|
${o?`<img id="topbar-avatar" class="topbar-avatar" src="${d(o)}" alt="">`:""}
|
|
3
3
|
<span id="topbar-user-name" class="topbar-user-name">${d(t.name)}</span>
|
|
4
4
|
<span class="topbar-role-badge topbar-role-badge--${d(t.role)}">${e}</span>
|
|
@@ -19,4 +19,4 @@ import{views as q}from"./views/index.js";import{withPluginChrome as B}from"./lib
|
|
|
19
19
|
<span data-icon="log-out"></span>
|
|
20
20
|
<span>Sign out</span>
|
|
21
21
|
</a>
|
|
22
|
-
`);const a=$("#topbar-bell").get(0);a&&(a.hidden=!0);const n=$("#topbar-settings").get(0);n&&(n.hidden=!h),$("#topbar-logout-btn").on("click",s=>{s.preventDefault(),G()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),L(),u||(u=setInterval(L,6e4))}function d(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}let u=null;async function L(){if(!(!c()||w===!1))try{const{count:t}=await r.system.notifications.unreadCount();$("#topbar-bell").removeAttr("hidden");const i=$("#topbar-bell-badge");t>0?i.text(t>99?"99+":String(t)).removeAttr("hidden"):i.attr("hidden","").text(""),t>0&&et()&&tt(await r.system.notifications.list().catch(()=>[]))}catch{}}(async()=>{if(!c()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash.startsWith("#/login")||(window.location.hash=K(window.location.hash));else if(await I(),F()){const a=await g(),n=(window.location.hash||"#/").slice(1)||"/",s=D(n);s&&(window.location.hash=`#${s}`),await A(a),
|
|
22
|
+
`);const a=$("#topbar-bell").get(0);a&&(a.hidden=!0);const n=$("#topbar-settings").get(0);n&&(n.hidden=!h),$("#topbar-logout-btn").on("click",s=>{s.preventDefault(),G()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"}),L(),u||(u=setInterval(L,6e4))}function d(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}let u=null;async function L(){if(!(!c()||w===!1))try{const{count:t}=await r.system.notifications.unreadCount();$("#topbar-bell").removeAttr("hidden");const i=$("#topbar-bell-badge");t>0?i.text(t>99?"99+":String(t)).removeAttr("hidden"):i.attr("hidden","").text(""),t>0&&et()&&tt(await r.system.notifications.list().catch(()=>[]))}catch{}}(async()=>{if(!c()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash.startsWith("#/login")||(window.location.hash=K(window.location.hash));else if(await I(),F()){const a=await g(),n=(window.location.hash||"#/").slice(1)||"/",s=D(n);s&&(window.location.hash=`#${s}`),await A(a),T()}const t=R._extractParams.bind(R);R._extractParams=function(o,a){const n=a.indexOf("?");if(n!==-1&&(a=a.slice(0,n)),o.endsWith("/*")){const s=o.slice(0,-2);return a.startsWith(s+"/")?{}:null}return t(o,a)};const i=R._renderNotFound.bind(R);let e="";R._renderNotFound=async function(o){return c()&&e!==o&&(e=o,await I(),R._matchRoute(o).route)?(e="",R._handleRouteChange()):(e="",c()&&$("#admin-sidebar").get(0)?.style.display==="none"?(E.toast("That link goes nowhere - here is your home page.",{type:"warning"}),R.navigate("/")):i(o))},R.init({container:"#view-container",routes:k,views:p,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}})})()});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="book"></span> Dashboard</h1>
|
|
3
|
+
<a href="#/documentation" class="btn btn-ghost btn-sm"><span data-icon="arrow-left"></span> All usage topics</a>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="row">
|
|
7
|
+
<div class="col-12">
|
|
8
|
+
<div class="docs-body">
|
|
9
|
+
|
|
10
|
+
<p>The <strong>Dashboard</strong> is the admin's home: what the site is doing and what needs you, at a glance -
|
|
11
|
+
page views today, visitors now, pages and drafts, form submissions, site health, the traffic chart and a
|
|
12
|
+
"Needs attention" list gathered from every screen's sidebar badge. Every site has it, and it looks the same
|
|
13
|
+
for everyone.</p>
|
|
14
|
+
|
|
15
|
+
<h3>Dashboard Pro: a Dashboard you build yourself</h3>
|
|
16
|
+
<p>With the <strong>Dashboard Pro</strong> Tool (from the Marketplace), the Dashboard becomes a board of
|
|
17
|
+
<strong>widgets</strong> that each person arranges for themselves. Where another Tool owns the Dashboard - the
|
|
18
|
+
fleet manager's, say - the board is at <strong>My Dashboard</strong> in the sidebar instead. Switch Dashboard
|
|
19
|
+
Pro off and the Dashboard goes back to the fixed one at once; everyone's boards are kept for when it comes back.</p>
|
|
20
|
+
|
|
21
|
+
<h3>Adding widgets</h3>
|
|
22
|
+
<p>Choose <strong>Add widget</strong> in the banner, or right-click anywhere on the board. The gallery lists every
|
|
23
|
+
widget from the Tools you may open, grouped by Tool, with a search box. There are three kinds of source:</p>
|
|
24
|
+
<ul>
|
|
25
|
+
<li><strong>Built in</strong>: Traffic (the last 30 days), Visitors now, Needs attention, Recent activity,
|
|
26
|
+
Site health and Top pages. The administrator-only ones (traffic, activity, health) are offered to
|
|
27
|
+
administrators only.</li>
|
|
28
|
+
<li><strong>Every Tool with a count in the sidebar</strong>: unread email, bookings, support tickets, form
|
|
29
|
+
submissions, pages, media and the rest - the same number as its badge, with the badge's details beneath.</li>
|
|
30
|
+
<li><strong>Widgets a Tool adds of its own</strong> - anything richer than a count.</li>
|
|
31
|
+
</ul>
|
|
32
|
+
<p>You only ever see widgets for Tools you are allowed to open.</p>
|
|
33
|
+
|
|
34
|
+
<h3>Arranging the board</h3>
|
|
35
|
+
<ul>
|
|
36
|
+
<li><strong>Move</strong>: drag a card by its grip (the three lines at its left), or use Move earlier / Move
|
|
37
|
+
later in its menu. With a card focused, Alt+arrow keys move it and Delete removes it.</li>
|
|
38
|
+
<li><strong>Size</strong>: Small, Medium, Large or Full width, from the card's ⋮ menu or by right-clicking it.</li>
|
|
39
|
+
<li><strong>Fold</strong>: the chevron folds a card to its title bar; the double chevron in the banner folds or
|
|
40
|
+
opens them all. A folded card does not refresh until you open it again.</li>
|
|
41
|
+
<li><strong>Remove</strong>: from the card's menu. It stays in the gallery to add again.</li>
|
|
42
|
+
</ul>
|
|
43
|
+
<p>Cards pack upwards, so a short one never leaves a gap that a later card could fill. Each card refreshes on
|
|
44
|
+
its own, and at once when its Tool's count changes.</p>
|
|
45
|
+
|
|
46
|
+
<h3>Several boards</h3>
|
|
47
|
+
<p>Keep up to twelve boards - Sales, Content, Support - as tabs above the Dashboard. <strong>+</strong> makes a
|
|
48
|
+
new one and opens the gallery for it. Right-click a tab (or double-click it to rename) to rename, duplicate,
|
|
49
|
+
move or delete it. Each board has its own widgets and arrangement, and the board you were last on is the one
|
|
50
|
+
that opens.</p>
|
|
51
|
+
|
|
52
|
+
<h3>Settings (the cog)</h3>
|
|
53
|
+
<p>The cog in the banner holds your own settings:</p>
|
|
54
|
+
<ul>
|
|
55
|
+
<li><strong>Name</strong> - what your Dashboard is called, in its banner, the browser tab and the sidebar.</li>
|
|
56
|
+
<li><strong>Greeting</strong> - good morning, your name and today's date.</li>
|
|
57
|
+
<li><strong>Columns</strong> - 3, 4 or 6 on a wide screen. A narrow window always uses fewer.</li>
|
|
58
|
+
<li><strong>Cards</strong> - normal or compact.</li>
|
|
59
|
+
<li><strong>Updates</strong> - Live, Slower (three times as long between refreshes), or Paused (only when you
|
|
60
|
+
press Refresh).</li>
|
|
61
|
+
<li><strong>Open the admin on this board</strong> - signing in, or pressing Dashboard, brings you here.</li>
|
|
62
|
+
</ul>
|
|
63
|
+
|
|
64
|
+
<h3>For administrators</h3>
|
|
65
|
+
<ul>
|
|
66
|
+
<li><strong>The default</strong>: <em>Make this the default for everyone</em> in the banner makes the board you
|
|
67
|
+
are on the starting point for everyone who has not arranged their own. <em>Reset</em> takes anyone back to it.</li>
|
|
68
|
+
<li><strong>Board name</strong> (in the cog, under For everyone) - what the Dashboard is called for everyone who
|
|
69
|
+
has not named their own.</li>
|
|
70
|
+
<li><strong>Lock the layout</strong> - everyone but administrators sees the site's default board and can fold
|
|
71
|
+
and refresh widgets but not add, move, resize or remove them. Turning the lock off gives people their own
|
|
72
|
+
boards back.</li>
|
|
73
|
+
</ul>
|
|
74
|
+
|
|
75
|
+
<h3>For Tool authors</h3>
|
|
76
|
+
<p>A Tool with a sidebar count already has a widget. For anything more, a Tool lists widgets in its
|
|
77
|
+
<code>plugin.json</code> under <code>admin.widgets</code> - a kind (count, stat, list or chart) and a data
|
|
78
|
+
route, no code. See <strong>Dashboard widgets</strong> in the plugin guide.</p>
|
|
79
|
+
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
<div class="card-body"><h3 style="margin-top:0;"><span data-icon="list"></span> Built-in Tools</h3>
|
|
63
63
|
<p class="text-muted" style="margin-bottom:0;">Contacts, Notes, Todo, Analytics, SEO, notifications and search.</p></div>
|
|
64
64
|
</a>
|
|
65
|
+
<a href="#/docs/usage/dashboard" class="card" style="text-decoration:none;display:block;">
|
|
66
|
+
<div class="card-body"><h3 style="margin-top:0;"><span data-icon="home"></span> Dashboard</h3>
|
|
67
|
+
<p class="text-muted" style="margin-bottom:0;">The admin's home, and Dashboard Pro: widgets, boards and settings.</p></div>
|
|
68
|
+
</a>
|
|
65
69
|
<a href="#/docs/usage/plugins" class="card" style="text-decoration:none;display:block;">
|
|
66
70
|
<div class="card-body"><h3 style="margin-top:0;"><span data-icon="package"></span> Plugins</h3>
|
|
67
71
|
<p class="text-muted" style="margin-bottom:0;">The Marketplace: install, update, restarts, switches and dependencies.</p></div>
|
|
@@ -1,30 +1,63 @@
|
|
|
1
|
-
import{apiRequest as
|
|
1
|
+
import{apiRequest as m,getUser as ke}from"../api.js";import{addBannerAction as A}from"../lib/plugin-chrome.js";import{helpPopovers as ne}from"../lib/help-popover.js";import{openMenuAt as ie}from"../lib/tool-kit.js";import{SIZE_LABELS as Se,SIZES as Ee,addWidget as $e,anyOpen as R,boardCards as Ce,dropWidget as Le,galleryGroups as Ae,removeWidget as de,resizeWidget as De,sameLayout as re,setAllCollapsed as Me,setCollapsed as Te,shiftWidget as z,withToday as xe}from"../lib/dashboard-board-arrange.js";import{destroyWidget as qe,drawError as Ne,drawWidget as Oe}from"../lib/widget-render.js";const Re=400,ze=h=>String(h||"").replace(/^\/api(?=\/)/,""),r=h=>String(h??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");export async function mountBoard(h,f,{isAdmin:Y=!1}={}){const G=h.get(0),c=G.querySelector(".dbx")||G;c.classList.add("dbp"),c.replaceChildren(),c.insertAdjacentHTML("beforeend",`
|
|
2
|
+
<p class="dbp-greeting" hidden></p>
|
|
2
3
|
<div class="dbp-head">
|
|
3
|
-
<p class="dbp-hint">Your own Dashboard.
|
|
4
|
-
<span data-help-title="Dashboard Pro" data-help="Add widgets from the Tools you use
|
|
4
|
+
<p class="dbp-hint">Your own Dashboard.
|
|
5
|
+
<span data-help-title="Dashboard Pro" data-help="Add widgets from the Tools you use - Add widget in the banner, or right-click the board. Each card keeps itself up to date. Keep several boards as tabs: + makes one, right-click a tab to rename, duplicate, move or delete it. Your boards are yours - other people arrange their own. Reset goes back to the site's default; an administrator can make a board the default for everyone who has not arranged their own."></span></p>
|
|
5
6
|
<span class="dbp-source" aria-live="polite"></span>
|
|
6
7
|
</div>
|
|
8
|
+
<div class="dbp-tabs" role="tablist" aria-label="Your boards" hidden></div>
|
|
7
9
|
<div class="dbp-board" role="list" aria-label="Dashboard widgets"></div>
|
|
8
10
|
<div class="dbp-empty-board" hidden>
|
|
9
11
|
<p>Nothing on your Dashboard yet.</p>
|
|
10
12
|
<button type="button" class="btn btn-primary dbp-add-first"><span data-icon="plus" data-icon-size="14"></span> Add a widget</button>
|
|
11
|
-
</div>`);const
|
|
13
|
+
</div>`);const b=c.querySelector(".dbp-board"),le=c.querySelector(".dbp-source"),U=c.querySelector(".dbp-greeting"),ce=c.querySelector(".dbp-hint"),w=c.querySelector(".dbp-tabs"),H=()=>c.isConnected;let y=[],i={version:1,widgets:[]},o={version:2,active:"main",boards:[{id:"main",name:"Main",widgets:[]}]},q="",D="built-in",d={mine:{name:"",greeting:!0,columns:4,density:"normal",updates:"live",home:!1},site:{name:"My Dashboard",locked:!1},name:"My Dashboard",isAdmin:Y,locked:!1};const M=new Map;f.add(()=>{for(const e of M.values())clearInterval(e);M.clear()});async function pe(){const[e,t,a]=await Promise.all([m("/widgets",{method:"GET"}).catch(()=>[]),m("/dashboard/boards",{method:"GET"}).catch(()=>null),m("/dashboard/settings",{method:"GET"}).catch(()=>null)]);a&&(d={...d,...a}),y=Array.isArray(e)?e:[],t?.boards?.length&&(o={version:2,active:t.active,boards:t.boards}),i={version:1,widgets:T().widgets},q=JSON.stringify(o),D=t?.source||"built-in"}const T=()=>o.boards.find(e=>e.id===o.active)||o.boards[0],N=()=>{T().widgets=i.widgets};let P=null;function S(){clearTimeout(P),!d.locked&&(P=setTimeout(async()=>{N();const e=JSON.stringify(o);if(e!==q)try{const t=await m("/dashboard/boards",{method:"PUT",body:e});q=e,D=t?.source||"mine",B()}catch(t){E.toast(`Your layout was not saved: ${t.message||t}`,{type:"error"})}},Re))}f.add(()=>clearTimeout(P));function B(){le.textContent={mine:"Your layout",site:"The site's default layout","built-in":"The starting layout",locked:"Set by your administrator"}[D]||""}function g(e){re(e,i)||(i=e,k(),S())}function F(e,t){i=Te(i,e,t);const a=b.querySelector(`.dbp-card[data-id="${CSS.escape(e)}"]`);a&&j(a,t),S()}function j(e,t){const a=e.classList.contains("is-collapsed");e.classList.toggle("is-collapsed",t);const s=e.querySelector(".dbp-body");s&&(s.hidden=t);const n=e.querySelector(".dbp-fold"),l=e.getAttribute("aria-label")||"";n&&(n.setAttribute("aria-expanded",t?"false":"true"),n.title=t?"Open":"Fold",n.setAttribute("aria-label",`${t?"Open":"Fold"} ${l}`)),a&&!t&&v(e)}function J(e){i=Me(i,e),b.querySelectorAll(".dbp-card").forEach(t=>j(t,e)),S()}const be=4,ue=16;function K(e){const t=e.getBoundingClientRect().height;t&&(e.style.gridRowEnd=`span ${Math.max(1,Math.ceil((t+ue)/be))}`)}const W=typeof ResizeObserver=="function"?new ResizeObserver(e=>{for(const t of e)K(t.target)}):null;f.add(()=>W?.disconnect());async function v(e){const t=y.find(s=>s.id===e.dataset.id),a=e.querySelector(".dbp-body");if(!(!t||!a)){e.classList.add("is-loading");try{const s=t.kind==="count"?xe(t.dataUrl):t.dataUrl,n=await m(ze(s),{method:"GET"});if(!H()||!e.isConnected)return;await Oe(a,t.kind,n)}catch(s){e.isConnected&&Ne(a,s.status===403?"You cannot see this any more.":"Could not load.")}finally{e.classList.remove("is-loading")}}}function he(e){const t=e.href?`<a class="dbp-title" href="${r(e.href)}" title="Open ${r(e.title)}">${r(e.title)}</a>`:`<span class="dbp-title">${r(e.title)}</span>`;return`<section class="dbp-card dbp-size-${r(e.size)}${e.collapsed?" is-collapsed":""}" data-id="${r(e.id)}" role="listitem" tabindex="0" aria-label="${r(e.title)}">
|
|
12
14
|
<header class="dbp-card-head">
|
|
13
15
|
<span class="dbp-grip" title="Drag to move" aria-hidden="true"><span data-icon="menu" data-icon-size="14"></span></span>
|
|
14
|
-
<span class="dbp-icon" aria-hidden="true"><span data-icon="${
|
|
16
|
+
<span class="dbp-icon" aria-hidden="true"><span data-icon="${r(e.icon||"grid")}" data-icon-size="16"></span></span>
|
|
15
17
|
${t}
|
|
16
|
-
<small class="dbp-from">${e.source&&e.source!=="Built in"?
|
|
17
|
-
<button type="button" class="dbp-fold" aria-expanded="${e.collapsed?"false":"true"}" aria-label="${e.collapsed?"Open":"Fold"} ${
|
|
18
|
-
<button type="button" class="dbp-kebab" aria-label="Actions for ${
|
|
18
|
+
<small class="dbp-from">${e.source&&e.source!=="Built in"?r(e.source):""}</small>
|
|
19
|
+
<button type="button" class="dbp-fold" aria-expanded="${e.collapsed?"false":"true"}" aria-label="${e.collapsed?"Open":"Fold"} ${r(e.title)}" title="${e.collapsed?"Open":"Fold"}"><span data-icon="chevron-down" data-icon-size="14"></span></button>
|
|
20
|
+
<button type="button" class="dbp-kebab" aria-label="Actions for ${r(e.title)}" title="Actions">\u22EE</button>
|
|
19
21
|
</header>
|
|
20
22
|
<div class="dbp-body"${e.collapsed?" hidden":""}><div class="dbp-skeleton"></div></div>
|
|
21
|
-
</section>`}function
|
|
23
|
+
</section>`}function $(){if(w.hidden=d.locked===!0,d.locked){w.replaceChildren();return}w.innerHTML=o.boards.map(e=>`<button type="button" class="dbp-tab${e.id===o.active?" is-active":""}" role="tab"
|
|
24
|
+
aria-selected="${e.id===o.active}" data-board="${r(e.id)}" title="${r(e.name)} - right-click to rename, duplicate or delete">${r(e.name)}</button>`).join("")+(o.boards.length<12?'<button type="button" class="dbp-tab dbp-tab-new" data-new-board title="New board" aria-label="New board"><span data-icon="plus" data-icon-size="14"></span></button>':""),I.scan(w)}function _(e){e===o.active||!o.boards.some(t=>t.id===e)||(N(),o.active=e,i={version:1,widgets:T().widgets},$(),k(),S())}const V=()=>`b-${Date.now().toString(36)}${Math.random().toString(36).slice(2,5)}`;async function X(e,t=""){const a=await E.prompt(e,{inputValue:r(t),inputPlaceholder:"Sales, Content, Support\u2026"});return a==null?null:String(a).replace(/\s+/g," ").trim().slice(0,40)||null}async function Z(){if(o.boards.length>=12)return;const e=await X("Name the new board");if(!e)return;N();const t={id:V(),name:e,widgets:[]};o.boards.push(t),o.active=t.id,i={version:1,widgets:[]},$(),k(),S(),x()}async function C(e,t){const a=o.boards.findIndex(n=>n.id===e);if(a<0)return;N();const s=o.boards[a];if(t==="rename"){const n=await X("Rename the board",s.name);if(!n)return;s.name=n}else if(t==="duplicate"){if(o.boards.length>=12)return;const n={id:V(),name:`${s.name} copy`.slice(0,40),widgets:s.widgets.map(l=>({...l}))};o.boards.splice(a+1,0,n),o.active=n.id}else if(t==="delete"){if(o.boards.length<2||!await E.confirm(`Delete the board "${r(s.name)}"? Its widgets go with it (they stay available to add again).`))return;o.boards.splice(a,1),o.active===e&&(o.active=o.boards[Math.max(0,a-1)].id)}else t==="left"&&a>0?[o.boards[a-1],o.boards[a]]=[o.boards[a],o.boards[a-1]]:t==="right"&&a<o.boards.length-1&&([o.boards[a+1],o.boards[a]]=[o.boards[a],o.boards[a+1]]);i={version:1,widgets:T().widgets},$(),k(),S()}w.addEventListener("click",e=>{if(e.target.closest("[data-new-board]")){Z();return}const t=e.target.closest("[data-board]");t&&_(t.dataset.board)}),w.addEventListener("dblclick",e=>{const t=e.target.closest("[data-board]");t&&C(t.dataset.board,"rename")});const fe=E.contextMenu(w,{cursor:"glyph",match:"[data-board]",inherit:!1,density:"compact",items:e=>{const t=e.dataset.board,a=o.boards.findIndex(s=>s.id===t);return[{header:o.boards[a]?.name||"Board"},{label:"Open",icon:"arrow-right",disabled:t===o.active,action:()=>_(t)},{label:"Rename\u2026",icon:"edit",action:()=>C(t,"rename")},{label:"Duplicate",icon:"copy",disabled:o.boards.length>=12,action:()=>C(t,"duplicate")},{type:"divider"},{label:"Move left",icon:"arrow-left",disabled:a<=0,action:()=>C(t,"left")},{label:"Move right",icon:"arrow-right",disabled:a>=o.boards.length-1,action:()=>C(t,"right")},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,disabled:o.boards.length<2,action:()=>C(t,"delete")}]}});f.add(()=>fe?.destroy?.());function k(){for(const t of M.values())clearInterval(t);M.clear(),b.querySelectorAll(".dbp-body").forEach(qe);const e=Ce(i,y);b.innerHTML=e.map(he).join(""),c.querySelector(".dbp-empty-board").hidden=e.length>0,W?.disconnect();for(const t of b.querySelectorAll(".dbp-card"))K(t),W?.observe(t);I.scan(b);for(const t of b.querySelectorAll(".dbp-card")){t.classList.contains("is-collapsed")||v(t);const a=y.find(n=>n.id===t.dataset.id);if(d.mine.updates==="paused")continue;const s=(a?.refresh||60)*1e3*(d.mine.updates==="slower"?3:1);M.set(t.dataset.id,setInterval(()=>{!document.hidden&&t.isConnected&&!t.classList.contains("is-collapsed")&&v(t)},s))}B()}const Q=e=>{const t=e.detail?.url;if(t)for(const a of b.querySelectorAll(".dbp-card"))y.find(n=>n.id===a.dataset.id)?.href===t&&v(a)};window.addEventListener("dm:sidebar-count",Q),f.add(()=>window.removeEventListener("dm:sidebar-count",Q));function me(e){const t=e.dataset.id,a=y.find(p=>p.id===t),s=i.widgets.find(p=>p.id===t);if(!a||!s)return[];const n=i.widgets.findIndex(p=>p.id===t),l=[{header:a.title},...a.href?[{label:`Open ${a.title}`,icon:"arrow-right",action:()=>{location.hash=a.href}}]:[],{label:s.collapsed?"Open":"Fold",icon:s.collapsed?"chevron-down":"chevron-up",action:()=>F(t,!s.collapsed)},{label:"Refresh",icon:"refresh-cw",disabled:s.collapsed===!0,action:()=>v(e)}];return d.locked?l:[{header:a.title},{label:"Add widget\u2026",icon:"plus",action:()=>x()},{type:"divider"},...a.href?[{label:`Open ${a.title}`,icon:"arrow-right",action:()=>{location.hash=a.href}}]:[],{label:s.collapsed?"Open":"Fold",icon:s.collapsed?"chevron-down":"chevron-up",action:()=>F(t,!s.collapsed)},{label:"Refresh",icon:"refresh-cw",disabled:s.collapsed===!0,action:()=>v(e)},{type:"divider"},...Ee.map(p=>({label:Se[p],icon:s.size===p?"check":"maximize-2",disabled:s.size===p,action:()=>g(De(i,t,p))})),{type:"divider"},{label:"Move earlier",icon:"arrow-left",disabled:n<=0,action:()=>g(z(i,t,-1))},{label:"Move later",icon:"arrow-right",disabled:n>=i.widgets.length-1,action:()=>g(z(i,t,1))},{type:"divider"},{label:"Remove from the Dashboard",icon:"x",action:()=>g(de(i,t))}]}const ge=E.contextMenu(b,{cursor:"glyph",match:".dbp-card",inherit:!1,density:"compact",items:e=>me(e)});f.add(()=>ge?.destroy?.());const ee=e=>{const t=e.target.closest(".dbp-kebab");if(t){ie(t);return}const a=e.target.closest(".dbp-fold");if(a){const s=a.closest(".dbp-card");s&&F(s.dataset.id,!s.classList.contains("is-collapsed"))}},te=e=>{const t=e.target.closest(".dbp-card");if(t){if(e.key==="F10"&&e.shiftKey){e.preventDefault(),ie(t);return}e.target===t&&(e.altKey&&(e.key==="ArrowLeft"||e.key==="ArrowUp")?(e.preventDefault(),g(z(i,t.dataset.id,-1)),ae(t.dataset.id)):e.altKey&&(e.key==="ArrowRight"||e.key==="ArrowDown")?(e.preventDefault(),g(z(i,t.dataset.id,1)),ae(t.dataset.id)):e.key==="Delete"&&(e.preventDefault(),g(de(i,t.dataset.id))))}},ae=e=>requestAnimationFrame(()=>b.querySelector(`.dbp-card[data-id="${CSS.escape(e)}"]`)?.focus());if(b.addEventListener("click",ee),b.addEventListener("keydown",te),f.add(()=>{b.removeEventListener("click",ee),b.removeEventListener("keydown",te)}),typeof E.sortable=="function"){const e=E.sortable(b,{items:".dbp-card",handle:".dbp-grip",live:!1,axis:"x",onDrop({key:t,targetKey:a,zone:s}){if(d.locked||s!=="before"&&s!=="after")return!1;const n=Le(i,t,a,s);return re(n,i)?!1:(g(n),!0)}});f.add(()=>e?.destroy?.())}function x(){const e=E.slideover({title:"Add a widget",size:"md",position:"right"}),t=document.createElement("div");t.className="dbp-gallery",t.innerHTML=`<input type="search" class="form-input dbp-gallery-search" placeholder="Search widgets" aria-label="Search widgets">
|
|
22
25
|
<p class="dbp-gallery-hint">Widgets from the Tools you can open. Each one is added at its usual size - resize it from its \u22EE menu.</p>
|
|
23
|
-
<div class="dbp-gallery-list"></div>`,e.element.appendChild(t);const
|
|
24
|
-
<h4 class="dbp-gallery-group">${
|
|
25
|
-
${
|
|
26
|
+
<div class="dbp-gallery-list"></div>`,e.element.appendChild(t);const a=t.querySelector(".dbp-gallery-list"),s=t.querySelector(".dbp-gallery-search");function n(){const l=Ae(y,i,s.value);a.innerHTML=l.length?l.map(p=>`
|
|
27
|
+
<h4 class="dbp-gallery-group">${r(p.source)}</h4>
|
|
28
|
+
${p.widgets.map(u=>`
|
|
26
29
|
<div class="dbp-gallery-item">
|
|
27
|
-
<span class="dbp-icon"><span data-icon="${
|
|
28
|
-
<span class="dbp-gallery-main"><strong>${
|
|
29
|
-
${u.added?'<span class="dbp-gallery-added">On the board</span>':`<button type="button" class="btn btn-sm btn-primary" data-add="${
|
|
30
|
-
</div>`).join("")}`).join(""):'<p class="dbp-empty">No widgets match.</p>',I.scan(
|
|
30
|
+
<span class="dbp-icon"><span data-icon="${r(u.icon||"grid")}" data-icon-size="16"></span></span>
|
|
31
|
+
<span class="dbp-gallery-main"><strong>${r(u.title)}</strong><small>${r(u.description||"")}</small></span>
|
|
32
|
+
${u.added?'<span class="dbp-gallery-added">On the board</span>':`<button type="button" class="btn btn-sm btn-primary" data-add="${r(u.id)}">Add</button>`}
|
|
33
|
+
</div>`).join("")}`).join(""):'<p class="dbp-empty">No widgets match.</p>',I.scan(a)}a.addEventListener("click",l=>{const p=l.target.closest("[data-add]")?.dataset.add,u=p&&y.find(O=>O.id===p);u&&(g($e(i,u)),E.toast(`${u.title} added.`,{type:"success",duration:2e3}),n())}),s.addEventListener("input",n),e.open(),n(),setTimeout(()=>s.focus(),50)}c.querySelector(".dbp-add-first").addEventListener("click",x);function ye(e){const t=G.querySelector(".dm-plugin-banner-name");if(!t)return;const a=t.querySelector(".dm-plugin-banner-sep");if(a){for(;a.nextSibling;)a.nextSibling.remove();t.appendChild(document.createTextNode(e))}else t.textContent=e}function ve(){const e=new Date().getHours(),t=e<12?"Good morning":e<18?"Good afternoon":"Good evening",a=String(ke()?.name||"").trim().split(/\s+/)[0],s=new Date().toLocaleDateString("en-GB",{weekday:"long",day:"numeric",month:"long"});return`${t}${a?`, ${a}`:""}. ${s}.`}function se(){const e=d.mine;ye(d.name||"My Dashboard"),document.title=`${d.name||"My Dashboard"} - Domma CMS`,U.hidden=!e.greeting,U.textContent=e.greeting?ve():"",c.classList.remove("dbp-cols-3","dbp-cols-4","dbp-cols-6"),c.classList.add(`dbp-cols-${e.columns||4}`),c.classList.toggle("is-compact",e.density==="compact"),c.classList.toggle("is-locked",d.locked===!0),ce.firstChild.textContent=d.locked?"Your administrator has set this layout. You can fold and refresh widgets; the cog has your own settings. ":"Your own Dashboard. Right-click the board to add widgets; drag a card by its grip; the chevron folds it; a card's \u22EE resizes or removes it. Tabs hold your boards. ",d.locked&&(D="locked"),B()}const L=(e,t,a)=>`<option value="${r(e)}"${String(a)===String(e)?" selected":""}>${r(t)}</option>`;function oe(){const e=d.mine,t=E.slideover({title:"Dashboard settings",size:"md",position:"right"}),a=document.createElement("form");a.className="dbp-settings",a.noValidate=!0,a.innerHTML=`
|
|
34
|
+
<h4 class="dbp-settings-section">Yours</h4>
|
|
35
|
+
<label class="dbp-field"><span class="form-label">Name
|
|
36
|
+
<span data-help="What your board is called - in its banner and in the sidebar. Leave it empty to use the site's name for it."></span></span>
|
|
37
|
+
<input class="form-input" name="name" maxlength="40" placeholder="${r(d.site.name||"My Dashboard")}" value="${r(e.name)}" autocomplete="off"></label>
|
|
38
|
+
<label class="dbp-field dbp-switch"><input type="checkbox" class="form-switch" name="greeting"${e.greeting?" checked":""}>
|
|
39
|
+
<span>Greeting <span data-help="A line at the top: good morning, your name and today's date."></span></span></label>
|
|
40
|
+
<div class="dbp-field-row">
|
|
41
|
+
<label class="dbp-field"><span class="form-label">Columns
|
|
42
|
+
<span data-help="How many columns the board has on a wide screen. More columns fit more small widgets side by side; a narrow window always uses fewer."></span></span>
|
|
43
|
+
<select class="form-select" name="columns">${[3,4,6].map(n=>L(n,`${n} columns`,e.columns)).join("")}</select></label>
|
|
44
|
+
<label class="dbp-field"><span class="form-label">Cards
|
|
45
|
+
<span data-help="Compact cards use less padding and smaller figures, so more fits on the screen."></span></span>
|
|
46
|
+
<select class="form-select" name="density">${L("normal","Normal",e.density)}${L("compact","Compact",e.density)}</select></label>
|
|
47
|
+
</div>
|
|
48
|
+
<label class="dbp-field"><span class="form-label">Updates
|
|
49
|
+
<span data-help="Live: each widget refreshes on its own interval. Slower: three times as long between refreshes - kinder to a slow connection. Paused: only when you press Refresh. A Tool saying its count changed still updates its widget."></span></span>
|
|
50
|
+
<select class="form-select" name="updates">${L("live","Live",e.updates)}${L("slower","Slower",e.updates)}${L("paused","Paused - refresh by hand",e.updates)}</select></label>
|
|
51
|
+
<label class="dbp-field dbp-switch"><input type="checkbox" class="form-switch" name="home"${e.home?" checked":""}>
|
|
52
|
+
<span>Open the admin on this board <span data-help="Signing in, or pressing Dashboard, brings you here instead of the usual Dashboard. Takes effect the next time the admin loads."></span></span></label>
|
|
53
|
+
${d.isAdmin?`
|
|
54
|
+
<h4 class="dbp-settings-section">For everyone</h4>
|
|
55
|
+
<label class="dbp-field"><span class="form-label">Board name
|
|
56
|
+
<span data-help="What the board is called for everyone who has not named their own."></span></span>
|
|
57
|
+
<input class="form-input" name="siteName" maxlength="40" placeholder="My Dashboard" value="${r(d.site.name)}" autocomplete="off"></label>
|
|
58
|
+
<label class="dbp-field dbp-switch"><input type="checkbox" class="form-switch" name="locked"${d.site.locked?" checked":""}>
|
|
59
|
+
<span>Lock the layout <span data-help="Everyone but administrators sees the site's default layout and cannot add, move, resize or remove widgets - they can still fold and refresh them. Set the default with Make this the default for everyone. Turning the lock off gives people their own layouts back."></span></span></label>`:""}
|
|
60
|
+
<div class="dbp-settings-actions">
|
|
61
|
+
<button type="button" class="btn btn-ghost" data-act="cancel">Cancel</button>
|
|
62
|
+
<button type="submit" class="btn btn-primary"><span data-icon="check" data-icon-size="14"></span> Save</button>
|
|
63
|
+
</div>`,t.element.appendChild(a);const s=ne(a,{position:"left"});I.scan(a),a.querySelector('[data-act="cancel"]').addEventListener("click",()=>t.close()),a.addEventListener("submit",async n=>{n.preventDefault();const l=a.elements,p={name:l.name.value.trim(),greeting:l.greeting.checked,columns:Number(l.columns.value),density:l.density.value,updates:l.updates.value,home:l.home.checked};try{const u=await m("/dashboard/settings",{method:"PUT",body:JSON.stringify(p)});if(d={...d,mine:u.mine,site:u.site,name:u.name},d.isAdmin&&l.siteName){const O=await m("/dashboard/settings/site",{method:"PUT",body:JSON.stringify({name:l.siteName.value.trim(),locked:l.locked.checked})});d.site=O.site,d.name=u.mine.name||O.site.name}se(),$(),k();try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}E.toast("Dashboard settings saved.",{type:"success"}),s?.(),t.close()}catch(u){E.toast(`Not saved: ${u.message||u}`,{type:"error"})}}),t.open()}if(await pe(),!H())return;d.locked||A(h,{id:"dashboard-add-widget",icon:"plus",title:"Add widget",onClick:x}),A(h,{id:"dashboard-fold-all",icon:"chevrons-up",title:"Fold or open every widget",onClick:()=>J(R(i))}),A(h,{id:"dashboard-refresh",icon:"refresh-cw",title:"Refresh every widget",onClick:()=>b.querySelectorAll(".dbp-card").forEach(v)}),d.locked||A(h,{id:"dashboard-reset",icon:"rotate-ccw",title:"Reset to the site's default layout",onClick:async()=>{if(await E.confirm("Go back to the site's default layout? All your boards are replaced by the one default board."))try{await m("/dashboard/layout",{method:"DELETE"});const e=await m("/dashboard/boards",{method:"GET"});o={version:2,active:e.active,boards:e.boards},i={version:1,widgets:T().widgets},q=JSON.stringify(o),D=e?.source||"built-in",$(),k(),E.toast("Back to the default layout.",{type:"success"})}catch(e){E.toast(`Could not reset: ${e.message||e}`,{type:"error"})}}}),Y&&A(h,{id:"dashboard-make-default",icon:"users",title:"Make this the default for everyone",onClick:async()=>{if(await E.confirm("Make this layout the default? Everyone who has not arranged their own Dashboard sees it. (Widgets they may not use are left out for them.)"))try{await m("/dashboard/layout/default",{method:"PUT",body:JSON.stringify(i)}),E.toast("This is now the default layout.",{type:"success"})}catch(e){E.toast(`Could not set the default: ${e.message||e}`,{type:"error"})}}}),A(h,{id:"dashboard-settings",icon:"settings",title:"Dashboard settings",onClick:oe});const we=E.contextMenu(c,{cursor:!1,inherit:!1,density:"compact",items:()=>[{header:d.name||"My Dashboard"},...d.locked?[]:[{label:"Add widget\u2026",icon:"plus",action:()=>x()},{label:"New board\u2026",icon:"layout",disabled:o.boards.length>=12,action:()=>Z()},{type:"divider"}],{label:R(i)?"Fold every widget":"Open every widget",icon:R(i)?"chevrons-up":"chevrons-down",action:()=>J(R(i))},{label:"Refresh every widget",icon:"refresh-cw",action:()=>b.querySelectorAll(".dbp-card").forEach(v)},{type:"divider"},{label:"Dashboard settings\u2026",icon:"settings",action:()=>oe()}]});f.add(()=>we?.destroy?.()),f.add(ne(c,{position:"bottom"})),I.scan(c),se(),$(),k()}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mountScaffolder as i}from"../lib/crud-tutorial.js";function t(o,n){return{templateUrl:`/admin/js/templates/docs/${o}.html`,async onMount(e){typeof n=="function"&&n(e),Domma.icons.scan(),Domma.syntax.scan()}}}export const docViews={tutorialCrud:t("tutorial-crud",o=>{const n=o.find("#tutorial-scaffolder-mount").get(0);n&&i(n,null,null)}),tutorialPlugin:t("tutorial-plugin"),tutorialForms:t("tutorial-forms"),componentsReference:t("components-reference"),componentsHowto:t("components-howto"),componentsWalkthrough:t("components-walkthrough"),componentsRules:t("components-rules")};const
|
|
1
|
+
import{mountScaffolder as i}from"../lib/crud-tutorial.js";function t(o,n){return{templateUrl:`/admin/js/templates/docs/${o}.html`,async onMount(e){typeof n=="function"&&n(e),Domma.icons.scan(),Domma.syntax.scan()}}}export const docViews={tutorialCrud:t("tutorial-crud",o=>{const n=o.find("#tutorial-scaffolder-mount").get(0);n&&i(n,null,null)}),tutorialPlugin:t("tutorial-plugin"),tutorialForms:t("tutorial-forms"),componentsReference:t("components-reference"),componentsHowto:t("components-howto"),componentsWalkthrough:t("components-walkthrough"),componentsRules:t("components-rules")};const a=["pages","media","navigation","dconfig","shortcodes","site-settings","plugins","users-roles","views","actions","cta-shortcode","editions","collections","tools","dashboard"],c=["authentication","pages","settings","layouts","navigation","media","users","plugins","collections","views","actions","forms","scaffold","external","content","builder"],s=o=>o.split("-").map(n=>n.charAt(0).toUpperCase()+n.slice(1)).join("");for(const o of a)docViews["usage"+s(o)]=t(`usage-${o}`);for(const o of c)docViews["apiRef"+s(o)]=t(`api-${o}`);
|
package/package.json
CHANGED
|
@@ -48,6 +48,8 @@ import {isSmtpConfigured} from '../../services/email.js';
|
|
|
48
48
|
import {loginFailed, loginRateLimited} from '../../services/notification-sources.js';
|
|
49
49
|
import {afterIndexOf, checkPassword, runLoginChecks, verifyChallenge} from '../../services/authChecks.js';
|
|
50
50
|
import {createSession, isLegacyTokenRevoked, listSessions, revokeLegacyToken, revokeSession, revokeUserSessions, useSession} from '../../services/sessions.js';
|
|
51
|
+
import {isDashboardPro} from '../../services/widgets.js';
|
|
52
|
+
import {readPrefs} from '../../services/dashboardSettings.js';
|
|
51
53
|
|
|
52
54
|
const { accessTokenExpiry, refreshTokenExpiry } = config.auth;
|
|
53
55
|
|
|
@@ -240,7 +242,7 @@ export async function authRoutes(fastify) {
|
|
|
240
242
|
const adminScope = getAdminScope(roles);
|
|
241
243
|
// adminHome: the screen `#/` opens for this user - see services/adminHome.js.
|
|
242
244
|
// The menu is read only when a site home has to be checked against it.
|
|
243
|
-
|
|
245
|
+
let adminHome = await resolveAdminHome({
|
|
244
246
|
scope: adminScope,
|
|
245
247
|
permissions,
|
|
246
248
|
siteHome: getConfig('site')?.adminHome,
|
|
@@ -248,6 +250,8 @@ export async function authRoutes(fastify) {
|
|
|
248
250
|
menuItems: async () => (await getMenu('admin-sidebar').catch(() => null))?.items || [],
|
|
249
251
|
canSee: (visibility) => checkVisibility(request.user, visibility)
|
|
250
252
|
});
|
|
253
|
+
// Dashboard Pro's "Open the admin on my board" - for a user the admin does not confine.
|
|
254
|
+
if (!adminScope && isDashboardPro() && (await readPrefs(request.user)).home) adminHome = '/my-dashboard';
|
|
251
255
|
return {permissions, adminScope, adminHome};
|
|
252
256
|
});
|
|
253
257
|
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
import {authenticate as defaultAuthenticate, checkVisibility} from '../../middleware/auth.js';
|
|
18
18
|
import {getRegisteredSidebarItems} from '../../services/hooks.js';
|
|
19
19
|
import {filterItemsForUser, getMenu} from '../../services/menus.js';
|
|
20
|
+
import {isDashboardPro} from '../../services/widgets.js';
|
|
21
|
+
import {boardName, readPrefs, readSite} from '../../services/dashboardSettings.js';
|
|
20
22
|
|
|
21
23
|
export const ADMIN_SIDEBAR_SLUG = 'admin-sidebar';
|
|
22
24
|
|
|
@@ -35,7 +37,14 @@ export async function sidebarRoutes(fastify, opts = {}) {
|
|
|
35
37
|
});
|
|
36
38
|
|
|
37
39
|
fastify.get('/sidebar/registered-items', signedIn, async (request) => {
|
|
40
|
+
// Dashboard Pro: My Dashboard wears the name this person (or the site) gave it.
|
|
41
|
+
const boardLabel = isDashboardPro()
|
|
42
|
+
? boardName(await readPrefs(request.user), await readSite())
|
|
43
|
+
: null;
|
|
38
44
|
return getRegisteredSidebarItems()
|
|
45
|
+
.map(entry => (boardLabel && entry?.item?.url === '#/my-dashboard'
|
|
46
|
+
? {...entry, item: {...entry.item, text: boardLabel}}
|
|
47
|
+
: entry))
|
|
39
48
|
.filter(entry => !entry?.item?.hidden && checkVisibility(request.user, entry?.item?.visibility))
|
|
40
49
|
.map(entry => Array.isArray(entry.item?.items)
|
|
41
50
|
? {...entry, item: {...entry.item, items: filterItemsForUser(entry.item.items, request.user)}}
|
|
@@ -3,12 +3,20 @@
|
|
|
3
3
|
*
|
|
4
4
|
* GET /api/dashboard/pro {enabled} - whether this site has the board (any signed-in user)
|
|
5
5
|
* GET /api/widgets the widgets this user may add (Pro)
|
|
6
|
-
* GET /api/dashboard/
|
|
6
|
+
* GET /api/dashboard/boards {boards: [{id, name, widgets}], active, source} (Pro)
|
|
7
|
+
* PUT /api/dashboard/boards save all of mine (Pro)
|
|
8
|
+
* GET /api/dashboard/layout the active board's layout {layout, source: mine|site|built-in} (Pro)
|
|
7
9
|
* PUT /api/dashboard/layout save mine (Pro)
|
|
8
10
|
* DELETE /api/dashboard/layout go back to the site's default (Pro)
|
|
9
11
|
* GET /api/dashboard/layout/default the site default (Pro, admins)
|
|
10
12
|
* PUT /api/dashboard/layout/default set it (Pro, admins)
|
|
13
|
+
* GET /api/dashboard/settings {mine, site, name, isAdmin, locked} - the cog (Pro)
|
|
14
|
+
* PUT /api/dashboard/settings save mine (Pro)
|
|
15
|
+
* PUT /api/dashboard/settings/site the name for everyone, and the layout lock (Pro, admins)
|
|
11
16
|
*
|
|
17
|
+
* A LOCKED site (settings/site `locked`) holds everyone but administrators to
|
|
18
|
+
* the site default: their GET answers it with source "locked", and their
|
|
19
|
+
* PUT / DELETE answer 423.
|
|
12
20
|
* Every route but /dashboard/pro answers 404 while no Pro Tool has switched the
|
|
13
21
|
* board on (services/widgets.js) - a free site has none of this.
|
|
14
22
|
*
|
|
@@ -23,8 +31,10 @@ import {getPermissionsForRoles} from '../../services/roles.js';
|
|
|
23
31
|
import {getEffectiveLevel, getEffectiveRoles} from '../../services/userRoles.js';
|
|
24
32
|
import {getEffectiveRegistry} from '../../services/permissionRegistry.js';
|
|
25
33
|
import {
|
|
26
|
-
DEFAULT_LAYOUT, getWidgets, isDashboardPro, layoutProblems,
|
|
34
|
+
DEFAULT_LAYOUT, activeBoard, boardsProblems, getWidgets, isDashboardPro, layoutProblems, normaliseBoards,
|
|
35
|
+
normaliseLayout, widgetsFor
|
|
27
36
|
} from '../../services/widgets.js';
|
|
37
|
+
import {boardName, prefsProblems, readPrefs, readSite, siteProblems, writePrefs, writeSite} from '../../services/dashboardSettings.js';
|
|
28
38
|
|
|
29
39
|
const DIR = path.resolve('content', 'dashboard');
|
|
30
40
|
const LAYOUTS = path.join(DIR, 'layouts');
|
|
@@ -72,23 +82,63 @@ export async function widgetRoutes(fastify, opts = {}) {
|
|
|
72
82
|
|
|
73
83
|
fastify.get('/widgets', {preHandler: [authenticate, pro]}, async (request) => widgetsFor(getWidgets(), who(request.user)));
|
|
74
84
|
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
/** Whether this user is held to the site default (the lock, and not an admin). */
|
|
86
|
+
const heldToDefault = async (user) => !who(user).isAdmin && (await readSite()).locked;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* This person's boards, and where they came from: their own (mine), or -
|
|
90
|
+
* before they have arranged anything - one board from the site default
|
|
91
|
+
* (site / built-in). A locked site gives a non-admin that one board only.
|
|
92
|
+
*/
|
|
93
|
+
async function boardsFor(user) {
|
|
94
|
+
if (await heldToDefault(user)) {
|
|
95
|
+
const d = await siteDefault();
|
|
96
|
+
return {...normaliseBoards({boards: [{id: 'main', name: 'Main', widgets: d.layout.widgets}]}), source: 'locked'};
|
|
97
|
+
}
|
|
98
|
+
const file = userFile(user);
|
|
77
99
|
const mine = file ? await readJson(file) : null;
|
|
78
|
-
|
|
100
|
+
if (mine) return {...normaliseBoards(mine), source: 'mine'};
|
|
101
|
+
const d = await siteDefault();
|
|
102
|
+
return {...normaliseBoards({boards: [{id: 'main', name: 'Main', widgets: d.layout.widgets}]}), source: d.source};
|
|
103
|
+
}
|
|
104
|
+
const locked423 = (reply) => reply.status(423).send({error: 'Your administrator has locked the Dashboard layout.'});
|
|
105
|
+
|
|
106
|
+
fastify.get('/dashboard/boards', {preHandler: [authenticate, pro]}, async (request) => boardsFor(request.user));
|
|
107
|
+
|
|
108
|
+
fastify.put('/dashboard/boards', {preHandler: [authenticate, pro]}, async (request, reply) => {
|
|
109
|
+
if (await heldToDefault(request.user)) return locked423(reply);
|
|
110
|
+
const problems = boardsProblems(request.body);
|
|
111
|
+
if (problems.length) return reply.status(400).send({error: problems[0]});
|
|
112
|
+
const file = userFile(request.user);
|
|
113
|
+
if (!file) return reply.status(400).send({error: 'Your account cannot keep a layout.'});
|
|
114
|
+
const set = normaliseBoards(request.body);
|
|
115
|
+
await writeJson(file, set);
|
|
116
|
+
return {...set, source: 'mine'};
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// The single-layout routes work on the ACTIVE board (what the board drew
|
|
120
|
+
// before there were several, and what a script may still call).
|
|
121
|
+
fastify.get('/dashboard/layout', {preHandler: [authenticate, pro]}, async (request) => {
|
|
122
|
+
const set = await boardsFor(request.user);
|
|
123
|
+
return {layout: {version: 1, widgets: activeBoard(set).widgets}, source: set.source};
|
|
79
124
|
});
|
|
80
125
|
|
|
81
126
|
fastify.put('/dashboard/layout', {preHandler: [authenticate, pro]}, async (request, reply) => {
|
|
127
|
+
if (await heldToDefault(request.user)) return locked423(reply);
|
|
82
128
|
const problems = layoutProblems(request.body);
|
|
83
129
|
if (problems.length) return reply.status(400).send({error: problems[0]});
|
|
84
130
|
const file = userFile(request.user);
|
|
85
131
|
if (!file) return reply.status(400).send({error: 'Your account cannot keep a layout.'});
|
|
132
|
+
const set = await boardsFor(request.user);
|
|
86
133
|
const layout = normaliseLayout(request.body);
|
|
87
|
-
|
|
134
|
+
activeBoard(set).widgets = layout.widgets;
|
|
135
|
+
await writeJson(file, normaliseBoards(set));
|
|
88
136
|
return {layout, source: 'mine'};
|
|
89
137
|
});
|
|
90
138
|
|
|
91
|
-
|
|
139
|
+
// Reset: every board goes, and the person starts again from the site default.
|
|
140
|
+
fastify.delete('/dashboard/layout', {preHandler: [authenticate, pro]}, async (request, reply) => {
|
|
141
|
+
if (await heldToDefault(request.user)) return locked423(reply);
|
|
92
142
|
const file = userFile(request.user);
|
|
93
143
|
if (file) await fs.rm(file, {force: true});
|
|
94
144
|
return siteDefault();
|
|
@@ -103,4 +153,29 @@ export async function widgetRoutes(fastify, opts = {}) {
|
|
|
103
153
|
await writeJson(DEFAULT_FILE, layout);
|
|
104
154
|
return {layout, source: 'site'};
|
|
105
155
|
});
|
|
156
|
+
|
|
157
|
+
// ---- The cog ----
|
|
158
|
+
fastify.get('/dashboard/settings', {preHandler: [authenticate, pro]}, async (request) => {
|
|
159
|
+
const [mine, site] = await Promise.all([readPrefs(request.user), readSite()]);
|
|
160
|
+
const isAdmin = who(request.user).isAdmin;
|
|
161
|
+
return {mine, site, name: boardName(mine, site), isAdmin, locked: site.locked && !isAdmin};
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
fastify.put('/dashboard/settings', {preHandler: [authenticate, pro]}, async (request, reply) => {
|
|
165
|
+
const problems = prefsProblems(request.body);
|
|
166
|
+
if (problems.length) return reply.status(400).send({error: problems[0]});
|
|
167
|
+
try {
|
|
168
|
+
const mine = await writePrefs(request.user, request.body);
|
|
169
|
+
const site = await readSite();
|
|
170
|
+
return {mine, site, name: boardName(mine, site)};
|
|
171
|
+
} catch (err) {
|
|
172
|
+
return reply.status(400).send({error: err.message});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
fastify.put('/dashboard/settings/site', {preHandler: [authenticate, pro, requireAdmin]}, async (request, reply) => {
|
|
177
|
+
const problems = siteProblems(request.body);
|
|
178
|
+
if (problems.length) return reply.status(400).send({error: problems[0]});
|
|
179
|
+
return {site: await writeSite(request.body)};
|
|
180
|
+
});
|
|
106
181
|
}
|
package/server/server.js
CHANGED
|
@@ -670,6 +670,7 @@ registerSidebarItem({folder: 'system', item: {source: 'Built in', text: 'Content
|
|
|
670
670
|
// sub-folders have no key in older menus; findFolder() falls back to their name.
|
|
671
671
|
registerSidebarItem({folder: 'Usage', item: {source: 'Built in', text: 'Collections & Forms', url: '#/docs/usage/collections', icon: 'database'}});
|
|
672
672
|
registerSidebarItem({folder: 'Usage', item: {source: 'Built in', text: 'Built-in Tools', url: '#/docs/usage/tools', icon: 'tool'}});
|
|
673
|
+
registerSidebarItem({folder: 'Usage', item: {source: 'Built in', text: 'Dashboard', url: '#/docs/usage/dashboard', icon: 'home'}});
|
|
673
674
|
registerSidebarItem({folder: 'Usage', item: {source: 'Built in', text: 'Editions & Licences', url: '#/docs/usage/editions', icon: 'key'}});
|
|
674
675
|
registerSidebarItem({folder: 'API Reference', item: {source: 'Built in', text: 'Forms', url: '#/docs/api/forms', icon: 'layout'}});
|
|
675
676
|
registerSidebarItem({folder: 'API Reference', item: {source: 'Built in', text: 'Scaffold', url: '#/docs/api/scaffold', icon: 'package'}});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Pro settings - the board's cog (admin/js/views/dashboard-board.js).
|
|
3
|
+
*
|
|
4
|
+
* Two layers:
|
|
5
|
+
*
|
|
6
|
+
* mine - each person's own: what their board is called, a greeting, how many
|
|
7
|
+
* columns and how dense, how often widgets update, and whether the
|
|
8
|
+
* admin opens on their board. content/dashboard/prefs/<user id>.json -
|
|
9
|
+
* per user, so ignored in a site repo like the layouts beside it.
|
|
10
|
+
* site - an administrator's, for everyone: the board's default name and a
|
|
11
|
+
* lock that holds everyone (but administrators) to the default
|
|
12
|
+
* layout. content/dashboard/site.json, tracked.
|
|
13
|
+
*
|
|
14
|
+
* Pure normalisers plus small file helpers; the routes are in
|
|
15
|
+
* routes/api/widgets.js. Tested in tests/services/dashboard-settings.test.js.
|
|
16
|
+
*/
|
|
17
|
+
import fs from 'fs/promises';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
|
|
20
|
+
export const COLUMNS = [3, 4, 6];
|
|
21
|
+
export const DENSITIES = ['normal', 'compact'];
|
|
22
|
+
/** live = each widget's own interval; slower = three times as long; paused = only by hand. */
|
|
23
|
+
export const UPDATES = ['live', 'slower', 'paused'];
|
|
24
|
+
export const NAME_MAX = 40;
|
|
25
|
+
|
|
26
|
+
export const DEFAULT_PREFS = {name: '', greeting: true, columns: 4, density: 'normal', updates: 'live', home: false};
|
|
27
|
+
export const DEFAULT_SITE = {name: 'My Dashboard', locked: false};
|
|
28
|
+
|
|
29
|
+
const cleanName = (v) => String(v ?? '').replace(/[\u0000-\u001f<>]/g, '').replace(/\s+/g, ' ').trim().slice(0, NAME_MAX);
|
|
30
|
+
|
|
31
|
+
/** A person's settings, cleaned: unknown values fall back to the defaults. */
|
|
32
|
+
export function normalisePrefs(input) {
|
|
33
|
+
const p = input && typeof input === 'object' ? input : {};
|
|
34
|
+
return {
|
|
35
|
+
name: cleanName(p.name),
|
|
36
|
+
greeting: p.greeting !== false,
|
|
37
|
+
columns: COLUMNS.includes(Number(p.columns)) ? Number(p.columns) : DEFAULT_PREFS.columns,
|
|
38
|
+
density: DENSITIES.includes(p.density) ? p.density : DEFAULT_PREFS.density,
|
|
39
|
+
updates: UPDATES.includes(p.updates) ? p.updates : DEFAULT_PREFS.updates,
|
|
40
|
+
home: p.home === true
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The site's settings, cleaned. A blank name falls back to "My Dashboard". */
|
|
45
|
+
export function normaliseSite(input) {
|
|
46
|
+
const s = input && typeof input === 'object' ? input : {};
|
|
47
|
+
return {name: cleanName(s.name) || DEFAULT_SITE.name, locked: s.locked === true};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** What is wrong with submitted settings, as messages ([] when they are fine). */
|
|
51
|
+
export function prefsProblems(input) {
|
|
52
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) return ['Settings must be an object.'];
|
|
53
|
+
const out = [];
|
|
54
|
+
if (input.name != null && String(input.name).length > NAME_MAX) out.push(`The name can be at most ${NAME_MAX} characters.`);
|
|
55
|
+
if (input.columns != null && !COLUMNS.includes(Number(input.columns))) out.push(`Columns must be one of ${COLUMNS.join(', ')}.`);
|
|
56
|
+
if (input.density != null && !DENSITIES.includes(input.density)) out.push(`Density must be ${DENSITIES.join(' or ')}.`);
|
|
57
|
+
if (input.updates != null && !UPDATES.includes(input.updates)) out.push(`Updates must be one of ${UPDATES.join(', ')}.`);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function siteProblems(input) {
|
|
62
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) return ['Settings must be an object.'];
|
|
63
|
+
if (input.name != null && String(input.name).length > NAME_MAX) return [`The name can be at most ${NAME_MAX} characters.`];
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** What the board is called for this person: their own name, else the site's. */
|
|
68
|
+
export function boardName(prefs, site) {
|
|
69
|
+
return cleanName(prefs?.name) || normaliseSite(site).name;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---- Files -------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
const DIR = () => path.resolve('content', 'dashboard');
|
|
75
|
+
const USER_ID_RE = /^[A-Za-z0-9_-]{1,80}$/;
|
|
76
|
+
|
|
77
|
+
async function readJson(file) {
|
|
78
|
+
try { return JSON.parse(await fs.readFile(file, 'utf8')); } catch { return null; }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function writeJson(file, data) {
|
|
82
|
+
await fs.mkdir(path.dirname(file), {recursive: true});
|
|
83
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
84
|
+
await fs.writeFile(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
85
|
+
await fs.rename(tmp, file);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const prefsFile = (user) => (USER_ID_RE.test(String(user?.id || '')) ? path.join(DIR(), 'prefs', `${user.id}.json`) : null);
|
|
89
|
+
|
|
90
|
+
export async function readPrefs(user) {
|
|
91
|
+
const file = prefsFile(user);
|
|
92
|
+
return normalisePrefs(file ? await readJson(file) : null);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function writePrefs(user, input) {
|
|
96
|
+
const file = prefsFile(user);
|
|
97
|
+
if (!file) throw new Error('This account cannot keep settings.');
|
|
98
|
+
const prefs = normalisePrefs(input);
|
|
99
|
+
await writeJson(file, prefs);
|
|
100
|
+
return prefs;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function readSite() {
|
|
104
|
+
return normaliseSite(await readJson(path.join(DIR(), 'site.json')));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function writeSite(input) {
|
|
108
|
+
const site = normaliseSite(input);
|
|
109
|
+
await writeJson(path.join(DIR(), 'site.json'), site);
|
|
110
|
+
return site;
|
|
111
|
+
}
|
package/server/services/docs.js
CHANGED
|
@@ -58,6 +58,7 @@ const MAP = [
|
|
|
58
58
|
['usage-editions', 'usage/editions', 'Editions & Licences'],
|
|
59
59
|
['usage-collections', 'usage/collections', 'Collections & Forms'],
|
|
60
60
|
['usage-tools', 'usage/tools', 'Built-in Tools'],
|
|
61
|
+
['usage-dashboard', 'usage/dashboard', 'Dashboard'],
|
|
61
62
|
['tutorial-crud', 'tutorials/crud', 'Building a CRUD App'],
|
|
62
63
|
['tutorial-plugin', 'tutorials/plugin', 'Writing a Plugin'],
|
|
63
64
|
['tutorial-forms', 'tutorials/forms', 'Form Follow-Up'],
|
|
@@ -319,6 +319,7 @@ export function buildGitignore({sitePlugins = [], pluginContent = {}, sitePublic
|
|
|
319
319
|
'/content/preview-links.json',
|
|
320
320
|
// Each admin's own Dashboard Pro layout (keyed by user id); the site default beside it is tracked.
|
|
321
321
|
'/content/dashboard/layouts/',
|
|
322
|
+
'/content/dashboard/prefs/',
|
|
322
323
|
'/config/connections.json*',
|
|
323
324
|
'/config/keys/',
|
|
324
325
|
'/plugins/*/data/*.jsonl',
|
|
@@ -267,3 +267,59 @@ export function _resetWidgetsForTests() {
|
|
|
267
267
|
owners.clear();
|
|
268
268
|
_registered.length = 0;
|
|
269
269
|
}
|
|
270
|
+
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
// Boards - a person's several named layouts, shown as tabs
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
export const MAX_BOARDS = 12;
|
|
276
|
+
export const BOARD_NAME_MAX = 40;
|
|
277
|
+
const BOARD_ID_RE = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
278
|
+
|
|
279
|
+
const boardName = (v, fallback) => String(v ?? '').replace(/[\u0000-\u001f<>]/g, '').replace(/\s+/g, ' ').trim().slice(0, BOARD_NAME_MAX) || fallback;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* A person's boards, cleaned. Accepts the stored v2 shape
|
|
283
|
+
* ({version: 2, active, boards: [{id, name, widgets}]}) and the v1 shape a
|
|
284
|
+
* board-less layout file has ({widgets}), which becomes one board, "Main".
|
|
285
|
+
* There is always at least one board, and `active` always names one of them.
|
|
286
|
+
*
|
|
287
|
+
* @param {*} input
|
|
288
|
+
* @returns {{version: number, active: string, boards: Array<{id: string, name: string, widgets: object[]}>}}
|
|
289
|
+
*/
|
|
290
|
+
export function normaliseBoards(input) {
|
|
291
|
+
const src = input && typeof input === 'object' ? input : {};
|
|
292
|
+
const list = Array.isArray(src.boards) ? src.boards : (Array.isArray(src.widgets) || Array.isArray(src) ? [{id: 'main', name: 'Main', widgets: src.widgets || src}] : []);
|
|
293
|
+
const seen = new Set();
|
|
294
|
+
const boards = [];
|
|
295
|
+
for (const b of list) {
|
|
296
|
+
const id = String(b?.id || '').trim();
|
|
297
|
+
if (!BOARD_ID_RE.test(id) || seen.has(id)) continue;
|
|
298
|
+
seen.add(id);
|
|
299
|
+
boards.push({id, name: boardName(b.name, `Board ${boards.length + 1}`), widgets: normaliseLayout(b.widgets).widgets});
|
|
300
|
+
if (boards.length >= MAX_BOARDS) break;
|
|
301
|
+
}
|
|
302
|
+
if (!boards.length) boards.push({id: 'main', name: 'Main', widgets: []});
|
|
303
|
+
const active = boards.some(b => b.id === src.active) ? src.active : boards[0].id;
|
|
304
|
+
return {version: 2, active, boards};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** What is wrong with a submitted set of boards ([] when it is fine). */
|
|
308
|
+
export function boardsProblems(input) {
|
|
309
|
+
if (!input || typeof input !== 'object' || !Array.isArray(input.boards)) return ['boards must be an array'];
|
|
310
|
+
if (!input.boards.length) return ['keep at least one board'];
|
|
311
|
+
if (input.boards.length > MAX_BOARDS) return [`at most ${MAX_BOARDS} boards`];
|
|
312
|
+
const out = [];
|
|
313
|
+
const ids = new Set();
|
|
314
|
+
input.boards.forEach((b, i) => {
|
|
315
|
+
if (!BOARD_ID_RE.test(String(b?.id || ''))) out.push(`boards[${i}].id is not a board id`);
|
|
316
|
+
else if (ids.has(b.id)) out.push(`boards[${i}].id "${b.id}" is used twice`);
|
|
317
|
+
ids.add(b?.id);
|
|
318
|
+
if (b?.name != null && String(b.name).length > BOARD_NAME_MAX) out.push(`boards[${i}].name can be at most ${BOARD_NAME_MAX} characters`);
|
|
319
|
+
out.push(...layoutProblems({widgets: b?.widgets ?? []}).map(p => `boards[${i}]: ${p}`));
|
|
320
|
+
});
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** The active board of a set. */
|
|
325
|
+
export const activeBoard = (set) => set.boards.find(b => b.id === set.active) || set.boards[0];
|