style/security: эмодзи→SVG, safeUrl в ассистенте, prefs в localStorage (Спринт3)
- Убраны эмодзи (правило: только inline SVG .ic): classes.html 🃏→layers, collection-rb.html ⭐→star, pet.html 😋/😢→текст (textContent не держит SVG). - assistant.js: safeUrl() на динамических href (FAQ/поиск/RAG/правила) — блокирует javascript:/data:, пропускает /… и https://…. - LS.prefs: персистентность через localStorage (раньше sync был отключён, настройки терялись при перезагрузке). Грузим синхронно + flush на pagehide. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,8 @@
|
||||
|
||||
/* ── helpers ─────────────────────────────────────────────────────────── */
|
||||
function esc(s) { return (window.LS && LS.escapeHtml) ? LS.escapeHtml(String(s == null ? '' : s)) : String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]; }); }
|
||||
// Безопасный href: только внутренний путь /… или https://… — блокирует javascript:/data:.
|
||||
function safeUrl(u) { u = String(u == null ? '' : u).trim(); return (/^\//.test(u) || /^https?:\/\//i.test(u)) ? u : '#'; }
|
||||
function lsGet(k) { try { return localStorage.getItem(k); } catch (e) { return null; } }
|
||||
function lsSet(k, v) { try { localStorage.setItem(k, v); } catch (e) {} }
|
||||
function todayKey() { return new Date().toISOString().slice(0, 10); }
|
||||
@@ -372,7 +374,7 @@
|
||||
|
||||
function hintHtml(rule) {
|
||||
var act = null; try { act = rule.action(); } catch (e) {}
|
||||
var actHtml = act && act.url ? '<a class="asst-btn" href="' + esc(act.url) + '">' + esc(act.label || 'Открыть') + '</a>' : '';
|
||||
var actHtml = act && act.url ? '<a class="asst-btn" href="' + esc(safeUrl(act.url)) + '">' + esc(act.label || 'Открыть') + '</a>' : '';
|
||||
var dismiss = (rule.scope !== 'celebration') ? '<button class="asst-link" data-a="dismiss">Не показывать</button>' : '';
|
||||
return '<div class="asst-name">' + esc(PET && PET.petName ? PET.petName : 'Квантик') + '</div>' +
|
||||
'<div class="asst-text">' + esc(rule.text ? rule.text() : rule.text) + '</div>' +
|
||||
@@ -633,13 +635,13 @@
|
||||
// источники (RAG)
|
||||
if (model && sources.length) {
|
||||
var sc = document.createElement('div'); sc.className = 'asst-src';
|
||||
sc.innerHTML = 'Источник: ' + sources.map(function (s) { return '<a href="' + esc(srcUrl(s)) + '">' + esc(s.title) + (s.section ? ', ' + esc(s.section) : '') + '</a>'; }).join('; ');
|
||||
sc.innerHTML = 'Источник: ' + sources.map(function (s) { return '<a href="' + esc(safeUrl(srcUrl(s))) + '">' + esc(s.title) + (s.section ? ', ' + esc(s.section) : '') + '</a>'; }).join('; ');
|
||||
chatEl.appendChild(sc);
|
||||
}
|
||||
// ссылки FAQ/платформа
|
||||
var links = '';
|
||||
if (!model && ans.length) links += ans.slice(0, 2).filter(function (a) { return a.url; }).map(function (a) { return '<a class="asst-ans-link" href="' + esc(a.url) + '">' + esc(a.q) + '</a>'; }).join(' · ');
|
||||
if (found.length) links += (links ? '<br>' : '') + '<span style="color:#8a94a6">На платформе: </span>' + found.slice(0, 3).map(function (f) { return '<a class="asst-ans-link" href="' + esc(f.url || '#') + '">' + esc(f.title || '…') + '</a>'; }).join(' · ');
|
||||
if (!model && ans.length) links += ans.slice(0, 2).filter(function (a) { return a.url; }).map(function (a) { return '<a class="asst-ans-link" href="' + esc(safeUrl(a.url)) + '">' + esc(a.q) + '</a>'; }).join(' · ');
|
||||
if (found.length) links += (links ? '<br>' : '') + '<span style="color:#8a94a6">На платформе: </span>' + found.slice(0, 3).map(function (f) { return '<a class="asst-ans-link" href="' + esc(safeUrl(f.url)) + '">' + esc(f.title || '…') + '</a>'; }).join(' · ');
|
||||
if (links) { var l = document.createElement('div'); l.className = 'asst-msg-links'; l.innerHTML = links; chatEl.appendChild(l); }
|
||||
// оценка ответа
|
||||
if (model) {
|
||||
|
||||
Reference in New Issue
Block a user