From 85aca970d6afcab5a436a06647fb20318233760d Mon Sep 17 00:00:00 2001 From: MAEDA Go Date: Mon, 19 Oct 2026 18:52:05 +0900 Subject: [PATCH] Include seconds in pasted image filenames Filenames of images pasted from the clipboard only included the time to the minute, so images pasted within the same minute could not be sorted in paste order by filename after downloading them. The filename now includes seconds, as in clipboard-20261019180359-bm9uy.png. The random part keeps five characters so that name collisions remain unlikely even when many images are pasted within the same second, for example by automated tools. Co-Authored-By: Claude Opus 5.5 --- app/assets/javascripts/attachments.js | 3 ++- test/system/issues_test.rb | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/attachments.js b/app/assets/javascripts/attachments.js index 5dac85803..d4d1f7207 100644 --- a/app/assets/javascripts/attachments.js +++ b/app/assets/javascripts/attachments.js @@ -277,7 +277,7 @@ async function getInlineAttachmentMarkup(file) { const inlineFilename = encodeURIComponent(sanitizedFilename) .replace(/[!()]/g, function(match) { return "%" + match.charCodeAt(0).toString(16) }); - const isFromClipboard = /^clipboard-\d{12}-[a-z0-9]{5}\.\w+$/.test(file.name); + const isFromClipboard = /^clipboard-\d{14}-[a-z0-9]{5}\.\w+$/.test(file.name); let imageDisplayWidth; if (isFromClipboard) { const imageWidth = await getImageWidth(file).catch(() => 0); @@ -353,6 +353,7 @@ function copyImageFromClipboard(e) { + ('0'+date.getDate()).slice(-2) + ('0'+date.getHours()).slice(-2) + ('0'+date.getMinutes()).slice(-2) + + ('0'+date.getSeconds()).slice(-2) + '-' + randomKey(5).toLocaleLowerCase() + '.' + file.name.split('.').pop(); diff --git a/test/system/issues_test.rb b/test/system/issues_test.rb index e902a8df9..9a317cd98 100644 --- a/test/system/issues_test.rb +++ b/test/system/issues_test.rb @@ -217,6 +217,31 @@ class IssuesSystemTest < ApplicationSystemTestCase assert_selector '.attachments_fields > span', :count => 2 end + def test_pasted_image_filename_should_include_seconds + set_tmp_attachments_directory + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + + page.execute_script(<<~JS) + const RealDate = Date; + window.Date = class extends RealDate { + constructor(...args) { + super(...(args.length ? args : [2026, 9, 19, 18, 3, 59])); + } + }; + // 1x1 1-bit grayscale PNG image + const png = Uint8Array.from(atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVR42mNgAAAAAgAB5Sfe/AAAAABJRU5ErkJggg=='), c => c.charCodeAt(0)); + const clipboardData = new DataTransfer(); + clipboardData.items.add(new File([png], 'image.png', { type: 'image/png' })); + const event = new Event('paste', { bubbles: true, cancelable: true }); + Object.defineProperty(event, 'clipboardData', { value: clipboardData }); + document.getElementById('issue_description').dispatchEvent(event); + window.Date = RealDate; + JS + filename = find('.attachments_fields input.filename').value + assert_match /\Aclipboard-20261019180359-[a-z0-9]{5}\.png\z/, filename + end + def test_create_issue_with_new_target_version log_user('jsmith', 'jsmith') -- 2.55.0