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:
2026-08-17 17:32:01 +08:00
parent 4c049050ce
commit 5e466f4553
11 changed files with 438 additions and 53 deletions
+12 -2
View File
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
function escapeAttr(s) {
return String(s == null ? '' : s)
let v = sanitizeDisplay(s);
return v
.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
+19 -6
View File
@@ -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>
+62 -28
View File
@@ -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);
+23 -6
View File
@@ -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>
+16 -6
View File
@@ -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>
+10 -1
View File
@@ -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("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
+10 -1
View File
@@ -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('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
+10 -1
View File
@@ -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('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
+10 -1
View File
@@ -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("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
+265
View File
@@ -0,0 +1,265 @@
Advanced Display Block NBT Dump
Generated: 2026-08-16 14:08:16
============================================================
BlockReader: right
[Block Registry Name]
createrailwaysnavigator:advanced_display_block
[Block NBT Data]
{
DisplayId = "createrailwaysnavigator:platform/focus"
DisplaySettings = {
{
BackColor = 0
FontColor = -1
PlatformWidth = -1
PlatformWidthNextStop = -1
ShowLineColor = 0
ShowTrainMultipleTimes = 1
TimeDisplay = 0
TrainNameWidth = 12
TrainNameWidthNextStop = -1
TrainText = 4
}
}
EnableCT = 1
Filter = "CE-03 2"
Glowing = 0
IsController = 1
Item = {}
LastRefreshed = 1303
Material = {
{
Name = "create:copycat_base"
}
}
Platform = "2"
TrainStops = {
[1] = {
FirstStop = "??"
IsFirst = 1
IsLast = 1
IsNextSectionExcluded = 0
IsPrevSectionExcluded = 0
State = 1
Station = {
{
Destination = "??"
Index = 2
RealTimeArrival = 2247
RealTimeDeparture = 1347
RealTimeStation = {
{
Id = {
[1] = -284816241
[2] = -119452467
[3] = -2021313202
[4] = 1655163467
}
StationInfo = {
{
Platform = "2"
}
}
StationName = "CE-03 2"
TagName = "??"
}
}
ScheduledArrival = 3869
ScheduledDeparture = 4769
ScheduledStation = {
{
Id = {
[1] = -284816241
[2] = -119452467
[3] = -2021313202
[4] = 1655163467
}
StationInfo = {
{
Platform = "2"
}
}
StationName = "CE-03 2"
TagName = "??"
}
}
TrainName = "???"
}
}
Stopovers = {
[1] = "????"
[2] = "???"
}
Train = {
{
Cancelled = 0
Carriages = 1
Icon = "create:traditional"
Id = {
[1] = 1934164313
[2] = -793689251
[3] = -1147623584
[4] = 557989055
}
StateData = {
{
["0"] = {
{
CategoryId = {
[1] = 186313593
[2] = 376131208
[3] = -1142882515
[4] = 1849467770
}
Color = -14790494
DisplayName = "???"
LineId = {
[1] = -1767394825
[2] = 1017529725
[3] = -1140094085
[4] = 280637596
}
}
}
["1"] = {
{
CategoryId = {
[1] = 186313593
[2] = 376131208
[3] = -1142882515
[4] = 1849467770
}
Color = -14790494
DisplayName = "???"
LineId = {
[1] = -1767394825
[2] = 1017529725
[3] = -1140094085
[4] = 280637596
}
}
}
}
}
Status = {}
}
}
}
[2] = {
FirstStop = "??"
IsFirst = 1
IsLast = 1
IsNextSectionExcluded = 0
IsPrevSectionExcluded = 0
State = 1
Station = {
{
Destination = "??"
Index = 2
RealTimeArrival = 6965
RealTimeDeparture = 6065
RealTimeStation = {
{
Id = {
[1] = -284816241
[2] = -119452467
[3] = -2021313202
[4] = 1655163467
}
StationInfo = {
{
Platform = "2"
}
}
StationName = "CE-03 2"
TagName = "??"
}
}
ScheduledArrival = 9856
ScheduledDeparture = 10756
ScheduledStation = {
{
Id = {
[1] = -284816241
[2] = -119452467
[3] = -2021313202
[4] = 1655163467
}
StationInfo = {
{
Platform = "2"
}
}
StationName = "CE-03 2"
TagName = "??"
}
}
TrainName = "???"
}
}
Stopovers = {
[1] = "????"
[2] = "???"
}
Train = {
{
Cancelled = 0
Carriages = 1
Icon = "create:traditional"
Id = {
[1] = 2056578835
[2] = 774784604
[3] = -1876554799
[4] = 2012426381
}
StateData = {
{
["0"] = {
{
CategoryId = {
[1] = 186313593
[2] = 376131208
[3] = -1142882515
[4] = 1849467770
}
Color = -14790494
DisplayName = "???"
LineId = {
[1] = -1767394825
[2] = 1017529725
[3] = -1140094085
[4] = 280637596
}
}
}
["1"] = {
{
CategoryId = {
[1] = 186313593
[2] = 376131208
[3] = -1142882515
[4] = 1849467770
}
Color = -14790494
DisplayName = "???"
LineId = {
[1] = -1767394825
[2] = 1017529725
[3] = -1140094085
[4] = 280637596
}
}
}
}
}
Status = {}
}
}
}
}
XSize = 1
YSize = 1
}
+1 -1
View File
@@ -1,6 +1,6 @@
-- 1. Configuration & Constants
local APP_VERSION_TEXT = "v1.0.106"
local APP_VERSION_TEXT = "v1.0.107"
local CONFIG = {
CLOUD_URL = "ws://stabadmin.fse-media.group",