Добавить SHiNE теги в DM и безопасный рендер превью

This commit is contained in:
AidarKC
2026-07-10 18:57:44 +04:00
parent 076b43e542
commit 0d2d7b6dc5
10 changed files with 358 additions and 18 deletions
+8 -4
View File
@@ -20,6 +20,7 @@ import { startOutgoingCall } from '../services/call-service.js';
import { openSpeechInputModal } from '../components/speech-input-modal.js';
import { isSpeechToTextConfigured, isTextToSpeechConfigured, speakTextBySettings } from '../services/speech-tools-service.js';
import { showToast } from '../services/channels-ux.js';
import { parseDmTechBlocks, sanitizeUserDmTextForSend } from '../services/dm-tech-blocks.js';
export const pageMeta = { id: 'chat-view', title: 'Чат' };
const CONVERSATION_CLEAR_NOTICE_TEXT = 'История переписки очищена с этого места';
@@ -483,12 +484,14 @@ function renderLog(list, chatId, { onOpenActions, markAsRead = true, scrollMode
}
const bubble = document.createElement('div');
const bubbleKind = String(msg?.kind || '').trim();
const parsedText = parseDmTechBlocks(String(msg?.text || ''));
const autoKind = parsedText.callSummary ? 'call-tech' : '';
const bubbleKind = String(msg?.kind || autoKind || '').trim();
bubble.className = `bubble ${msg.from}${bubbleKind ? ` ${bubbleKind}` : ''}`;
const textNode = document.createElement('div');
textNode.className = 'bubble-text';
textNode.textContent = msg.text || '';
textNode.textContent = parsedText.displayText || '';
bubble.append(textNode);
const metaNode = document.createElement('div');
@@ -582,7 +585,8 @@ export function render({ navigate, route }) {
showTtsMissingConfigDialog(navigate);
return;
}
await speakTextBySettings(String(msg?.text || ''), state.entrySettings);
const parsedText = parseDmTechBlocks(String(msg?.text || ''));
await speakTextBySettings(String(parsedText.displayText || ''), state.entrySettings);
};
const handleStartCall = async () => {
@@ -888,7 +892,7 @@ export function render({ navigate, route }) {
};
const sendTextMessage = async (rawText) => {
const text = String(rawText || '').trim();
const text = sanitizeUserDmTextForSend(String(rawText || '')).trim();
if (!text) return;
const editing = activeEdit;
const tempId = editing ? '' : addOutgoingPendingMessage(chatId, text);
+19 -4
View File
@@ -10,6 +10,7 @@ import {
import { loadCurrentRelations } from '../services/user-connections.js';
import { renderUserAvatar } from '../components/avatar-image.js';
import { loadProfileSnapshot } from '../services/user-profile-params.js';
import { parseDmTechBlocks } from '../services/dm-tech-blocks.js';
export const pageMeta = { id: 'messages-list', title: 'Личные сообщения' };
const dmAvatarSnapshotCache = new Map();
@@ -64,6 +65,12 @@ function createDmAvatar(login) {
return avatarEl;
}
function resolveLastMessagePreview(text = '') {
const parsed = parseDmTechBlocks(String(text || ''));
const display = String(parsed.displayText || '').trim();
return display || '';
}
function formatChatRowTime(ts) {
const value = Number(ts || 0);
if (!Number.isFinite(value) || value <= 0) return '';
@@ -88,12 +95,14 @@ export function render({ navigate }) {
<div class="dm-head-brand">
<div class="dm-head-hex">${(login[0] || 'A').toUpperCase()}</div>
<div class="dm-head-id">
<span class="dm-head-name">${login}</span>
<span class="dm-head-name"></span>
</div>
</div>
<h1 class="dm-head-title">Контакты</h1>
<button type="button" class="dm-head-plus" aria-label="Новый диалог">+</button>
`;
const headName = head.querySelector('.dm-head-name');
if (headName) headName.textContent = login;
head.querySelector('.dm-head-plus')?.addEventListener('click', () => navigate('contact-search-view'));
const divider = document.createElement('div');
@@ -113,19 +122,25 @@ export function render({ navigate }) {
row.innerHTML = `
<div class="dm-row-main">
<div class="dm-row-titleline dm-row-titlewrap">
<strong class="dm-row-title">${item.name}</strong>
<strong class="dm-row-title"></strong>
${item.notInContacts ? '<span class="dm-contact-note">не в контактах</span>' : ''}
</div>
<p class="dm-row-last-message">${item.lastMessage}</p>
<p class="dm-row-last-message"></p>
</div>
<div class="dm-row-meta-col">
${item.unread ? `<span class="dm-unread-badge">${item.unread > 99 ? '99+' : item.unread}</span>` : '<span class="dm-row-meta-spacer" aria-hidden="true"></span>'}
<div class="dm-row-meta-line">
${item.time ? `<span class="dm-row-time">${item.time}</span>` : '<span class="dm-row-time dm-row-time--empty"></span>'}
${item.time ? '<span class="dm-row-time"></span>' : '<span class="dm-row-time dm-row-time--empty"></span>'}
<span class="dm-chevron">${SVG_CHEVRON}</span>
</div>
</div>
`;
const titleEl = row.querySelector('.dm-row-title');
const previewEl = row.querySelector('.dm-row-last-message');
const timeEl = row.querySelector('.dm-row-time');
if (titleEl) titleEl.textContent = String(item.name || '');
if (previewEl) previewEl.textContent = resolveLastMessagePreview(item.lastMessage) || 'Диалог пока пуст.';
if (timeEl) timeEl.textContent = String(item.time || '');
row.prepend(avatarWrap);
row.addEventListener('click', () => navigate(`chat-view/${encodeURIComponent(normalizeDmChatId(item.id))}`));
return row;