source: Klonkt/src/views/pages/admin-audio.ejs@ 6401681

main
Last change on this file since 6401681 was 6401681, checked in by Robin <roboburr@…>, 4 weeks ago

Het paneel hoort bij wie je bent, niet bij je bestanden (Robins correctie)

Verhuisd van Beheer -> Audio naar het profielscherm van de site-eigenaar, en
daar staat het NAAST de aliassen -- want dat is dezelfde vraag. Een alias zegt
wie je elders in de fediverse bent, een MBID zegt wie je in het muziekregister
bent. Bij de audiobestanden ging het over beheer van spullen; hier gaat het over
identiteit, en daar hoort het thuis.

DE VERHUIZING VERANDERDE DE VORM, en dat is de moeite waard om te weten. Het
profielscherm IS een formulier, en een formulier in een formulier bestaat niet
in HTML. Dus geen eigen opslaan-knop meer: de keuze zet een verborgen veld en
gaat mee met de Opslaan van de pagina, precies zoals de profielfoto. Dat leest
ook beter -- een halve wijziging die je nog kunt herzien voor je opslaat.

Ontkoppelen is nu het veld leegmaken, geen aparte route. Minder oppervlak.

Het zoek-endpoint ging mee naar /admin/sites/:slug/api/musicbrainz. Een endpoint
onder /admin/audio laten staan voor een scherm in /admin/sites is precies zo'n
naad die later niemand meer kan plaatsen. De teksten heten nu asite.mb_* in
plaats van aaud.mb_*, in alle drie de talen, met een placeholder erbij.

Wat NIET veranderde: de zoekopdracht draait server-side (hun regel geldt per
applicatie), wij kiezen niet voor de artiest, en alleen een echte MBID komt de
kolom in.

Co-Authored-By: Claude Opus 5 <noreply@…>

  • Property mode set to 100644
File size: 20.3 KB
Line 
1<div class="container ax-page">
2
3 <p><a href="/admin" class="btn">&larr; <%= t('nav.admin') %></a></p>
4 <%- include('../partials/media-tabs', { active: 'audio', audioOn: true }) %>
5 <header class="ax-header">
6 <div>
7 <h1><%= t('aaud.title') %></h1>
8 <p class="ax-tagline"><%= t('aaud.tagline_pre') %> <code>[[track:&lt;id&gt;]]</code> <%= t('aaud.tagline_post') %></p>
9 </div>
10 </header>
11
12 <% if (success) { %><div class="ax-flash ax-flash-ok"><%= success %></div><% } %>
13 <% if (error) { %><div class="ax-flash ax-flash-err"><%= error %></div><% } %>
14
15 <%# ── UPLOAD ──────────────────────────────────────────────── %>
16 <section class="ax-card">
17 <div class="ax-card-title"><%= t('aaud.upload') %></div>
18
19 <%# Bulk drag-drop zone. Multiple files at once, transcoded sequentially.
20 Title is auto-derived from each filename. Artist/album/cover are
21 SHARED across the batch (set once, applied to every track) — same
22 pattern as iTunes/Plex bulk-add. %>
23 <div class="ax-form">
24
25 <%# Shared metadata fields (apply to ALL files in the batch). %>
26 <div class="ax-row ax-row-2">
27 <label class="ax-field">
28 <span><%= t('aaud.artist') %> <small><%= t('aaud.applied_all') %></small></span>
29 <input type="text" id="batch-artist" placeholder="<%= t('aaud.optional') %>">
30 </label>
31 <label class="ax-field">
32 <span><%= t('aaud.album') %> <small><%= t('aaud.applied_all') %></small></span>
33 <input type="text" id="batch-album" placeholder="<%= t('aaud.optional') %>">
34 </label>
35 </div>
36 <label class="ax-field ax-file">
37 <span><%= t('aaud.cover') %> <small><%= t('aaud.cover_hint') %></small></span>
38 <span class="ax-file-control">
39 <span class="ax-btn ax-btn-secondary ax-file-btn">🖼 <%= t('aaud.choose_cover') %></span>
40 <span class="ax-file-name" data-empty><%= t('aaud.no_file') %></span>
41 <input type="file" id="batch-cover" accept="image/jpeg,image/png,image/webp,image/gif">
42 </span>
43 </label>
44
45 <%# The drop-zone itself. Click anywhere to open file picker. %>
46 <label class="ax-dropzone" id="audio-dropzone" tabindex="0">
47 <span class="ax-dropzone-icon">🎵</span>
48 <strong class="ax-dropzone-title"><%= t('aaud.drag_here') %></strong>
49 <span class="ax-dropzone-sub"><%= t('aaud.or_click') %><br><small>mp3, m4a, ogg, opus, flac · max <%= maxBytesMb %> MB · wav max <%= maxWavMb %> MB</small></span>
50 <input type="file" id="audio-files" accept="audio/*" multiple>
51 </label>
52
53 <%# Per-file progress list. Populated by JS as files are queued. %>
54 <ul class="ax-upload-queue" id="upload-queue" hidden></ul>
55
56 <div class="ax-form-actions" id="upload-actions" hidden>
57 <button type="button" class="ax-btn ax-btn-primary" id="start-upload-btn">⬆ <%= t('aaud.start_upload') %></button>
58 <button type="button" class="ax-btn" id="clear-queue-btn"><%= t('aaud.clear_list') %></button>
59 </div>
60 </div>
61 </section>
62
63 <%# ── TRACK LIST ─────────────────────────────────────────── %>
64 <section class="ax-card">
65 <div class="ax-card-title" style="display:flex;align-items:center;gap:.5rem">
66 <%= t('aaud.tracks') %> <span class="ax-count"><%= tracks.length %></span>
67 <button type="button" class="ax-btn ax-btn-secondary" id="add-link-track-btn"
68 style="margin-left:auto;font-size:.85rem" title="<%= t('aaud.add_link_track_title') %>">
69 + <%= t('aaud.add_link_track') %>
70 </button>
71 </div>
72
73 <% if (!tracks.length) { %>
74 <div class="ax-empty">
75 <div class="ax-empty-icon">♫</div>
76 <p><%= t('aaud.no_tracks') %></p>
77 </div>
78 <% } else { %>
79 <%
80 // Pre-fetch loop labels — `t` is shadowed by the loop var below.
81 var _aaudUntitled = t('aaud.untitled');
82 var _aaudPlay = t('aaud.play');
83 var _aaudEdit = t('aaud.edit');
84 var _aaudDlOn = t('aaud.dl_on');
85 var _aaudDlOff = t('aaud.dl_off');
86 var _aaudDelete = t('aaud.delete');
87 var _aaudCopy = t('aaud.copy_click');
88 var _aaudDelConfirm = t('aaud.delete_confirm');
89 %>
90 <ul class="ax-list">
91 <% tracks.forEach(function(t) { %>
92 <li class="ax-track" data-track-id="<%= t.id %>">
93 <% if (t.cover_url) { %>
94 <img class="ax-track-cover" src="<%= t.cover_url %>" alt="" data-cover-thumb>
95 <% } else { %>
96 <span class="ax-track-cover ax-track-cover-empty" data-cover-thumb>♫</span>
97 <% } %>
98
99 <div class="ax-track-meta">
100 <div class="ax-track-title" data-cell="title"><%= t.title || _aaudUntitled %></div>
101 <div class="ax-track-sub">
102 <span data-cell="artist"><%= t.artist || '—' %></span>
103 <% if (t.album) { %><span class="ax-track-sep">·</span><span data-cell="album"><%= t.album %></span><% } else { %><span data-cell="album" hidden></span><% } %>
104 <span class="ax-track-sep">·</span><span><%= t.size ? Math.round(t.size/1024) + ' KB' : '—' %></span>
105 </div>
106 <code class="ax-embed" data-copy="[[track:<%= t.id %>]]" title="<%= _aaudCopy %>">[[track:<%= t.id %>]]</code>
107 </div>
108
109 <div class="ax-track-actions">
110 <% if (t.stream_url) { %>
111 <button type="button" class="ax-icon-btn ax-track-play"
112 data-stream-url="<%= t.stream_url %>"
113 data-track-id="<%= t.id %>"
114 aria-label="<%= _aaudPlay %>" title="<%= _aaudPlay %>">
115 <span class="ax-track-play-icon" aria-hidden="true">▶</span>
116 </button>
117 <% } %>
118 <button type="button" class="ax-icon-btn" data-track-edit data-id="<%= t.id %>" aria-label="<%= _aaudEdit %>" title="<%= _aaudEdit %>">✎</button>
119 <% if (typeof premiumUnlocked === 'undefined' || premiumUnlocked) { %>
120 <button type="button" class="ax-icon-btn" data-track-dl data-id="<%= t.id %>" data-on="<%= t.downloadable ? '1' : '0' %>"
121 aria-label="<%= t.downloadable ? _aaudDlOn : _aaudDlOff %>" title="<%= t.downloadable ? _aaudDlOn : _aaudDlOff %>"
122 style="<%= t.downloadable ? 'color:var(--accent,#6b8f71)' : 'opacity:.5' %>">⬇</button>
123 <% } %>
124 <form action="/admin/audio/<%= t.id %>/delete" method="post" data-confirm="<%= _aaudDelConfirm %>" class="ax-track-delete">
125 <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label="<%= _aaudDelete %>" title="<%= _aaudDelete %>">🗑</button>
126 </form>
127 </div>
128 </li>
129 <% }); %>
130 </ul>
131 <% } %>
132 </section>
133
134 <% if ((typeof premiumUnlocked === 'undefined' || premiumUnlocked) && typeof embedUrl !== 'undefined') { %>
135 <section class="ax-section" style="margin-top:1.5rem">
136 <h2 style="margin:0 0 .4rem">🔊 <%= t('aaud.embed_player') %></h2>
137 <p style="opacity:.75;font-size:.9rem;margin:0 0 .6rem"><%= t('aaud.embed_hint') %></p>
138 <textarea readonly rows="3" data-selectall style="width:100%;font-family:monospace;font-size:12px;padding:10px;border-radius:8px;border:1px solid var(--rule);background:var(--paper-2);color:inherit;">&lt;iframe src="<%= embedUrl %>" width="100%" height="420" frameborder="0" loading="lazy" style="border-radius:12px"&gt;&lt;/iframe&gt;</textarea>
139 <p style="margin:.6rem 0 0"><a class="btn" href="<%= embedUrl %>" target="_blank">▶ <%= t('aaud.preview_player') %></a></p>
140 </section>
141 <% } %>
142
143</div>
144
145<style>
146/* ─── Page wrapper ──────────────────────────────────────────────── */
147.ax-page {
148 max-width: 880px;
149 margin: 1.5rem auto 4rem;
150 padding: 0 1rem;
151}
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.ax-card-title {
206 font-family: var(--font-display, serif);
207 font-size: 1.15rem;
208 font-weight: 600;
209 margin-bottom: 1rem;
210 display: flex; align-items: baseline; gap: 0.5rem;
211}
212.ax-count {
213 font-family: var(--font-ui, system-ui);
214 font-size: 0.8rem;
215 color: var(--ink-muted, var(--ink-soft));
216 background: var(--paper-2);
217 padding: 0.1em 0.55em;
218 border-radius: 999px;
219 font-weight: 500;
220}
221
222/* ─── Form fields ──────────────────────────────────────────────── */
223.ax-form {
224 display: flex; flex-direction: column;
225 gap: 0.85rem;
226}
227.ax-row {
228 display: grid; gap: 0.85rem;
229}
230.ax-row-2 { grid-template-columns: 1fr 1fr; }
231@media (max-width: 600px) {
232 .ax-row-2 { grid-template-columns: 1fr; }
233}
234.ax-field {
235 display: flex; flex-direction: column;
236 gap: 0.35rem;
237 min-width: 0;
238}
239.ax-field > span {
240 font-size: 0.8rem;
241 font-weight: 600;
242 color: var(--ink-soft);
243 display: flex; align-items: baseline; gap: 0.4rem;
244 flex-wrap: wrap;
245}
246.ax-field > span small {
247 font-weight: 400; color: var(--ink-muted);
248 font-size: 0.95em;
249}
250.ax-field input[type="text"],
251.ax-field input[type="url"],
252.ax-field input[type="email"],
253.ax-field select,
254.ax-field textarea {
255 width: 100%;
256 box-sizing: border-box;
257 padding: 0.6rem 0.75rem;
258 border: 1px solid var(--rule);
259 border-radius: 6px;
260 background: var(--paper-2);
261 color: var(--ink);
262 font-family: var(--font-ui, system-ui), sans-serif;
263 font-size: 0.95rem;
264 -webkit-appearance: none; appearance: none;
265 transition: border-color 120ms;
266}
267.ax-field input:focus,
268.ax-field select:focus,
269.ax-field textarea:focus {
270 outline: 2px solid var(--accent);
271 outline-offset: -1px;
272 border-color: var(--accent);
273}
274
275/* ─── File inputs (custom-styled wrapper) ─────────────────────── */
276.ax-file-control {
277 display: flex; align-items: center; gap: 0.5rem;
278 flex-wrap: wrap;
279 position: relative;
280 min-height: 40px;
281}
282.ax-file-control input[type="file"] {
283 position: absolute; inset: 0;
284 opacity: 0; cursor: pointer;
285 z-index: 2;
286}
287.ax-file-btn {
288 pointer-events: none; /* let clicks pass through to the file input */
289}
290.ax-file-name {
291 font-size: 0.85rem;
292 color: var(--ink);
293 word-break: break-all;
294 flex: 1;
295 min-width: 0;
296}
297.ax-file-name[data-empty] { color: var(--ink-muted, var(--ink-soft)); }
298
299.ax-form-actions {
300 display: flex; gap: 0.5rem;
301 margin-top: 0.25rem;
302}
303.ax-form-actions[hidden] { display: none; }
304
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.ax-track-play.is-playing {
345 border-color: var(--accent);
346 color: var(--accent);
347 background: color-mix(in srgb, var(--accent) 12%, var(--paper-2));
348}
349.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.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.ax-track-delete { display: inline; }
437
438/* ─── Mobile tweaks ───────────────────────────────────────────── */
439@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}
455
456/* ─── Bulk drop-zone ──────────────────────────────────────────── */
457.ax-dropzone {
458 display: flex; flex-direction: column; align-items: center; justify-content: center;
459 gap: 0.4rem;
460 padding: 2rem 1rem;
461 border: 2px dashed var(--rule);
462 border-radius: 12px;
463 background: var(--paper-2);
464 cursor: pointer;
465 text-align: center;
466 transition: border-color 150ms, background 150ms;
467 position: relative;
468 min-height: 160px;
469}
470.ax-dropzone:hover,
471.ax-dropzone:focus-within {
472 border-color: var(--accent);
473}
474.ax-dropzone.is-dragover {
475 border-color: var(--accent);
476 background: color-mix(in srgb, var(--accent) 8%, var(--paper-2));
477}
478.ax-dropzone input[type="file"] {
479 /* Cover the whole dropzone but invisible — clicks bubble through to it. */
480 position: absolute; inset: 0;
481 opacity: 0;
482 cursor: pointer;
483}
484.ax-dropzone-icon {
485 font-size: 2rem;
486 opacity: 0.5;
487 line-height: 1;
488}
489.ax-dropzone-title {
490 font-size: 1rem;
491 font-weight: 600;
492 color: var(--ink);
493}
494.ax-dropzone-sub {
495 font-size: 0.85rem;
496 color: var(--ink-muted, var(--ink-soft));
497 line-height: 1.4;
498}
499.ax-dropzone-sub small {
500 font-size: 0.75rem;
501 opacity: 0.8;
502}
503
504/* ─── Upload queue ────────────────────────────────────────────── */
505.ax-upload-queue {
506 list-style: none;
507 padding: 0; margin: 0.75rem 0 0;
508 display: flex; flex-direction: column;
509 gap: 0.4rem;
510}
511.ax-queue-item {
512 display: grid;
513 grid-template-columns: 1fr auto;
514 gap: 0.75rem;
515 align-items: center;
516 padding: 0.6rem 0.85rem;
517 background: var(--paper);
518 border: 1px solid var(--rule);
519 border-radius: 8px;
520 font-size: 0.875rem;
521 transition: border-color 150ms, opacity 150ms;
522}
523.ax-queue-item--active {
524 border-color: var(--accent);
525}
526.ax-queue-item--done {
527 border-color: rgba(40,160,90,0.4);
528 opacity: 0.85;
529}
530.ax-queue-item--error {
531 border-color: rgba(220,60,60,0.5);
532}
533.ax-queue-name {
534 font-weight: 500;
535 color: var(--ink);
536 overflow: hidden;
537 text-overflow: ellipsis;
538 white-space: nowrap;
539 min-width: 0;
540}
541.ax-queue-size {
542 font-size: 0.75rem;
543 color: var(--ink-muted, var(--ink-soft));
544 margin-left: 0.5rem;
545}
546.ax-queue-status {
547 font-size: 0.75rem;
548 font-weight: 500;
549 padding: 0.15em 0.6em;
550 border-radius: 999px;
551 background: var(--paper-2);
552 color: var(--ink-soft);
553 white-space: nowrap;
554 flex-shrink: 0;
555}
556.ax-queue-status--queued { color: var(--ink-muted, var(--ink-soft)); }
557.ax-queue-status--uploading,
558.ax-queue-status--transcoding {
559 color: var(--accent);
560 background: color-mix(in srgb, var(--accent) 10%, var(--paper-2));
561}
562.ax-queue-status--done {
563 color: #2a9d5e;
564 background: rgba(40,160,90,0.12);
565}
566.ax-queue-status--error {
567 color: #dc2626;
568 background: rgba(220,60,60,0.12);
569}
570</style>
571
572<%# Track editor modal (exposes window.openTrackEditor) %>
573<%- include('../partials/track-editor', { csrfToken: (typeof csrfToken !== 'undefined' ? csrfToken : '') }) %>
574
575<%# Het script staat in assets/js/mod/admin-audio.js; de gegevens via partials/page-data.ejs (shaer-bqr). %>
576<%- include('../partials/page-data', { pageData: { change_failed: t('aaud.change_failed'), copied: t('aaud.copied'), create_failed: t('aaud.create_failed'), dl_off: t('aaud.dl_off'), dl_on: t('aaud.dl_on'), editor_not_loaded: t('aaud.editor_not_loaded'), err_unexpected: t('aaud.err_unexpected'), failed: t('aaud.failed'), new_track: t('aaud.new_track'), no_file: t('aaud.no_file'), pause: t('aaud.pause'), play: t('aaud.play'), st_done: t('aaud.st_done'), st_error: t('aaud.st_error'), st_queued: t('aaud.st_queued'), st_transcoding: t('aaud.st_transcoding'), st_uploading: t('aaud.st_uploading'), untitled: t('aaud.untitled') } }) %>
Note: See TracBrowser for help on using the repository browser.