From 9ae3256c35821e93224b942822914de182586278 Mon Sep 17 00:00:00 2001 From: dimitrievgs Date: Sun, 3 May 2026 20:10:32 +0300 Subject: [PATCH] Add buttons to scroll to start of message and copy text of message --- src/components/VoicePanel.ts | 49 +++++++++++++++++++++++++++++++++--- src/css/styles.css | 31 +++++++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/components/VoicePanel.ts b/src/components/VoicePanel.ts index 801d4f6..3052424 100644 --- a/src/components/VoicePanel.ts +++ b/src/components/VoicePanel.ts @@ -1,5 +1,5 @@ import LLMAgentPlugin from 'main'; -import { MarkdownRenderer } from 'obsidian'; +import { MarkdownRenderer, setIcon, Notice } from 'obsidian'; export class VoicePanel { private activeTab: 'res1' | 'res2' | 'trans' = 'res1'; @@ -77,6 +77,7 @@ export class VoicePanel { } if (this.activeTab === 'trans') { + // Для транскрибации оставляем простой текст, так как она меняется постоянно if (body.innerText !== data.transcription) { body.innerText = data.transcription || ''; } @@ -107,7 +108,7 @@ export class VoicePanel { if (!existing) { // Создаем контейнер const msgContainer = document.createElement('div'); - msgContainer.addClass('voice-message-block', 'markdown-rendered'); + msgContainer.addClass('voice-message-block', 'markdown-rendered', 'group/turn-messages'); // Добавлен класс group для hover-эффекта msgContainer.setAttr('data-id', entry.id); // Добавляем В НАЧАЛО контейнера @@ -116,9 +117,49 @@ export class VoicePanel { // Рендерим Markdown await MarkdownRenderer.renderMarkdown(entry.content, msgContainer, '', this.plugin); - // Если нужно добавить разделитель (hr), в такой логике его проще - // добавить стилями CSS (border-bottom), либо вставлять внутрь msgContainer + // ДОБАВЛЯЕМ КНОПКИ (Copy и Scroll to Top) + this.createVoiceMessageToolset(msgContainer, entry.content); } } } + + private createVoiceMessageToolset(messageElement: HTMLElement, content: string): void { + const toolsetContainer = messageElement.createDiv({ + cls: 'voice-message-toolset' + }); + + // Кнопка "Копировать" + const copyButton = toolsetContainer.createEl('button', { + cls: 'voice-tool-btn', + attr: { 'aria-label': 'Копировать всё' } + }); + const copyIconContainer = copyButton.createSpan(); + setIcon(copyIconContainer, 'copy'); + + copyButton.addEventListener('click', async () => { + try { + await navigator.clipboard.writeText(content); + setIcon(copyIconContainer, 'check'); + setTimeout(() => setIcon(copyIconContainer, 'copy'), 1000); + } catch (err) { + new Notice('Ошибка копирования'); + } + }); + + // Кнопка "Наверх" (перемотка к началу сообщения) + const scrollToTopButton = toolsetContainer.createEl('button', { + cls: 'voice-tool-btn', + attr: { 'aria-label': 'К началу сообщения' } + }); + const scrollIconContainer = scrollToTopButton.createSpan(); + setIcon(scrollIconContainer, 'arrow-up'); + + scrollToTopButton.addEventListener('click', () => { + // Используем встроенный метод scrollIntoView для точного позиционирования + messageElement.scrollIntoView({ + behavior: 'smooth', + block: 'start' // Прокрутить так, чтобы начало элемента совпало с верхом контейнера + }); + }); + } } diff --git a/src/css/styles.css b/src/css/styles.css index 546e038..9f5a01d 100644 --- a/src/css/styles.css +++ b/src/css/styles.css @@ -833,12 +833,43 @@ border-bottom: 1px solid var(--background-modifier-border); padding-bottom: 10px; margin-bottom: 10px; + position: relative; /* Для позиционирования тулсета */ + scroll-margin-top: 10px; } /* Чтобы у последнего сообщения (самого нижнего) не было линии */ .voice-message-block:last-child { border-bottom: none; } +.voice-message-toolset { + display: flex; + gap: 4px; + margin-top: 8px; + transition: opacity 0.2s ease; +} + +.voice-tool-btn { + background: transparent; + border: none; + cursor: pointer; + padding: 4px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); +} + +.voice-tool-btn:hover { + background-color: var(--background-modifier-hover); + color: var(--text-normal); +} + +.voice-tool-btn svg { + width: 16px; + height: 16px; +} + .v-tabs { display: flex; gap: 2px;