diff --git a/assets/js/upload.js b/assets/js/upload.js index 0f931037..99145df4 100644 --- a/assets/js/upload.js +++ b/assets/js/upload.js @@ -6,6 +6,7 @@ import { assertNotNull } from './utils/assert'; import { fetchJson, handleError } from './utils/requests'; import { $, $$, clearEl, hideEl, makeEl, showEl } from './utils/dom'; import { addTag } from './tagsinput'; +import { oncePersistedPageShown } from './utils/events'; const MATROSKA_MAGIC = 0x1a45dfa3; @@ -64,17 +65,21 @@ function setupImageUpload() { imgPreviews.appendChild(label); }); } + function showError() { clearEl(imgPreviews); showEl(scraperError); enableFetch(); } + function hideError() { hideEl(scraperError); } + function disableFetch() { fetchButton.setAttribute('disabled', ''); } + function enableFetch() { fetchButton.removeAttribute('disabled'); } @@ -231,13 +236,30 @@ function setupImageUpload() { function disableUploadButton() { const submitButton = $('.button.input--separate-top'); - if (submitButton !== null) { - submitButton.disabled = true; - submitButton.innerText = 'Please wait...'; + + if (!submitButton) { + return; } + const originalButtonText = submitButton.innerText; + + submitButton.disabled = true; + submitButton.innerText = 'Please wait...'; + // delay is needed because Safari stops the submit if the button is immediately disabled requestAnimationFrame(() => submitButton.setAttribute('disabled', 'disabled')); + + // Rolling back the disabled state when user navigated back to the form. + oncePersistedPageShown(() => { + if (!submitButton.disabled) { + return; + } + + submitButton.disabled = false; + submitButton.innerText = originalButtonText; + + submitButton.removeAttribute('disabled'); + }); } function submitHandler(event) { diff --git a/assets/js/utils/__tests__/events.spec.ts b/assets/js/utils/__tests__/events.spec.ts index ab1dbd67..f53b22ce 100644 --- a/assets/js/utils/__tests__/events.spec.ts +++ b/assets/js/utils/__tests__/events.spec.ts @@ -1,4 +1,12 @@ -import { delegate, fire, mouseMoveThenOver, leftClick, on, PhilomenaAvailableEventsMap } from '../events'; +import { + delegate, + fire, + mouseMoveThenOver, + leftClick, + on, + PhilomenaAvailableEventsMap, + oncePersistedPageShown, +} from '../events'; import { getRandomArrayItem } from '../../../test/randomness'; import { fireEvent } from '@testing-library/dom'; @@ -129,6 +137,51 @@ describe('Event utils', () => { }); }); + describe('oncePersistedPageShown', () => { + it('should NOT fire on usual page show', () => { + const mockHandler = vi.fn(); + + oncePersistedPageShown(mockHandler); + + fireEvent.pageShow(window, { persisted: false }); + + expect(mockHandler).toHaveBeenCalledTimes(0); + }); + + it('should fire on persisted pageshow', () => { + const mockHandler = vi.fn(); + + oncePersistedPageShown(mockHandler); + + fireEvent.pageShow(window, { persisted: true }); + + expect(mockHandler).toHaveBeenCalledTimes(1); + }); + + it('should keep waiting until the first persistent page shown fired', () => { + const mockHandler = vi.fn(); + + oncePersistedPageShown(mockHandler); + + fireEvent.pageShow(window, { persisted: false }); + fireEvent.pageShow(window, { persisted: false }); + fireEvent.pageShow(window, { persisted: true }); + + expect(mockHandler).toHaveBeenCalledTimes(1); + }); + + it('should NOT fire more than once', () => { + const mockHandler = vi.fn(); + + oncePersistedPageShown(mockHandler); + + fireEvent.pageShow(window, { persisted: true }); + fireEvent.pageShow(window, { persisted: true }); + + expect(mockHandler).toHaveBeenCalledTimes(1); + }); + }); + describe('delegate', () => { it('should call the native addEventListener method on the element', () => { const mockElement = document.createElement('div'); diff --git a/assets/js/utils/events.ts b/assets/js/utils/events.ts index 458df039..f9d31c29 100644 --- a/assets/js/utils/events.ts +++ b/assets/js/utils/events.ts @@ -54,6 +54,23 @@ export function mouseMoveThenOver(element: El, func: (e: ); } +export function oncePersistedPageShown(func: (e: PageTransitionEvent) => void) { + const controller = new AbortController(); + + window.addEventListener( + 'pageshow', + (e: PageTransitionEvent) => { + if (!e.persisted) { + return; + } + + controller.abort(); + func(e); + }, + { signal: controller.signal }, + ); +} + export function delegate( node: PhilomenaEventElement, event: K,