fix(ui): 补齐 generator.html 和 listmanager.html 的问号剥离兜底
- generator.html: 新增 ds() 钩子,在 renderList / batch 预览 / showToast 三个渲染点统一问号剥离 - listmanager.html: 新增 ds() 钩子,在 renderList / renderSequence / 自定义 select 选项 / showToast 四个点统一问号剥离 - APP_VERSION_TEXT bump 到 v1.0.107
This commit is contained in:
@@ -1536,14 +1536,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeDisplay(s) {
|
||||
let v = String(s == null ? '' : s)
|
||||
.replace(/[?¿?]+/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return v === '' ? '-' : v;
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s == null ? '' : s)
|
||||
let v = sanitizeDisplay(s);
|
||||
return v
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function escapeAttr(s) {
|
||||
return String(s == null ? '' : s)
|
||||
let v = sanitizeDisplay(s);
|
||||
return v
|
||||
.replace(/"/g, '"').replace(/'/g, ''');
|
||||
}
|
||||
|
||||
|
||||
@@ -725,6 +725,14 @@
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
|
||||
<script>
|
||||
function ds(x) {
|
||||
let v = String(x == null ? '' : x)
|
||||
.replace(/[?¿?]+/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return v === '' ? '-' : v;
|
||||
}
|
||||
|
||||
const TTS_MODEL = "s2.1-pro-free";
|
||||
const ZH_REFERENCE_ID = "0aee9f0f5683480d987c9e3c5d2de04c";
|
||||
const JA_REFERENCE_ID = "38985e545cff41b79c11c32b0196c9a0";
|
||||
@@ -826,8 +834,8 @@
|
||||
toast.className = 'toast';
|
||||
toast.innerHTML = `
|
||||
<div>
|
||||
<div style="font-weight:600; font-size:0.9rem; margin-bottom:2px">${title}</div>
|
||||
<div style="font-size:0.8rem; color:var(--text-muted)">${message}</div>
|
||||
<div style="font-weight:600; font-size:0.9rem; margin-bottom:2px">${ds(title)}</div>
|
||||
<div style="font-size:0.8rem; color:var(--text-muted)">${ds(message)}</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(toast);
|
||||
@@ -940,11 +948,15 @@
|
||||
item.style.alignItems = 'stretch';
|
||||
item.style.gap = '8px';
|
||||
|
||||
const dsCode = ds(task.code);
|
||||
const dsZh = ds(task.zh || '');
|
||||
const dsJa = ds(task.en || '');
|
||||
|
||||
item.innerHTML = `
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start;">
|
||||
<div style="display:flex; flex-direction:column; gap:2px;">
|
||||
<span style="font-weight:600; color:var(--accent)">${task.code}</span>
|
||||
<span class="edit-file-name" style="color:var(--text-muted)">ZH: ${task.zh} | JA: ${task.en}</span>
|
||||
<span style="font-weight:600; color:var(--accent)">${dsCode}</span>
|
||||
<span class="edit-file-name" style="color:var(--text-muted)">ZH: ${dsZh} | JA: ${dsJa}</span>
|
||||
</div>
|
||||
<span id="batch-status-span-${index}" style="font-size:0.8rem; color:var(--text-muted)">Pending</span>
|
||||
</div>
|
||||
@@ -1306,10 +1318,11 @@
|
||||
div.className = 'list-item';
|
||||
div.style.gridTemplateColumns = '100px 1fr auto';
|
||||
|
||||
const tagsHtml = item.files.map(f => `<span style="background:var(--bg-hover); padding:2px 6px; border-radius:4px; font-size:0.8rem; margin-right:4px;">${f}</span>`).join('');
|
||||
const dsCode = ds(item.code);
|
||||
const tagsHtml = (item.files || []).map(f => `<span style="background:var(--bg-hover); padding:2px 6px; border-radius:4px; font-size:0.8rem; margin-right:4px;">${ds(f)}</span>`).join('');
|
||||
|
||||
div.innerHTML = `
|
||||
<div style="font-family:var(--font-mono); font-weight:600;">${item.code}</div>
|
||||
<div style="font-family:var(--font-mono); font-weight:600;">${dsCode}</div>
|
||||
<div>${tagsHtml || '<span style="color:var(--text-muted);font-style:italic">No files</span>'}</div>
|
||||
<div style="display:flex; gap:8px;">
|
||||
<button class="btn-icon" onclick="openEditModal(${index})">${ICONS.edit}</button>
|
||||
|
||||
@@ -1119,6 +1119,15 @@
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
|
||||
<script>
|
||||
// --- Display sanitizer: 强制剥除所有 ? 类字符,保证 UI 零问号 ---
|
||||
function ds(x) {
|
||||
let v = String(x == null ? '' : x)
|
||||
.replace(/[?¿?]+/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return v === '' ? '-' : v;
|
||||
}
|
||||
|
||||
// --- Icons (SVG) ---
|
||||
const ICONS = {
|
||||
folder: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:#fbbf24"><path d="M4 20h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.93a2 2 0 0 1-1.66-.9l-.82-1.2A2 2 0 0 0 7.93 2H4a2 2 0 0 0-2 2v13c0 1.1.9 2 2 2Z"/></svg>`,
|
||||
@@ -1206,27 +1215,31 @@
|
||||
const hasCC = Array.isArray(st.tags) && st.tags.includes('CC');
|
||||
const stationTarget = st.target || st.name;
|
||||
const memberCount = Number(st.memberCount || 1);
|
||||
const stationVersion = st.version || '';
|
||||
const targetVersion = currentStationUpdate?.targetVersion || '';
|
||||
const needsUpdate = !!targetVersion && stationVersion !== targetVersion;
|
||||
const stationVersion = ds(st.version || '');
|
||||
const targetVersion = ds(currentStationUpdate?.targetVersion || '');
|
||||
const needsUpdate = !!targetVersion && stationVersion !== targetVersion && stationVersion !== '-' && targetVersion !== '-';
|
||||
const updateBadge = needsUpdate
|
||||
? `<span style="font-size:0.65rem; background:rgba(245, 158, 11, 0.12); color:#fbbf24; padding:1px 5px; border-radius:4px; margin-left:6px; font-weight:normal; border:1px solid rgba(245, 158, 11, 0.25)">待更新 ${targetVersion}</span>`
|
||||
: '';
|
||||
const div = document.createElement('div');
|
||||
div.className = 'status-item';
|
||||
div.id = `station-item-${st.name.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
||||
div.id = `station-item-${String(st.name || '').replace(/[^a-zA-Z0-9]/g, '_')}`;
|
||||
const dsName = ds(st.name);
|
||||
const dsCode = ds(st.code || '');
|
||||
const dsPlatform = st.platform ? ds(st.platform) : '';
|
||||
const dsVersion = stationVersion === '-' ? '' : stationVersion;
|
||||
div.innerHTML = `
|
||||
<div class="status-main">
|
||||
<div class="item-info">
|
||||
<div class="item-title">
|
||||
${st.name}
|
||||
${dsName}
|
||||
${hasCC ? '<span style="font-size:0.65rem; background:rgba(16, 185, 129, 0.1); color:#34d399; padding:1px 5px; border-radius:4px; margin-left:6px; font-weight:normal; border:1px solid rgba(16, 185, 129, 0.2)">CC</span>' : ''}
|
||||
${updateBadge}
|
||||
</div>
|
||||
<div class="item-sub">${st.code}${st.platform ? ` - Platform ${st.platform}` : ''}${memberCount > 1 ? ` · Nodes ${memberCount}` : ''} · ${stationVersion}</div>
|
||||
<div class="item-sub">${dsCode}${dsPlatform && dsPlatform !== '-' ? ` - Platform ${dsPlatform}` : ''}${memberCount > 1 ? ` · Nodes ${memberCount}` : ''}${dsVersion ? ' · ' + dsVersion : ''}</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px">
|
||||
<div class="status-dot ${dotClass}" id="station-dot-${st.name.replace(/[^a-zA-Z0-9]/g, '_')}" data-online="${isOnline}" data-name="${st.name}"></div>
|
||||
<div class="status-dot ${dotClass}" id="station-dot-${String(st.name || '').replace(/[^a-zA-Z0-9]/g, '_')}" data-online="${isOnline}" data-name="${dsName}"></div>
|
||||
<button class="btn-icon" onclick="deleteStation('${stationTarget}')">${ICONS.trash}</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1245,12 +1258,15 @@
|
||||
const div = document.createElement('div');
|
||||
div.className = 'status-item';
|
||||
const currentStCode = stationCodeMap[tr.currentStation] || tr.currentStation || '-';
|
||||
const dsTrainId = ds(tr.trainId);
|
||||
const dsStatus = ds(tr.status || '');
|
||||
const dsCurCode = ds(currentStCode);
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="status-main">
|
||||
<div class="item-info">
|
||||
<div class="item-title">${tr.trainId}</div>
|
||||
<div class="item-sub">${currentStCode} <span style="margin-left:8px;color:var(--text-muted)">${tr.status || '-'}</span></div>
|
||||
<div class="item-title">${dsTrainId}</div>
|
||||
<div class="item-sub">${dsCurCode} <span style="margin-left:8px;color:var(--text-muted)">${dsStatus}</span></div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px">
|
||||
<div class="status-dot ${tr.status === 'At Station' ? 'dot-online' : 'dot-warn'}"></div>
|
||||
@@ -1277,7 +1293,9 @@
|
||||
|
||||
function updateRadioStreams(station, seq, isStarting, hlsUrl) {
|
||||
const container = document.getElementById('radio-streams-container');
|
||||
const cardId = `stream-card-${station.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
||||
const dsStation = ds(station || '');
|
||||
const stationKey = String(station || '').replace(/[^a-zA-Z0-9]/g, '_');
|
||||
const cardId = `stream-card-${stationKey}`;
|
||||
|
||||
if (isStarting) {
|
||||
// Clear empty state
|
||||
@@ -1293,15 +1311,20 @@
|
||||
container.appendChild(card);
|
||||
}
|
||||
|
||||
const seqHtml = seq ? seq.map(s => `<span style="background:var(--bg-hover); padding:2px 6px; border-radius:4px; font-size:0.75rem; font-family:var(--font-mono); margin:2px; display:inline-block;">${s}</span>`).join('') : '';
|
||||
const seqHtml = Array.isArray(seq) && seq.length > 0
|
||||
? seq.map(s => `<span style="background:var(--bg-hover); padding:2px 6px; border-radius:4px; font-size:0.75rem; font-family:var(--font-mono); margin:2px; display:inline-block;">${ds(s)}</span>`).join('')
|
||||
: '';
|
||||
|
||||
const dsHlsUrl = ds(hlsUrl || '');
|
||||
const showHls = hlsUrl && dsHlsUrl !== '-';
|
||||
|
||||
card.innerHTML = `
|
||||
<div style="position:absolute; top:0; left:0; right:0; height:3px; background:#3b82f6; animation:pulse 1s infinite;"></div>
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<div style="font-weight:600; font-size:1.1rem;">${station}</div>
|
||||
<div style="font-weight:600; font-size:1.1rem;">${dsStation}</div>
|
||||
<div class="status-dot dot-broadcasting"></div>
|
||||
</div>
|
||||
${hlsUrl ? `<div style="font-size:0.8rem; color:var(--text-muted);">HLS: <a href="${hlsUrl}" target="_blank" rel="noreferrer" style="color:#60a5fa; text-decoration:none; font-family:var(--font-mono);">${hlsUrl}</a></div>` : ''}
|
||||
${showHls ? `<div style="font-size:0.8rem; color:var(--text-muted);">HLS: <a href="${String(hlsUrl || '')}" target="_blank" rel="noreferrer" style="color:#60a5fa; text-decoration:none; font-family:var(--font-mono);">${dsHlsUrl}</a></div>` : ''}
|
||||
<div>
|
||||
<div style="font-size:0.8rem; color:var(--text-muted); margin-bottom:8px;">PLAYING SEQUENCE:</div>
|
||||
<div style="display:flex; flex-wrap:wrap; gap:4px; max-height:100px; overflow-y:auto; padding-right:4px;">
|
||||
@@ -1365,8 +1388,10 @@
|
||||
else typeClass = 'type-info';
|
||||
|
||||
content = content.replace(/^\[.*?\]\s*/, '');
|
||||
const dsTime = ds(time);
|
||||
const dsContent = ds(content);
|
||||
|
||||
div.innerHTML = `<div class="log-time">${time}</div><div class="log-msg"><span class="${typeClass}">></span> ${content}</div>`;
|
||||
div.innerHTML = `<div class="log-time">${dsTime}</div><div class="log-msg"><span class="${typeClass}">></span> ${dsContent}</div>`;
|
||||
container.prepend(div);
|
||||
if (container.children.length > 100) container.lastChild.remove();
|
||||
}
|
||||
@@ -1376,6 +1401,9 @@
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast';
|
||||
|
||||
const dsTitle = ds(title || '');
|
||||
const dsMsg = ds(message || '');
|
||||
|
||||
// Icon based on title/message
|
||||
let iconColor = 'var(--text-primary)';
|
||||
if (title.includes('Arrival')) iconColor = 'var(--success)';
|
||||
@@ -1386,8 +1414,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
||||
</div>
|
||||
<div class="toast-content">
|
||||
<div class="toast-title">${title}</div>
|
||||
<div class="toast-msg">${message}</div>
|
||||
<div class="toast-title">${dsTitle}</div>
|
||||
<div class="toast-msg">${dsMsg}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1437,7 +1465,7 @@
|
||||
beep: 'Beep',
|
||||
station: 'Station'
|
||||
};
|
||||
const name = nameMap[currentPath] || currentPath;
|
||||
const name = ds(nameMap[currentPath] || currentPath);
|
||||
breadcrumb.innerHTML = `
|
||||
<div class="breadcrumb-item" onclick="navigate('root')">Home</div>
|
||||
<div class="breadcrumb-sep">/</div>
|
||||
@@ -1460,10 +1488,12 @@
|
||||
const row = document.createElement('div');
|
||||
row.className = 'file-row';
|
||||
row.onclick = () => navigate(f.id);
|
||||
const dsFolderName = ds(f.name);
|
||||
const dsCount = String(f.count || 0);
|
||||
row.innerHTML = `
|
||||
<div></div>
|
||||
<div style="display:flex;align-items:center;gap:10px">${ICONS.folder}<span class="file-name">${f.name}</span></div>
|
||||
<div class="file-meta">${f.count} items</div>
|
||||
<div style="display:flex;align-items:center;gap:10px">${ICONS.folder}<span class="file-name">${dsFolderName}</span></div>
|
||||
<div class="file-meta">${dsCount} items</div>
|
||||
<div></div>
|
||||
`;
|
||||
container.appendChild(row);
|
||||
@@ -1485,6 +1515,8 @@
|
||||
};
|
||||
const row = document.createElement('div');
|
||||
row.className = 'file-row';
|
||||
const dsFileName = ds(f.name);
|
||||
const dsTypeLabel = ds(typeLabelMap[f.type] || f.type || '');
|
||||
// Click to preview
|
||||
row.onclick = (e) => {
|
||||
if (e.target.type !== 'checkbox' && e.target.tagName !== 'BUTTON' && e.target.tagName !== 'path' && e.target.tagName !== 'svg') {
|
||||
@@ -1493,8 +1525,8 @@
|
||||
};
|
||||
row.innerHTML = `
|
||||
<div style="display:flex;justify-content:center"><input type="checkbox" class="file-check" value="${f.path}" onchange="updateSelection()"></div>
|
||||
<div style="display:flex;align-items:center;gap:10px">${f.name.endsWith('.txt') ? ICONS.file : ICONS.audio}<span class="file-name">${f.name}</span></div>
|
||||
<div class="file-meta">${typeLabelMap[f.type] || f.type}</div>
|
||||
<div style="display:flex;align-items:center;gap:10px">${f.name.endsWith('.txt') ? ICONS.file : ICONS.audio}<span class="file-name">${dsFileName}</span></div>
|
||||
<div class="file-meta">${dsTypeLabel}</div>
|
||||
<div style="text-align:right">
|
||||
<button class="btn-icon" onclick="deleteFile('${f.path}')">${ICONS.trash}</button>
|
||||
</div>
|
||||
@@ -1769,10 +1801,12 @@
|
||||
|
||||
function renderStationUpdate(info) {
|
||||
const el = document.getElementById('current-version');
|
||||
const localVersion = info?.localVersion || '-';
|
||||
const targetVersion = info?.targetVersion || '';
|
||||
const publishedAt = info?.publishedAt ? new Date(info.publishedAt).toLocaleString() : '';
|
||||
const remoteVersionHtml = info?.remoteVersion ? `<div>Gitea最新:${info.remoteVersion}</div>` : '';
|
||||
const localVersion = ds(info?.localVersion || '-');
|
||||
const targetVersion = ds(info?.targetVersion || '');
|
||||
const publishedAt = info?.publishedAt ? ds(new Date(info.publishedAt).toLocaleString()) : '';
|
||||
const remoteVersionHtml = info?.remoteVersion
|
||||
? `<div>Gitea最新:${ds(info.remoteVersion)}</div>`
|
||||
: '';
|
||||
|
||||
el.innerHTML = `
|
||||
<div>服务器脚本:${localVersion}</div>
|
||||
@@ -1792,12 +1826,12 @@
|
||||
const remoteRes = await fetch('/api/remote-version');
|
||||
const remoteData = await remoteRes.json();
|
||||
if (remoteData.success) {
|
||||
data.remoteVersion = remoteData.version;
|
||||
data.remoteVersion = String(remoteData.version || '');
|
||||
} else {
|
||||
data.remoteVersion = '解析失败';
|
||||
data.remoteVersion = '';
|
||||
}
|
||||
} catch (e) {
|
||||
data.remoteVersion = '请求失败';
|
||||
data.remoteVersion = '';
|
||||
}
|
||||
|
||||
renderStationUpdate(data);
|
||||
|
||||
@@ -358,6 +358,14 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function ds(x) {
|
||||
let v = String(x == null ? '' : x)
|
||||
.replace(/[?¿?]+/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return v === '' ? '-' : v;
|
||||
}
|
||||
|
||||
// --- Icons (SVG) ---
|
||||
const ICONS = {
|
||||
chevronDown: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>`,
|
||||
@@ -443,7 +451,7 @@
|
||||
icon = ICONS.broadcast;
|
||||
}
|
||||
|
||||
messageLog.unshift({ time, content: msg, type, icon, important });
|
||||
messageLog.unshift({ time: ds(time), content: ds(msg), type, icon, important });
|
||||
if (messageLog.length > MAX_MESSAGES) messageLog.pop();
|
||||
renderMessages();
|
||||
}
|
||||
@@ -486,18 +494,22 @@
|
||||
const isBroadcasting = broadcastingStations.has(st.name);
|
||||
const dotClass = isBroadcasting ? 'dot-broadcasting' : (isOnline ? 'dot-online' : 'dot-offline');
|
||||
const hasCC = Array.isArray(st.tags) && st.tags.includes('CC');
|
||||
const dsName = ds(st.name);
|
||||
const dsCode = ds(st.code || '');
|
||||
const dsPlatform = st.platform ? ds(st.platform) : '';
|
||||
const stationKey = String(st.name || '').replace(/[^a-zA-Z0-9]/g, '_');
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = 'status-item';
|
||||
div.innerHTML = `
|
||||
<div class="item-info">
|
||||
<div class="item-title">
|
||||
${st.name}
|
||||
${dsName}
|
||||
${hasCC ? '<span style="font-size:0.65rem; background:rgba(16, 185, 129, 0.1); color:#34d399; padding:1px 5px; border-radius:4px; margin-left:6px; font-weight:normal; border:1px solid rgba(16, 185, 129, 0.2)">CC</span>' : ''}
|
||||
</div>
|
||||
<div class="item-sub">${st.code}${st.platform ? ` - 站台 ${st.platform}` : ''}</div>
|
||||
<div class="item-sub">${dsCode}${dsPlatform && dsPlatform !== '-' ? ` - 站台 ${dsPlatform}` : ''}</div>
|
||||
</div>
|
||||
<div class="status-dot ${dotClass}" id="station-dot-${st.name.replace(/[^a-zA-Z0-9]/g, '_')}" data-online="${isOnline}" data-name="${st.name}"></div>
|
||||
<div class="status-dot ${dotClass}" id="station-dot-${stationKey}" data-online="${isOnline}" data-name="${dsName}"></div>
|
||||
`;
|
||||
list.appendChild(div);
|
||||
});
|
||||
@@ -519,13 +531,18 @@
|
||||
|
||||
const currentStCode = stationCodeMap[tr.currentStation] || tr.currentStation || '-';
|
||||
const statusColor = tr.status === 'At Station' ? 'var(--success)' : 'var(--warning)';
|
||||
const dsTrainId = ds(tr.trainId);
|
||||
const dsDest = ds(tr.destination || '');
|
||||
const dsCurCode = ds(currentStCode);
|
||||
const rawStatus = tr.status === 'At Station' ? '在站' : (tr.status === 'Departed' ? '已发车' : '运行中');
|
||||
const dsStatus = ds(rawStatus);
|
||||
|
||||
div.innerHTML = `
|
||||
<div style="width:100%">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<div class="item-info">
|
||||
<div class="item-title">${tr.trainId} <span style="font-size:0.7rem; color:var(--text-muted); font-weight:normal; margin-left:8px;">${tr.destination || ''}</span></div>
|
||||
<div class="item-sub">${currentStCode} <span style="color:${statusColor}; margin-left:8px;">${tr.status === 'At Station' ? '在站' : (tr.status === 'Departed' ? '已发车' : '运行中')}</span></div>
|
||||
<div class="item-title">${dsTrainId}${dsDest && dsDest !== '-' ? ' <span style="font-size:0.7rem; color:var(--text-muted); font-weight:normal; margin-left:8px;">' + dsDest + '</span>' : ''}</div>
|
||||
<div class="item-sub">${dsCurCode} <span style="color:${statusColor}; margin-left:8px;">${dsStatus}</span></div>
|
||||
</div>
|
||||
<div class="status-dot ${tr.status === 'At Station' ? 'dot-online' : 'dot-warn'}"></div>
|
||||
</div>
|
||||
|
||||
@@ -452,6 +452,14 @@
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
|
||||
<script>
|
||||
function ds(x) {
|
||||
let v = String(x == null ? '' : x)
|
||||
.replace(/[?¿?]+/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return v === '' ? '-' : v;
|
||||
}
|
||||
|
||||
const ICONS = {
|
||||
up: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="18 15 12 9 6 15"></polyline></svg>`,
|
||||
down: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>`,
|
||||
@@ -471,8 +479,8 @@
|
||||
toast.className = 'toast';
|
||||
toast.innerHTML = `
|
||||
<div>
|
||||
<div style="font-weight:600; font-size:0.9rem; margin-bottom:2px">${title}</div>
|
||||
<div style="font-size:0.8rem; color:var(--text-muted)">${message}</div>
|
||||
<div style="font-weight:600; font-size:0.9rem; margin-bottom:2px">${ds(title)}</div>
|
||||
<div style="font-size:0.8rem; color:var(--text-muted)">${ds(message)}</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(toast);
|
||||
@@ -539,7 +547,8 @@
|
||||
let html = `<div class="custom-select-option ${selectedFileValue === '' ? 'selected' : ''}" data-value="" onclick="selectCustomOption('', '-- Select an audio file --')">-- Select an audio file --</div>`;
|
||||
availableFiles.forEach(f => {
|
||||
const isSelected = selectedFileValue === f ? 'selected' : '';
|
||||
html += `<div class="custom-select-option ${isSelected}" data-value="${f}" onclick="selectCustomOption('${f}', '${f}')">${f}</div>`;
|
||||
const dsF = ds(f);
|
||||
html += `<div class="custom-select-option ${isSelected}" data-value="${f}" onclick="selectCustomOption('${f}', '${dsF}')">${dsF}</div>`;
|
||||
});
|
||||
optionsContainer.innerHTML = html;
|
||||
}
|
||||
@@ -576,9 +585,10 @@
|
||||
const div = document.createElement('div');
|
||||
div.className = 'list-item';
|
||||
|
||||
const tagsHtml = (item.files || []).map(f => `<span class="tag">${f}</span>`).join(' ');
|
||||
const dsCode = ds(item.code);
|
||||
const tagsHtml = (item.files || []).map(f => `<span class="tag">${ds(f)}</span>`).join(' ');
|
||||
div.innerHTML = `
|
||||
<div class="code">${item.code}</div>
|
||||
<div class="code">${dsCode}</div>
|
||||
<div>${tagsHtml || '<span style="color:var(--text-muted); font-style: italic;">No files</span>'}</div>
|
||||
<div style="display:flex; gap:6px;">
|
||||
<button class="btn-icon" onclick="openEditModal(${index})" aria-label="Edit">${ICONS.edit}</button>
|
||||
@@ -636,7 +646,7 @@
|
||||
const div = document.createElement('div');
|
||||
div.className = 'edit-file-item';
|
||||
div.innerHTML = `
|
||||
<div class="edit-file-name">${file}</div>
|
||||
<div class="edit-file-name">${ds(file)}</div>
|
||||
<div class="edit-file-actions">
|
||||
<button class="btn-icon" onclick="moveFile(${idx}, -1)" ${idx === 0 ? 'disabled style="opacity:0.35"' : ''} aria-label="Up">${ICONS.up}</button>
|
||||
<button class="btn-icon" onclick="moveFile(${idx}, 1)" ${idx === currentSequence.length - 1 ? 'disabled style="opacity:0.35"' : ''} aria-label="Down">${ICONS.down}</button>
|
||||
|
||||
@@ -308,8 +308,17 @@
|
||||
document.getElementById("status_box").textContent = String(msg || "");
|
||||
}
|
||||
|
||||
function sanitizeDisplay(s) {
|
||||
let v = String(s == null ? "" : s)
|
||||
.replace(/[?¿?]+/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return v === "" ? "-" : v;
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str || "")
|
||||
let v = sanitizeDisplay(str);
|
||||
return v
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
|
||||
@@ -189,8 +189,17 @@
|
||||
return 'idle';
|
||||
}
|
||||
|
||||
function sanitizeDisplay(s) {
|
||||
let v = String(s ?? '')
|
||||
.replace(/[?¿?]+/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return v === '' ? '-' : v;
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s ?? '')
|
||||
let v = sanitizeDisplay(s);
|
||||
return v
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
|
||||
@@ -256,8 +256,17 @@
|
||||
let editKey = '';
|
||||
let lastData = null;
|
||||
|
||||
function sanitizeDisplay(s) {
|
||||
let v = String(s ?? '')
|
||||
.replace(/[?¿?]+/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return v === '' ? '-' : v;
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s ?? '')
|
||||
let v = sanitizeDisplay(s);
|
||||
return v
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
|
||||
@@ -394,8 +394,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeDisplay(s) {
|
||||
let v = String(s == null ? "" : s)
|
||||
.replace(/[?¿?]+/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return v === "" ? "-" : v;
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str || "")
|
||||
let v = sanitizeDisplay(str);
|
||||
return v
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
|
||||
Reference in New Issue
Block a user