diff --git a/src/components/VoicePanel.ts b/src/components/VoicePanel.ts index 657beda..801d4f6 100644 --- a/src/components/VoicePanel.ts +++ b/src/components/VoicePanel.ts @@ -27,15 +27,19 @@ export class VoicePanel { -
+ `; this.container.querySelectorAll('.v-tab').forEach((tab) => { tab.addEventListener('click', (e) => { + const target = e.target as HTMLElement; this.container.querySelectorAll('.v-tab').forEach((t) => t.classList.remove('active')); - (e.target as HTMLElement).classList.add('active'); - this.activeTab = (e.target as HTMLElement).dataset.tab as any; + target.classList.add('active'); + this.activeTab = target.dataset.tab as any; + // При переключении вкладок очищаем контейнер, чтобы отрисовать актуальный список + const body = this.container.querySelector('#v-body') as HTMLElement; + if (body) body.empty(); }); }); @@ -59,44 +63,62 @@ export class VoicePanel { const data = await res.json(); this.updateContent(data); } catch (e) { - console.error("Voice status fetch error", e); + console.error('Voice status fetch error', e); } - }, 1500); + }, 500); } async updateContent(data: any) { const body = this.container.querySelector('#v-body') as HTMLElement; if (!body) return; - if (data.obsidian_settings_fetched == false) { + if (data.obsidian_settings_fetched === false) { this.plugin.syncSettings(); } - // Выбираем контент для отображения - let contentToRender = ""; - if (this.activeTab === 'trans') { - // Транскрипцию обычно выводим как простой текст (или тоже можно через МК) - body.innerText = data.transcription || ""; + if (body.innerText !== data.transcription) { + body.innerText = data.transcription || ''; + } return; } - if (this.activeTab === 'res1') { - contentToRender = data.response1 || ""; - } else if (this.activeTab === 'res2') { - // Склеиваем массив ответов через горизонтальную линию Markdown - contentToRender = Array.isArray(data.response2) - ? data.response2.join('\n\n---\n\n') - : (data.response2 || ""); - } + // Получаем записи (они уже приходят отсортированные: новые в начале) + const entries: { id: string; content: string }[] = this.activeTab === 'res1' ? data.response1 : data.response2; - // Очищаем и рендерим Markdown - body.empty(); - await MarkdownRenderer.renderMarkdown( - contentToRender, - body, - '', - this.plugin - ); + // 1. Удаляем элементы, которых больше нет в данных (те, что вытеснены лимитом на беке) + const currentIds = new Set(entries.map((e) => e.id)); + const domElements = body.querySelectorAll('.voice-message-block'); + domElements.forEach((el) => { + const id = el.getAttr('data-id'); + if (id && !currentIds.has(id)) { + el.remove(); + } + }); + + // 2. Добавляем новые элементы. + // Важно: перебираем в ОБРАТНОМ порядке (от старых к новым), + // чтобы при использовании prepend в итоге самый новый оказался самым первым. + const reversedEntries = [...entries].reverse(); + + for (const entry of reversedEntries) { + let existing = body.querySelector(`[data-id="${entry.id}"]`); + + if (!existing) { + // Создаем контейнер + const msgContainer = document.createElement('div'); + msgContainer.addClass('voice-message-block', 'markdown-rendered'); + msgContainer.setAttr('data-id', entry.id); + + // Добавляем В НАЧАЛО контейнера + body.prepend(msgContainer); + + // Рендерим Markdown + await MarkdownRenderer.renderMarkdown(entry.content, msgContainer, '', this.plugin); + + // Если нужно добавить разделитель (hr), в такой логике его проще + // добавить стилями CSS (border-bottom), либо вставлять внутрь msgContainer + } + } } -} \ No newline at end of file +} diff --git a/src/css/styles.css b/src/css/styles.css index ac22bfd..546e038 100644 --- a/src/css/styles.css +++ b/src/css/styles.css @@ -788,13 +788,37 @@ .voice-body { flex: 1; overflow-y: auto; + /* Убираем горизонтальный скролл на уровне контейнера */ + overflow-x: hidden; background-color: var(--background-primary); border: 1px inset var(--background-modifier-border); margin: 4px; padding: 8px; - user-select: text; + user-select: text; -webkit-user-select: text; - cursor: text; /* Меняет курсор на текстовый при наведении */ + cursor: text; + + /* Добавляем перенос слов для обычного текста */ + word-wrap: break-word; + overflow-wrap: break-word; +} + +.voice-body pre { + white-space: pre-wrap; /* Включает перенос строк в pre */ + word-wrap: break-word; /* Перенос длинных слов */ + word-break: break-word; + overflow-x: hidden; /* Запрет горизонтальной прокрутки */ + background-color: var(--background-secondary-alt); + padding: 10px; + border-radius: 4px; + max-width: 100%; /* Чтобы блок не расширял родителя */ + box-sizing: border-box; +} + +.voice-body code { + white-space: pre-wrap; /* Перенос для inline и блочного кода */ + word-break: break-word; + max-width: 100%; } .voice-header { @@ -805,6 +829,16 @@ border-bottom: 1px solid var(--background-modifier-border); } +.voice-message-block { + border-bottom: 1px solid var(--background-modifier-border); + padding-bottom: 10px; + margin-bottom: 10px; +} +/* Чтобы у последнего сообщения (самого нижнего) не было линии */ +.voice-message-block:last-child { + border-bottom: none; +} + .v-tabs { display: flex; gap: 2px;