refac: deprecate textarea input

This commit is contained in:
Timothy Jaeryang Baek 2025-09-12 20:54:34 +04:00
parent 19e18bc461
commit 153afd832c
3 changed files with 60 additions and 303 deletions

View file

@ -1464,18 +1464,8 @@
prompt = ''; prompt = '';
const messages = createMessagesList(history, history.currentId); const messages = createMessagesList(history, history.currentId);
// Reset chat input textarea
if (!($settings?.richTextInput ?? true)) {
const chatInputElement = document.getElementById('chat-input');
if (chatInputElement) {
await tick();
chatInputElement.style.height = '';
}
}
const _files = JSON.parse(JSON.stringify(files)); const _files = JSON.parse(JSON.stringify(files));
chatFiles.push(..._files.filter((item) => ['doc', 'file', 'collection'].includes(item.type))); chatFiles.push(..._files.filter((item) => ['doc', 'file', 'collection'].includes(item.type)));
chatFiles = chatFiles.filter( chatFiles = chatFiles.filter(
// Remove duplicates // Remove duplicates
@ -2378,11 +2368,7 @@
if (e.detail || files.length > 0) { if (e.detail || files.length > 0) {
await tick(); await tick();
submitPrompt( submitPrompt(e.detail.replaceAll('\n\n', '\n'));
($settings?.richTextInput ?? true)
? e.detail.replaceAll('\n\n', '\n')
: e.detail
);
} }
}} }}
/> />
@ -2431,11 +2417,7 @@
clearDraft(); clearDraft();
if (e.detail || files.length > 0) { if (e.detail || files.length > 0) {
await tick(); await tick();
submitPrompt( submitPrompt(e.detail.replaceAll('\n\n', '\n'));
($settings?.richTextInput ?? true)
? e.detail.replaceAll('\n\n', '\n')
: e.detail
);
} }
}} }}
/> />

View file

@ -277,29 +277,8 @@
const chatInput = document.getElementById('chat-input'); const chatInput = document.getElementById('chat-input');
if (chatInput) { if (chatInput) {
if ($settings?.richTextInput ?? true) { chatInputElement.replaceVariables(variables);
chatInputElement.replaceVariables(variables); chatInputElement.focus();
chatInputElement.focus();
} else {
// Get current value from the input element
let currentValue = chatInput.value || '';
// Replace template variables using regex
const updatedValue = currentValue.replace(
/{{\s*([^|}]+)(?:\|[^}]*)?\s*}}/g,
(match, varName) => {
const trimmedVarName = varName.trim();
return variables.hasOwnProperty(trimmedVarName)
? String(variables[trimmedVarName])
: match;
}
);
// Update the input value
chatInput.value = updatedValue;
chatInput.focus();
chatInput.dispatchEvent(new Event('input', { bubbles: true }));
}
} }
}; };
@ -309,16 +288,8 @@
if (chatInput) { if (chatInput) {
text = await textVariableHandler(text || ''); text = await textVariableHandler(text || '');
if ($settings?.richTextInput ?? true) { chatInputElement?.setText(text);
chatInputElement?.setText(text); chatInputElement?.focus();
chatInputElement?.focus();
} else {
chatInput.value = text;
prompt = text;
chatInput.focus();
chatInput.dispatchEvent(new Event('input'));
}
text = await inputVariableHandler(text); text = await inputVariableHandler(text);
await tick(); await tick();
@ -341,12 +312,7 @@
let word = ''; let word = '';
if (chatInput) { if (chatInput) {
if ($settings?.richTextInput ?? true) { word = chatInputElement?.getWordAtDocPos();
word = chatInputElement?.getWordAtDocPos();
} else {
const cursor = chatInput ? chatInput.selectionStart : prompt.length;
word = getWordAtCursor(prompt, cursor);
}
} }
return word; return word;
@ -364,15 +330,7 @@
const chatInput = document.getElementById('chat-input'); const chatInput = document.getElementById('chat-input');
if (!chatInput) return; if (!chatInput) return;
if ($settings?.richTextInput ?? true) { chatInputElement?.replaceCommandWithText(text);
chatInputElement?.replaceCommandWithText(text);
} else {
const cursor = chatInput.selectionStart;
const { start, end } = getWordBoundsAtCursor(prompt, cursor);
prompt = prompt.slice(0, start) + text + prompt.slice(end);
chatInput.focus();
chatInput.setSelectionRange(start + text.length, start + text.length);
}
}; };
const insertTextAtCursor = async (text: string) => { const insertTextAtCursor = async (text: string) => {
@ -384,14 +342,7 @@
if (command) { if (command) {
replaceCommandWithText(text); replaceCommandWithText(text);
} else { } else {
if ($settings?.richTextInput ?? true) { chatInputElement?.insertContent(text);
chatInputElement?.insertContent(text);
} else {
const cursor = chatInput.selectionStart;
prompt = prompt.slice(0, cursor) + text + prompt.slice(cursor);
chatInput.focus();
chatInput.setSelectionRange(cursor + text.length, cursor + text.length);
}
} }
await tick(); await tick();
@ -413,18 +364,6 @@
if (words.length > 0) { if (words.length > 0) {
const word = words.at(0); const word = words.at(0);
await tick(); await tick();
if (!($settings?.richTextInput ?? true)) {
// Move scroll to the first word
chatInput.setSelectionRange(word.startIndex, word.endIndex + 1);
chatInput.focus();
const selectionRow =
(word?.startIndex - (word?.startIndex % chatInput.cols)) / chatInput.cols;
const lineHeight = chatInput.clientHeight / chatInput.rows;
chatInput.scrollTop = lineHeight * selectionRow;
}
} else { } else {
chatInput.scrollTop = chatInput.scrollHeight; chatInput.scrollTop = chatInput.scrollHeight;
} }
@ -1230,12 +1169,12 @@
{/if} {/if}
<div class="px-2.5"> <div class="px-2.5">
{#if $settings?.richTextInput ?? true} <div
<div class="scrollbar-hidden rtl:text-right ltr:text-left bg-transparent dark:text-gray-100 outline-hidden w-full pt-2.5 pb-[5px] px-1 resize-none h-fit max-h-80 overflow-auto"
class="scrollbar-hidden rtl:text-right ltr:text-left bg-transparent dark:text-gray-100 outline-hidden w-full pt-2.5 pb-[5px] px-1 resize-none h-fit max-h-80 overflow-auto" id="chat-input-container"
id="chat-input-container" >
> {#if suggestions}
{#if suggestions} {#key $settings?.richTextInput ?? true}
{#key $settings?.showFormattingToolbar ?? false} {#key $settings?.showFormattingToolbar ?? false}
<RichTextInput <RichTextInput
bind:this={chatInputElement} bind:this={chatInputElement}
@ -1245,6 +1184,7 @@
command = getCommand(); command = getCommand();
}} }}
json={true} json={true}
richText={$settings?.richTextInput ?? true}
messageInput={true} messageInput={true}
showFormattingToolbar={$settings?.showFormattingToolbar ?? false} showFormattingToolbar={$settings?.showFormattingToolbar ?? false}
floatingMenuPlacement={'top-start'} floatingMenuPlacement={'top-start'}
@ -1429,195 +1369,9 @@
}} }}
/> />
{/key} {/key}
{/if} {/key}
</div> {/if}
{:else} </div>
<textarea
id="chat-input"
dir={$settings?.chatDirection ?? 'auto'}
bind:this={chatInputElement}
class="scrollbar-hidden bg-transparent dark:text-gray-200 outline-hidden w-full pt-4 pb-1 px-1 resize-none"
placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
bind:value={prompt}
on:input={() => {
command = getCommand();
}}
on:click={() => {
command = getCommand();
}}
on:compositionstart={() => (isComposing = true)}
on:compositionend={(e) => {
compositionEndedAt = e.timeStamp;
isComposing = false;
}}
on:keydown={async (e) => {
const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
const suggestionsContainerElement =
document.getElementById('suggestions-container');
if (e.key === 'Escape') {
stopResponse();
}
// Command/Ctrl + Shift + Enter to submit a message pair
if (isCtrlPressed && e.key === 'Enter' && e.shiftKey) {
e.preventDefault();
createMessagePair(prompt);
}
// Check if Ctrl + R is pressed
if (prompt === '' && isCtrlPressed && e.key.toLowerCase() === 'r') {
e.preventDefault();
console.log('regenerate');
const regenerateButton = [
...document.getElementsByClassName('regenerate-response-button')
]?.at(-1);
regenerateButton?.click();
}
if (prompt === '' && e.key == 'ArrowUp') {
e.preventDefault();
const userMessageElement = [
...document.getElementsByClassName('user-message')
]?.at(-1);
const editButton = [
...document.getElementsByClassName('edit-user-message-button')
]?.at(-1);
console.log(userMessageElement);
userMessageElement?.scrollIntoView({ block: 'center' });
editButton?.click();
}
if (!suggestionsContainerElement) {
if (
!$mobile ||
!(
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0
)
) {
if (inOrNearComposition(e)) {
return;
}
// Prevent Enter key from creating a new line
const isCtrlPressed = e.ctrlKey || e.metaKey;
const enterPressed =
($settings?.ctrlEnterToSend ?? false)
? (e.key === 'Enter' || e.keyCode === 13) && isCtrlPressed
: (e.key === 'Enter' || e.keyCode === 13) && !e.shiftKey;
if (enterPressed) {
e.preventDefault();
}
// Submit the prompt when Enter key is pressed
if ((prompt !== '' || files.length > 0) && enterPressed) {
dispatch('submit', prompt);
}
}
}
if (e.key === 'Tab') {
const words = extractCurlyBraceWords(prompt);
if (words.length > 0) {
const word = words.at(0);
if (word && e.target instanceof HTMLTextAreaElement) {
// Prevent default tab behavior
e.preventDefault();
e.target.setSelectionRange(word?.startIndex, word.endIndex + 1);
e.target.focus();
const selectionRow =
(word?.startIndex - (word?.startIndex % e.target.cols)) /
e.target.cols;
const lineHeight = e.target.clientHeight / e.target.rows;
e.target.scrollTop = lineHeight * selectionRow;
}
}
e.target.style.height = '';
e.target.style.height = Math.min(e.target.scrollHeight, 320) + 'px';
}
if (e.key === 'Escape') {
console.log('Escape');
atSelectedModel = undefined;
selectedToolIds = [];
selectedFilterIds = [];
webSearchEnabled = false;
imageGenerationEnabled = false;
codeInterpreterEnabled = false;
}
}}
rows="1"
on:input={async (e) => {
e.target.style.height = '';
e.target.style.height = Math.min(e.target.scrollHeight, 320) + 'px';
}}
on:focus={async (e) => {
e.target.style.height = '';
e.target.style.height = Math.min(e.target.scrollHeight, 320) + 'px';
}}
on:paste={async (e) => {
const clipboardData = e.clipboardData || window.clipboardData;
if (clipboardData && clipboardData.items) {
for (const item of clipboardData.items) {
console.log(item);
if (item.type.indexOf('image') !== -1) {
const blob = item.getAsFile();
const reader = new FileReader();
reader.onload = function (e) {
files = [
...files,
{
type: 'image',
url: `${e.target.result}`
}
];
};
reader.readAsDataURL(blob);
} else if (item?.kind === 'file') {
const file = item.getAsFile();
if (file) {
const _files = [file];
await inputFilesHandler(_files);
e.preventDefault();
}
} else if (item.type === 'text/plain') {
if (($settings?.largeTextAsFile ?? false) && !shiftKey) {
const text = clipboardData.getData('text/plain');
if (text.length > PASTED_TEXT_CHARACTER_LIMIT) {
e.preventDefault();
const blob = new Blob([text], { type: 'text/plain' });
const file = new File([blob], `Pasted_Text_${Date.now()}.txt`, {
type: 'text/plain'
});
await uploadFileHandler(file, true);
}
}
}
}
}
}}
/>
{/if}
</div> </div>
<div class=" flex justify-between mt-0.5 mb-2.5 mx-0.5 max-w-full" dir="ltr"> <div class=" flex justify-between mt-0.5 mb-2.5 mx-0.5 max-w-full" dir="ltr">

View file

@ -162,10 +162,11 @@
export let className = 'input-prose'; export let className = 'input-prose';
export let placeholder = 'Type here...'; export let placeholder = 'Type here...';
export let richText = true;
export let link = false; export let link = false;
export let image = false; export let image = false;
export let fileHandler = false; export let fileHandler = false;
export let suggestions = null; export let suggestions = null;
export let onFileDrop = (currentEditor, files, pos) => { export let onFileDrop = (currentEditor, files, pos) => {
@ -964,11 +965,23 @@
Placeholder.configure({ placeholder }), Placeholder.configure({ placeholder }),
SelectionDecoration, SelectionDecoration,
CodeBlockLowlight.configure({ ...(richText
lowlight ? [
}), CodeBlockLowlight.configure({
Highlight, lowlight
Typography, }),
Highlight,
Typography,
TableKit.configure({
table: { resizable: true }
}),
ListKit.configure({
taskItem: {
nested: true
}
})
]
: []),
...(suggestions ...(suggestions
? [ ? [
Mention.configure({ Mention.configure({
@ -978,14 +991,6 @@
] ]
: []), : []),
TableKit.configure({
table: { resizable: true }
}),
ListKit.configure({
taskItem: {
nested: true
}
}),
CharacterCount.configure({}), CharacterCount.configure({}),
...(image ? [Image] : []), ...(image ? [Image] : []),
...(fileHandler ...(fileHandler
@ -996,8 +1001,7 @@
}) })
] ]
: []), : []),
...(richText && autocomplete
...(autocomplete
? [ ? [
AIAutocompletion.configure({ AIAutocompletion.configure({
generateCompletion: async (text) => { generateCompletion: async (text) => {
@ -1015,8 +1019,7 @@
}) })
] ]
: []), : []),
...(richText && showFormattingToolbar
...(showFormattingToolbar
? [ ? [
BubbleMenu.configure({ BubbleMenu.configure({
element: bubbleMenuElement, element: bubbleMenuElement,
@ -1091,6 +1094,22 @@
}, },
editorProps: { editorProps: {
attributes: { id }, attributes: { id },
handlePaste: (view, event) => {
// Force plain-text pasting when richText === false
if (!richText) {
const text = (event.clipboardData?.getData('text/plain') ?? '').replace(/\r\n/g, '\n');
// swallow HTML completely
event.preventDefault();
// Insert as pure text (no HTML parsing)
const { state, dispatch } = view;
const { from, to } = state.selection;
dispatch(state.tr.insertText(text, from, to).scrollIntoView());
return true; // handled
}
return false;
},
handleDOMEvents: { handleDOMEvents: {
compositionstart: (view, event) => { compositionstart: (view, event) => {
oncompositionstart(event); oncompositionstart(event);
@ -1277,7 +1296,9 @@
editor.storage.files = files; editor.storage.files = files;
} }
}, },
onSelectionUpdate: onSelectionUpdate onSelectionUpdate: onSelectionUpdate,
enableInputRules: richText,
enablePasteRules: richText
}); });
if (messageInput) { if (messageInput) {