fix(shorts): плановая очистка, бэкап, часовой пояс и экранирование
- purgeScheduledDeletions() теперь чистит short_messages по purge_at - short_messages добавлена в BACKUP_TABLES/BACKUP_SEQUENCE_TABLES, normalizeRestoreData и restore (поле автора проверяется на живость) - сид short_messages_retention_days перенесён и в db/init.sql - used_at по умолчанию берётся в зоне приложения (appTodayIso), а не по UTC - поле «Использовано» скрыто при создании, открыто при правке - удалён пустой escInline и мёртвые data-message/canEdit: сырой текст сообщения попадал в атрибут неэкранированным
This commit is contained in:
+8
-10
@@ -58,21 +58,20 @@ async function loadShorts() {
|
||||
const dateStr = r.lesson_date ? fmtDateOnlyIso(r.lesson_date) : '';
|
||||
const usedDateStr = r.used_at ? fmtDateOnlyIso(r.used_at) : '—';
|
||||
const author = r.author_name || r.author_username || '—';
|
||||
const canEdit = true;
|
||||
return `
|
||||
<tr data-edit-row data-message="${escInline(r.message || '')}">
|
||||
<tr data-edit-row>
|
||||
<td>${esc(r.resident)}</td>
|
||||
<td style="white-space:nowrap">${dateStr}</td>
|
||||
<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${esc(r.topic || '')}">${esc(r.topic || '')}</td>
|
||||
<td style="max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${esc(r.message || '')}">${esc(r.message || '')}</td>
|
||||
<td style="white-space:nowrap">${esc(author)}</td>
|
||||
<td style="white-space:nowrap">
|
||||
<input type="checkbox" class="used-check" data-id="${r.id}" ${r.used ? 'checked' : ''} title="Использовано" ${canEdit ? '' : 'disabled'}>
|
||||
<input type="checkbox" class="used-check" data-id="${r.id}" ${r.used ? 'checked' : ''} title="Использовано">
|
||||
<div style="font-size:.72rem;color:var(--muted)">${r.used ? esc(usedDateStr) : '—'}</div>
|
||||
</td>
|
||||
<td>
|
||||
${canEdit ? `<button class="row-btn" data-edit="${r.id}" title="Редактировать"><i data-lucide="pencil"></i></button>
|
||||
<button class="row-btn del" data-del="${r.id}" title="Удалить (мягко)"><i data-lucide="trash-2"></i></button>` : '—'}
|
||||
<button class="row-btn" data-edit="${r.id}" title="Редактировать"><i data-lucide="pencil"></i></button>
|
||||
<button class="row-btn del" data-del="${r.id}" title="Удалить (мягко)"><i data-lucide="trash-2"></i></button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
@@ -129,11 +128,14 @@ function openShortsModal(row) {
|
||||
editingId = row ? row.id : null;
|
||||
document.getElementById('shortsModalTitle').textContent = row ? `Сообщение #${row.id}` : 'Новое сообщение';
|
||||
document.getElementById('shortsLinesHint').textContent = row ? ' (только текст сообщения)' : ' (формат: ФИО - текст или ФИО — текст)';
|
||||
document.getElementById('shortsDate').value = row ? row.lesson_date : '';
|
||||
document.getElementById('shortsDate').value = row ? row.lesson_date : todayIso();
|
||||
document.getElementById('shortsTopic').value = row ? (row.topic || '') : '';
|
||||
document.getElementById('shortsResident').value = row ? (row.resident || '') : '';
|
||||
document.getElementById('shortsUsed').checked = row ? !!row.used : false;
|
||||
document.getElementById('shortsUsedAt').value = row && row.used_at ? String(row.used_at).slice(0, 10) : '';
|
||||
const usedDisplay = row ? '' : 'none';
|
||||
document.getElementById('shortsUsedField').style.display = usedDisplay;
|
||||
document.getElementById('shortsUsedAtField').style.display = usedDisplay;
|
||||
if (row) {
|
||||
document.getElementById('shortsLines').value = row.message || '';
|
||||
} else {
|
||||
@@ -317,8 +319,4 @@ function acceptShortsAi() {
|
||||
closeShortsAi();
|
||||
}
|
||||
|
||||
function escInline(s) {
|
||||
return String(s || '').replace(/'/g, "'").replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
Reference in New Issue
Block a user