Merge commit 'b7902225d698a107df2cf8b4ca221caad38fa464' into glitch-soc/merge-upstream

Conflicts:
- `spec/validators/status_length_validator_spec.rb`:
  Upstream refactored tests to stub `StatusLengthValidator::MAX_CHARS`
  while glitch-soc had custom code to read from `MAX_TOOT_CHARS`.
  Switched to using upstream's implementation of the tests.
This commit is contained in:
Claire committed 2024-05-04 16:28:30 +02:00
commit 3789d9f825
49 files changed
+911 -205

No files matched your search

@@ -1,119 +0,0 @@
import * as WebAuthnJSON from '@github/webauthn-json';
import axios from 'axios';
import ready from '../mastodon/ready';
import 'regenerator-runtime/runtime';
function getCSRFToken() {
var CSRFSelector = document.querySelector('meta[name="csrf-token"]');
if (CSRFSelector) {
return CSRFSelector.getAttribute('content');
} else {
return null;
}
}
function hideFlashMessages() {
Array.from(document.getElementsByClassName('flash-message')).forEach(function(flashMessage) {
flashMessage.classList.add('hidden');
});
}
function callback(url, body) {
axios.post(url, JSON.stringify(body), {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-Token': getCSRFToken(),
},
credentials: 'same-origin',
}).then(function(response) {
window.location.replace(response.data.redirect_path);
}).catch(function(error) {
if (error.response.status === 422) {
const errorMessage = document.getElementById('security-key-error-message');
errorMessage.classList.remove('hidden');
console.error(error.response.data.error);
} else {
console.error(error);
}
});
}
ready(() => {
if (!WebAuthnJSON.supported()) {
const unsupported_browser_message = document.getElementById('unsupported-browser-message');
if (unsupported_browser_message) {
unsupported_browser_message.classList.remove('hidden');
document.querySelector('.btn.js-webauthn').disabled = true;
}
}
const webAuthnCredentialRegistrationForm = document.getElementById('new_webauthn_credential');
if (webAuthnCredentialRegistrationForm) {
webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => {
event.preventDefault();
var nickname = event.target.querySelector('input[name="new_webauthn_credential[nickname]"]');
if (nickname.value) {
axios.get('/settings/security_keys/options')
.then((response) => {
const credentialOptions = response.data;
WebAuthnJSON.create({ 'publicKey': credentialOptions }).then((credential) => {
var params = { 'credential': credential, 'nickname': nickname.value };
callback('/settings/security_keys', params);
}).catch((error) => {
const errorMessage = document.getElementById('security-key-error-message');
errorMessage.classList.remove('hidden');
console.error(error);
});
}).catch((error) => {
console.error(error.response.data.error);
});
} else {
nickname.focus();
}
});
}
const webAuthnCredentialAuthenticationForm = document.getElementById('webauthn-form');
if (webAuthnCredentialAuthenticationForm) {
webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => {
event.preventDefault();
axios.get('sessions/security_key_options')
.then((response) => {
const credentialOptions = response.data;
WebAuthnJSON.get({ 'publicKey': credentialOptions }).then((credential) => {
var params = { 'user': { 'credential': credential } };
callback('sign_in', params);
}).catch((error) => {
const errorMessage = document.getElementById('security-key-error-message');
errorMessage.classList.remove('hidden');
console.error(error);
});
}).catch((error) => {
console.error(error.response.data.error);
});
});
const otpAuthenticationForm = document.getElementById('otp-authentication-form');
const linkToOtp = document.getElementById('link-to-otp');
linkToOtp.addEventListener('click', () => {
webAuthnCredentialAuthenticationForm.classList.add('hidden');
otpAuthenticationForm.classList.remove('hidden');
hideFlashMessages();
});
const linkToWebAuthn = document.getElementById('link-to-webauthn');
linkToWebAuthn.addEventListener('click', () => {
otpAuthenticationForm.classList.add('hidden');
webAuthnCredentialAuthenticationForm.classList.remove('hidden');
hideFlashMessages();
});
}
});
@@ -0,0 +1,197 @@
import * as WebAuthnJSON from '@github/webauthn-json';
import axios, { AxiosError } from 'axios';
import ready from '../mastodon/ready';
import 'regenerator-runtime/runtime';
type PublicKeyCredentialCreationOptionsJSON =
WebAuthnJSON.CredentialCreationOptionsJSON['publicKey'];
function exceptionHasAxiosError(
error: unknown,
): error is AxiosError<{ error: unknown }> {
return (
error instanceof AxiosError &&
typeof error.response?.data === 'object' &&
'error' in error.response.data
);
}
function logAxiosResponseError(error: unknown) {
if (exceptionHasAxiosError(error)) console.error(error);
}
function getCSRFToken() {
return document
.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')
?.getAttribute('content');
}
function hideFlashMessages() {
document.querySelectorAll('.flash-message').forEach((flashMessage) => {
flashMessage.classList.add('hidden');
});
}
async function callback(
url: string,
body:
| {
credential: WebAuthnJSON.PublicKeyCredentialWithAttestationJSON;
nickname: string;
}
| {
user: { credential: WebAuthnJSON.PublicKeyCredentialWithAssertionJSON };
},
) {
try {
const response = await axios.post<{ redirect_path: string }>(
url,
JSON.stringify(body),
{
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'X-CSRF-Token': getCSRFToken(),
},
},
);
window.location.replace(response.data.redirect_path);
} catch (error) {
if (error instanceof AxiosError && error.response?.status === 422) {
const errorMessage = document.getElementById(
'security-key-error-message',
);
errorMessage?.classList.remove('hidden');
logAxiosResponseError(error);
} else {
console.error(error);
}
}
}
async function handleWebauthnCredentialRegistration(nickname: string) {
try {
const response = await axios.get<PublicKeyCredentialCreationOptionsJSON>(
'/settings/security_keys/options',
);
const credentialOptions = response.data;
try {
const credential = await WebAuthnJSON.create({
publicKey: credentialOptions,
});
const params = {
credential: credential,
nickname: nickname,
};
await callback('/settings/security_keys', params);
} catch (error) {
const errorMessage = document.getElementById(
'security-key-error-message',
);
errorMessage?.classList.remove('hidden');
console.error(error);
}
} catch (error) {
logAxiosResponseError(error);
}
}
async function handleWebauthnCredentialAuthentication() {
try {
const response = await axios.get<PublicKeyCredentialCreationOptionsJSON>(
'sessions/security_key_options',
);
const credentialOptions = response.data;
try {
const credential = await WebAuthnJSON.get({
publicKey: credentialOptions,
});
const params = { user: { credential: credential } };
void callback('sign_in', params);
} catch (error) {
const errorMessage = document.getElementById(
'security-key-error-message',
);
errorMessage?.classList.remove('hidden');
console.error(error);
}
} catch (error) {
logAxiosResponseError(error);
}
}
ready(() => {
if (!WebAuthnJSON.supported()) {
const unsupported_browser_message = document.getElementById(
'unsupported-browser-message',
);
if (unsupported_browser_message) {
unsupported_browser_message.classList.remove('hidden');
const button = document.querySelector<HTMLButtonElement>(
'button.btn.js-webauthn',
);
if (button) button.disabled = true;
}
}
const webAuthnCredentialRegistrationForm =
document.querySelector<HTMLFormElement>('form#new_webauthn_credential');
if (webAuthnCredentialRegistrationForm) {
webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => {
event.preventDefault();
if (!(event.target instanceof HTMLFormElement)) return;
const nickname = event.target.querySelector<HTMLInputElement>(
'input[name="new_webauthn_credential[nickname]"]',
);
if (nickname?.value) {
void handleWebauthnCredentialRegistration(nickname.value);
} else {
nickname?.focus();
}
});
}
const webAuthnCredentialAuthenticationForm =
document.getElementById('webauthn-form');
if (webAuthnCredentialAuthenticationForm) {
webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => {
event.preventDefault();
void handleWebauthnCredentialAuthentication();
});
const otpAuthenticationForm = document.getElementById(
'otp-authentication-form',
);
const linkToOtp = document.getElementById('link-to-otp');
linkToOtp?.addEventListener('click', () => {
webAuthnCredentialAuthenticationForm.classList.add('hidden');
otpAuthenticationForm?.classList.remove('hidden');
hideFlashMessages();
});
const linkToWebAuthn = document.getElementById('link-to-webauthn');
linkToWebAuthn?.addEventListener('click', () => {
otpAuthenticationForm?.classList.add('hidden');
webAuthnCredentialAuthenticationForm.classList.remove('hidden');
hideFlashMessages();
});
}
}).catch((e: unknown) => {
throw e;
});
+12
View File
@@ -308,6 +308,8 @@
"follow_requests.unlocked_explanation": "Benque tu conto non es serrate, le personal de {domain} pensa que es un bon idea que tu revide manualmente le sequente requestas de iste contos.",
"follow_suggestions.curated_suggestion": "Selection del equipa",
"follow_suggestions.dismiss": "Non monstrar novemente",
"follow_suggestions.featured_longer": "Seligite con cura per le equipa de {domain}",
"follow_suggestions.friends_of_friends_longer": "Popular inter le gente que tu seque",
"follow_suggestions.hints.featured": "Iste profilo ha essite seligite manualmente per le equipa de {domain}.",
"follow_suggestions.hints.friends_of_friends": "Iste profilo es popular inter le gente que tu seque.",
"follow_suggestions.hints.most_followed": "Iste profilo es un del plus sequites sur {domain}.",
@@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Iste profilo es similar al profilos que tu ha recentemente sequite.",
"follow_suggestions.personalized_suggestion": "Suggestion personalisate",
"follow_suggestions.popular_suggestion": "Suggestion personalisate",
"follow_suggestions.popular_suggestion_longer": "Popular sur {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Similar al profilos que tu ha sequite recentemente",
"follow_suggestions.view_all": "Vider toto",
"follow_suggestions.who_to_follow": "Qui sequer",
"followed_tags": "Hashtags sequite",
@@ -470,6 +474,14 @@
"notification.follow_request": "{name} ha requestate de sequer te",
"notification.mention": "{name} te ha mentionate",
"notification.moderation-warning.learn_more": "Apprender plus",
"notification.moderation_warning": "Tu ha recipite un advertimento de moderation",
"notification.moderation_warning.action_delete_statuses": "Alcunes de tu messages ha essite removite.",
"notification.moderation_warning.action_disable": "Tu conto ha essite disactivate.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcunes de tu messages ha essite marcate como sensibile.",
"notification.moderation_warning.action_none": "Tu conto ha recipite un advertimento de moderation.",
"notification.moderation_warning.action_sensitive": "Tu messages essera marcate como sensibile a partir de ora.",
"notification.moderation_warning.action_silence": "Tu conto ha essite limitate.",
"notification.moderation_warning.action_suspend": "Tu conto ha essite suspendite.",
"notification.own_poll": "Tu sondage ha finite",
"notification.poll": "Un sondage in le qual tu ha votate ha finite",
"notification.reblog": "{name} ha impulsate tu message",
@@ -308,6 +308,8 @@
"follow_requests.unlocked_explanation": "Apesar de seu perfil não ser trancado, {domain} exige que você revise a solicitação para te seguir destes perfis manualmente.",
"follow_suggestions.curated_suggestion": "Escolha da equipe",
"follow_suggestions.dismiss": "Não mostrar novamente",
"follow_suggestions.featured_longer": "Escolhido à mão pela equipe de {domain}",
"follow_suggestions.friends_of_friends_longer": "Popular entre as pessoas que você segue",
"follow_suggestions.hints.featured": "Este perfil foi escolhido a dedo pela equipe {domain}.",
"follow_suggestions.hints.friends_of_friends": "Este perfil é popular entre as pessoas que você segue.",
"follow_suggestions.hints.most_followed": "Este perfil é um dos mais seguidos em {domain}.",
@@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Este perfil é semelhante aos perfis que você seguiu recentemente.",
"follow_suggestions.personalized_suggestion": "Sugestão personalizada",
"follow_suggestions.popular_suggestion": "Sugestão popular",
"follow_suggestions.popular_suggestion_longer": "Popular em {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Similar a perfis que você seguiu recentemente",
"follow_suggestions.view_all": "Visualizar tudo",
"follow_suggestions.who_to_follow": "Quem seguir",
"followed_tags": "Hashtags seguidas",
@@ -469,6 +473,15 @@
"notification.follow": "{name} te seguiu",
"notification.follow_request": "{name} quer te seguir",
"notification.mention": "{name} te mencionou",
"notification.moderation-warning.learn_more": "Aprender mais",
"notification.moderation_warning": "Você recebeu um aviso de moderação",
"notification.moderation_warning.action_delete_statuses": "Algumas das suas publicações foram removidas.",
"notification.moderation_warning.action_disable": "Sua conta foi desativada.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Algumas de suas publicações foram marcadas por ter conteúdo sensível.",
"notification.moderation_warning.action_none": "Sua conta recebeu um aviso de moderação.",
"notification.moderation_warning.action_sensitive": "Suas publicações serão marcadas como sensíveis a partir de agora.",
"notification.moderation_warning.action_silence": "Sua conta foi limitada.",
"notification.moderation_warning.action_suspend": "Sua conta foi suspensa.",
"notification.own_poll": "Sua enquete terminou",
"notification.poll": "Uma enquete que você votou terminou",
"notification.reblog": "{name} deu boost no teu toot",
+2
View File
@@ -448,6 +448,7 @@
"notification.own_poll": "Vaša anketa sa skončila",
"notification.poll": "Anketa, v ktorej ste hlasovali, sa skončila",
"notification.reblog": "{name} zdieľa váš príspevok",
"notification.relationships_severance_event": "Stratené prepojenia s {name}",
"notification.relationships_severance_event.learn_more": "Zisti viac",
"notification.status": "{name} uverejňuje niečo nové",
"notification.update": "{name} upravuje príspevok",
@@ -490,6 +491,7 @@
"notifications.policy.filter_new_accounts_title": "Nové účty",
"notifications.policy.filter_not_followers_title": "Ľudia, ktorí ťa nenasledujú",
"notifications.policy.filter_not_following_title": "Ľudia, ktorých nenasleduješ",
"notifications.policy.filter_private_mentions_title": "Nevyžiadané priame spomenutia",
"notifications.policy.title": "Filtrovať oznámenia od…",
"notifications_permission_banner.enable": "Povoliť upozornenia na ploche",
"notifications_permission_banner.how_to_control": "Ak chcete dostávať upozornenia, keď Mastodon nie je otvorený, povoľte upozornenia na ploche. Po ich zapnutí môžete presne kontrolovať, ktoré typy interakcií generujú upozornenia na ploche, a to prostredníctvom tlačidla {icon} vyššie.",
@@ -308,6 +308,8 @@
"follow_requests.unlocked_explanation": "Iako vaš nalog nije zaključan, osoblje {domain} smatra da biste možda želeli da ručno pregledate zahteve za praćenje sa ovih naloga.",
"follow_suggestions.curated_suggestion": "Izbor osoblja",
"follow_suggestions.dismiss": "Ne prikazuj ponovo",
"follow_suggestions.featured_longer": "Ručno odabrao tim {domain}",
"follow_suggestions.friends_of_friends_longer": "Popularno među ljudima koje pratite",
"follow_suggestions.hints.featured": "Ovaj profil je ručno izabrao tim {domain}.",
"follow_suggestions.hints.friends_of_friends": "Ovaj profil je popularan među ljudima koje pratite.",
"follow_suggestions.hints.most_followed": "Ovaj profil je jedan od najpraćenijih na {domain}.",
@@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Ovaj profil je sličan profilima koje ste nedavno zapratili.",
"follow_suggestions.personalized_suggestion": "Personalizovani predlog",
"follow_suggestions.popular_suggestion": "Popularni predlog",
"follow_suggestions.popular_suggestion_longer": "Popularno na {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Slično profilima koje ste nedavno zapratili",
"follow_suggestions.view_all": "Prikaži sve",
"follow_suggestions.who_to_follow": "Koga pratiti",
"followed_tags": "Praćene heš oznake",
@@ -469,6 +473,15 @@
"notification.follow": "{name} vas je zapratio",
"notification.follow_request": "{name} je zatražio da vas prati",
"notification.mention": "{name} vas je pomenuo",
"notification.moderation-warning.learn_more": "Saznajte više",
"notification.moderation_warning": "Dobili ste moderatorsko upozorenje",
"notification.moderation_warning.action_delete_statuses": "Neke od vaših objava su uklonjene.",
"notification.moderation_warning.action_disable": "Vaš nalog je onemogućen.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Neke od vaših objava su obeležene kao osetljive.",
"notification.moderation_warning.action_none": "Vaš nalog je dobio moderatorsko upozorenje.",
"notification.moderation_warning.action_sensitive": "Vaše objave će ubuduće biti označene kao osetljive.",
"notification.moderation_warning.action_silence": "Vaš nalog je ograničen.",
"notification.moderation_warning.action_suspend": "Vaš nalog je suspendovan.",
"notification.own_poll": "Vaša anketa je završena",
"notification.poll": "Završena je anketa u kojoj ste glasali",
"notification.reblog": "{name} je podržao vašu objavu",
+13
View File
@@ -308,6 +308,8 @@
"follow_requests.unlocked_explanation": "Иако ваш налог није закључан, особље {domain} сматра да бисте можда желели да ручно прегледате захтеве за праћење са ових налога.",
"follow_suggestions.curated_suggestion": "Избор особља",
"follow_suggestions.dismiss": "Не приказуј поново",
"follow_suggestions.featured_longer": "Ручно одабрао тим {domain}",
"follow_suggestions.friends_of_friends_longer": "Популарно међу људима које пратите",
"follow_suggestions.hints.featured": "Овај профил је ручно изабрао тим {domain}.",
"follow_suggestions.hints.friends_of_friends": "Овај профил је популаран међу људима које пратите.",
"follow_suggestions.hints.most_followed": "Овај профил је један од најпраћенијих на {domain}.",
@@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Овај профил је сличан профилима које сте недавно запратили.",
"follow_suggestions.personalized_suggestion": "Персонализовани предлог",
"follow_suggestions.popular_suggestion": "Популарни предлог",
"follow_suggestions.popular_suggestion_longer": "Популарно на {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Слично профилима које сте недавно запратили",
"follow_suggestions.view_all": "Прикажи све",
"follow_suggestions.who_to_follow": "Кога пратити",
"followed_tags": "Праћене хеш ознаке",
@@ -469,6 +473,15 @@
"notification.follow": "{name} вас је запратио",
"notification.follow_request": "{name} је затражио да вас прати",
"notification.mention": "{name} вас је поменуо",
"notification.moderation-warning.learn_more": "Сазнајте више",
"notification.moderation_warning": "Добили сте модераторско упозорење",
"notification.moderation_warning.action_delete_statuses": "Неке од ваших објава су уклоњене.",
"notification.moderation_warning.action_disable": "Ваш налог је онемогућен.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Неке од ваших објава су обележене као осетљиве.",
"notification.moderation_warning.action_none": "Ваш налог је добио модераторско упозорење.",
"notification.moderation_warning.action_sensitive": "Ваше објаве ће убудуће бити означене као осетљиве.",
"notification.moderation_warning.action_silence": "Ваш налог је ограничен.",
"notification.moderation_warning.action_suspend": "Ваш налог је суспендован.",
"notification.own_poll": "Ваша анкета је завршена",
"notification.poll": "Завршена је анкета у којој сте гласали",
"notification.reblog": "{name} је подржао вашу објаву",
@@ -10145,6 +10145,7 @@ noscript {
font-weight: 500;
font-size: 11px;
line-height: 16px;
word-break: keep-all;
&__badge {
background: $ui-button-background-color;