mirror of
https://github.com/glitch-soc/mastodon.git
synced 2024-11-28 11:00:26 -05:00
1cd7f5ff17
Conflicts: - `.eslintrc.js`: Upstream moved a configuration block in which we had added a glitch-only path. Moved the configuration block as upstream did. - other files: Upstream reordered imports, and those files had different ones. Kept our version and reordered imports using the same rules.
257 lines
9.0 KiB
JavaScript
257 lines
9.0 KiB
JavaScript
import './public-path';
|
|
|
|
import { createRoot } from 'react-dom/client';
|
|
|
|
import * as IntlMessageFormat from 'intl-messageformat';
|
|
import { defineMessages } from 'react-intl';
|
|
|
|
import { delegate } from '@rails/ujs';
|
|
import axios from 'axios';
|
|
import { createBrowserHistory } from 'history';
|
|
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 { 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 { localeData } = getLocale();
|
|
|
|
const scrollToDetailedStatus = () => {
|
|
const history = createBrowserHistory();
|
|
const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status');
|
|
const location = history.location;
|
|
|
|
if (detailedStatuses.length === 1 && (!location.state || !location.state.scrolledToDetailedStatus)) {
|
|
detailedStatuses[0].scrollIntoView();
|
|
history.replace(location.pathname, { ...location.state, scrolledToDetailedStatus: true });
|
|
}
|
|
};
|
|
|
|
const getEmojiAnimationHandler = (swapTo) => {
|
|
return ({ target }) => {
|
|
target.src = target.getAttribute(swapTo);
|
|
};
|
|
};
|
|
|
|
ready(() => {
|
|
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',
|
|
timeFormat: false,
|
|
});
|
|
|
|
const timeFormat = new Intl.DateTimeFormat(locale, {
|
|
timeStyle: 'short',
|
|
hour12: false,
|
|
});
|
|
|
|
const formatMessage = ({ id, defaultMessage }, values) => {
|
|
const messageFormat = new IntlMessageFormat(localeData[id] || defaultMessage, locale);
|
|
return messageFormat.format(values);
|
|
};
|
|
|
|
[].forEach.call(document.querySelectorAll('.emojify'), (content) => {
|
|
content.innerHTML = emojify(content.innerHTML);
|
|
});
|
|
|
|
[].forEach.call(document.querySelectorAll('time.formatted'), (content) => {
|
|
const datetime = new Date(content.getAttribute('datetime'));
|
|
const formattedDate = dateTimeFormat.format(datetime);
|
|
|
|
content.title = formattedDate;
|
|
content.textContent = formattedDate;
|
|
});
|
|
|
|
const isToday = 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);
|
|
|
|
[].forEach.call(document.querySelectorAll('time.relative-formatted'), (content) => {
|
|
const datetime = new Date(content.getAttribute('datetime'));
|
|
|
|
let formattedContent;
|
|
|
|
if (isToday(datetime)) {
|
|
const formattedTime = timeFormat.format(datetime);
|
|
|
|
formattedContent = todayFormat.format({ time: formattedTime });
|
|
} else {
|
|
formattedContent = dateFormat.format(datetime);
|
|
}
|
|
|
|
content.title = formattedContent;
|
|
content.textContent = formattedContent;
|
|
});
|
|
|
|
[].forEach.call(document.querySelectorAll('time.time-ago'), (content) => {
|
|
const datetime = new Date(content.getAttribute('datetime'));
|
|
const now = new Date();
|
|
|
|
const timeGiven = content.getAttribute('datetime').includes('T');
|
|
content.title = timeGiven ? dateTimeFormat.format(datetime) : dateFormat.format(datetime);
|
|
content.textContent = timeAgoString({
|
|
formatMessage,
|
|
formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date),
|
|
}, datetime, now, 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 }) => {
|
|
[].forEach.call(reactComponents, (component) => {
|
|
[].forEach.call(component.children, (child) => {
|
|
component.removeChild(child);
|
|
});
|
|
});
|
|
|
|
const content = document.createElement('div');
|
|
|
|
const root = createRoot(content);
|
|
root.render(<MediaContainer locale={locale} components={reactComponents} />);
|
|
document.body.appendChild(content);
|
|
scrollToDetailedStatus();
|
|
})
|
|
.catch(error => {
|
|
console.error(error);
|
|
scrollToDetailedStatus();
|
|
});
|
|
} else {
|
|
scrollToDetailedStatus();
|
|
}
|
|
|
|
delegate(document, '#user_account_attributes_username', 'input', throttle(() => {
|
|
const username = document.getElementById('user_account_attributes_username');
|
|
|
|
if (username.value && username.value.length > 0) {
|
|
axios.get('/api/v1/accounts/lookup', { params: { acct: username.value } }).then(() => {
|
|
username.setCustomValidity(formatMessage(messages.usernameTaken));
|
|
}).catch(() => {
|
|
username.setCustomValidity('');
|
|
});
|
|
} else {
|
|
username.setCustomValidity('');
|
|
}
|
|
}, 500, { leading: false, trailing: true }));
|
|
|
|
delegate(document, '#user_password,#user_password_confirmation', 'input', () => {
|
|
const password = document.getElementById('user_password');
|
|
const confirmation = document.getElementById('user_password_confirmation');
|
|
if (!confirmation) 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('');
|
|
}
|
|
});
|
|
|
|
delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original'));
|
|
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
|
|
|
|
delegate(document, '.status__content__spoiler-link', 'click', function() {
|
|
const statusEl = this.parentNode.parentNode;
|
|
|
|
if (statusEl.dataset.spoiler === 'expanded') {
|
|
statusEl.dataset.spoiler = 'folded';
|
|
this.textContent = (new IntlMessageFormat(localeData['status.show_more'] || 'Show more', locale)).format();
|
|
} else {
|
|
statusEl.dataset.spoiler = 'expanded';
|
|
this.textContent = (new IntlMessageFormat(localeData['status.show_less'] || 'Show less', locale)).format();
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
[].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => {
|
|
const statusEl = spoilerLink.parentNode.parentNode;
|
|
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();
|
|
});
|
|
});
|
|
|
|
const toggleSidebar = () => {
|
|
const sidebar = document.querySelector('.sidebar ul');
|
|
const toggleButton = document.querySelector('.sidebar__toggle__icon');
|
|
|
|
if (sidebar.classList.contains('visible')) {
|
|
document.body.style.overflow = null;
|
|
toggleButton.setAttribute('aria-expanded', 'false');
|
|
} else {
|
|
document.body.style.overflow = 'hidden';
|
|
toggleButton.setAttribute('aria-expanded', 'true');
|
|
}
|
|
|
|
toggleButton.classList.toggle('active');
|
|
sidebar.classList.toggle('visible');
|
|
};
|
|
|
|
delegate(document, '.sidebar__toggle__icon', 'click', () => {
|
|
toggleSidebar();
|
|
});
|
|
|
|
delegate(document, '.sidebar__toggle__icon', 'keydown', e => {
|
|
if (e.key === ' ' || e.key === 'Enter') {
|
|
e.preventDefault();
|
|
toggleSidebar();
|
|
}
|
|
});
|
|
|
|
// Empty the honeypot fields in JS in case something like an extension
|
|
// automatically filled them.
|
|
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.getElementById(id);
|
|
if (field) {
|
|
field.value = '';
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
function main() {
|
|
ready(loaded);
|
|
}
|
|
|
|
loadPolyfills()
|
|
.then(main)
|
|
.then(loadKeyboardExtensions)
|
|
.catch(error => {
|
|
console.error(error);
|
|
});
|