Feat: Improve multiline input handling in popups (#4756) * feat: improve multiline input handling in popups - Added Ctrl+Enter requirement for submission in multiline input popups to prevent accidental sends - Exported PopupUtils class for external use * refactor: remove redundant higher/different rows from input popups - Removed rows: 2 from callGenericPopup calls where default behavior is sufficient - Increased rows from 2 to 4 in caption extension for better multiline input experience
Signed| @@ -7267,7 +7267,7 @@ export function setUserName(value, { toastPersonaNameChange = true } = {}) { | |||
| 7267 | 7267 | ||
| 7268 | async function doOnboarding(avatarId) { | 7268 | async function doOnboarding(avatarId) { |
| 7269 | const template = $('#onboarding_template .onboarding'); | 7269 | const template = $('#onboarding_template .onboarding'); |
| 7270 | let userName = await callGenericPopup(template, POPUP_TYPE.INPUT, currentUser?.name || name1, { rows: 2, wider: true, cancelButton: false }); | 7270 | let userName = await callGenericPopup(template, POPUP_TYPE.INPUT, currentUser?.name || name1, { wider: true, cancelButton: false }); |
| 7271 | 7271 | ||
| 7272 | if (userName) { | 7272 | if (userName) { |
| 7273 | userName = String(userName).replace('\n', ' '); | 7273 | userName = String(userName).replace('\n', ' '); |
| @@ -315,7 +315,7 @@ async function captionMultimodal(base64Img, externalPrompt) { | |||
| 315 | let prompt = externalPrompt || extension_settings.caption.prompt || PROMPT_DEFAULT; | 315 | let prompt = externalPrompt || extension_settings.caption.prompt || PROMPT_DEFAULT; |
| 316 | 316 | ||
| 317 | if (!externalPrompt && extension_settings.caption.prompt_ask) { | 317 | if (!externalPrompt && extension_settings.caption.prompt_ask) { |
| 318 | const customPrompt = await callGenericPopup('Enter a comment or question:', POPUP_TYPE.INPUT, prompt, { rows: 2 }); | 318 | const customPrompt = await callGenericPopup('Enter a comment or question:', POPUP_TYPE.INPUT, prompt, { rows: 4 }); |
| 319 | if (!customPrompt) { | 319 | if (!customPrompt) { |
| 320 | throw new Error('User aborted the caption sending.'); | 320 | throw new Error('User aborted the caption sending.'); |
| 321 | } | 321 | } |
| @@ -286,7 +286,7 @@ async function createConnectionProfile(forceName = null) { | |||
| 286 | }); | 286 | }); |
| 287 | const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n); | 287 | const isNameTaken = (n) => extension_settings.connectionManager.profiles.some(p => p.name === n); |
| 288 | const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken); | 288 | const suggestedName = getUniqueName(collapseSpaces(`${profile.api ?? ''} ${profile.model ?? ''} - ${profile.preset ?? ''}`), isNameTaken); |
| 289 | let name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName, { rows: 2 }); | 289 | let name = forceName ?? await callGenericPopup(template, POPUP_TYPE.INPUT, suggestedName); |
| 290 | // If it's cancelled, it will be false | 290 | // If it's cancelled, it will be false |
| 291 | if (!name) { | 291 | if (!name) { |
| 292 | return null; | 292 | return null; |
| @@ -607,7 +607,6 @@ async function renderDetailsContent(detailsContent) { | |||
| 607 | }, {}); | 607 | }, {}); |
| 608 | const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings })); | 608 | const template = $(await renderExtensionTemplateAsync(MODULE_NAME, 'edit', { name: profile.name, settings })); |
| 609 | let newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, { | 609 | let newName = await callGenericPopup(template, POPUP_TYPE.INPUT, profile.name, { |
| 610 | rows: 2, | ||
| 611 | customButtons: [{ | 610 | customButtons: [{ |
| 612 | text: t`Save and Update`, | 611 | text: t`Save and Update`, |
| 613 | classes: ['popup-button-ok'], | 612 | classes: ['popup-button-ok'], |
| @@ -461,6 +461,13 @@ export class Popup { | |||
| 461 | if (input instanceof HTMLInputElement && !shouldSendOnEnter()) | 461 | if (input instanceof HTMLInputElement && !shouldSendOnEnter()) |
| 462 | return; | 462 | return; |
| 463 | 463 | ||
| 464 | // If this is a multiline input popup, we should still not simply send on enter, that'd be weird. | ||
| 465 | // Let's still make it possible if CTRL is toggled though | ||
| 466 | if ((textarea instanceof HTMLTextAreaElement || input instanceof HTMLInputElement) | ||
| 467 | && !evt.ctrlKey && this.mainInput.rows > 1) { | ||
| 468 | return; | ||
| 469 | } | ||
| 470 | |||
| 464 | evt.preventDefault(); | 471 | evt.preventDefault(); |
| 465 | evt.stopPropagation(); | 472 | evt.stopPropagation(); |
| 466 | const result = Number(document.activeElement.getAttribute('data-result') ?? this.defaultResult); | 473 | const result = Number(document.activeElement.getAttribute('data-result') ?? this.defaultResult); |
| @@ -688,7 +695,7 @@ export class Popup { | |||
| 688 | }; | 695 | }; |
| 689 | } | 696 | } |
| 690 | 697 | ||
| 691 | class PopupUtils { | 698 | export class PopupUtils { |
| 692 | /** | 699 | /** |
| 693 | * Builds popup content with header and text below | 700 | * Builds popup content with header and text below |
| 694 | * | 701 | * |
| @@ -528,7 +528,7 @@ class YouTubeScraper { | |||
| 528 | async scrape() { | 528 | async scrape() { |
| 529 | let lang = ''; | 529 | let lang = ''; |
| 530 | const template = $(await renderExtensionTemplateAsync('attachments', 'youtube-scrape', {})); | 530 | const template = $(await renderExtensionTemplateAsync('attachments', 'youtube-scrape', {})); |
| 531 | const videoUrl = await callGenericPopup(template, POPUP_TYPE.INPUT, '', { wide: false, large: false, okButton: 'Scrape', cancelButton: 'Cancel', rows: 2 }); | 531 | const videoUrl = await callGenericPopup(template, POPUP_TYPE.INPUT, '', { wide: false, large: false, okButton: 'Scrape', cancelButton: 'Cancel' }); |
| 532 | 532 | ||
| 533 | template.find('input[name="youtubeLanguageCode"]').on('input', function () { | 533 | template.find('input[name="youtubeLanguageCode"]').on('input', function () { |
| 534 | lang = String($(this).val()).trim(); | 534 | lang = String($(this).val()).trim(); |