123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- import { createRoot } from 'react-dom/client';
- import './public-path';
- 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 { throttle } from 'lodash';
- import { start } from '../mastodon/common';
- import { timeAgoString } from '../mastodon/components/relative_timestamp';
- import emojify from '../mastodon/features/emoji/emoji';
- import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions';
- import { loadLocale, getLocale } from '../mastodon/locales';
- import { loadPolyfills } from '../mastodon/polyfills';
- import ready from '../mastodon/ready';
- import 'cocoon-js-vanilla';
- start();
- const messages = defineMessages({
- usernameTaken: {
- id: 'username.taken',
- defaultMessage: 'That username is taken. Try another',
- },
- passwordExceedsLength: {
- id: 'password_confirmation.exceeds_maxlength',
- defaultMessage: 'Password confirmation exceeds the maximum password length',
- },
- passwordDoesNotMatch: {
- id: 'password_confirmation.mismatching',
- defaultMessage: 'Password confirmation does not match',
- },
- });
- function loaded() {
- const { messages: localeData } = getLocale();
- const locale = document.documentElement.lang;
- const dateTimeFormat = new Intl.DateTimeFormat(locale, {
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- hour: 'numeric',
- minute: 'numeric',
- });
- const dateFormat = new Intl.DateTimeFormat(locale, {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- });
- const timeFormat = new Intl.DateTimeFormat(locale, {
- timeStyle: 'short',
- });
- const formatMessage = (
- { id, defaultMessage }: MessageDescriptor,
- values?: Record<string, PrimitiveType>,
- ) => {
- let message: string | undefined = undefined;
- if (id) message = localeData[id];
- if (!message) message = defaultMessage as string;
- const messageFormat = new IntlMessageFormat(message, locale);
- return messageFormat.format(values) as string;
- };
- document.querySelectorAll('.emojify').forEach((content) => {
- content.innerHTML = emojify(content.innerHTML);
- });
- document
- .querySelectorAll<HTMLTimeElement>('time.formatted')
- .forEach((content) => {
- const datetime = new Date(content.dateTime);
- const formattedDate = dateTimeFormat.format(datetime);
- content.title = formattedDate;
- content.textContent = formattedDate;
- });
- const isToday = (date: Date) => {
- const today = new Date();
- return (
- date.getDate() === today.getDate() &&
- date.getMonth() === today.getMonth() &&
- date.getFullYear() === today.getFullYear()
- );
- };
- const todayFormat = new IntlMessageFormat(
- localeData['relative_format.today'] ?? 'Today at {time}',
- locale,
- );
- document
- .querySelectorAll<HTMLTimeElement>('time.relative-formatted')
- .forEach((content) => {
- const datetime = new Date(content.dateTime);
- let formattedContent: string;
- if (isToday(datetime)) {
- const formattedTime = timeFormat.format(datetime);
- formattedContent = todayFormat.format({
- time: formattedTime,
- }) as string;
- } else {
- formattedContent = dateFormat.format(datetime);
- }
- content.title = formattedContent;
- content.textContent = formattedContent;
- });
- document
- .querySelectorAll<HTMLTimeElement>('time.time-ago')
- .forEach((content) => {
- const datetime = new Date(content.dateTime);
- const now = new Date();
- const timeGiven = content.dateTime.includes('T');
- content.title = timeGiven
- ? dateTimeFormat.format(datetime)
- : dateFormat.format(datetime);
- content.textContent = timeAgoString(
- {
- formatMessage,
- formatDate: (date: Date, options) =>
- new Intl.DateTimeFormat(locale, options).format(date),
- },
- datetime,
- now.getTime(),
- now.getFullYear(),
- timeGiven,
- );
- });
- const reactComponents = document.querySelectorAll('[data-component]');
- if (reactComponents.length > 0) {
- import(
- /* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container'
- )
- .then(({ default: MediaContainer }) => {
- reactComponents.forEach((component) => {
- Array.from(component.children).forEach((child) => {
- component.removeChild(child);
- });
- });
- const content = document.createElement('div');
- const root = createRoot(content);
- root.render(
- <MediaContainer locale={locale} components={reactComponents} />,
- );
- document.body.appendChild(content);
- return true;
- })
- .catch((error: unknown) => {
- console.error(error);
- });
- }
- Rails.delegate(
- document,
- 'input#user_account_attributes_username',
- 'input',
- throttle(
- ({ target }) => {
- if (!(target instanceof HTMLInputElement)) return;
- if (target.value && target.value.length > 0) {
- axios
- .get('/api/v1/accounts/lookup', { params: { acct: target.value } })
- .then(() => {
- target.setCustomValidity(formatMessage(messages.usernameTaken));
- return true;
- })
- .catch(() => {
- target.setCustomValidity('');
- });
- } else {
- target.setCustomValidity('');
- }
- },
- 500,
- { leading: false, trailing: true },
- ),
- );
- Rails.delegate(
- document,
- '#user_password,#user_password_confirmation',
- 'input',
- () => {
- const password = document.querySelector<HTMLInputElement>(
- 'input#user_password',
- );
- const confirmation = document.querySelector<HTMLInputElement>(
- '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('');
- }
- },
- );
- Rails.delegate(
- document,
- 'button.status__content__spoiler-link',
- 'click',
- function () {
- if (!(this instanceof HTMLButtonElement)) return;
- const statusEl = this.parentNode?.parentNode;
- if (
- !(
- statusEl instanceof HTMLDivElement &&
- statusEl.classList.contains('.status__content')
- )
- )
- return;
- if (statusEl.dataset.spoiler === 'expanded') {
- statusEl.dataset.spoiler = 'folded';
- this.textContent = new IntlMessageFormat(
- localeData['status.show_more'] ?? 'Show more',
- locale,
- ).format() as string;
- } else {
- statusEl.dataset.spoiler = 'expanded';
- this.textContent = new IntlMessageFormat(
- localeData['status.show_less'] ?? 'Show less',
- locale,
- ).format() as string;
- }
- },
- );
- document
- .querySelectorAll<HTMLButtonElement>('button.status__content__spoiler-link')
- .forEach((spoilerLink) => {
- const statusEl = spoilerLink.parentNode?.parentNode;
- if (
- !(
- statusEl instanceof HTMLDivElement &&
- statusEl.classList.contains('.status__content')
- )
- )
- return;
- const message =
- statusEl.dataset.spoiler === 'expanded'
- ? (localeData['status.show_less'] ?? 'Show less')
- : (localeData['status.show_more'] ?? 'Show more');
- spoilerLink.textContent = new IntlMessageFormat(
- message,
- locale,
- ).format() as string;
- });
- }
- Rails.delegate(
- document,
- '#edit_profile input[type=file]',
- 'change',
- ({ target }) => {
- if (!(target instanceof HTMLInputElement)) return;
- const avatar = document.querySelector<HTMLImageElement>(
- `img#${target.id}-preview`,
- );
- if (!avatar) return;
- let file: File | undefined;
- if (target.files) file = target.files[0];
- const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
- if (url) avatar.src = url;
- },
- );
- Rails.delegate(document, '.input-copy input', 'click', ({ target }) => {
- if (!(target instanceof HTMLInputElement)) return;
- target.focus();
- target.select();
- target.setSelectionRange(0, target.value.length);
- });
- Rails.delegate(document, '.input-copy button', 'click', ({ target }) => {
- if (!(target instanceof HTMLButtonElement)) return;
- const input = target.parentNode?.querySelector<HTMLInputElement>(
- '.input-copy__wrapper input',
- );
- if (!input) return;
- navigator.clipboard
- .writeText(input.value)
- .then(() => {
- const parent = target.parentElement;
- if (parent) {
- parent.classList.add('copied');
- setTimeout(() => {
- parent.classList.remove('copied');
- }, 700);
- }
- return true;
- })
- .catch((error: unknown) => {
- console.error(error);
- });
- });
- const toggleSidebar = () => {
- const sidebar = document.querySelector<HTMLUListElement>('.sidebar ul');
- const toggleButton = document.querySelector<HTMLAnchorElement>(
- 'a.sidebar__toggle__icon',
- );
- if (!sidebar || !toggleButton) return;
- if (sidebar.classList.contains('visible')) {
- document.body.style.overflow = '';
- toggleButton.setAttribute('aria-expanded', 'false');
- } else {
- document.body.style.overflow = 'hidden';
- toggleButton.setAttribute('aria-expanded', 'true');
- }
- toggleButton.classList.toggle('active');
- sidebar.classList.toggle('visible');
- };
- Rails.delegate(document, '.sidebar__toggle__icon', 'click', () => {
- toggleSidebar();
- });
- Rails.delegate(document, '.sidebar__toggle__icon', 'keydown', (e) => {
- if (e.key === ' ' || e.key === 'Enter') {
- e.preventDefault();
- toggleSidebar();
- }
- });
- Rails.delegate(document, 'img.custom-emoji', 'mouseover', ({ target }) => {
- if (target instanceof HTMLImageElement && target.dataset.original)
- target.src = target.dataset.original;
- });
- Rails.delegate(document, 'img.custom-emoji', 'mouseout', ({ target }) => {
- if (target instanceof HTMLImageElement && target.dataset.static)
- target.src = target.dataset.static;
- });
- const setInputDisabled = (
- input: HTMLInputElement | HTMLSelectElement,
- disabled: boolean,
- ) => {
- input.disabled = disabled;
- const wrapper = input.closest('.with_label');
- if (wrapper) {
- wrapper.classList.toggle('disabled', input.disabled);
- const hidden =
- input.type === 'checkbox' &&
- wrapper.querySelector<HTMLInputElement>('input[type=hidden][value="0"]');
- if (hidden) {
- hidden.disabled = input.disabled;
- }
- }
- };
- Rails.delegate(
- document,
- '#account_statuses_cleanup_policy_enabled',
- 'change',
- ({ 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);
- });
- },
- );
- // Empty the honeypot fields in JS in case something like an extension
- // automatically filled them.
- Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => {
- [
- 'user_website',
- 'user_confirm_password',
- 'registration_user_website',
- 'registration_user_confirm_password',
- ].forEach((id) => {
- const field = document.querySelector<HTMLInputElement>(`input#${id}`);
- if (field) {
- field.value = '';
- }
- });
- });
- function main() {
- ready(loaded).catch((error: unknown) => {
- console.error(error);
- });
- }
- loadPolyfills()
- .then(loadLocale)
- .then(main)
- .then(loadKeyboardExtensions)
- .catch((error: unknown) => {
- console.error(error);
- });
|