diff --git a/.gitignore b/.gitignore
index db63bc07f0..4727d9ec27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@
/public/packs
/public/packs-dev
/public/packs-test
+stats.html
.env
.env.production
node_modules/
diff --git a/app/javascript/entrypoints/public.tsx b/app/javascript/entrypoints/public.tsx
index fea3eb0d79..dd1956446d 100644
--- a/app/javascript/entrypoints/public.tsx
+++ b/app/javascript/entrypoints/public.tsx
@@ -70,7 +70,7 @@ function loaded() {
};
document.querySelectorAll('.emojify').forEach((content) => {
- content.innerHTML = emojify(content.innerHTML, {}, true); // Force emojify as public doesn't load the new emoji system.
+ content.innerHTML = emojify(content.innerHTML);
});
document
diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js
index 7723379804..32c3d76666 100644
--- a/app/javascript/mastodon/actions/importer/normalizer.js
+++ b/app/javascript/mastodon/actions/importer/normalizer.js
@@ -1,8 +1,5 @@
import escapeTextContentForBrowser from 'escape-html';
-import { makeEmojiMap } from 'mastodon/models/custom_emoji';
-
-import emojify from '../../features/emoji/emoji';
import { expandSpoilers } from '../../initial_state';
const domParser = new DOMParser();
@@ -88,11 +85,10 @@ export function normalizeStatus(status, normalOldStatus) {
const spoilerText = normalStatus.spoiler_text || '';
const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(/
/g, '\n').replace(/<\/p>
/g, '\n\n');
- const emojiMap = makeEmojiMap(normalStatus.emojis);
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
- normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
- normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
+ normalStatus.contentHtml = normalStatus.content;
+ normalStatus.spoilerHtml = escapeTextContentForBrowser(spoilerText);
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
// Remove quote fallback link from the DOM so it doesn't mess with paragraph margins
@@ -128,14 +124,12 @@ export function normalizeStatus(status, normalOldStatus) {
}
export function normalizeStatusTranslation(translation, status) {
- const emojiMap = makeEmojiMap(status.get('emojis').toJS());
-
const normalTranslation = {
detected_source_language: translation.detected_source_language,
language: translation.language,
provider: translation.provider,
- contentHtml: emojify(translation.content, emojiMap),
- spoilerHtml: emojify(escapeTextContentForBrowser(translation.spoiler_text), emojiMap),
+ contentHtml: translation.content,
+ spoilerHtml: escapeTextContentForBrowser(translation.spoiler_text),
spoiler_text: translation.spoiler_text,
};
@@ -149,9 +143,8 @@ export function normalizeStatusTranslation(translation, status) {
export function normalizeAnnouncement(announcement) {
const normalAnnouncement = { ...announcement };
- const emojiMap = makeEmojiMap(normalAnnouncement.emojis);
- normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap);
+ normalAnnouncement.contentHtml = normalAnnouncement.content;
return normalAnnouncement;
}
diff --git a/app/javascript/mastodon/components/account_bio.tsx b/app/javascript/mastodon/components/account_bio.tsx
index e87ae654fd..6d4ab1ddd4 100644
--- a/app/javascript/mastodon/components/account_bio.tsx
+++ b/app/javascript/mastodon/components/account_bio.tsx
@@ -1,11 +1,6 @@
-import { useCallback } from 'react';
-
import classNames from 'classnames';
-import { useLinks } from 'mastodon/hooks/useLinks';
-
import { useAppSelector } from '../store';
-import { isModernEmojiEnabled } from '../utils/environment';
import { EmojiHTML } from './emoji/html';
import { useElementHandledLink } from './status/handled_link';
@@ -21,22 +16,6 @@ export const AccountBio: React.FC = ({
accountId,
showDropdown = false,
}) => {
- const handleClick = useLinks(showDropdown);
- const handleNodeChange = useCallback(
- (node: HTMLDivElement | null) => {
- if (
- !showDropdown ||
- !node ||
- node.childNodes.length === 0 ||
- isModernEmojiEnabled()
- ) {
- return;
- }
- addDropdownToHashtags(node, accountId);
- },
- [showDropdown, accountId],
- );
-
const htmlHandlers = useElementHandledLink({
hashtagAccountId: showDropdown ? accountId : undefined,
});
@@ -62,30 +41,7 @@ export const AccountBio: React.FC = ({
htmlString={note}
extraEmojis={extraEmojis}
className={classNames(className, 'translate')}
- onClickCapture={handleClick}
- ref={handleNodeChange}
{...htmlHandlers}
/>
);
};
-
-function addDropdownToHashtags(node: HTMLElement | null, accountId: string) {
- if (!node) {
- return;
- }
- for (const childNode of node.childNodes) {
- if (!(childNode instanceof HTMLElement)) {
- continue;
- }
- if (
- childNode instanceof HTMLAnchorElement &&
- (childNode.classList.contains('hashtag') ||
- childNode.innerText.startsWith('#')) &&
- !childNode.dataset.menuHashtag
- ) {
- childNode.dataset.menuHashtag = accountId;
- } else if (childNode.childNodes.length > 0) {
- addDropdownToHashtags(childNode, accountId);
- }
- }
-}
diff --git a/app/javascript/mastodon/components/emoji/context.tsx b/app/javascript/mastodon/components/emoji/context.tsx
index 730ae743ed..3682b94141 100644
--- a/app/javascript/mastodon/components/emoji/context.tsx
+++ b/app/javascript/mastodon/components/emoji/context.tsx
@@ -7,8 +7,6 @@ import {
useState,
} from 'react';
-import classNames from 'classnames';
-
import { cleanExtraEmojis } from '@/mastodon/features/emoji/normalize';
import { autoPlayGif } from '@/mastodon/initial_state';
import { polymorphicForwardRef } from '@/types/polymorphic';
@@ -65,11 +63,7 @@ export const AnimateEmojiProvider = polymorphicForwardRef<
const parentContext = useContext(AnimateEmojiContext);
if (parentContext !== null) {
return (
-
+
{children}
);
@@ -78,7 +72,7 @@ export const AnimateEmojiProvider = polymorphicForwardRef<
return (
(
+export const EmojiHTML = polymorphicForwardRef<'div', EmojiHTMLProps>(
(
{
extraEmojis,
@@ -59,32 +56,4 @@ export const ModernEmojiHTML = polymorphicForwardRef<'div', EmojiHTMLProps>(
);
},
);
-ModernEmojiHTML.displayName = 'ModernEmojiHTML';
-
-export const LegacyEmojiHTML = polymorphicForwardRef<'div', EmojiHTMLProps>(
- (props, ref) => {
- const {
- as: asElement,
- htmlString,
- extraEmojis,
- className,
- onElement,
- onAttribute,
- ...rest
- } = props;
- const Wrapper = asElement ?? 'div';
- return (
-
- );
- },
-);
-LegacyEmojiHTML.displayName = 'LegacyEmojiHTML';
-
-export const EmojiHTML = isModernEmojiEnabled()
- ? ModernEmojiHTML
- : LegacyEmojiHTML;
+EmojiHTML.displayName = 'EmojiHTML';
diff --git a/app/javascript/mastodon/components/hover_card_account.tsx b/app/javascript/mastodon/components/hover_card_account.tsx
index 471d488415..b51af40e94 100644
--- a/app/javascript/mastodon/components/hover_card_account.tsx
+++ b/app/javascript/mastodon/components/hover_card_account.tsx
@@ -23,8 +23,6 @@ import { domain } from 'mastodon/initial_state';
import { getAccountHidden } from 'mastodon/selectors/accounts';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
-import { useLinks } from '../hooks/useLinks';
-
export const HoverCardAccount = forwardRef<
HTMLDivElement,
{ accountId?: string }
@@ -66,8 +64,6 @@ export const HoverCardAccount = forwardRef<
!isMutual &&
!isFollower;
- const handleClick = useLinks();
-
return (
-
+
onParentElement?.(...args) ?? onLinkElement(...args),
[onLinkElement, onParentElement],
);
- return ;
+ return ;
},
);
diff --git a/app/javascript/mastodon/components/poll.tsx b/app/javascript/mastodon/components/poll.tsx
index a9229e6ee4..98954fc2d9 100644
--- a/app/javascript/mastodon/components/poll.tsx
+++ b/app/javascript/mastodon/components/poll.tsx
@@ -13,9 +13,7 @@ import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import { openModal } from 'mastodon/actions/modal';
import { fetchPoll, vote } from 'mastodon/actions/polls';
import { Icon } from 'mastodon/components/icon';
-import emojify from 'mastodon/features/emoji/emoji';
import { useIdentity } from 'mastodon/identity_context';
-import { makeEmojiMap } from 'mastodon/models/custom_emoji';
import type * as Model from 'mastodon/models/poll';
import type { Status } from 'mastodon/models/status';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
@@ -235,12 +233,11 @@ const PollOption: React.FC = (props) => {
let titleHtml = option.translation?.titleHtml ?? option.titleHtml;
if (!titleHtml) {
- const emojiMap = makeEmojiMap(poll.emojis);
- titleHtml = emojify(escapeTextContentForBrowser(title), emojiMap);
+ titleHtml = escapeTextContentForBrowser(title);
}
return titleHtml;
- }, [option, poll, title]);
+ }, [option, title]);
// Handlers
const handleOptionChange = useCallback(() => {
diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx
index cef2be6281..81396bb85e 100644
--- a/app/javascript/mastodon/components/status_content.jsx
+++ b/app/javascript/mastodon/components/status_content.jsx
@@ -15,8 +15,6 @@ import { Poll } from 'mastodon/components/poll';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { languages as preloadedLanguages } from 'mastodon/initial_state';
-import { isModernEmojiEnabled } from '../utils/environment';
-
import { EmojiHTML } from './emoji/html';
import { HandledLink } from './status/handled_link';
@@ -119,41 +117,6 @@ class StatusContent extends PureComponent {
onCollapsedToggle(collapsed);
}
-
- // Exit if modern emoji is enabled, as it handles links using the HandledLink component.
- if (isModernEmojiEnabled()) {
- return;
- }
-
- const links = node.querySelectorAll('a');
-
- let link, mention;
-
- for (var i = 0; i < links.length; ++i) {
- link = links[i];
-
- if (link.classList.contains('status-link')) {
- continue;
- }
-
- link.classList.add('status-link');
-
- mention = this.props.status.get('mentions').find(item => compareUrls(link, item.get('url')));
-
- if (mention) {
- link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
- link.setAttribute('title', `@${mention.get('acct')}`);
- link.setAttribute('href', `/@${mention.get('acct')}`);
- link.setAttribute('data-hover-card-account', mention.get('id'));
- } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
- link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
- link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`);
- link.setAttribute('data-menu-hashtag', this.props.status.getIn(['account', 'id']));
- } else {
- link.setAttribute('title', link.href);
- link.classList.add('unhandled-link');
- }
- }
}
componentDidMount () {
@@ -164,22 +127,6 @@ class StatusContent extends PureComponent {
this._updateStatusLinks();
}
- onMentionClick = (mention, e) => {
- if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- this.props.history.push(`/@${mention.get('acct')}`);
- }
- };
-
- onHashtagClick = (hashtag, e) => {
- hashtag = hashtag.replace(/^#/, '');
-
- if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- this.props.history.push(`/tags/${hashtag}`);
- }
- };
-
handleMouseDown = (e) => {
this.startXY = [e.clientX, e.clientY];
};
diff --git a/app/javascript/mastodon/components/verified_badge.tsx b/app/javascript/mastodon/components/verified_badge.tsx
index 43edbc7951..cc8d8bf409 100644
--- a/app/javascript/mastodon/components/verified_badge.tsx
+++ b/app/javascript/mastodon/components/verified_badge.tsx
@@ -1,30 +1,10 @@
import { EmojiHTML } from '@/mastodon/components/emoji/html';
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
-import { isModernEmojiEnabled } from '../utils/environment';
import type { OnAttributeHandler } from '../utils/html';
import { Icon } from './icon';
-const domParser = new DOMParser();
-
-const stripRelMe = (html: string) => {
- if (isModernEmojiEnabled()) {
- return html;
- }
- const document = domParser.parseFromString(html, 'text/html').documentElement;
-
- document.querySelectorAll('a[rel]').forEach((link) => {
- link.rel = link.rel
- .split(' ')
- .filter((x: string) => x !== 'me')
- .join(' ');
- });
-
- const body = document.querySelector('body');
- return body?.innerHTML ?? '';
-};
-
const onAttribute: OnAttributeHandler = (name, value, tagName) => {
if (name === 'rel' && tagName === 'a') {
if (value === 'me') {
@@ -47,10 +27,6 @@ interface Props {
export const VerifiedBadge: React.FC = ({ link }) => (
-
+
);
diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx
index 2bf636d060..040ca16c72 100644
--- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx
+++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx
@@ -49,7 +49,6 @@ import { ShortNumber } from 'mastodon/components/short_number';
import { AccountNote } from 'mastodon/features/account/components/account_note';
import { DomainPill } from 'mastodon/features/account/components/domain_pill';
import FollowRequestNoteContainer from 'mastodon/features/account/containers/follow_request_note_container';
-import { useLinks } from 'mastodon/hooks/useLinks';
import { useIdentity } from 'mastodon/identity_context';
import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state';
import type { Account } from 'mastodon/models/account';
@@ -198,7 +197,6 @@ export const AccountHeader: React.FC<{
state.relationships.get(accountId),
);
const hidden = useAppSelector((state) => getAccountHidden(state, accountId));
- const handleLinkClick = useLinks();
const handleBlock = useCallback(() => {
if (!account) {
@@ -852,10 +850,7 @@ export const AccountHeader: React.FC<{
{!(suspended || hidden) && (
-
+
{account.id !== me && signedIn && (
)}
diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js
index cc0d6fe195..8ca86d16cd 100644
--- a/app/javascript/mastodon/features/emoji/emoji.js
+++ b/app/javascript/mastodon/features/emoji/emoji.js
@@ -1,6 +1,5 @@
import Trie from 'substring-trie';
-import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
import { assetHost } from 'mastodon/utils/config';
import { autoPlayGif } from '../../initial_state';
@@ -153,13 +152,9 @@ const emojifyNode = (node, customEmojis) => {
* Legacy emoji processing function.
* @param {string} str
* @param {object} customEmojis
- * @param {boolean} force If true, always emojify even if modern emoji is enabled
* @returns {string}
*/
-const emojify = (str, customEmojis = {}, force = false) => {
- if (isModernEmojiEnabled() && !force) {
- return str;
- }
+const emojify = (str, customEmojis = {}) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = str;
diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.mjs b/app/javascript/mastodon/features/emoji/emoji_compressed.mjs
index db8a4bc122..c565b861fd 100644
--- a/app/javascript/mastodon/features/emoji/emoji_compressed.mjs
+++ b/app/javascript/mastodon/features/emoji/emoji_compressed.mjs
@@ -14,8 +14,7 @@ import { uncompress as emojiMartUncompress } from 'emoji-mart/dist/utils/data';
import data from './emoji_data.json';
import emojiMap from './emoji_map.json';
-import { unicodeToFilename } from './unicode_to_filename';
-import { unicodeToUnifiedName } from './unicode_to_unified_name';
+import { unicodeToFilename, unicodeToUnifiedName } from './unicode_utils';
emojiMartUncompress(data);
diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts
index 1315518179..59d995e25f 100644
--- a/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts
+++ b/app/javascript/mastodon/features/emoji/emoji_mart_data_light.ts
@@ -9,7 +9,7 @@ import type {
ShortCodesToEmojiData,
} from 'virtual:mastodon-emoji-compressed';
-import { unicodeToUnifiedName } from './unicode_to_unified_name';
+import { unicodeToUnifiedName } from './unicode_utils';
type Emojis = Record<
NonNullable
,
@@ -23,7 +23,7 @@ type Emojis = Record<
const [
shortCodesToEmojiData,
- skins,
+ _skins,
categories,
short_names,
_emojisWithoutShortCodes,
@@ -47,4 +47,4 @@ Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
};
});
-export { emojis, skins, categories, short_names };
+export { emojis, categories, short_names };
diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
index 83e154b0b2..038dd120c9 100644
--- a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
+++ b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
@@ -1,7 +1,7 @@
// This code is largely borrowed from:
// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js
-import * as data from './emoji_mart_data_light';
+import { emojis, categories } from './emoji_mart_data_light';
import { getData, getSanitizedData, uniq, intersect } from './emoji_utils';
let originalPool = {};
@@ -10,8 +10,8 @@ let emojisList = {};
let emoticonsList = {};
let customEmojisList = [];
-for (let emoji in data.emojis) {
- let emojiData = data.emojis[emoji];
+for (let emoji in emojis) {
+ let emojiData = emojis[emoji];
let { short_names, emoticons } = emojiData;
let id = short_names[0];
@@ -84,14 +84,14 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
if (include.length || exclude.length) {
pool = {};
- data.categories.forEach(category => {
+ categories.forEach(category => {
let isIncluded = include && include.length ? include.indexOf(category.name.toLowerCase()) > -1 : true;
let isExcluded = exclude && exclude.length ? exclude.indexOf(category.name.toLowerCase()) > -1 : false;
if (!isIncluded || isExcluded) {
return;
}
- category.emojis.forEach(emojiId => pool[emojiId] = data.emojis[emojiId]);
+ category.emojis.forEach(emojiId => pool[emojiId] = emojis[emojiId]);
});
if (custom.length) {
@@ -171,7 +171,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
if (results) {
if (emojisToShowFilter) {
- results = results.filter((result) => emojisToShowFilter(data.emojis[result.id]));
+ results = results.filter((result) => emojisToShowFilter(emojis[result.id]));
}
if (results && results.length > maxResults) {
diff --git a/app/javascript/mastodon/features/emoji/emoji_picker.tsx b/app/javascript/mastodon/features/emoji/emoji_picker.tsx
index 38363d4310..37fc94dde7 100644
--- a/app/javascript/mastodon/features/emoji/emoji_picker.tsx
+++ b/app/javascript/mastodon/features/emoji/emoji_picker.tsx
@@ -2,7 +2,6 @@ import type { EmojiProps, PickerProps } from 'emoji-mart';
import EmojiRaw from 'emoji-mart/dist-es/components/emoji/nimble-emoji';
import PickerRaw from 'emoji-mart/dist-es/components/picker/nimble-picker';
-import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
import { assetHost } from 'mastodon/utils/config';
import { EMOJI_MODE_NATIVE } from './constants';
@@ -27,7 +26,7 @@ const Emoji = ({
sheetSize={sheetSize}
sheetColumns={sheetColumns}
sheetRows={sheetRows}
- native={mode === EMOJI_MODE_NATIVE && isModernEmojiEnabled()}
+ native={mode === EMOJI_MODE_NATIVE}
backgroundImageFn={backgroundImageFn}
{...props}
/>
@@ -51,7 +50,7 @@ const Picker = ({
sheetColumns={sheetColumns}
sheetRows={sheetRows}
backgroundImageFn={backgroundImageFn}
- native={mode === EMOJI_MODE_NATIVE && isModernEmojiEnabled()}
+ native={mode === EMOJI_MODE_NATIVE}
{...props}
/>
);
diff --git a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts
index a53496be2a..ecf36e3ea8 100644
--- a/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts
+++ b/app/javascript/mastodon/features/emoji/emoji_unicode_mapping_light.ts
@@ -8,7 +8,7 @@ import type {
ShortCodesToEmojiDataKey,
} from 'virtual:mastodon-emoji-compressed';
-import { unicodeToFilename } from './unicode_to_filename';
+import { unicodeToFilename } from './unicode_utils';
type UnicodeMapping = Record<
FilenameData[number][0],
diff --git a/app/javascript/mastodon/features/emoji/emoji_utils.js b/app/javascript/mastodon/features/emoji/emoji_utils.js
index c13d250567..75b2acafa5 100644
--- a/app/javascript/mastodon/features/emoji/emoji_utils.js
+++ b/app/javascript/mastodon/features/emoji/emoji_utils.js
@@ -209,50 +209,9 @@ function intersect(a, b) {
return uniqA.filter(item => uniqB.indexOf(item) >= 0);
}
-function deepMerge(a, b) {
- let o = {};
-
- for (let key in a) {
- let originalValue = a[key],
- value = originalValue;
-
- if (Object.hasOwn(b, key)) {
- value = b[key];
- }
-
- if (typeof value === 'object') {
- value = deepMerge(originalValue, value);
- }
-
- o[key] = value;
- }
-
- return o;
-}
-
-// https://github.com/sonicdoe/measure-scrollbar
-function measureScrollbar() {
- const div = document.createElement('div');
-
- div.style.width = '100px';
- div.style.height = '100px';
- div.style.overflow = 'scroll';
- div.style.position = 'absolute';
- div.style.top = '-9999px';
-
- document.body.appendChild(div);
- const scrollbarWidth = div.offsetWidth - div.clientWidth;
- document.body.removeChild(div);
-
- return scrollbarWidth;
-}
-
export {
getData,
getSanitizedData,
uniq,
intersect,
- deepMerge,
- unifiedToNative,
- measureScrollbar,
};
diff --git a/app/javascript/mastodon/features/emoji/handlers.ts b/app/javascript/mastodon/features/emoji/handlers.ts
deleted file mode 100644
index 3b02028f3c..0000000000
--- a/app/javascript/mastodon/features/emoji/handlers.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import { autoPlayGif } from '@/mastodon/initial_state';
-
-const PARENT_MAX_DEPTH = 10;
-
-export function handleAnimateGif(event: MouseEvent) {
- // We already check this in ui/index.jsx, but just to be sure.
- if (autoPlayGif) {
- return;
- }
-
- const { target, type } = event;
- const animate = type === 'mouseover'; // Mouse over = animate, mouse out = don't animate.
-
- if (target instanceof HTMLImageElement) {
- setAnimateGif(target, animate);
- } else if (!(target instanceof HTMLElement) || target === document.body) {
- return;
- }
-
- let parent: HTMLElement | null = null;
- let iter = 0;
-
- if (target.classList.contains('animate-parent')) {
- parent = target;
- } else {
- // Iterate up to PARENT_MAX_DEPTH levels up the DOM tree to find a parent with the class 'animate-parent'.
- let current: HTMLElement | null = target;
- while (current) {
- if (iter >= PARENT_MAX_DEPTH) {
- return; // We can just exit right now.
- }
- current = current.parentElement;
- if (current?.classList.contains('animate-parent')) {
- parent = current;
- break;
- }
- iter++;
- }
- }
-
- // Affect all animated children within the parent.
- if (parent) {
- const animatedChildren =
- parent.querySelectorAll('img.custom-emoji');
- for (const child of animatedChildren) {
- setAnimateGif(child, animate);
- }
- }
-}
-
-function setAnimateGif(image: HTMLImageElement, animate: boolean) {
- const { classList, dataset } = image;
- if (
- !classList.contains('custom-emoji') ||
- !dataset.static ||
- !dataset.original
- ) {
- return;
- }
- image.src = animate ? dataset.original : dataset.static;
-}
diff --git a/app/javascript/mastodon/features/emoji/unicode_to_filename.js b/app/javascript/mastodon/features/emoji/unicode_to_filename.js
deleted file mode 100644
index cfe5539c7b..0000000000
--- a/app/javascript/mastodon/features/emoji/unicode_to_filename.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// taken from:
-// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
-export const unicodeToFilename = (str) => {
- let result = '';
- let charCode = 0;
- let p = 0;
- let i = 0;
- while (i < str.length) {
- charCode = str.charCodeAt(i++);
- if (p) {
- if (result.length > 0) {
- result += '-';
- }
- result += (0x10000 + ((p - 0xD800) << 10) + (charCode - 0xDC00)).toString(16);
- p = 0;
- } else if (0xD800 <= charCode && charCode <= 0xDBFF) {
- p = charCode;
- } else {
- if (result.length > 0) {
- result += '-';
- }
- result += charCode.toString(16);
- }
- }
- return result;
-};
diff --git a/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js b/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js
deleted file mode 100644
index 15f60aa7c3..0000000000
--- a/app/javascript/mastodon/features/emoji/unicode_to_unified_name.js
+++ /dev/null
@@ -1,21 +0,0 @@
-function padLeft(str, num) {
- while (str.length < num) {
- str = '0' + str;
- }
-
- return str;
-}
-
-export const unicodeToUnifiedName = (str) => {
- let output = '';
-
- for (let i = 0; i < str.length; i += 2) {
- if (i > 0) {
- output += '-';
- }
-
- output += padLeft(str.codePointAt(i).toString(16).toUpperCase(), 4);
- }
-
- return output;
-};
diff --git a/app/javascript/mastodon/features/emoji/unicode_utils.ts b/app/javascript/mastodon/features/emoji/unicode_utils.ts
new file mode 100644
index 0000000000..04175ee9f9
--- /dev/null
+++ b/app/javascript/mastodon/features/emoji/unicode_utils.ts
@@ -0,0 +1,43 @@
+// taken from:
+// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
+export function unicodeToFilename(str: string) {
+ let result = '';
+ let charCode = 0;
+ let p = 0;
+ let i = 0;
+ while (i < str.length) {
+ charCode = str.charCodeAt(i++);
+ if (p) {
+ if (result.length > 0) {
+ result += '-';
+ }
+ result += (0x10000 + ((p - 0xd800) << 10) + (charCode - 0xdc00)).toString(
+ 16,
+ );
+ p = 0;
+ } else if (0xd800 <= charCode && charCode <= 0xdbff) {
+ p = charCode;
+ } else {
+ if (result.length > 0) {
+ result += '-';
+ }
+ result += charCode.toString(16);
+ }
+ }
+ return result;
+}
+
+export function unicodeToUnifiedName(str: string) {
+ let output = '';
+
+ for (let i = 0; i < str.length; i += 2) {
+ if (i > 0) {
+ output += '-';
+ }
+
+ output +=
+ str.codePointAt(i)?.toString(16).toUpperCase().padStart(4, '0') ?? '';
+ }
+
+ return output;
+}
diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.jsx b/app/javascript/mastodon/features/getting_started/components/announcements.jsx
deleted file mode 100644
index 96bd995d2b..0000000000
--- a/app/javascript/mastodon/features/getting_started/components/announcements.jsx
+++ /dev/null
@@ -1,458 +0,0 @@
-import PropTypes from 'prop-types';
-import { PureComponent, useCallback, useMemo } from 'react';
-
-import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
-
-import classNames from 'classnames';
-import { withRouter } from 'react-router-dom';
-
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-import { animated, useTransition } from '@react-spring/web';
-import ReactSwipeableViews from 'react-swipeable-views';
-
-import elephantUIPlane from '@/images/elephant_ui_plane.svg';
-import AddIcon from '@/material-icons/400-24px/add.svg?react';
-import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
-import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
-import { AnimatedNumber } from 'mastodon/components/animated_number';
-import { Icon } from 'mastodon/components/icon';
-import { IconButton } from 'mastodon/components/icon_button';
-import EmojiPickerDropdown from 'mastodon/features/compose/containers/emoji_picker_dropdown_container';
-import { unicodeMapping } from 'mastodon/features/emoji/emoji_unicode_mapping_light';
-import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'mastodon/initial_state';
-import { assetHost } from 'mastodon/utils/config';
-import { WithRouterPropTypes } from 'mastodon/utils/react_router';
-
-const messages = defineMessages({
- close: { id: 'lightbox.close', defaultMessage: 'Close' },
- previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
- next: { id: 'lightbox.next', defaultMessage: 'Next' },
-});
-
-class ContentWithRouter extends ImmutablePureComponent {
- static propTypes = {
- announcement: ImmutablePropTypes.map.isRequired,
- ...WithRouterPropTypes,
- };
-
- setRef = c => {
- this.node = c;
- };
-
- componentDidMount () {
- this._updateLinks();
- }
-
- componentDidUpdate () {
- this._updateLinks();
- }
-
- _updateLinks () {
- const node = this.node;
-
- if (!node) {
- return;
- }
-
- const links = node.querySelectorAll('a');
-
- for (var i = 0; i < links.length; ++i) {
- let link = links[i];
-
- if (link.classList.contains('status-link')) {
- continue;
- }
-
- link.classList.add('status-link');
-
- let mention = this.props.announcement.get('mentions').find(item => link.href === item.get('url'));
-
- if (mention) {
- link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
- link.setAttribute('title', mention.get('acct'));
- } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
- link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
- } else {
- let status = this.props.announcement.get('statuses').find(item => link.href === item.get('url'));
- if (status) {
- link.addEventListener('click', this.onStatusClick.bind(this, status), false);
- }
- link.setAttribute('title', link.href);
- link.classList.add('unhandled-link');
- }
-
- link.setAttribute('target', '_blank');
- link.setAttribute('rel', 'noopener');
- }
- }
-
- onMentionClick = (mention, e) => {
- if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- this.props.history.push(`/@${mention.get('acct')}`);
- }
- };
-
- onHashtagClick = (hashtag, e) => {
- hashtag = hashtag.replace(/^#/, '');
-
- if (this.props.history&& e.button === 0 && !(e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- this.props.history.push(`/tags/${hashtag}`);
- }
- };
-
- onStatusClick = (status, e) => {
- if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
- }
- };
-
- render () {
- const { announcement } = this.props;
-
- return (
-
- );
- }
-
-}
-
-const Content = withRouter(ContentWithRouter);
-
-class Emoji extends PureComponent {
-
- static propTypes = {
- emoji: PropTypes.string.isRequired,
- emojiMap: ImmutablePropTypes.map.isRequired,
- hovered: PropTypes.bool.isRequired,
- };
-
- render () {
- const { emoji, emojiMap, hovered } = this.props;
-
- if (unicodeMapping[emoji]) {
- const { filename, shortCode } = unicodeMapping[this.props.emoji];
- const title = shortCode ? `:${shortCode}:` : '';
-
- return (
-
- );
- } else if (emojiMap.get(emoji)) {
- const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']);
- const shortCode = `:${emoji}:`;
-
- return (
-
- );
- } else {
- return null;
- }
- }
-
-}
-
-class Reaction extends ImmutablePureComponent {
-
- static propTypes = {
- announcementId: PropTypes.string.isRequired,
- reaction: ImmutablePropTypes.map.isRequired,
- addReaction: PropTypes.func.isRequired,
- removeReaction: PropTypes.func.isRequired,
- emojiMap: ImmutablePropTypes.map.isRequired,
- style: PropTypes.object,
- };
-
- state = {
- hovered: false,
- };
-
- handleClick = () => {
- const { reaction, announcementId, addReaction, removeReaction } = this.props;
-
- if (reaction.get('me')) {
- removeReaction(announcementId, reaction.get('name'));
- } else {
- addReaction(announcementId, reaction.get('name'));
- }
- };
-
- handleMouseEnter = () => this.setState({ hovered: true });
-
- handleMouseLeave = () => this.setState({ hovered: false });
-
- render () {
- const { reaction } = this.props;
-
- let shortCode = reaction.get('name');
-
- if (unicodeMapping[shortCode]) {
- shortCode = unicodeMapping[shortCode].shortCode;
- }
-
- return (
-
-
-
-
-
-
-
-
- );
- }
-
-}
-
-const ReactionsBar = ({
- announcementId,
- reactions,
- emojiMap,
- addReaction,
- removeReaction,
-}) => {
- const visibleReactions = useMemo(() => reactions.filter(x => x.get('count') > 0).toArray(), [reactions]);
-
- const handleEmojiPick = useCallback((emoji) => {
- addReaction(announcementId, emoji.native.replaceAll(/:/g, ''));
- }, [addReaction, announcementId]);
-
- const transitions = useTransition(visibleReactions, {
- from: {
- scale: 0,
- },
- enter: {
- scale: 1,
- },
- leave: {
- scale: 0,
- },
- keys: visibleReactions.map(x => x.get('name')),
- });
-
- return (
-
- {transitions(({ scale }, reaction) => (
- `scale(${s})`) }}
- addReaction={addReaction}
- removeReaction={removeReaction}
- announcementId={announcementId}
- emojiMap={emojiMap}
- />
- ))}
-
- {visibleReactions.length < 8 && (
- }
- />
- )}
-
- );
-};
-ReactionsBar.propTypes = {
- announcementId: PropTypes.string.isRequired,
- reactions: ImmutablePropTypes.list.isRequired,
- addReaction: PropTypes.func.isRequired,
- removeReaction: PropTypes.func.isRequired,
- emojiMap: ImmutablePropTypes.map.isRequired,
-};
-
-class Announcement extends ImmutablePureComponent {
-
- static propTypes = {
- announcement: ImmutablePropTypes.map.isRequired,
- emojiMap: ImmutablePropTypes.map.isRequired,
- addReaction: PropTypes.func.isRequired,
- removeReaction: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- selected: PropTypes.bool,
- };
-
- state = {
- unread: !this.props.announcement.get('read'),
- };
-
- componentDidUpdate () {
- const { selected, announcement } = this.props;
- if (!selected && this.state.unread !== !announcement.get('read')) {
- this.setState({ unread: !announcement.get('read') });
- }
- }
-
- render () {
- const { announcement } = this.props;
- const { unread } = this.state;
- const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at'));
- const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at'));
- const now = new Date();
- const hasTimeRange = startsAt && endsAt;
- const skipTime = announcement.get('all_day');
-
- let timestamp = null;
- if (hasTimeRange) {
- const skipYear = startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear();
- const skipEndDate = startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
- timestamp = (
- <>
- -
- >
- );
- } else {
- const publishedAt = new Date(announcement.get('published_at'));
- timestamp = (
-
- );
- }
-
- return (
-
-
-
- ยท {timestamp}
-
-
-
-
-
-
- {unread && }
-
- );
- }
-
-}
-
-class Announcements extends ImmutablePureComponent {
-
- static propTypes = {
- announcements: ImmutablePropTypes.list,
- emojiMap: ImmutablePropTypes.map.isRequired,
- dismissAnnouncement: PropTypes.func.isRequired,
- addReaction: PropTypes.func.isRequired,
- removeReaction: PropTypes.func.isRequired,
- intl: PropTypes.object.isRequired,
- };
-
- state = {
- index: 0,
- };
-
- static getDerivedStateFromProps(props, state) {
- if (props.announcements.size > 0 && state.index >= props.announcements.size) {
- return { index: props.announcements.size - 1 };
- } else {
- return null;
- }
- }
-
- componentDidMount () {
- this._markAnnouncementAsRead();
- }
-
- componentDidUpdate () {
- this._markAnnouncementAsRead();
- }
-
- _markAnnouncementAsRead () {
- const { dismissAnnouncement, announcements } = this.props;
- const { index } = this.state;
- const announcement = announcements.get(announcements.size - 1 - index);
- if (!announcement.get('read')) dismissAnnouncement(announcement.get('id'));
- }
-
- handleChangeIndex = index => {
- this.setState({ index: index % this.props.announcements.size });
- };
-
- handleNextClick = () => {
- this.setState({ index: (this.state.index + 1) % this.props.announcements.size });
- };
-
- handlePrevClick = () => {
- this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size });
- };
-
- render () {
- const { announcements, intl } = this.props;
- const { index } = this.state;
-
- if (announcements.isEmpty()) {
- return null;
- }
-
- return (
-
-

-
-
-
- {announcements.map((announcement, idx) => (
-
- )).reverse()}
-
-
- {announcements.size > 1 && (
-
-
- {index + 1} / {announcements.size}
-
-
- )}
-
-
- );
- }
-
-}
-
-export default injectIntl(Announcements);
diff --git a/app/javascript/mastodon/features/getting_started/containers/announcements_container.js b/app/javascript/mastodon/features/getting_started/containers/announcements_container.js
deleted file mode 100644
index 3bb1b8e8d1..0000000000
--- a/app/javascript/mastodon/features/getting_started/containers/announcements_container.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { createSelector } from '@reduxjs/toolkit';
-import { Map as ImmutableMap } from 'immutable';
-import { connect } from 'react-redux';
-
-
-import { addReaction, removeReaction, dismissAnnouncement } from 'mastodon/actions/announcements';
-
-import Announcements from '../components/announcements';
-
-const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap()));
-
-const mapStateToProps = state => ({
- announcements: state.getIn(['announcements', 'items']),
- emojiMap: customEmojiMap(state),
-});
-
-const mapDispatchToProps = dispatch => ({
- dismissAnnouncement: id => dispatch(dismissAnnouncement(id)),
- addReaction: (id, name) => dispatch(addReaction(id, name)),
- removeReaction: (id, name) => dispatch(removeReaction(id, name)),
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(Announcements);
diff --git a/app/javascript/mastodon/features/home_timeline/components/announcements/index.tsx b/app/javascript/mastodon/features/home_timeline/components/announcements/index.tsx
index 8c7c704849..335e0f1a38 100644
--- a/app/javascript/mastodon/features/home_timeline/components/announcements/index.tsx
+++ b/app/javascript/mastodon/features/home_timeline/components/announcements/index.tsx
@@ -10,10 +10,8 @@ import ReactSwipeableViews from 'react-swipeable-views';
import elephantUIPlane from '@/images/elephant_ui_plane.svg';
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
import { IconButton } from '@/mastodon/components/icon_button';
-import LegacyAnnouncements from '@/mastodon/features/getting_started/containers/announcements_container';
import { mascot, reduceMotion } from '@/mastodon/initial_state';
import { createAppSelector, useAppSelector } from '@/mastodon/store';
-import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
@@ -32,7 +30,7 @@ const announcementSelector = createAppSelector(
(announcements.get('items')?.toJS() as IAnnouncement[] | undefined) ?? [],
);
-export const ModernAnnouncements: FC = () => {
+export const Announcements: FC = () => {
const intl = useIntl();
const announcements = useAppSelector(announcementSelector);
@@ -112,7 +110,3 @@ export const ModernAnnouncements: FC = () => {
);
};
-
-export const Announcements = isModernEmojiEnabled()
- ? ModernAnnouncements
- : LegacyAnnouncements;
diff --git a/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx b/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx
index 1d1b684b80..9e7f66d112 100644
--- a/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx
+++ b/app/javascript/mastodon/features/notifications_v2/components/embedded_status_content.tsx
@@ -1,47 +1,17 @@
import { useCallback, useMemo } from 'react';
-import { useHistory } from 'react-router-dom';
-
import type { List } from 'immutable';
-import type { History } from 'history';
-
-import type { ApiMentionJSON } from '@/mastodon/api_types/statuses';
import { EmojiHTML } from '@/mastodon/components/emoji/html';
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
import type { Status } from '@/mastodon/models/status';
-import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
import type { Mention } from './embedded_status';
-const handleMentionClick = (
- history: History,
- mention: ApiMentionJSON,
- e: MouseEvent,
-) => {
- if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- history.push(`/@${mention.acct}`);
- }
-};
-
-const handleHashtagClick = (
- history: History,
- hashtag: string,
- e: MouseEvent,
-) => {
- if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- history.push(`/tags/${hashtag.replace(/^#/, '')}`);
- }
-};
-
export const EmbeddedStatusContent: React.FC<{
status: Status;
className?: string;
}> = ({ status, className }) => {
- const history = useHistory();
-
const mentions = useMemo(
() => (status.get('mentions') as List
).toJS(),
[status],
@@ -57,55 +27,10 @@ export const EmbeddedStatusContent: React.FC<{
hrefToMention,
});
- const handleContentRef = useCallback(
- (node: HTMLDivElement | null) => {
- if (!node || isModernEmojiEnabled()) {
- return;
- }
-
- const links = node.querySelectorAll('a');
-
- for (const link of links) {
- if (link.classList.contains('status-link')) {
- continue;
- }
-
- link.classList.add('status-link');
-
- const mention = mentions.find((item) => link.href === item.url);
-
- if (mention) {
- link.addEventListener(
- 'click',
- handleMentionClick.bind(null, history, mention),
- false,
- );
- link.setAttribute('title', `@${mention.acct}`);
- link.setAttribute('href', `/@${mention.acct}`);
- } else if (
- link.textContent.startsWith('#') ||
- link.previousSibling?.textContent?.endsWith('#')
- ) {
- link.addEventListener(
- 'click',
- handleHashtagClick.bind(null, history, link.text),
- false,
- );
- link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`);
- } else {
- link.setAttribute('title', link.href);
- link.classList.add('unhandled-link');
- }
- }
- },
- [mentions, history],
- );
-
return (
diff --git a/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx b/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx
index f260444265..ae4c4ed4f7 100644
--- a/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx
+++ b/app/javascript/mastodon/features/ui/components/compare_history_modal.jsx
@@ -14,7 +14,6 @@ import { IconButton } from 'mastodon/components/icon_button';
import InlineAccount from 'mastodon/components/inline_account';
import MediaAttachments from 'mastodon/components/media_attachments';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
-import emojify from 'mastodon/features/emoji/emoji';
import { EmojiHTML } from '@/mastodon/components/emoji/html';
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
@@ -48,13 +47,8 @@ class CompareHistoryModal extends PureComponent {
const { index, versions, language, onClose } = this.props;
const currentVersion = versions.get(index);
- const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => {
- obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
- return obj;
- }, {});
-
- const content = emojify(currentVersion.get('content'), emojiMap);
- const spoilerContent = emojify(escapeTextContentForBrowser(currentVersion.get('spoiler_text')), emojiMap);
+ const content = currentVersion.get('content');
+ const spoilerContent = escapeTextContentForBrowser(currentVersion.get('spoiler_text'));
const formattedDate = ;
const formattedName = ;
@@ -99,7 +93,7 @@ class CompareHistoryModal extends PureComponent {
diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx
index f53870a314..209e4b4a87 100644
--- a/app/javascript/mastodon/features/ui/index.jsx
+++ b/app/javascript/mastodon/features/ui/index.jsx
@@ -22,12 +22,11 @@ import { identityContextPropShape, withIdentity } from 'mastodon/identity_contex
import { layoutFromWindow } from 'mastodon/is_mobile';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
-import { handleAnimateGif } from '../emoji/handlers';
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
import { clearHeight } from '../../actions/height_cache';
import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server';
import { expandHomeTimeline } from '../../actions/timelines';
-import { initialState, me, owner, singleUserMode, trendsEnabled, landingPage, localLiveFeedAccess, disableHoverCards, autoPlayGif } from '../../initial_state';
+import { initialState, me, owner, singleUserMode, trendsEnabled, landingPage, localLiveFeedAccess, disableHoverCards } from '../../initial_state';
import BundleColumnError from './components/bundle_column_error';
import { NavigationBar } from './components/navigation_bar';
@@ -382,11 +381,6 @@ class UI extends PureComponent {
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
window.addEventListener('resize', this.handleResize, { passive: true });
- if (!autoPlayGif) {
- window.addEventListener('mouseover', handleAnimateGif, { passive: true });
- window.addEventListener('mouseout', handleAnimateGif, { passive: true });
- }
-
document.addEventListener('dragenter', this.handleDragEnter, false);
document.addEventListener('dragover', this.handleDragOver, false);
document.addEventListener('drop', this.handleDrop, false);
@@ -412,8 +406,6 @@ class UI extends PureComponent {
window.removeEventListener('blur', this.handleWindowBlur);
window.removeEventListener('beforeunload', this.handleBeforeUnload);
window.removeEventListener('resize', this.handleResize);
- window.removeEventListener('mouseover', handleAnimateGif);
- window.removeEventListener('mouseout', handleAnimateGif);
document.removeEventListener('dragenter', this.handleDragEnter);
document.removeEventListener('dragover', this.handleDragOver);
diff --git a/app/javascript/mastodon/hooks/useLinks.ts b/app/javascript/mastodon/hooks/useLinks.ts
deleted file mode 100644
index 77609181be..0000000000
--- a/app/javascript/mastodon/hooks/useLinks.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { useCallback } from 'react';
-
-import { useHistory } from 'react-router-dom';
-
-import { isFulfilled, isRejected } from '@reduxjs/toolkit';
-
-import { openURL } from 'mastodon/actions/search';
-import { useAppDispatch } from 'mastodon/store';
-
-import { isModernEmojiEnabled } from '../utils/environment';
-
-const isMentionClick = (element: HTMLAnchorElement) =>
- element.classList.contains('mention') &&
- !element.classList.contains('hashtag');
-
-const isHashtagClick = (element: HTMLAnchorElement) =>
- element.textContent.startsWith('#') ||
- element.previousSibling?.textContent?.endsWith('#');
-
-export const useLinks = (skipHashtags?: boolean) => {
- const history = useHistory();
- const dispatch = useAppDispatch();
-
- const handleHashtagClick = useCallback(
- (element: HTMLAnchorElement) => {
- const { textContent } = element;
-
- if (!textContent) return;
-
- history.push(`/tags/${textContent.replace(/^#/, '')}`);
- },
- [history],
- );
-
- const handleMentionClick = useCallback(
- async (element: HTMLAnchorElement) => {
- const result = await dispatch(openURL({ url: element.href }));
-
- if (isFulfilled(result)) {
- if (result.payload.accounts[0]) {
- history.push(`/@${result.payload.accounts[0].acct}`);
- } else if (result.payload.statuses[0]) {
- history.push(
- `/@${result.payload.statuses[0].account.acct}/${result.payload.statuses[0].id}`,
- );
- } else {
- window.location.href = element.href;
- }
- } else if (isRejected(result)) {
- window.location.href = element.href;
- }
- },
- [dispatch, history],
- );
-
- const handleClick = useCallback(
- (e: React.MouseEvent) => {
- // Exit early if modern emoji is enabled, as this is handled by HandledLink.
- if (isModernEmojiEnabled()) {
- return;
- }
-
- const target = (e.target as HTMLElement).closest('a');
-
- if (!target || e.button !== 0 || e.ctrlKey || e.metaKey) {
- return;
- }
-
- if (isMentionClick(target)) {
- e.preventDefault();
- void handleMentionClick(target);
- } else if (isHashtagClick(target) && !skipHashtags) {
- e.preventDefault();
- handleHashtagClick(target);
- }
- },
- [skipHashtags, handleMentionClick, handleHashtagClick],
- );
-
- return handleClick;
-};
diff --git a/app/javascript/mastodon/main.tsx b/app/javascript/mastodon/main.tsx
index 456cc21c31..f89baf66cd 100644
--- a/app/javascript/mastodon/main.tsx
+++ b/app/javascript/mastodon/main.tsx
@@ -9,11 +9,8 @@ import { me, reduceMotion } from 'mastodon/initial_state';
import ready from 'mastodon/ready';
import { store } from 'mastodon/store';
-import {
- isProduction,
- isDevelopment,
- isModernEmojiEnabled,
-} from './utils/environment';
+import { initializeEmoji } from './features/emoji';
+import { isProduction, isDevelopment } from './utils/environment';
function main() {
perf.start('main()');
@@ -33,10 +30,7 @@ function main() {
});
}
- if (isModernEmojiEnabled()) {
- const { initializeEmoji } = await import('@/mastodon/features/emoji');
- initializeEmoji();
- }
+ initializeEmoji();
const root = createRoot(mountNode);
root.render();
diff --git a/app/javascript/mastodon/models/account.ts b/app/javascript/mastodon/models/account.ts
index 3b0c41be81..8fbc0cdf41 100644
--- a/app/javascript/mastodon/models/account.ts
+++ b/app/javascript/mastodon/models/account.ts
@@ -8,11 +8,10 @@ import type {
ApiAccountRoleJSON,
ApiAccountJSON,
} from 'mastodon/api_types/accounts';
-import emojify from 'mastodon/features/emoji/emoji';
import { unescapeHTML } from 'mastodon/utils/html';
-import { CustomEmojiFactory, makeEmojiMap } from './custom_emoji';
-import type { CustomEmoji, EmojiMap } from './custom_emoji';
+import { CustomEmojiFactory } from './custom_emoji';
+import type { CustomEmoji } from './custom_emoji';
// AccountField
interface AccountFieldShape extends Required {
@@ -102,17 +101,11 @@ export const accountDefaultValues: AccountShape = {
const AccountFactory = ImmutableRecord(accountDefaultValues);
-function createAccountField(
- jsonField: ApiAccountFieldJSON,
- emojiMap: EmojiMap,
-) {
+function createAccountField(jsonField: ApiAccountFieldJSON) {
return AccountFieldFactory({
...jsonField,
- name_emojified: emojify(
- escapeTextContentForBrowser(jsonField.name),
- emojiMap,
- ),
- value_emojified: emojify(jsonField.value, emojiMap),
+ name_emojified: escapeTextContentForBrowser(jsonField.name),
+ value_emojified: jsonField.value,
value_plain: unescapeHTML(jsonField.value),
});
}
@@ -120,8 +113,6 @@ function createAccountField(
export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
const { moved, ...accountJSON } = serverJSON;
- const emojiMap = makeEmojiMap(accountJSON.emojis);
-
const displayName =
accountJSON.display_name.trim().length === 0
? accountJSON.username
@@ -134,7 +125,7 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
...accountJSON,
moved: moved?.id,
fields: ImmutableList(
- serverJSON.fields.map((field) => createAccountField(field, emojiMap)),
+ serverJSON.fields.map((field) => createAccountField(field)),
),
emojis: ImmutableList(
serverJSON.emojis.map((emoji) => CustomEmojiFactory(emoji)),
@@ -142,11 +133,8 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
roles: ImmutableList(
serverJSON.roles?.map((role) => AccountRoleFactory(role)),
),
- display_name_html: emojify(
- escapeTextContentForBrowser(displayName),
- emojiMap,
- ),
- note_emojified: emojify(accountNote, emojiMap),
+ display_name_html: escapeTextContentForBrowser(displayName),
+ note_emojified: accountNote,
note_plain: unescapeHTML(accountNote),
url:
accountJSON.url?.startsWith('http://') ||
diff --git a/app/javascript/mastodon/models/poll.ts b/app/javascript/mastodon/models/poll.ts
index 6f5655680d..46cbb1111d 100644
--- a/app/javascript/mastodon/models/poll.ts
+++ b/app/javascript/mastodon/models/poll.ts
@@ -1,10 +1,9 @@
import escapeTextContentForBrowser from 'escape-html';
import type { ApiPollJSON, ApiPollOptionJSON } from 'mastodon/api_types/polls';
-import emojify from 'mastodon/features/emoji/emoji';
-import { CustomEmojiFactory, makeEmojiMap } from './custom_emoji';
-import type { CustomEmoji, EmojiMap } from './custom_emoji';
+import { CustomEmojiFactory } from './custom_emoji';
+import type { CustomEmoji } from './custom_emoji';
interface PollOptionTranslation {
title: string;
@@ -17,16 +16,12 @@ export interface PollOption extends ApiPollOptionJSON {
translation: PollOptionTranslation | null;
}
-export function createPollOptionTranslationFromServerJSON(
- translation: { title: string },
- emojiMap: EmojiMap,
-) {
+export function createPollOptionTranslationFromServerJSON(translation: {
+ title: string;
+}) {
return {
...translation,
- titleHtml: emojify(
- escapeTextContentForBrowser(translation.title),
- emojiMap,
- ),
+ titleHtml: escapeTextContentForBrowser(translation.title),
} as PollOptionTranslation;
}
@@ -50,8 +45,6 @@ export function createPollFromServerJSON(
serverJSON: ApiPollJSON,
previousPoll?: Poll,
) {
- const emojiMap = makeEmojiMap(serverJSON.emojis);
-
return {
...pollDefaultValues,
...serverJSON,
@@ -60,20 +53,15 @@ export function createPollFromServerJSON(
const option = {
...optionJSON,
voted: serverJSON.own_votes?.includes(index) || false,
- titleHtml: emojify(
- escapeTextContentForBrowser(optionJSON.title),
- emojiMap,
- ),
+ titleHtml: escapeTextContentForBrowser(optionJSON.title),
} as PollOption;
const prevOption = previousPoll?.options[index];
if (prevOption?.translation && prevOption.title === option.title) {
const { translation } = prevOption;
- option.translation = createPollOptionTranslationFromServerJSON(
- translation,
- emojiMap,
- );
+ option.translation =
+ createPollOptionTranslationFromServerJSON(translation);
}
return option;
diff --git a/app/javascript/mastodon/reducers/polls.ts b/app/javascript/mastodon/reducers/polls.ts
index aadf6741c1..ac0917bd20 100644
--- a/app/javascript/mastodon/reducers/polls.ts
+++ b/app/javascript/mastodon/reducers/polls.ts
@@ -1,7 +1,6 @@
import type { Reducer } from '@reduxjs/toolkit';
import { importPolls } from 'mastodon/actions/importer/polls';
-import { makeEmojiMap } from 'mastodon/models/custom_emoji';
import { createPollOptionTranslationFromServerJSON } from 'mastodon/models/poll';
import type { Poll } from 'mastodon/models/poll';
@@ -20,16 +19,11 @@ const statusTranslateSuccess = (state: PollsState, pollTranslation?: Poll) => {
if (!poll) return;
- const emojiMap = makeEmojiMap(poll.emojis);
-
pollTranslation.options.forEach((item, index) => {
const option = poll.options[index];
if (!option) return;
- option.translation = createPollOptionTranslationFromServerJSON(
- item,
- emojiMap,
- );
+ option.translation = createPollOptionTranslationFromServerJSON(item);
});
};
diff --git a/app/javascript/mastodon/utils/environment.ts b/app/javascript/mastodon/utils/environment.ts
index aa125b0922..95075454f2 100644
--- a/app/javascript/mastodon/utils/environment.ts
+++ b/app/javascript/mastodon/utils/environment.ts
@@ -12,16 +12,8 @@ export function isProduction() {
else return import.meta.env.PROD;
}
-export type Features = 'modern_emojis' | 'fasp';
+export type Features = 'fasp' | 'http_message_signatures';
export function isFeatureEnabled(feature: Features) {
return initialState?.features.includes(feature) ?? false;
}
-
-export function isModernEmojiEnabled() {
- try {
- return isFeatureEnabled('modern_emojis');
- } catch {
- return false;
- }
-}
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index d562d90688..5f8921e246 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -31,7 +31,7 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:use_blurhash] = object_account_user.setting_use_blurhash
store[:use_pending_items] = object_account_user.setting_use_pending_items
store[:show_trends] = Setting.trends && object_account_user.setting_trends
- store[:emoji_style] = object_account_user.settings['web.emoji_style'] if Mastodon::Feature.modern_emojis_enabled?
+ store[:emoji_style] = object_account_user.settings['web.emoji_style']
else
store[:auto_play_gif] = Setting.auto_play_gif
store[:display_media] = Setting.display_media
diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml
index e1ee4ac0b0..e6e96f97d5 100644
--- a/app/views/settings/preferences/appearance/show.html.haml
+++ b/app/views/settings/preferences/appearance/show.html.haml
@@ -31,16 +31,15 @@
label: I18n.t('simple_form.labels.defaults.setting_theme'),
wrapper: :with_label
- - if Mastodon::Feature.modern_emojis_enabled?
- .fields-group
- = f.simple_fields_for :settings, current_user.settings do |ff|
- = ff.input :'web.emoji_style',
- collection: %w(auto twemoji native),
- include_blank: false,
- hint: I18n.t('simple_form.hints.defaults.setting_emoji_style'),
- label: I18n.t('simple_form.labels.defaults.setting_emoji_style'),
- label_method: ->(emoji_style) { I18n.t("emoji_styles.#{emoji_style}", default: emoji_style) },
- wrapper: :with_label
+ .fields-group
+ = f.simple_fields_for :settings, current_user.settings do |ff|
+ = ff.input :'web.emoji_style',
+ collection: %w(auto twemoji native),
+ include_blank: false,
+ hint: I18n.t('simple_form.hints.defaults.setting_emoji_style'),
+ label: I18n.t('simple_form.labels.defaults.setting_emoji_style'),
+ label_method: ->(emoji_style) { I18n.t("emoji_styles.#{emoji_style}", default: emoji_style) },
+ wrapper: :with_label
- unless I18n.locale == :en
.flash-message.translation-prompt