diff --git a/src/interface/obsidian/src/chat_view.ts b/src/interface/obsidian/src/chat_view.ts index 190e2660..14ca1aff 100644 --- a/src/interface/obsidian/src/chat_view.ts +++ b/src/interface/obsidian/src/chat_view.ts @@ -1,7 +1,7 @@ import { MarkdownRenderer, WorkspaceLeaf, request, requestUrl, setIcon } from 'obsidian'; import { KhojSetting } from 'src/settings'; import { KhojPaneView } from 'src/pane_view'; -import { KhojView } from 'src/utils'; +import { KhojView, createCopyParentText } from 'src/utils'; export interface ChatJsonResult { image?: string; @@ -214,7 +214,7 @@ export class KhojChatView extends KhojPaneView { referenceExpandButton.innerHTML = expandButtonText; } - renderMessage(chatEl: Element, message: string, sender: string, dt?: Date, raw: boolean=false): Element { + renderMessage(chatEl: Element, message: string, sender: string, dt?: Date, raw: boolean=false, willReplace: boolean=true): Element { let message_time = this.formatDate(dt ?? new Date()); let emojified_sender = sender == "khoj" ? "🏮 Khoj" : "🤔 You"; @@ -236,6 +236,16 @@ export class KhojChatView extends KhojPaneView { MarkdownRenderer.renderMarkdown(message, chat_message_body_text_el, '', null); } + // Add a copy button to each chat message + if (willReplace === true) { + let copyButton = chatMessageEl.createEl('button'); + copyButton.classList.add("copy-button"); + copyButton.title = "Copy Message to Clipboard"; + setIcon(copyButton, "copy-plus"); + copyButton.addEventListener('click', createCopyParentText(message)); + chat_message_body_text_el.append(copyButton); + } + // Remove user-select: none property to make text selectable chatMessageEl.style.userSelect = "text"; diff --git a/src/interface/obsidian/src/utils.ts b/src/interface/obsidian/src/utils.ts index aa25e5fe..b0160c48 100644 --- a/src/interface/obsidian/src/utils.ts +++ b/src/interface/obsidian/src/utils.ts @@ -304,3 +304,32 @@ export async function populateHeaderPane(headerEl: Element, setting: KhojSetting export enum KhojView { CHAT = "khoj-chat-view", } + +function copyParentText(event: MouseEvent, message: string, originalButton: string) { + const button = event.currentTarget as HTMLElement; + if (!button || !button?.parentNode?.textContent) return; + if (!!button.firstChild) button.removeChild(button.firstChild as HTMLImageElement); + const textContent = message ?? button.parentNode.textContent.trim(); + navigator.clipboard.writeText(textContent).then(() => { + setIcon((button as HTMLElement), 'copy-check'); + setTimeout(() => { + setIcon((button as HTMLElement), originalButton); + }, 1000); + }).catch((error) => { + console.error("Error copying text to clipboard:", error); + const originalButtonText = button.innerHTML; + button.innerHTML = "⛔️"; + setTimeout(() => { + button.innerHTML = originalButtonText; + setIcon((button as HTMLElement), originalButton); + }, 2000); + }); + + return textContent; +} + +export function createCopyParentText(message: string, originalButton: string = 'copy-plus') { + return function(event: MouseEvent) { + return copyParentText(event, message, originalButton); + } +} diff --git a/src/interface/obsidian/styles.css b/src/interface/obsidian/styles.css index 4bab47ae..e98dd753 100644 --- a/src/interface/obsidian/styles.css +++ b/src/interface/obsidian/styles.css @@ -416,6 +416,42 @@ span.khoj-nav-item-text { padding-left: 8px; } +/* Copy button */ +button.copy-button { + border-radius: 4px; + background-color: var(--background-color); + border: 1px solid var(--main-text-color); + text-align: center; + font-size: 16px; + transition: all 0.5s; + cursor: pointer; + padding: 4px; + float: right; +} +button.copy-button span { + cursor: pointer; + display: inline-block; + position: relative; + transition: 0.5s; +} +img.copy-icon { + width: 16px; + height: 16px; +} +.you button.copy-button { + color: var(--text-on-accent); +} +.khoj button.copy-button { + color: var(--khoj-storm-grey); +} +.you button.copy-button:hover { + color: var(--khoj-storm-grey); + background: var(--text-on-accent); +} +.khoj button.copy-button:hover { + background: var(--text-on-accent); +} + @media only screen and (max-width: 600px) { div.khoj-header { display: grid;