Add multi-language audio tracks and self-hosted flag-icons
Introduce per-video language support and multiple audio tracks (VideoAudioTrack model + migrations for language, description, title), a reusable language-select component, and a track-editor form. Bundle the self-hosted flag-icons v7.2.3 library and a NAS auto-sync command. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
f8d13457fa
commit
66fd78c10f
314 files changed
+16507
-3172
No files matched your search
@@ -15,7 +15,7 @@
|
||||
$uid = 'csd_' . ($id ?? $name) . '_' . substr(md5(uniqid()), 0, 8);
|
||||
$inputId = $id ?? $name;
|
||||
|
||||
$selFlag = '🌐';
|
||||
$selFlag = '';
|
||||
$selLabel = $placeholder;
|
||||
$selSub = '';
|
||||
if ($value) {
|
||||
@@ -45,14 +45,15 @@
|
||||
}
|
||||
.csd-btn:hover, .csd-btn:focus-visible { border-color: var(--brand-red, #e61e1e); }
|
||||
.csd-btn[aria-expanded="true"] { border-color: var(--brand-red, #e61e1e); }
|
||||
.csd-ico { font-size: 18px; line-height: 1; flex-shrink: 0; user-select: none; }
|
||||
.csd-ico { font-size: 18px; line-height: 1; flex-shrink: 0; user-select: none; display: flex; align-items: center; }
|
||||
.csd-ico .fi, .csd-opt-ico .fi { width: 22px; height: 16px; border-radius: 2px; display: inline-block; flex-shrink: 0; }
|
||||
.csd-val { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.csd-val.ph { color: var(--text-secondary, #aaa); }
|
||||
.csd-sub { font-size: 12px; color: var(--text-secondary, #aaa); white-space: nowrap; flex-shrink: 0; }
|
||||
.csd-arr { flex-shrink: 0; font-size: 11px; color: var(--text-secondary, #aaa); margin-left: 4px; transition: transform .2s; }
|
||||
.csd-btn[aria-expanded="true"] .csd-arr { transform: rotate(180deg); }
|
||||
.csd-panel {
|
||||
position: fixed; z-index: 1060; min-width: 220px;
|
||||
position: fixed; z-index: 1080; min-width: 220px;
|
||||
background: var(--bg-secondary, #1e1e1e); border: 1px solid var(--border-color, #303030);
|
||||
border-radius: 10px; box-shadow: 0 12px 32px rgba(0,0,0,.55); overflow: hidden;
|
||||
}
|
||||
@@ -159,7 +160,7 @@
|
||||
_move(i) { const all=[...this.list.querySelectorAll('.csd-opt:not([hidden])')]; if(all[i]){all[i].tabIndex=0;all[i].focus();} }
|
||||
_pick(opt) {
|
||||
this.hidden.value = opt.dataset.v;
|
||||
this.icoEl.textContent = opt.querySelector('.csd-opt-ico').textContent;
|
||||
this.icoEl.innerHTML = opt.querySelector('.csd-opt-ico').innerHTML;
|
||||
this.valEl.textContent = opt.querySelector('.csd-opt-main').textContent;
|
||||
this.valEl.classList.remove('ph');
|
||||
if (this.subEl) { const s=opt.querySelector('.csd-opt-sub'); this.subEl.textContent=s?s.textContent:''; }
|
||||
@@ -185,7 +186,7 @@
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded="false"
|
||||
aria-label="{{ $label ?? 'Select timezone' }}">
|
||||
<span class="csd-ico">{{ $selFlag }}</span>
|
||||
<span class="csd-ico"><span class="fi fi-{{ $selFlag ?: 'xx' }}"></span></span>
|
||||
<span class="csd-val{{ $isPlaceholder ? ' ph' : '' }}">{{ $selLabel }}</span>
|
||||
@if(!$isPlaceholder && $selSub)
|
||||
<span class="csd-sub">{{ $selSub }}</span>
|
||||
@@ -206,7 +207,7 @@
|
||||
data-v="{{ $opt['value'] }}"
|
||||
data-s="{{ $opt['search'] }}"
|
||||
aria-selected="{{ $value === $opt['value'] ? 'true' : 'false' }}">
|
||||
<span class="csd-opt-ico">{{ $opt['flag'] }}</span>
|
||||
<span class="csd-opt-ico"><span class="fi fi-{{ $opt['flag'] ?: 'xx' }}"></span></span>
|
||||
<span class="csd-opt-main">{{ $opt['label'] }}</span>
|
||||
<span class="csd-opt-sub">{{ $opt['utc'] }}</span>
|
||||
</li>
|
||||
|
||||
Reference in new issue
Block a user