- Timestamp:
- 08/16/2026 03:06:18 PM (4 weeks ago)
- Branches:
- main
- Children:
- a5caa73
- Parents:
- a5515b4 (diff), 9026de5 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the(diff)links above to see all the changes relative to each parent. - Location:
- src/views
- Files:
-
- 3 added
- 6 edited
-
pages/admin-audio.ejs (modified) (4 diffs)
-
pages/admin-listeners.ejs (added)
-
pages/admin-media.ejs (modified) (1 diff)
-
pages/admin-playlists.ejs (modified) (2 diffs)
-
pages/admin-videos.ejs (modified) (1 diff)
-
partials/admin-back.ejs (added)
-
partials/admin-styles.ejs (added)
-
partials/media-tabs.ejs (modified) (1 diff)
-
partials/playlist-editor.ejs (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/views/pages/admin-audio.ejs
ra5515b4 r749605a 1 <%# De gedeelde ax-*-primitieven. MOET boven het eigen style-blok van deze 2 pagina staan: wat hieronder blijft staan wijkt bewust af en hoort dus 3 later te komen, anders wint de partial ervan. %> 4 <%- include('../partials/admin-styles') %> 1 5 <div class="container ax-page"> 2 6 3 < p><a href="/admin" class="btn">← <%= t('nav.admin') %></a></p>7 <%- include('../partials/admin-back') %> 4 8 <%- include('../partials/media-tabs', { active: 'audio', audioOn: true }) %> 5 9 <header class="ax-header"> … … 150 154 padding: 0 1rem; 151 155 } 152 .ax-header {153 display: flex; align-items: flex-start; gap: 0.75rem;154 margin-bottom: 1.5rem;155 }156 .ax-header h1 {157 font-family: var(--font-display, serif);158 font-size: 1.75rem;159 margin: 0 0 0.25rem;160 }161 .ax-tagline {162 color: var(--ink-muted, var(--ink-soft));163 margin: 0;164 font-size: 0.9rem;165 line-height: 1.5;166 }167 .ax-tagline code {168 background: var(--paper-2);169 padding: 0.1em 0.4em;170 border-radius: 4px;171 font-size: 0.9em;172 }173 .ax-back {174 display: inline-flex; align-items: center; justify-content: center;175 width: 40px; height: 40px;176 border: 1px solid var(--rule);177 border-radius: 8px;178 background: var(--paper);179 color: var(--ink);180 text-decoration: none;181 font-size: 1.1rem;182 flex-shrink: 0;183 transition: border-color 120ms;184 }185 .ax-back:hover { border-color: var(--accent); }186 187 /* ─── Flash messages ───────────────────────────────────────────── */188 .ax-flash {189 padding: 0.75rem 1rem;190 border-radius: 8px;191 margin-bottom: 1rem;192 font-size: 0.9rem;193 }194 .ax-flash-ok { background: rgba(40,160,90,.15); color: #2a9d5e; border: 1px solid rgba(40,160,90,.3); }195 .ax-flash-err { background: rgba(200,60,60,.15); color: #c33; border: 1px solid rgba(200,60,60,.3); }196 197 /* ─── Card sections ────────────────────────────────────────────── */198 .ax-card {199 background: var(--paper);200 border: 1px solid var(--rule);201 border-radius: 12px;202 padding: 1.25rem;203 margin-bottom: 1rem;204 }205 156 .ax-card-title { 206 157 font-family: var(--font-display, serif); … … 303 254 .ax-form-actions[hidden] { display: none; } 304 255 305 /* ─── Buttons ──────────────────────────────────────────────────── */306 .ax-btn {307 display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;308 padding: 0.55rem 1rem;309 border: 1px solid var(--rule);310 background: var(--paper-2);311 color: var(--ink);312 font-family: var(--font-ui, system-ui), sans-serif;313 font-size: 0.9rem; font-weight: 500;314 text-decoration: none;315 border-radius: 6px;316 cursor: pointer;317 min-height: 40px;318 transition: background 120ms, border-color 120ms;319 -webkit-tap-highlight-color: transparent;320 }321 .ax-btn:hover { border-color: var(--accent); }322 .ax-btn-secondary { background: var(--paper-2); }323 .ax-btn-primary {324 background: var(--accent); color: white;325 border-color: var(--accent);326 }327 .ax-btn-primary:hover { opacity: 0.92; border-color: var(--accent); }328 329 /* Tiny icon-only button for row actions */330 .ax-icon-btn {331 display: inline-flex; align-items: center; justify-content: center;332 width: 40px; height: 40px;333 border: 1px solid var(--rule);334 background: var(--paper-2);335 color: var(--ink);336 border-radius: 8px;337 cursor: pointer;338 font-size: 1rem;339 transition: background 120ms, border-color 120ms, color 120ms;340 }341 .ax-icon-btn:hover { border-color: var(--accent); }342 .ax-icon-btn-danger:hover { color: #dc2626; border-color: #dc2626; }343 344 256 .ax-track-play.is-playing { 345 257 border-color: var(--accent); … … 348 260 } 349 261 .ax-track-play-icon { font-size: 0.85rem; line-height: 1; } 350 351 /* ─── Empty state ──────────────────────────────────────────────── */352 .ax-empty {353 text-align: center;354 padding: 2rem 1rem;355 color: var(--ink-muted, var(--ink-soft));356 }357 .ax-empty-icon {358 font-size: 2.5rem;359 font-family: var(--font-display, serif);360 color: var(--ink-soft);361 opacity: 0.4;362 margin-bottom: 0.5rem;363 }364 .ax-empty p { margin: 0; }365 366 /* ─── Track list (card per row) ────────────────────────────────── */367 .ax-list {368 list-style: none; padding: 0; margin: 0;369 display: flex; flex-direction: column;370 gap: 0.6rem;371 }372 .ax-track {373 display: grid;374 grid-template-columns: 56px 1fr auto;375 gap: 0.85rem;376 align-items: center;377 padding: 0.75rem;378 background: var(--paper-2);379 border: 1px solid var(--rule);380 border-radius: 10px;381 transition: border-color 120ms;382 }383 .ax-track:hover { border-color: var(--accent); }384 .ax-track-cover {385 width: 56px; height: 56px;386 border-radius: 6px;387 object-fit: cover;388 background: var(--paper);389 display: flex; align-items: center; justify-content: center;390 font-family: var(--font-display, serif);391 font-size: 1.5rem;392 color: var(--accent);393 flex-shrink: 0;394 }395 262 .ax-track-cover-empty { /* span variant */ } 396 .ax-track-meta {397 min-width: 0; /* allow text-overflow on long titles */398 display: flex; flex-direction: column; gap: 0.2rem;399 }400 .ax-track-title {401 font-weight: 600;402 font-size: 0.95rem;403 color: var(--ink);404 overflow: hidden;405 text-overflow: ellipsis;406 white-space: nowrap;407 }408 .ax-track-sub {409 font-size: 0.8rem;410 color: var(--ink-muted, var(--ink-soft));411 display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center;412 }413 .ax-track-sep { opacity: 0.5; }414 .ax-embed {415 display: inline-block;416 background: var(--paper);417 padding: 0.15rem 0.5rem;418 border-radius: 4px;419 font-family: var(--font-mono, ui-monospace, monospace);420 font-size: 0.75rem;421 color: var(--ink-soft);422 cursor: pointer;423 user-select: all;424 margin-top: 0.15rem;425 word-break: break-all;426 align-self: flex-start;427 transition: background 120ms;428 }429 .ax-embed:hover { background: var(--rule); }430 .ax-embed.is-copied { background: rgba(40,160,90,0.2); color: #2a9d5e; }431 432 .ax-track-actions {433 display: flex; gap: 0.4rem;434 flex-shrink: 0;435 }436 263 .ax-track-delete { display: inline; } 437 264 438 265 /* ─── Mobile tweaks ───────────────────────────────────────────── */ 439 266 @media (max-width: 480px) { 440 .ax-track {441 grid-template-columns: 48px 1fr;442 grid-template-areas: "cover meta" "actions actions";443 gap: 0.6rem;444 }445 .ax-track-cover { grid-area: cover; width: 48px; height: 48px; }446 .ax-track-meta { grid-area: meta; }447 .ax-track-actions {448 grid-area: actions;449 justify-content: flex-end;450 border-top: 1px solid var(--rule);451 padding-top: 0.5rem;452 margin-top: 0.25rem;453 }454 267 } 455 268 -
src/views/pages/admin-media.ejs
ra5515b4 r749605a 1 <%# Beheer → Media: image library + cleanup (the Audio half links out to /admin/audio). %> 2 <div class="admin-wrap" style="max-width:980px;margin:0 auto;padding:1.5rem 1rem;"> 3 <a href="/admin" class="btn" style="margin-bottom:1rem;display:inline-block;">← <%= t('nav.admin') %></a> 4 <h1 style="font-family:var(--display,serif);margin:.2rem 0 1rem;"><%= t('admin.t_media') %></h1> 1 <%# Beheer -> Media: de afbeeldingenbibliotheek met opruiming. 2 De audio-helft zit onder /admin/audio, als eigen tab. 5 3 4 Deze pagina stond volledig op inline styles en viel daarmee buiten het 5 ax-*-systeem waar audio en playlists al op zaten. Nu op dezelfde romp: 6 zelfde breedte, zelfde kop, tabs op dezelfde plek. %> 7 <%- include('../partials/admin-styles') %> 8 <div class="container ax-page"> 9 10 <%- include('../partials/admin-back') %> 6 11 <%- include('../partials/media-tabs', { active: 'images', audioOn: audioOn }) %> 12 <header class="ax-header"> 13 <div> 14 <h1><%= t('admin.t_media') %></h1> 15 <p class="ax-tagline"> 16 <%= items.length %> <%= t('admin.media_count') %><% if (unusedCount) { %> · <strong><%= unusedCount %></strong> <%= t('admin.media_unused') %><% } %> 17 </p> 18 </div> 19 </header> 7 20 8 <% if (success) { %>< p style="background:rgba(60,160,90,.15);border:1px solid rgba(60,160,90,.4);padding:.5rem .8rem;border-radius:8px;"><%= success %></p><% } %>21 <% if (success) { %><div class="ax-flash ax-flash-ok"><%= success %></div><% } %> 9 22 10 <div style="display:flex;justify-content:space-between;align-items:center;gap:1rem;flex-wrap:wrap;margin-bottom:1rem;"> 11 <p style="margin:0;opacity:.75;"> 12 <%= items.length %> <%= t('admin.media_count') %><% if (unusedCount) { %> · <strong><%= unusedCount %></strong> <%= t('admin.media_unused') %><% } %> 13 </p> 14 <% if (unusedCount) { %> 15 <button type="button" id="media-cleanup" class="btn" style="background:#b4452e;color:#fff;border-color:#b4452e;">🗑 <%= t('admin.media_cleanup') %> (<%= unusedCount %>)</button> 16 <% } %> 23 <% if (unusedCount) { %> 24 <div class="ax-actions-bar"> 25 <button type="button" id="media-cleanup" class="ax-btn ax-btn-danger">🗑 <%= t('admin.media_cleanup') %> (<%= unusedCount %>)</button> 17 26 </div> 27 <% } %> 18 28 19 29 <% if (!items.length) { %> 20 <p style="opacity:.7;"><%= t('admin.media_empty') %></p> 30 <div class="ax-empty"> 31 <div class="ax-empty-icon">📷</div> 32 <p><%= t('admin.media_empty') %></p> 33 </div> 21 34 <% } else { %> 22 <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:1rem;">35 <div class="ax-grid"> 23 36 <% items.forEach(function(it){ %> 24 <div class=" media-card" data-file="<%= it.file %>" style="border:1px solid var(--rule,rgba(128,128,128,.25));border-radius:10px;overflow:hidden;background:var(--paper-2,rgba(128,128,128,.06));">25 <div style="position:relative;aspect-ratio:1;background:#0a0e1a;">26 <img src="<%= thumb(it.url, 320) %>" alt="" loading="lazy" decoding="async" style=" width:100%;height:100%;object-fit:cover;display:block;">27 <% if (it.hasVideo) { %><span title="animated cover" style="position:absolute;top:6px;right:6px;background:rgba(0,0,0,.6);border-radius:6px;padding:0 6px;font-size:13px;">🎬</span><% } %>28 <% if (!it.usedCount) { %><span style="position:absolute;top:6px;left:6px;background:#b4452e;color:#fff;border-radius:6px;padding:1px 6px;font-size:11px;"><%= t('admin.media_unused') %></span><% } %>37 <div class="ax-tile media-card" data-file="<%= it.file %>"> 38 <div class="ax-tile-media" style="aspect-ratio:1;"> 39 <img src="<%= thumb(it.url, 320) %>" alt="" loading="lazy" decoding="async" style="object-fit:cover;height:100%;"> 40 <% if (it.hasVideo) { %><span class="ax-tile-badge ax-tile-badge-r" title="animated cover">🎬</span><% } %> 41 <% if (!it.usedCount) { %><span class="ax-tile-badge"><%= t('admin.media_unused') %></span><% } %> 29 42 </div> 30 <div style="padding:.5rem .6rem;font-size:13px;">31 <div style="opacity:.7;"><%= it.kb %> KB · <%= it.usedCount %>×</div>32 <div style="display:flex;gap:.4rem;margin-top:.45rem;">33 <button type="button" class=" btn" style="padding:3px 8px;font-size:12px;" data-copy="<%= it.url %>"><%= t('admin.media_copy') %></button>34 <button type="button" class=" btn" style="padding:3px 8px;font-size:12px;background:transparent;border-color:#b4452e;color:#b4452e;" data-del="<%= it.file %>">🗑</button>43 <div class="ax-tile-body"> 44 <div class="ax-tile-meta"><%= it.kb %> KB · <%= it.usedCount %>×</div> 45 <div class="ax-tile-actions"> 46 <button type="button" class="ax-btn ax-btn-sm" data-copy="<%= it.url %>"><%= t('admin.media_copy') %></button> 47 <button type="button" class="ax-btn ax-btn-sm ax-btn-danger" data-del="<%= it.file %>">🗑</button> 35 48 </div> 36 49 </div> -
src/views/pages/admin-playlists.ejs
ra5515b4 r749605a 5 5 const _csrf = (typeof csrfToken !== 'undefined' && csrfToken) || ''; 6 6 %> 7 <%# De gedeelde ax-*-primitieven. MOET boven het eigen style-blok van deze 8 pagina staan: wat hieronder blijft staan wijkt bewust af en hoort dus 9 later te komen, anders wint de partial ervan. %> 10 <%- include('../partials/admin-styles') %> 7 11 <div class="container ax-page"> 8 12 9 < p><a href="/admin" class="btn">← <%= t('nav.admin') %></a></p>13 <%- include('../partials/admin-back') %> 10 14 <%- include('../partials/media-tabs', { active: 'playlists', audioOn: true }) %> 11 15 <header class="ax-header"> … … 83 87 84 88 .ax-page { max-width: 880px; margin: 1.5rem auto 4rem; padding: 0 1rem; } 85 .ax-header { display: flex; align-items: flex-start; gap: 0.75rem; margin-bottom: 1.5rem; }86 .ax-header h1 { font-family: var(--font-display, serif); font-size: 1.75rem; margin: 0 0 0.25rem; }87 .ax-tagline { color: var(--ink-muted, var(--ink-soft)); margin: 0; font-size: 0.9rem; line-height: 1.5; }88 .ax-tagline code { background: var(--paper-2); padding: 0.1em 0.4em; border-radius: 4px; font-size: 0.9em; }89 .ax-back {90 display: inline-flex; align-items: center; justify-content: center;91 width: 40px; height: 40px;92 border: 1px solid var(--rule); border-radius: 8px;93 background: var(--paper); color: var(--ink); text-decoration: none;94 font-size: 1.1rem; flex-shrink: 0; transition: border-color 120ms;95 }96 .ax-back:hover { border-color: var(--accent); }97 98 .ax-actions-bar {99 display: flex;100 margin-bottom: 1rem;101 }102 103 .ax-card {104 background: var(--paper);105 border: 1px solid var(--rule);106 border-radius: 12px;107 padding: 1.25rem;108 margin-bottom: 1rem;109 }110 111 /* ─── Buttons (same primitives as admin-audio) ─────────────────── */112 .ax-btn {113 display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;114 padding: 0.6rem 1.1rem;115 border: 1px solid var(--rule); background: var(--paper-2);116 color: var(--ink);117 font-family: var(--font-ui, system-ui), sans-serif;118 font-size: 0.95rem; font-weight: 500;119 text-decoration: none; border-radius: 8px; cursor: pointer;120 min-height: 44px;121 transition: background 120ms, border-color 120ms;122 -webkit-tap-highlight-color: transparent;123 }124 .ax-btn:hover { border-color: var(--accent); }125 .ax-btn-primary { background: var(--accent); color: white; border-color: var(--accent); }126 .ax-btn-primary:hover { opacity: 0.92; }127 128 .ax-icon-btn {129 display: inline-flex; align-items: center; justify-content: center;130 width: 40px; height: 40px;131 border: 1px solid var(--rule); border-radius: 8px;132 background: var(--paper-2); color: var(--ink);133 cursor: pointer; font-size: 1rem;134 transition: background 120ms, border-color 120ms, color 120ms;135 }136 .ax-icon-btn:hover { border-color: var(--accent); }137 .ax-icon-btn-danger:hover { color: #dc2626; border-color: #dc2626; }138 139 /* ─── Empty state ──────────────────────────────────────────────── */140 .ax-empty { text-align: center; padding: 2rem 1rem; color: var(--ink-muted, var(--ink-soft)); }141 .ax-empty-icon { font-size: 2.5rem; margin-bottom: 0.5rem; opacity: 0.5; }142 .ax-empty p { margin: 0; }143 .ax-empty-sub { font-size: 0.85rem; margin-top: 0.5rem !important; }144 145 /* ─── List items (same as audio tracks) ───────────────────────── */146 .ax-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.6rem; }147 .ax-track {148 display: grid;149 grid-template-columns: 64px 1fr auto;150 gap: 0.85rem; align-items: center;151 padding: 0.85rem;152 background: var(--paper);153 border: 1px solid var(--rule);154 border-radius: 12px;155 transition: border-color 120ms;156 }157 .ax-track:hover { border-color: var(--accent); }158 .ax-track-cover {159 width: 64px; height: 64px; border-radius: 8px;160 background: var(--paper-2);161 display: flex; align-items: center; justify-content: center;162 font-family: var(--font-display, serif);163 font-size: 1.5rem; color: var(--accent);164 flex-shrink: 0;165 }166 .ax-track-meta { min-width: 0; display: flex; flex-direction: column; gap: 0.25rem; }167 .ax-track-title {168 font-family: var(--font-display, serif);169 font-weight: 600; font-size: 1.05rem; color: var(--ink);170 display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap;171 }172 .ax-pill {173 font-family: var(--font-ui);174 font-size: 0.7rem; font-weight: 500;175 color: var(--ink-soft);176 background: var(--paper-2);177 padding: 0.15em 0.55em; border-radius: 999px;178 letter-spacing: 0;179 }180 .ax-track-sub {181 font-size: 0.85rem;182 color: var(--ink-muted, var(--ink-soft));183 display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center;184 }185 .ax-track-sep { opacity: 0.5; }186 .ax-embed {187 display: inline-block;188 background: var(--paper-2);189 padding: 0.15rem 0.5rem;190 border-radius: 4px;191 font-family: var(--font-mono, ui-monospace, monospace);192 font-size: 0.75rem;193 color: var(--ink-soft);194 cursor: pointer; user-select: all;195 margin-top: 0.15rem;196 word-break: break-all;197 align-self: flex-start;198 transition: background 120ms;199 }200 .ax-embed:hover { background: var(--rule); }201 .ax-embed.is-copied { background: rgba(40,160,90,0.2); color: #2a9d5e; }202 203 .ax-track-actions { display: flex; gap: 0.4rem; flex-shrink: 0; }204 89 205 90 /* ─── Mobile ──────────────────────────────────────────────────── */ 206 91 @media (max-width: 480px) { 207 .ax-track {208 grid-template-columns: 56px 1fr;209 grid-template-areas: "cover meta" "actions actions";210 gap: 0.6rem;211 }212 .ax-track-cover { grid-area: cover; width: 56px; height: 56px; }213 .ax-track-meta { grid-area: meta; }214 .ax-track-actions {215 grid-area: actions;216 justify-content: flex-end;217 border-top: 1px solid var(--rule);218 padding-top: 0.5rem;219 margin-top: 0.25rem;220 }221 92 } 222 93 </style> -
src/views/pages/admin-videos.ejs
ra5515b4 r749605a 1 <%# Beheer → Media → Video's (Robins opdracht, 30-7): the videos C2S uploads 2 brought in (Shaer's composer, the help buoy), with usage and cleanup. 3 Same shape as the image page; the preview is the video itself with 4 preload="metadata", so the browser shows the first frame as its 5 thumbnail until real poster generation lands (bead: ffmpeg ontbreekt). %> 6 <div class="admin-wrap" style="max-width:980px;margin:0 auto;padding:1.5rem 1rem;"> 7 <a href="/admin" class="btn" style="margin-bottom:1rem;display:inline-block;">← <%= t('nav.admin') %></a> 8 <h1 style="font-family:var(--display,serif);margin:.2rem 0 1rem;"><%= t('admin.t_media') %></h1> 1 <%# Beheer -> Media -> Video's (Robins opdracht, 30-7): de video's die via C2S 2 binnenkwamen (Shaers composer, de reddingsboei), met gebruik en opruiming. 9 3 4 De voorvertoning is de video zelf met preload="metadata", zodat de browser 5 het eerste beeld als miniatuur toont tot echte poster-generatie landt 6 (bead: ffmpeg ontbreekt). 7 8 Zelfde romp als de andere mediapagina's; stond hiervoor op inline styles. %> 9 <%- include('../partials/admin-styles') %> 10 <div class="container ax-page"> 11 12 <%- include('../partials/admin-back') %> 10 13 <%- include('../partials/media-tabs', { active: 'videos', audioOn: audioOn }) %> 14 <header class="ax-header"> 15 <div> 16 <h1><%= t('admin.t_media') %></h1> 17 <p class="ax-tagline"><%= items.length %> <%= t('admin.videos_count') %></p> 18 </div> 19 </header> 11 20 12 <% if (success) { %><p style="background:rgba(60,160,90,.15);border:1px solid rgba(60,160,90,.4);padding:.5rem .8rem;border-radius:8px;"><%= success %></p><% } %> 13 14 <p style="margin:0 0 1rem;opacity:.75;"><%= items.length %> <%= t('admin.videos_count') %></p> 21 <% if (success) { %><div class="ax-flash ax-flash-ok"><%= success %></div><% } %> 15 22 16 23 <% if (!items.length) { %> 17 <p style="opacity:.7;"><%= t('admin.videos_empty') %></p> 24 <div class="ax-empty"> 25 <div class="ax-empty-icon">🎬</div> 26 <p><%= t('admin.videos_empty') %></p> 27 </div> 18 28 <% } else { %> 19 <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:1rem;">29 <div class="ax-grid" style="--ax-tile:220px;"> 20 30 <% items.forEach(function(it){ %> 21 <div class=" media-card" data-file="<%= it.file %>" style="border:1px solid var(--rule,rgba(128,128,128,.25));border-radius:10px;overflow:hidden;background:var(--paper-2,rgba(128,128,128,.06));">22 <div style="position:relative;aspect-ratio:16/10;background:#000;">23 <video src="<%= it.url %>" preload="metadata" controls playsinline style=" width:100%;height:100%;object-fit:contain;display:block;"></video>24 <% if (!it.usedCount) { %><span style="position:absolute;top:6px;left:6px;background:#b4452e;color:#fff;border-radius:6px;padding:1px 6px;font-size:11px;z-index:1;"><%= t('admin.media_unused') %></span><% } %>31 <div class="ax-tile media-card" data-file="<%= it.file %>"> 32 <div class="ax-tile-media" style="aspect-ratio:16/10;background:#000;"> 33 <video src="<%= it.url %>" preload="metadata" controls playsinline style="height:100%;object-fit:contain;"></video> 34 <% if (!it.usedCount) { %><span class="ax-tile-badge"><%= t('admin.media_unused') %></span><% } %> 25 35 </div> 26 <div style="padding:.5rem .6rem;font-size:13px;">27 <div style="opacity:.7;word-break:break-all;"><%= it.file %></div>28 <div style="opacity:.7;"><%= it.kb %> KB · <%= it.usedCount %>×</div>29 <div style="display:flex;gap:.4rem;margin-top:.45rem;">30 <button type="button" class=" btn" style="padding:3px 8px;font-size:12px;" data-copy="<%= it.url %>"><%= t('admin.media_copy') %></button>36 <div class="ax-tile-body"> 37 <div class="ax-tile-meta" style="word-break:break-all;"><%= it.file %></div> 38 <div class="ax-tile-meta"><%= it.kb %> KB · <%= it.usedCount %>×</div> 39 <div class="ax-tile-actions"> 40 <button type="button" class="ax-btn ax-btn-sm" data-copy="<%= it.url %>"><%= t('admin.media_copy') %></button> 31 41 <% if (!it.usedCount) { %> 32 <button type="button" class=" btn" style="padding:3px 8px;font-size:12px;background:transparent;border-color:#b4452e;color:#b4452e;" data-del="<%= it.file %>">🗑</button>42 <button type="button" class="ax-btn ax-btn-sm ax-btn-danger" data-del="<%= it.file %>">🗑</button> 33 43 <% } %> 34 44 </div> -
src/views/partials/media-tabs.ejs
ra5515b4 r749605a 1 <%# Shared Media tab bar (Afbeeldingen / Audio / Playlists). Params: active = 'images'|'videos'|'audio'|'playlists'; audioOn (bool, default true). %> 1 <%# Gedeelde tabbalk voor Media (Afbeeldingen / Video's / Audio / Playlists / 2 Luisteraars). 3 4 Params: active = 'images'|'videos'|'audio'|'playlists'|'listeners'; 5 audioOn (bool, standaard true). 6 7 De opmaak stond hier volledig inline en zit nu in partials/admin-styles.ejs 8 bij de rest van de ax-*-primitieven. Een gedeeld element dat zijn eigen 9 opmaak meedroeg kon niet meebewegen met de pagina's eromheen -- en dat was 10 zichtbaar: de balk zag er hetzelfde uit op vijf pagina's die verder van 11 twee verschillende ontwerpen waren. 12 13 De actieve tab is `aria-current="page"` en niet een klasse: dat is wat een 14 schermlezer nodig heeft, en de opmaak kan er net zo goed op haken. %> 2 15 <% var _a = (typeof audioOn === 'undefined') ? true : audioOn; %> 3 <nav class=" media-tabs" aria-label="Media" style="display:flex;gap:.4rem;margin:0 0 1.25rem;border-bottom:1px solid var(--rule,rgba(128,128,128,.3));flex-wrap:wrap;">4 <a href="/admin/media" class=" btn" style="border-radius:8px 8px 0 0;<%= active === 'images' ? 'border-bottom:2px solid var(--accent,#e8b04b);font-weight:600;' : 'opacity:.8;' %>"><%= t('admin.media_images') %></a>5 <a href="/admin/media/videos" class=" btn" style="border-radius:8px 8px 0 0;<%= active === 'videos' ? 'border-bottom:2px solid var(--accent,#e8b04b);font-weight:600;' : 'opacity:.8;' %>"><%= t('admin.media_videos') %></a>16 <nav class="ax-tabs" aria-label="Media"> 17 <a href="/admin/media" class="ax-tab"<%- active === 'images' ? ' aria-current="page"' : '' %>><%= t('admin.media_images') %></a> 18 <a href="/admin/media/videos" class="ax-tab"<%- active === 'videos' ? ' aria-current="page"' : '' %>><%= t('admin.media_videos') %></a> 6 19 <% if (_a) { %> 7 <a href="/admin/audio" class="btn" style="border-radius:8px 8px 0 0;<%= active === 'audio' ? 'border-bottom:2px solid var(--accent,#e8b04b);font-weight:600;' : 'opacity:.8;' %>"><%= t('admin.b_audio') %></a> 8 <a href="/admin/playlists" class="btn" style="border-radius:8px 8px 0 0;<%= active === 'playlists' ? 'border-bottom:2px solid var(--accent,#e8b04b);font-weight:600;' : 'opacity:.8;' %>"><%= t('admin.b_playlists') %></a> 20 <a href="/admin/audio" class="ax-tab"<%- active === 'audio' ? ' aria-current="page"' : '' %>><%= t('admin.b_audio') %></a> 21 <a href="/admin/playlists" class="ax-tab"<%- active === 'playlists' ? ' aria-current="page"' : '' %>><%= t('admin.b_playlists') %></a> 22 <a href="/admin/listeners" class="ax-tab"<%- active === 'listeners' ? ' aria-current="page"' : '' %>><%= t('admin.b_listeners') %></a> 9 23 <% } %> 10 24 </nav> -
src/views/partials/playlist-editor.ejs
ra5515b4 r749605a 99 99 } 100 100 .pl-field { display: flex; flex-direction: column; gap: .25rem; } 101 /* `display: flex` wint van het hidden-attribuut, dus zonder deze regel doet 102 [hidden] op een .pl-field NIETS -- het veld blijft gewoon staan. De 103 uitgavevelden schakelen daarop (album versus afspeellijst). */ 104 .pl-field[hidden] { display: none; } 101 105 .pl-field-full { grid-column: 1 / -1; } 102 106 .pl-field > span { … … 280 284 </style> 281 285 282 <%# Het script staat in assets/js/mod/playlist-editor.js; de gegevens via partials/page-data.ejs (shaer-bqr). %> 286 <%# Het script staat in assets/js/mod/playlist-editor.js. 287 288 EIGEN GEGEVENSBLOK, niet dat van partials/page-data.ejs. pageData() pakt met 289 querySelector EEN blok, en deze modal wordt ingesloten op pagina's die er al 290 zelf een hebben (admin-playlists, post-edit) -- een tweede blok zou daar 291 stilzwijgend genegeerd worden en dan stond de hele modal weer in het 292 Nederlands. Eigen kenmerk, eigen lezer. 293 294 De teksten stonden hier tot 16-8 hard in het script. Dat viel niet op omdat 295 het toevallig de brontaal was: een Engelse of Duitse beheerder kreeg een 296 Nederlandse modal midden in een vertaalde pagina. %> 297 <script type="application/json" data-playlist-editor-i18n><%- JSON.stringify({ 298 titel: t('ple.titel'), artiest: t('ple.artiest'), jaar: t('ple.jaar'), type: t('ple.type'), 299 k_album: t('ple.k_album'), k_playlist: t('ple.k_playlist'), 300 uitgave: t('ple.uitgave'), mb_release: t('ple.mb_release'), 301 cover: t('ple.cover'), cover_kies: t('ple.cover_kies'), cover_url: t('ple.cover_url'), 302 tracks_in: t('ple.tracks_in'), sleep_hint: t('ple.sleep_hint'), 303 beschikbaar: t('ple.beschikbaar'), zoek: t('ple.zoek'), geen_res: t('ple.geen_res'), 304 leeg_sel: t('ple.leeg_sel'), 305 t_edit: t('ple.t_edit'), t_new: t('ple.t_new'), dialoog: t('ple.dialoog'), 306 sluiten: t('ple.sluiten'), annuleren: t('ple.annuleren'), 307 opslaan: t('ple.opslaan'), aanmaken: t('ple.aanmaken'), 308 bezig_opslaan: t('ple.bezig_opslaan'), 309 versleep: t('ple.versleep'), verwijder: t('ple.verwijder'), 310 geen_audio: t('ple.geen_audio'), e_geen_tracks: t('ple.e_geen_tracks'), 311 e_tracks: t('ple.e_tracks'), e_opslaan: t('ple.e_opslaan'), e_mislukt: t('ple.e_mislukt'), 312 e_alleen_afb: t('ple.e_alleen_afb'), bezig: t('ple.bezig'), e_upload: t('ple.e_upload'), 313 }).replace(/</g, '\\u003c') %></script>
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)