From 1266ef217902effc8feb3d1c17d9f2e50e7e0856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Dub=C3=A9?= Date: Sun, 23 Mar 2025 23:00:17 -0400 Subject: [PATCH] fix(ux): Make it easier to add file extensions to an app in bulk (#1205) * Add support for pasting multiple file extensions at once in Dev Center * Fix bulk file extension paste functionality in Dev Center * fix: add try-catch around tippy * Fix issues with bulk file extension pasting and add comma key support * Fix issue with duplicate red tags appearing temporarily * Implement robust bulk file extension paste functionality in Dev Center * Fix security issue with JSON.stringify to properly escape < characters --- src/dev-center/js/dev-center.js | 102 +++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 3 deletions(-) diff --git a/src/dev-center/js/dev-center.js b/src/dev-center/js/dev-center.js index caf075ce9..0cf1dfecf 100644 --- a/src/dev-center/js/dev-center.js +++ b/src/dev-center/js/dev-center.js @@ -569,7 +569,8 @@ function generate_edit_app_section(app) {

A list of file type specifiers. For example if you include .txt your apps could be opened when a user clicks on a TXT file.

- +

You can paste multiple extensions at once (comma, space, or tab separated) or press comma to add each extension.

+

Window

@@ -816,7 +817,8 @@ async function edit_app_section(cur_app_name, tab = 'deploy') { const filetype_association_input = document.querySelector('textarea[id=edit-app-filetype-associations]'); let tagify = new Tagify(filetype_association_input, { pattern: /\.(?:[a-z0-9]+)|(?:[a-z]+\/(?:[a-z0-9.-]+|\*))/, - delimiters: ", ", + delimiters: ",", // Use comma as delimiter + duplicates: false, // Prevent duplicate tags enforceWhitelist: false, dropdown : { // show the dropdown immediately on focus (0 character typed) @@ -1072,7 +1074,101 @@ async function edit_app_section(cur_app_name, tab = 'deploy') { // Focus on the first input $('#edit-app-title').focus(); - activate_tippy(); + try { + activate_tippy(); + } catch (e) { + console.log('no tippy:', e); + } + + // Custom function to handle bulk pasting of file extensions + if (tagify) { + // Create a completely separate paste handler + const handleBulkPaste = function(e) { + const clipboardData = e.clipboardData || window.clipboardData; + if (!clipboardData) return; + + const pastedText = clipboardData.getData('text'); + if (!pastedText) return; + + // Check if the pasted text contains delimiters + if (/[,;\t\s]/.test(pastedText)) { + e.stopPropagation(); + e.preventDefault(); + + // Process the pasted text to extract extensions + const extensions = pastedText.split(/[,;\t\s]+/) + .map(ext => ext.trim()) + .filter(ext => ext && (ext.startsWith('.') || ext.includes('/'))); + + if (extensions.length > 0) { + // Get existing values to prevent duplicates + const existingValues = tagify.value.map(tag => tag.value); + + // Only add extensions that don't already exist + const newExtensions = extensions.filter(ext => !existingValues.includes(ext)); + + if (newExtensions.length > 0) { + // Add the new tags + tagify.addTags(newExtensions); + + // Update the UI + setTimeout(() => { + toggleSaveButton(); + toggleResetButton(); + }, 10); + } + } + + // Clear the input element to prevent any text concatenation + setTimeout(() => { + if (tagify.DOM.input) { + tagify.DOM.input.textContent = ''; + } + }, 10); + } + }; + + // Add the paste handler directly to the tagify wrapper element + const tagifyWrapper = tagify.DOM.scope; + if (tagifyWrapper) { + tagifyWrapper.addEventListener('paste', handleBulkPaste, true); + } + + // Also add it to the input element for better coverage + if (tagify.DOM.input) { + tagify.DOM.input.addEventListener('paste', handleBulkPaste, true); + } + + // Add a comma key handler to support adding tags with comma + tagify.DOM.input.addEventListener('keydown', function(e) { + if (e.key === ',' && tagify.DOM.input.textContent.trim()) { + e.preventDefault(); + + const text = tagify.DOM.input.textContent.trim(); + + // Only add valid extensions + if ((text.startsWith('.') || text.includes('/')) && + tagify.settings.pattern.test(text)) { + + // Check for duplicates + const existingValues = tagify.value.map(tag => tag.value); + + if (!existingValues.includes(text)) { + tagify.addTags([text]); + + // Update UI + setTimeout(() => { + toggleSaveButton(); + toggleResetButton(); + }, 10); + } + + // Always clear the input + tagify.DOM.input.textContent = ''; + } + } + }); + } } $('.jip-submit-btn').on('click', async function (e) {