diff --git a/ts/packages/shell/src/renderer/src/chatInput.ts b/ts/packages/shell/src/renderer/src/chatInput.ts index 98b5266b..bbaed815 100644 --- a/ts/packages/shell/src/renderer/src/chatInput.ts +++ b/ts/packages/shell/src/renderer/src/chatInput.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import DOMPurify from "dompurify"; import { _arrayBufferToBase64 } from "./chatView"; import { iconMicrophone, @@ -57,11 +58,12 @@ export class ExpandableTextarea { if (this.entryHandlers.onChange !== undefined) { this.entryHandlers.onChange(this); } - + }); + this.textEntry.onchange = () => { if (sendButton !== undefined) { sendButton.disabled = this.textEntry.innerHTML.length == 0; } - }); + }; this.textEntry.onwheel = (event) => { if (this.entryHandlers.onMouseWheel !== undefined) { this.entryHandlers.onMouseWheel(this, event); @@ -80,12 +82,14 @@ export class ExpandableTextarea { // Set the cursor to the end of the text const r = document.createRange(); - r.setEnd(this.textEntry.childNodes[0], content?.length ?? 0); - r.collapse(false); - const s = document.getSelection(); - if (s) { - s.removeAllRanges(); - s.addRange(r); + if (this.textEntry.childNodes.length > 0) { + r.setEnd(this.textEntry.childNodes[0], content?.length ?? 0); + r.collapse(false); + const s = document.getSelection(); + if (s) { + s.removeAllRanges(); + s.addRange(r); + } } } @@ -187,6 +191,12 @@ export class ChatInput { this.sendButton, ); + this.textarea.getTextEntry().onpaste = (e: ClipboardEvent) => { + if (e.clipboardData !== null) { + this.getTextFromDataTransfer(e.clipboardData); + } + }; + this.textarea.getTextEntry().ondragenter = (e: DragEvent) => { if (!this.dragEnabled) { return; @@ -201,7 +211,8 @@ export class ChatInput { console.log("enter " + this.dragTemp); - this.textarea.getTextEntry().innerText = "Drop image files here..."; + this.textarea.getTextEntry().innerText = + "Drop image files or text here..."; this.textarea.getTextEntry().classList.add("chat-input-drag"); }; @@ -237,8 +248,8 @@ export class ChatInput { this.dragTemp = undefined; - if (e.dataTransfer != null && e.dataTransfer.files.length > 0) { - this.loadImageFile(e.dataTransfer.files[0]); + if (e.dataTransfer != null) { + this.getTextFromDataTransfer(e.dataTransfer); } e.preventDefault(); @@ -378,4 +389,37 @@ export class ChatInput { public focus() { this.textarea.focus(); } + + /** + * Takes dataTransfer and gets a plain text representation from the data there + * and loads it into the input box + * + * @param dataTransfer The dataTransfer object from drag/drop/paste events + */ + public getTextFromDataTransfer(dataTransfer: DataTransfer) { + if (dataTransfer.files.length > 0) { + this.loadImageFile(dataTransfer.files[0]); + } else if (dataTransfer.items.length > 0) { + let index: number = dataTransfer.types.indexOf("text/plain"); + let plainText: boolean = true; + if (index === -1) { + index = dataTransfer.types.indexOf("text/html"); + plainText = false; + } + + if (index === -1) { + this.textarea.getTextEntry().innerText = `Unsupported drag/drop data type '${dataTransfer.types.join(", ")}'`; + } else { + dataTransfer.items[index].getAsString((s) => { + if (plainText) { + this.textarea.getTextEntry().innerText = s; + } else { + // strip out all HTML from supplied input + this.textarea.getTextEntry().innerText += + DOMPurify.sanitize(s, { ALLOWED_TAGS: [] }); + } + }); + } + } + } } diff --git a/ts/packages/shell/src/renderer/src/chatView.ts b/ts/packages/shell/src/renderer/src/chatView.ts index 1f6f3771..304638d8 100644 --- a/ts/packages/shell/src/renderer/src/chatView.ts +++ b/ts/packages/shell/src/renderer/src/chatView.ts @@ -71,7 +71,7 @@ export class ChatView { chatInput: ChatInput; private partialCompletion: PartialCompletion | undefined; - private commandBackStack: (string | null)[] = []; + private commandBackStack: string[] = []; private commandBackStackIndex = 0; private hideMetrics = true; @@ -109,8 +109,9 @@ export class ChatView { // history if (!ev.altKey && !ev.ctrlKey) { if (ev.key == "ArrowUp" || ev.key == "ArrowDown") { - const currentContent = - this.chatInput.textarea.getTextEntry().textContent; + const currentContent: string = + this.chatInput.textarea.getTextEntry().innerHTML ?? + ""; if ( this.commandBackStack.length === 0 || @@ -118,16 +119,22 @@ export class ChatView { this.commandBackStackIndex ] !== currentContent ) { - const messages = this.messageDiv.querySelectorAll( - ".chat-message-user:not(.chat-message-hidden) .chat-message-content", - ); + const messages: NodeListOf = + this.messageDiv.querySelectorAll( + ".chat-message-container-user:not(.chat-message-hidden) .chat-message-content", + ); this.commandBackStack = Array.from(messages).map( - (m) => m.textContent, + (m: Element) => + m.firstElementChild?.innerHTML.replace( + 'class="chat-input-image"', + 'class="chat-input-dropImage"', + ) ?? "", ); this.commandBackStack.unshift(currentContent); this.commandBackStackIndex = 0; } + if ( ev.key == "ArrowUp" && this.commandBackStackIndex < @@ -143,7 +150,8 @@ export class ChatView { const content = this.commandBackStack[this.commandBackStackIndex]; - this.chatInput.textarea.setContent(content); + this.chatInput.textarea.getTextEntry().innerHTML = + content; return false; }