diff --git a/package.json b/package.json
index 8fae41ee1..b49929f42 100644
--- a/package.json
+++ b/package.json
@@ -70,7 +70,6 @@
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@tanstack/react-query": "^5.59.13",
- "@twemoji/svg": "^15.0.0",
"@types/escape-html": "^1.0.1",
"@types/http-link-header": "^1.0.3",
"@types/leaflet": "^1.8.0",
@@ -98,7 +97,6 @@
"comlink": "^4.4.1",
"cssnano": "^6.0.0",
"detect-passive-events": "^2.0.0",
- "emoji-datasource": "15.0.1",
"emoji-mart": "^5.6.0",
"escape-html": "^1.0.3",
"eslint-plugin-formatjs": "^5.2.2",
diff --git a/src/actions/soapbox.ts b/src/actions/soapbox.ts
index cee62ab4f..cd0170c90 100644
--- a/src/actions/soapbox.ts
+++ b/src/actions/soapbox.ts
@@ -3,7 +3,6 @@ import { createSelector } from 'reselect';
import { getHost } from 'soapbox/actions/instance.ts';
import { normalizeSoapboxConfig } from 'soapbox/normalizers/index.ts';
import KVStore from 'soapbox/storage/kv-store.ts';
-import { removeVS16s } from 'soapbox/utils/emoji.ts';
import { getFeatures } from 'soapbox/utils/features.ts';
import api from '../api/index.ts';
@@ -29,12 +28,6 @@ const getSoapboxConfig = createSelector([
if (soapbox.get('displayFqn') === undefined) {
soapboxConfig.set('displayFqn', features.federating);
}
-
- // If RGI reacts aren't supported, strip VS16s
- // https://git.pleroma.social/pleroma/pleroma/-/issues/2355
- if (features.emojiReactsNonRGI) {
- soapboxConfig.set('allowedEmoji', soapboxConfig.allowedEmoji.map(removeVS16s));
- }
});
});
diff --git a/src/components/account.tsx b/src/components/account.tsx
index 4dba320de..f9ffc41cf 100644
--- a/src/components/account.tsx
+++ b/src/components/account.tsx
@@ -214,11 +214,13 @@ const Account = ({
{emoji && (
-
+
+ {emojiUrl ? (
+

+ ) : (
+
+ )}
+
)}
diff --git a/src/components/autosuggest-emoji.tsx b/src/components/autosuggest-emoji.tsx
index fb4a4b4fc..411526b9c 100644
--- a/src/components/autosuggest-emoji.tsx
+++ b/src/components/autosuggest-emoji.tsx
@@ -1,3 +1,5 @@
+import EmojiComponent from 'soapbox/components/ui/emoji.tsx';
+import HStack from 'soapbox/components/ui/hstack.tsx';
import { isCustomEmoji } from 'soapbox/features/emoji/index.ts';
import unicodeMapping from 'soapbox/features/emoji/mapping.ts';
@@ -8,11 +10,10 @@ interface IAutosuggestEmoji {
}
const AutosuggestEmoji: React.FC = ({ emoji }) => {
- let url, alt;
+ let elem: React.ReactNode;
if (isCustomEmoji(emoji)) {
- url = emoji.imageUrl;
- alt = emoji.colons;
+ elem =
;
} else {
const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')];
@@ -20,19 +21,14 @@ const AutosuggestEmoji: React.FC = ({ emoji }) => {
return null;
}
- url = `/packs/emoji/${mapping.unified}.svg`;
- alt = emoji.native;
+ elem = ;
}
return (
-
-

- {emoji.colons}
-
+
+ {elem}
+ {emoji.colons}
+
);
};
diff --git a/src/components/emoji-graphic.tsx b/src/components/emoji-graphic.tsx
index 7facaa4ad..207fb409d 100644
--- a/src/components/emoji-graphic.tsx
+++ b/src/components/emoji-graphic.tsx
@@ -9,7 +9,7 @@ const EmojiGraphic: React.FC = ({ emoji }) => {
return (
);
diff --git a/src/components/status-action-button.tsx b/src/components/status-action-button.tsx
index b72218b4d..2819adaa3 100644
--- a/src/components/status-action-button.tsx
+++ b/src/components/status-action-button.tsx
@@ -46,8 +46,12 @@ const StatusActionButton = forwardRef((p
const renderIcon = () => {
if (emoji) {
return (
-
-
+
+ {emoji.url ? (
+
+ ) : (
+
+ )}
);
} else {
diff --git a/src/components/ui/emoji-selector.tsx b/src/components/ui/emoji-selector.tsx
index 2487bb127..6d4b716e0 100644
--- a/src/components/ui/emoji-selector.tsx
+++ b/src/components/ui/emoji-selector.tsx
@@ -41,7 +41,9 @@ const EmojiButton: React.FC = ({ emoji, className, onClick, tabInd
return (
);
};
diff --git a/src/components/ui/emoji.tsx b/src/components/ui/emoji.tsx
index da3449ccb..75cc4d111 100644
--- a/src/components/ui/emoji.tsx
+++ b/src/components/ui/emoji.tsx
@@ -1,30 +1,19 @@
-import { removeVS16s, toCodePoints } from 'soapbox/utils/emoji.ts';
-
-interface IEmoji extends React.ImgHTMLAttributes {
+interface IEmoji {
/** Unicode emoji character. */
- emoji?: string;
+ emoji: string;
+ /** Size to render the emoji. */
+ size?: number;
}
/** A single emoji image. */
const Emoji: React.FC = (props): JSX.Element | null => {
- const { emoji, alt, src, ...rest } = props;
-
- let filename;
-
- if (emoji) {
- const codepoints = toCodePoints(removeVS16s(emoji));
- filename = codepoints.join('-');
- }
-
- if (!filename && !src) return null;
+ const { emoji, size = 16 } = props;
+ const px = `${size}px`;
return (
-
+
+ {emoji}
+
);
};
diff --git a/src/features/compose/editor/nodes/emoji-node.tsx b/src/features/compose/editor/nodes/emoji-node.tsx
index 4c7155d00..d29f24af2 100644
--- a/src/features/compose/editor/nodes/emoji-node.tsx
+++ b/src/features/compose/editor/nodes/emoji-node.tsx
@@ -1,6 +1,5 @@
import { $applyNodeReplacement, DecoratorNode } from 'lexical';
-import Component from 'soapbox/components/ui/emoji.tsx';
import { isNativeEmoji, type Emoji } from 'soapbox/features/emoji/index.ts';
import type {
@@ -17,7 +16,7 @@ type SerializedEmojiNode = Spread<{
version: 1;
}, SerializedLexicalNode>;
-class EmojiNode extends DecoratorNode {
+class EmojiNode extends DecoratorNode {
__emoji: Emoji;
@@ -77,12 +76,12 @@ class EmojiNode extends DecoratorNode {
}
}
- decorate(): JSX.Element {
+ decorate(): React.ReactNode {
const emoji = this.__emoji;
if (isNativeEmoji(emoji)) {
- return ;
+ return emoji.native;
} else {
- return ;
+ return
;
}
}
diff --git a/src/features/compose/editor/plugins/autosuggest-plugin.tsx b/src/features/compose/editor/plugins/autosuggest-plugin.tsx
index 6efc85369..af5eb3e66 100644
--- a/src/features/compose/editor/plugins/autosuggest-plugin.tsx
+++ b/src/features/compose/editor/plugins/autosuggest-plugin.tsx
@@ -36,6 +36,7 @@ import ReactDOM from 'react-dom';
import { clearComposeSuggestions, fetchComposeSuggestions } from 'soapbox/actions/compose.ts';
import { chooseEmoji } from 'soapbox/actions/emojis.ts';
import AutosuggestEmoji from 'soapbox/components/autosuggest-emoji.tsx';
+import { isNativeEmoji } from 'soapbox/features/emoji/index.ts';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
import { useCompose } from 'soapbox/hooks/useCompose.ts';
import { selectAccount } from 'soapbox/selectors/index.ts';
@@ -316,7 +317,11 @@ const AutosuggestPlugin = ({
if (typeof suggestion === 'object') {
if (!suggestion.id) return;
dispatch(chooseEmoji(suggestion));
- replaceMatch($createEmojiNode(suggestion));
+ if (isNativeEmoji(suggestion)) {
+ replaceMatch(new TextNode(suggestion.native));
+ } else {
+ replaceMatch($createEmojiNode(suggestion));
+ }
} else if (suggestion[0] === '#') {
(node as TextNode).setTextContent(`${suggestion} `);
node.select();
diff --git a/src/features/emoji/components/emoji-picker-dropdown.tsx b/src/features/emoji/components/emoji-picker-dropdown.tsx
index bdae364b3..3a528c267 100644
--- a/src/features/emoji/components/emoji-picker-dropdown.tsx
+++ b/src/features/emoji/components/emoji-picker-dropdown.tsx
@@ -234,7 +234,6 @@ const EmojiPickerDropdown: React.FC = ({
skin={handleSkinTone}
emojiSize={22}
emojiButtonSize={34}
- set='twitter'
theme={theme}
i18n={getI18n()}
skinTonePosition='search'
diff --git a/src/features/emoji/components/emoji-picker.tsx b/src/features/emoji/components/emoji-picker.tsx
index a6dc860ff..3407c76c8 100644
--- a/src/features/emoji/components/emoji-picker.tsx
+++ b/src/features/emoji/components/emoji-picker.tsx
@@ -1,20 +1,13 @@
-import spriteSheet from 'emoji-datasource/img/twitter/sheets/32.png';
import { Picker as EmojiPicker } from 'emoji-mart';
import { useRef, useEffect } from 'react';
import data from '../data.ts';
-const getSpritesheetURL = () => spriteSheet;
-
-const getImageURL = (_set: string, name: string) => {
- return `/packs/emoji/${name}.svg`;
-};
-
const Picker: React.FC = (props) => {
const ref = useRef(null);
useEffect(() => {
- const input = { ...props, data, ref, getImageURL, getSpritesheetURL };
+ const input = { ...props, data, ref };
new EmojiPicker(input);
}, []);
diff --git a/src/features/emoji/index.ts b/src/features/emoji/index.ts
index 2c4995dae..1ff9f555d 100644
--- a/src/features/emoji/index.ts
+++ b/src/features/emoji/index.ts
@@ -67,9 +67,7 @@ const convertCustom = (shortname: string, filename: string) => {
};
const convertUnicode = (c: string) => {
- const { unified, shortcode } = unicodeMapping[c];
-
- return `
`;
+ return c;
};
const convertEmoji = (str: string, customEmojis: any) => {
diff --git a/src/utils/emoji.test.ts b/src/utils/emoji.test.ts
deleted file mode 100644
index 7587e57a5..000000000
--- a/src/utils/emoji.test.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { describe, expect, it } from 'vitest';
-
-import {
- removeVS16s,
- toCodePoints,
-} from './emoji.ts';
-
-const ASCII_HEART = 'β€'; // '\u2764\uFE0F'
-const RED_HEART_RGI = 'β€οΈ'; // '\u2764'
-const JOY = 'π';
-
-describe('removeVS16s()', () => {
- it('removes Variation Selector-16 characters from emoji', () => {
- // Sanity check
- expect(ASCII_HEART).not.toBe(RED_HEART_RGI);
-
- // It normalizes an emoji with VS16s
- expect(removeVS16s(RED_HEART_RGI)).toBe(ASCII_HEART);
-
- // Leaves a regular emoji alone
- expect(removeVS16s(JOY)).toBe(JOY);
- });
-});
-
-describe('toCodePoints()', () => {
- it('converts a plain emoji', () => {
- expect(toCodePoints('π')).toEqual(['1f602']);
- });
-
- it('converts a VS16 emoji', () => {
- expect(toCodePoints(RED_HEART_RGI)).toEqual(['2764', 'fe0f']);
- });
-
- it('converts an ASCII character', () => {
- expect(toCodePoints(ASCII_HEART)).toEqual(['2764']);
- });
-
- it('converts a sequence emoji', () => {
- expect(toCodePoints('πΊπΈ')).toEqual(['1f1fa', '1f1f8']);
- });
-});
diff --git a/src/utils/emoji.ts b/src/utils/emoji.ts
deleted file mode 100644
index 1d6da69d1..000000000
--- a/src/utils/emoji.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-// Taken from twemoji-parser
-// https://github.com/twitter/twemoji-parser/blob/a97ef3994e4b88316812926844d51c296e889f76/src/index.js
-
-/** Remove Variation Selector-16 characters from emoji */
-// https://emojipedia.org/variation-selector-16/
-const removeVS16s = (rawEmoji: string): string => {
- const vs16RegExp = /\uFE0F/g;
- const zeroWidthJoiner = String.fromCharCode(0x200d);
- return rawEmoji.indexOf(zeroWidthJoiner) < 0 ? rawEmoji.replace(vs16RegExp, '') : rawEmoji;
-};
-
-/** Convert emoji into an array of Unicode codepoints */
-const toCodePoints = (unicodeSurrogates: string): string[] => {
- const points = [];
- let char = 0;
- let previous = 0;
- let i = 0;
- while (i < unicodeSurrogates.length) {
- char = unicodeSurrogates.charCodeAt(i++);
- if (previous) {
- points.push((0x10000 + ((previous - 0xd800) << 10) + (char - 0xdc00)).toString(16));
- previous = 0;
- } else if (char > 0xd800 && char <= 0xdbff) {
- previous = char;
- } else {
- points.push(char.toString(16));
- }
- }
- return points;
-};
-
-export {
- removeVS16s,
- toCodePoints,
-};
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 50c0d9e3d..8e197193e 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -27,7 +27,7 @@ const config: Config = {
base: '0.9375rem',
},
fontFamily: {
- 'sans': [
+ sans: [
'Soapbox i18n',
'Inter',
'ui-sans-serif',
@@ -45,11 +45,20 @@ const config: Config = {
'Segoe UI Symbol',
'Noto Color Emoji',
],
- 'mono': [
+ mono: [
'Roboto Mono',
'ui-monospace',
'mono',
],
+ emoji: [
+ 'Segoe UI Emoji',
+ 'Segoe UI Symbol',
+ 'Segoe UI',
+ 'Apple Color Emoji',
+ 'Twemoji Mozilla',
+ 'Noto Color Emoji',
+ 'Android Emoji',
+ ],
},
spacing: {
'4.5': '1.125rem',
diff --git a/vite.config.ts b/vite.config.ts
index c056a0a4d..7ca8ca508 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -69,9 +69,6 @@ export default defineConfig(() => {
}),
viteStaticCopy({
targets: [{
- src: './node_modules/@twemoji/svg/*',
- dest: 'packs/emoji/',
- }, {
src: './src/instance',
dest: '.',
}, {
diff --git a/yarn.lock b/yarn.lock
index e1add8d3e..9a05530f0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2525,11 +2525,6 @@
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
-"@twemoji/svg@^15.0.0":
- version "15.0.0"
- resolved "https://registry.yarnpkg.com/@twemoji/svg/-/svg-15.0.0.tgz#0e3828c654726f1848fe11f31ef4e8a75854cc7f"
- integrity sha512-ZSPef2B6nBaYnfgdTbAy4jgW95o7pi2xPGwGCU+WMTxo7J6B1lMPTWwSq/wTuiMq+N0khQ90CcvYp1wFoQpo/w==
-
"@types/aria-query@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc"
@@ -4132,11 +4127,6 @@ electron-to-chromium@^1.5.28:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.28.tgz#aee074e202c6ee8a0030a9c2ef0b3fe9f967d576"
integrity sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==
-emoji-datasource@15.0.1:
- version "15.0.1"
- resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-15.0.1.tgz#6cc7676e4d48d7559c2e068ffcacf84ec653584c"
- integrity sha512-aF5Q6LCKXzJzpG4K0ETiItuzz0xLYxNexR9qWw45/shuuEDWZkOIbeGHA23uopOSYA/LmeZIXIFsySCx+YKg2g==
-
emoji-mart@^5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-5.6.0.tgz#71b3ed0091d3e8c68487b240d9d6d9a73c27f023"