diff --git a/app/javascript/entrypoints/admin.tsx b/app/javascript/entrypoints/admin.tsx index af9309d342c..a2c53d472b2 100644 --- a/app/javascript/entrypoints/admin.tsx +++ b/app/javascript/entrypoints/admin.tsx @@ -1,7 +1,7 @@ import { createRoot } from 'react-dom/client'; -import Rails from '@rails/ujs'; import { decode, ValidationError } from 'blurhash'; +import { on } from 'delegated-events'; import ready from '../mastodon/ready'; @@ -24,10 +24,9 @@ const setAnnouncementEndsAttributes = (target: HTMLInputElement) => { } }; -Rails.delegate( - document, - 'input[type="datetime-local"]#announcement_starts_at', +on( 'change', + 'input[type="datetime-local"]#announcement_starts_at', ({ target }) => { if (target instanceof HTMLInputElement) setAnnouncementEndsAttributes(target); @@ -63,7 +62,7 @@ const hideSelectAll = () => { if (hiddenField) hiddenField.value = '0'; }; -Rails.delegate(document, '#batch_checkbox_all', 'change', ({ target }) => { +on('change', '#batch_checkbox_all', ({ target }) => { if (!(target instanceof HTMLInputElement)) return; const selectAllMatchingElement = document.querySelector( @@ -85,7 +84,7 @@ Rails.delegate(document, '#batch_checkbox_all', 'change', ({ target }) => { } }); -Rails.delegate(document, '.batch-table__select-all button', 'click', () => { +on('click', '.batch-table__select-all button', () => { const hiddenField = document.querySelector( '#select_all_matching', ); @@ -113,7 +112,7 @@ Rails.delegate(document, '.batch-table__select-all button', 'click', () => { } }); -Rails.delegate(document, batchCheckboxClassName, 'change', () => { +on('change', batchCheckboxClassName, () => { const checkAllElement = document.querySelector( 'input#batch_checkbox_all', ); @@ -140,14 +139,9 @@ Rails.delegate(document, batchCheckboxClassName, 'change', () => { } }); -Rails.delegate( - document, - '.filter-subset--with-select select', - 'change', - ({ target }) => { - if (target instanceof HTMLSelectElement) target.form?.submit(); - }, -); +on('change', '.filter-subset--with-select select', ({ target }) => { + if (target instanceof HTMLSelectElement) target.form?.submit(); +}); const onDomainBlockSeverityChange = (target: HTMLSelectElement) => { const rejectMediaDiv = document.querySelector( @@ -168,11 +162,11 @@ const onDomainBlockSeverityChange = (target: HTMLSelectElement) => { } }; -Rails.delegate(document, '#domain_block_severity', 'change', ({ target }) => { +on('change', '#domain_block_severity', ({ target }) => { if (target instanceof HTMLSelectElement) onDomainBlockSeverityChange(target); }); -const onEnableBootstrapTimelineAccountsChange = (target: HTMLInputElement) => { +function onEnableBootstrapTimelineAccountsChange(target: HTMLInputElement) { const bootstrapTimelineAccountsField = document.querySelector( '#form_admin_settings_bootstrap_timeline_accounts', @@ -194,12 +188,11 @@ const onEnableBootstrapTimelineAccountsChange = (target: HTMLInputElement) => { ); } } -}; +} -Rails.delegate( - document, - '#form_admin_settings_enable_bootstrap_timeline_accounts', +on( 'change', + '#form_admin_settings_enable_bootstrap_timeline_accounts', ({ target }) => { if (target instanceof HTMLInputElement) onEnableBootstrapTimelineAccountsChange(target); @@ -239,11 +232,11 @@ const onChangeRegistrationMode = (target: HTMLSelectElement) => { }); }; -const convertUTCDateTimeToLocal = (value: string) => { +function convertUTCDateTimeToLocal(value: string) { const date = new Date(value + 'Z'); const twoChars = (x: number) => x.toString().padStart(2, '0'); return `${date.getFullYear()}-${twoChars(date.getMonth() + 1)}-${twoChars(date.getDate())}T${twoChars(date.getHours())}:${twoChars(date.getMinutes())}`; -}; +} function convertLocalDatetimeToUTC(value: string) { const date = new Date(value); @@ -251,14 +244,9 @@ function convertLocalDatetimeToUTC(value: string) { return fullISO8601.slice(0, fullISO8601.indexOf('T') + 6); } -Rails.delegate( - document, - '#form_admin_settings_registrations_mode', - 'change', - ({ target }) => { - if (target instanceof HTMLSelectElement) onChangeRegistrationMode(target); - }, -); +on('change', '#form_admin_settings_registrations_mode', ({ target }) => { + if (target instanceof HTMLSelectElement) onChangeRegistrationMode(target); +}); async function mountReactComponent(element: Element) { const componentName = element.getAttribute('data-admin-component'); @@ -305,7 +293,7 @@ ready(() => { if (registrationMode) onChangeRegistrationMode(registrationMode); const checkAllElement = document.querySelector( - 'input#batch_checkbox_all', + '#batch_checkbox_all', ); if (checkAllElement) { const allCheckboxes = Array.from( @@ -318,7 +306,7 @@ ready(() => { } document - .querySelector('a#add-instance-button') + .querySelector('a#add-instance-button') ?.addEventListener('click', (e) => { const domain = document.querySelector( 'input[type="text"]#by_domain', @@ -342,7 +330,7 @@ ready(() => { } }); - Rails.delegate(document, 'form', 'submit', ({ target }) => { + on('submit', 'form', ({ target }) => { if (target instanceof HTMLFormElement) target .querySelectorAll('input[type="datetime-local"]') diff --git a/app/javascript/entrypoints/public.tsx b/app/javascript/entrypoints/public.tsx index dd1956446da..6e88eb87780 100644 --- a/app/javascript/entrypoints/public.tsx +++ b/app/javascript/entrypoints/public.tsx @@ -4,8 +4,8 @@ import { IntlMessageFormat } from 'intl-messageformat'; import type { MessageDescriptor, PrimitiveType } from 'react-intl'; import { defineMessages } from 'react-intl'; -import Rails from '@rails/ujs'; import axios from 'axios'; +import { on } from 'delegated-events'; import { throttle } from 'lodash'; import { timeAgoString } from '../mastodon/components/relative_timestamp'; @@ -175,10 +175,9 @@ function loaded() { }); } - Rails.delegate( - document, - 'input#user_account_attributes_username', + on( 'input', + 'input#user_account_attributes_username', throttle( ({ target }) => { if (!(target instanceof HTMLInputElement)) return; @@ -202,60 +201,47 @@ function loaded() { ), ); - Rails.delegate( - document, - '#user_password,#user_password_confirmation', - 'input', - () => { - const password = document.querySelector( - 'input#user_password', - ); - const confirmation = document.querySelector( - 'input#user_password_confirmation', - ); - if (!confirmation || !password) return; + on('input', '#user_password,#user_password_confirmation', () => { + const password = document.querySelector( + 'input#user_password', + ); + const confirmation = document.querySelector( + 'input#user_password_confirmation', + ); + if (!confirmation || !password) return; - if ( - confirmation.value && - confirmation.value.length > password.maxLength - ) { - confirmation.setCustomValidity( - formatMessage(messages.passwordExceedsLength), - ); - } else if (password.value && password.value !== confirmation.value) { - confirmation.setCustomValidity( - formatMessage(messages.passwordDoesNotMatch), - ); - } else { - confirmation.setCustomValidity(''); - } - }, - ); + if (confirmation.value && confirmation.value.length > password.maxLength) { + confirmation.setCustomValidity( + formatMessage(messages.passwordExceedsLength), + ); + } else if (password.value && password.value !== confirmation.value) { + confirmation.setCustomValidity( + formatMessage(messages.passwordDoesNotMatch), + ); + } else { + confirmation.setCustomValidity(''); + } + }); } -Rails.delegate( - document, - '#edit_profile input[type=file]', - 'change', - ({ target }) => { - if (!(target instanceof HTMLInputElement)) return; +on('change', '#edit_profile input[type=file]', ({ target }) => { + if (!(target instanceof HTMLInputElement)) return; - const avatar = document.querySelector( - `img#${target.id}-preview`, - ); + const avatar = document.querySelector( + `img#${target.id}-preview`, + ); - if (!avatar) return; + if (!avatar) return; - let file: File | undefined; - if (target.files) file = target.files[0]; + let file: File | undefined; + if (target.files) file = target.files[0]; - const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; + const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc; - if (url) avatar.src = url; - }, -); + if (url) avatar.src = url; +}); -Rails.delegate(document, '.input-copy input', 'click', ({ target }) => { +on('click', '.input-copy input', ({ target }) => { if (!(target instanceof HTMLInputElement)) return; target.focus(); @@ -263,7 +249,7 @@ Rails.delegate(document, '.input-copy input', 'click', ({ target }) => { target.setSelectionRange(0, target.value.length); }); -Rails.delegate(document, '.input-copy button', 'click', ({ target }) => { +on('click', '.input-copy button', ({ target }) => { if (!(target instanceof HTMLButtonElement)) return; const input = target.parentNode?.querySelector( @@ -312,22 +298,22 @@ const toggleSidebar = () => { sidebar.classList.toggle('visible'); }; -Rails.delegate(document, '.sidebar__toggle__icon', 'click', () => { +on('click', '.sidebar__toggle__icon', () => { toggleSidebar(); }); -Rails.delegate(document, '.sidebar__toggle__icon', 'keydown', (e) => { +on('keydown', '.sidebar__toggle__icon', (e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); toggleSidebar(); } }); -Rails.delegate(document, 'img.custom-emoji', 'mouseover', ({ target }) => { +on('mouseover', 'img.custom-emoji', ({ target }) => { if (target instanceof HTMLImageElement && target.dataset.original) target.src = target.dataset.original; }); -Rails.delegate(document, 'img.custom-emoji', 'mouseout', ({ target }) => { +on('mouseout', 'img.custom-emoji', ({ target }) => { if (target instanceof HTMLImageElement && target.dataset.static) target.src = target.dataset.static; }); @@ -376,22 +362,17 @@ const setInputHint = ( } }; -Rails.delegate( - document, - '#account_statuses_cleanup_policy_enabled', - 'change', - ({ target }) => { - if (!(target instanceof HTMLInputElement) || !target.form) return; +on('change', '#account_statuses_cleanup_policy_enabled', ({ target }) => { + if (!(target instanceof HTMLInputElement) || !target.form) return; - target.form - .querySelectorAll< - HTMLInputElement | HTMLSelectElement - >('input:not([type=hidden], #account_statuses_cleanup_policy_enabled), select') - .forEach((input) => { - setInputDisabled(input, !target.checked); - }); - }, -); + target.form + .querySelectorAll< + HTMLInputElement | HTMLSelectElement + >('input:not([type=hidden], #account_statuses_cleanup_policy_enabled), select') + .forEach((input) => { + setInputDisabled(input, !target.checked); + }); +}); const updateDefaultQuotePrivacyFromPrivacy = ( privacySelect: EventTarget | null, @@ -414,18 +395,13 @@ const updateDefaultQuotePrivacyFromPrivacy = ( } }; -Rails.delegate( - document, - '#user_settings_attributes_default_privacy', - 'change', - ({ target }) => { - updateDefaultQuotePrivacyFromPrivacy(target); - }, -); +on('change', '#user_settings_attributes_default_privacy', ({ target }) => { + updateDefaultQuotePrivacyFromPrivacy(target); +}); // Empty the honeypot fields in JS in case something like an extension // automatically filled them. -Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => { +on('submit', '#registration_new_user,#new_user', () => { [ 'user_website', 'user_confirm_password', @@ -439,7 +415,7 @@ Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => { }); }); -Rails.delegate(document, '.rules-list button', 'click', ({ target }) => { +on('click', '.rules-list button', ({ target }) => { if (!(target instanceof HTMLElement)) { return; } diff --git a/package.json b/package.json index f502d015a12..c5fa667d527 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "core-js": "^3.30.2", "cross-env": "^10.0.0", "debug": "^4.4.1", + "delegated-events": "^1.1.2", "detect-passive-events": "^2.0.3", "emoji-mart": "npm:emoji-mart-lazyload@latest", "emojibase": "^16.0.0", diff --git a/yarn.lock b/yarn.lock index fd9db032c2d..9d765b86694 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2764,6 +2764,7 @@ __metadata: core-js: "npm:^3.30.2" cross-env: "npm:^10.0.0" debug: "npm:^4.4.1" + delegated-events: "npm:^1.1.2" detect-passive-events: "npm:^2.0.3" emoji-mart: "npm:emoji-mart-lazyload@latest" emojibase: "npm:^16.0.0" @@ -6388,6 +6389,15 @@ __metadata: languageName: node linkType: hard +"delegated-events@npm:^1.1.2": + version: 1.1.2 + resolution: "delegated-events@npm:1.1.2" + dependencies: + selector-set: "npm:^1.1.5" + checksum: 10c0/b295a6d6c6cef4b9312bfd4132ac3a1255f3c2fadf3692a04cf7ddf8f0d472bfce9de06323faa75e922d20e5674d45022e1a5378b8500cd7d573ffa0cf7ca602 + languageName: node + linkType: hard + "denque@npm:^2.1.0": version: 2.1.0 resolution: "denque@npm:2.1.0" @@ -12153,6 +12163,13 @@ __metadata: languageName: node linkType: hard +"selector-set@npm:^1.1.5": + version: 1.1.5 + resolution: "selector-set@npm:1.1.5" + checksum: 10c0/4835907846eb8496c2cc4e5ce48355cce1ef3b55e82789542739dcdc71ebfb756e133d1d7f7ec9f0cf4b1c11fc0375a1d3b99a482d9c973493ca85a6d4b012ab + languageName: node + linkType: hard + "semver@npm:^5.6.0": version: 5.7.2 resolution: "semver@npm:5.7.2"