From 8dcb8688983c79df1da0e91d49006608c21cd21f Mon Sep 17 00:00:00 2001 From: ChaosExAnima Date: Thu, 27 Nov 2025 13:07:06 +0100 Subject: [PATCH] replace ujs data-method and data-confirm functionality --- app/javascript/mastodon/common.ts | 8 +-- app/javascript/mastodon/utils/links.ts | 88 ++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 app/javascript/mastodon/utils/links.ts diff --git a/app/javascript/mastodon/common.ts b/app/javascript/mastodon/common.ts index e621a24e39f..33d2b5ad171 100644 --- a/app/javascript/mastodon/common.ts +++ b/app/javascript/mastodon/common.ts @@ -1,9 +1,5 @@ -import Rails from '@rails/ujs'; +import { setupLinkListeners } from './utils/links'; export function start() { - try { - Rails.start(); - } catch { - // If called twice - } + setupLinkListeners(); } diff --git a/app/javascript/mastodon/utils/links.ts b/app/javascript/mastodon/utils/links.ts new file mode 100644 index 00000000000..02b74bde4dd --- /dev/null +++ b/app/javascript/mastodon/utils/links.ts @@ -0,0 +1,88 @@ +import { on } from 'delegated-events'; + +export function setupLinkListeners() { + on('click', 'a[data-confirm]', handleConfirmLink); + + // We don't want to target links with data-confirm here, as those are handled already. + on('click', 'a[data-method]:not([data-confirm])', handleMethodLink); +} + +function handleConfirmLink(event: MouseEvent) { + const anchor = event.currentTarget; + if (!(anchor instanceof HTMLAnchorElement)) { + return; + } + const message = anchor.dataset.confirm; + if (!message || !window.confirm(message)) { + event.preventDefault(); + return; + } + + if (anchor.dataset.method) { + handleMethodLink(event); + } +} + +function handleMethodLink(event: MouseEvent) { + const anchor = event.currentTarget; + if (!(anchor instanceof HTMLAnchorElement)) { + return; + } + + const method = anchor.dataset.method?.toLowerCase(); + if (!method) { + return; + } + event.preventDefault(); + + // Create and submit a form with the specified method. + const form = document.createElement('form'); + form.method = 'post'; + form.action = anchor.href; + + // Add the hidden _method input to simulate other HTTP methods. + const methodInput = document.createElement('input'); + methodInput.type = 'hidden'; + methodInput.name = '_method'; + methodInput.value = method; + form.appendChild(methodInput); + + // Add CSRF token if available for same-origin requests. + const csrf = getCSRFToken(); + if (csrf && !isCrossDomain(anchor.href)) { + const csrfInput = document.createElement('input'); + csrfInput.type = 'hidden'; + csrfInput.name = csrf.param; + csrfInput.value = csrf.token; + form.appendChild(csrfInput); + } + + // The form needs to be in the document to be submitted. + form.style.display = 'none'; + document.body.appendChild(form); + + // We use requestSubmit to ensure any form submit handlers are properly invoked. + form.requestSubmit(); +} + +function getCSRFToken() { + const param = document.querySelector( + 'meta[name="csrf-param"]', + ); + const token = document.querySelector( + 'meta[name="csrf-token"]', + ); + if (param && token) { + return { param: param.content, token: token.content }; + } + return null; +} + +function isCrossDomain(href: string) { + const link = document.createElement('a'); + link.href = href; + return ( + link.protocol !== window.location.protocol || + link.host !== window.location.host + ); +}