mirror of
https://github.com/mastodon/mastodon.git
synced 2026-05-10 05:39:56 -05:00
remove usage of emojify
This commit is contained in:
parent
86e4ecfa20
commit
3c17fde5ef
|
|
@ -13,14 +13,17 @@ import axios from 'axios';
|
|||
import { on } from 'delegated-events';
|
||||
import { throttle } from 'lodash';
|
||||
|
||||
import { determineEmojiMode } from '@/mastodon/features/emoji/mode';
|
||||
import { updateHtmlWithEmoji } from '@/mastodon/features/emoji/render';
|
||||
import loadKeyboardExtensions from '@/mastodon/load_keyboard_extensions';
|
||||
import { loadLocale, getLocale } from '@/mastodon/locales';
|
||||
import { loadPolyfills } from '@/mastodon/polyfills';
|
||||
import ready from '@/mastodon/ready';
|
||||
import { assetHost } from '@/mastodon/utils/config';
|
||||
import { isRecord } from '@/mastodon/utils/objects';
|
||||
import { isDarkMode } from '@/mastodon/utils/theme';
|
||||
import { formatTime } from '@/mastodon/utils/time';
|
||||
|
||||
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';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
@ -38,7 +41,7 @@ const messages = defineMessages({
|
|||
},
|
||||
});
|
||||
|
||||
function loaded() {
|
||||
async function loaded() {
|
||||
const { messages: localeData } = getLocale();
|
||||
|
||||
const locale = document.documentElement.lang;
|
||||
|
|
@ -75,9 +78,32 @@ function loaded() {
|
|||
return messageFormat.format(values) as string;
|
||||
};
|
||||
|
||||
document.querySelectorAll('.emojify').forEach((content) => {
|
||||
content.innerHTML = emojify(content.innerHTML);
|
||||
});
|
||||
let emojiStyle = 'auto';
|
||||
const initialStateText =
|
||||
document.getElementById('initial-state')?.textContent;
|
||||
if (initialStateText) {
|
||||
const state = JSON.parse(initialStateText) as unknown;
|
||||
if (
|
||||
isRecord(state) &&
|
||||
'meta' in state &&
|
||||
isRecord(state.meta) &&
|
||||
'emoji_style' in state.meta &&
|
||||
typeof state.meta.emoji_style === 'string'
|
||||
) {
|
||||
emojiStyle = state.meta.emoji_style;
|
||||
}
|
||||
}
|
||||
const emojiMode = determineEmojiMode(emojiStyle);
|
||||
const darkTheme = isDarkMode();
|
||||
for (const element of document.querySelectorAll('.emojify')) {
|
||||
await updateHtmlWithEmoji({
|
||||
assetHost,
|
||||
element,
|
||||
locale,
|
||||
mode: emojiMode,
|
||||
darkTheme,
|
||||
});
|
||||
}
|
||||
|
||||
document
|
||||
.querySelectorAll<HTMLTimeElement>('time.formatted')
|
||||
|
|
|
|||
|
|
@ -34,6 +34,43 @@ export function useEmojiAppState(): EmojiAppState {
|
|||
};
|
||||
}
|
||||
|
||||
export function getEmojiAppState(): EmojiAppState {
|
||||
const currentLocale = toSupportedLocale(document.documentElement.lang);
|
||||
|
||||
let emojiStyle = 'auto';
|
||||
const initialStateText =
|
||||
document.getElementById('initial-state')?.textContent;
|
||||
if (initialStateText) {
|
||||
try {
|
||||
const state = JSON.parse(initialStateText) as unknown;
|
||||
if (
|
||||
state !== null &&
|
||||
typeof state === 'object' &&
|
||||
'meta' in state &&
|
||||
state.meta !== null &&
|
||||
typeof state.meta === 'object' &&
|
||||
'emoji_style' in state.meta &&
|
||||
typeof state.meta.emoji_style === 'string'
|
||||
) {
|
||||
emojiStyle = state.meta.emoji_style;
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
console.warn(
|
||||
'Failed to parse initial state for emoji, defaulting to auto. Error:',
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
currentLocale,
|
||||
locales: [currentLocale],
|
||||
mode: determineEmojiMode(emojiStyle),
|
||||
darkTheme: isDarkMode(),
|
||||
assetHost,
|
||||
};
|
||||
}
|
||||
|
||||
type Feature = Uint8ClampedArray;
|
||||
|
||||
// See: https://github.com/nolanlawson/emoji-picker-element/blob/master/src/picker/constants.js
|
||||
|
|
|
|||
|
|
@ -152,11 +152,11 @@ const CODES_WITH_LIGHT_BORDER = EMOJIS_WITH_LIGHT_BORDER.map(emojiToUnicodeHex);
|
|||
|
||||
export function unicodeHexToUrl({
|
||||
unicodeHex,
|
||||
darkTheme,
|
||||
darkTheme = true,
|
||||
assetHost,
|
||||
}: {
|
||||
unicodeHex: string;
|
||||
darkTheme: boolean;
|
||||
darkTheme?: boolean;
|
||||
assetHost: string;
|
||||
}): string {
|
||||
const normalizedHex = unicodeToTwemojiHex(unicodeHex);
|
||||
|
|
|
|||
|
|
@ -137,6 +137,18 @@ describe('loadEmojiDataToState', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('converts unicode emoji code to hexcode when loading data', async () => {
|
||||
const dbCall = vi
|
||||
.spyOn(db, 'loadEmojiByHexcode')
|
||||
.mockResolvedValue(unicodeEmojiFactory());
|
||||
const unicodeState = {
|
||||
type: 'unicode',
|
||||
code: '😊',
|
||||
} as const satisfies EmojiStateUnicode;
|
||||
await loadEmojiDataToState(unicodeState, 'en');
|
||||
expect(dbCall).toHaveBeenCalledWith('1F60A', 'en');
|
||||
});
|
||||
|
||||
test('returns null for custom emoji without data', async () => {
|
||||
const customState = {
|
||||
type: 'custom',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import {
|
|||
EMOJI_TYPE_UNICODE,
|
||||
EMOJI_TYPE_CUSTOM,
|
||||
} from './constants';
|
||||
import { emojiToInversionClassName, unicodeHexToUrl } from './normalize';
|
||||
import type {
|
||||
EmojiAppState,
|
||||
EmojiLoadedState,
|
||||
EmojiMode,
|
||||
EmojiState,
|
||||
|
|
@ -47,14 +49,14 @@ export function tokenizeText(text: string): TokenizedText {
|
|||
if (code.startsWith(':') && code.endsWith(':')) {
|
||||
// Custom emoji
|
||||
tokens.push({
|
||||
type: EMOJI_TYPE_CUSTOM,
|
||||
code,
|
||||
type: EMOJI_TYPE_CUSTOM,
|
||||
} satisfies EmojiStateCustom);
|
||||
} else {
|
||||
// Unicode emoji
|
||||
tokens.push({
|
||||
code,
|
||||
type: EMOJI_TYPE_UNICODE,
|
||||
code: code,
|
||||
} satisfies EmojiStateUnicode);
|
||||
}
|
||||
lastIndex = match.index + code.length;
|
||||
|
|
@ -76,8 +78,8 @@ export function stringToEmojiState(
|
|||
): EmojiStateUnicode | Required<EmojiStateCustom> | null {
|
||||
if (isUnicodeEmoji(code)) {
|
||||
return {
|
||||
type: EMOJI_TYPE_UNICODE,
|
||||
code: emojiToUnicodeHex(code),
|
||||
type: EMOJI_TYPE_UNICODE,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +97,65 @@ export function stringToEmojiState(
|
|||
return null;
|
||||
}
|
||||
|
||||
export async function updateHtmlWithEmoji({
|
||||
assetHost,
|
||||
darkTheme,
|
||||
element,
|
||||
mode,
|
||||
locale,
|
||||
}: {
|
||||
element: Element;
|
||||
locale: string;
|
||||
} & Omit<EmojiAppState, 'currentLocale' | 'locales'>) {
|
||||
if (mode === EMOJI_MODE_NATIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tokens = tokenizeText(element.innerHTML);
|
||||
const newChildren: (string | Element)[] = [];
|
||||
for (const token of tokens) {
|
||||
if (typeof token === 'string') {
|
||||
newChildren.push(token);
|
||||
continue;
|
||||
}
|
||||
|
||||
const state = await loadEmojiDataToState(token, locale);
|
||||
// Ignore custom emoji if we encounter them.
|
||||
if (!state || state.type === EMOJI_TYPE_CUSTOM) {
|
||||
newChildren.push(token.code);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!shouldRenderImage(state, mode)) {
|
||||
newChildren.push(state.data.unicode);
|
||||
continue;
|
||||
}
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = unicodeHexToUrl({
|
||||
assetHost,
|
||||
darkTheme,
|
||||
unicodeHex: state.data.hexcode,
|
||||
});
|
||||
img.alt = state.data.unicode;
|
||||
img.title = state.data.label;
|
||||
img.classList.add('emojione');
|
||||
|
||||
const inversionClass = emojiToInversionClassName(state.data.unicode);
|
||||
if (inversionClass) {
|
||||
img.classList.add(inversionClass);
|
||||
}
|
||||
|
||||
newChildren.push(img);
|
||||
}
|
||||
|
||||
element.innerHTML = newChildren.reduce<string>(
|
||||
(prev, curr) =>
|
||||
typeof curr === 'string' ? prev + curr : prev + curr.outerHTML,
|
||||
'',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads emoji data into the given state if not already loaded.
|
||||
* @param state Emoji state to load data for.
|
||||
|
|
@ -121,17 +182,19 @@ export async function loadEmojiDataToState(
|
|||
LocaleNotLoadedError,
|
||||
} = await import('./database');
|
||||
|
||||
const code = isUnicodeEmoji(state.code)
|
||||
? emojiToUnicodeHex(state.code)
|
||||
: state.code;
|
||||
|
||||
// First, try to load the data from IndexedDB.
|
||||
try {
|
||||
const legacyCode = await loadLegacyShortcodesByShortcode(state.code);
|
||||
const legacyCode = await loadLegacyShortcodesByShortcode(code);
|
||||
// This is duplicative, but that's because TS can't distinguish the state type easily.
|
||||
const data = await loadEmojiByHexcode(
|
||||
legacyCode?.hexcode ?? state.code,
|
||||
locale,
|
||||
);
|
||||
const data = await loadEmojiByHexcode(legacyCode?.hexcode ?? code, locale);
|
||||
if (data) {
|
||||
return {
|
||||
...state,
|
||||
code,
|
||||
type: EMOJI_TYPE_UNICODE,
|
||||
data,
|
||||
// TODO: Use CLDR shortcodes when the picker supports them.
|
||||
|
|
@ -140,14 +203,14 @@ export async function loadEmojiDataToState(
|
|||
}
|
||||
|
||||
// If not found, assume it's not an emoji and return null.
|
||||
log('Could not find emoji %s for locale %s', state.code, locale);
|
||||
log('Could not find emoji %s for locale %s', code, locale);
|
||||
return null;
|
||||
} catch (err: unknown) {
|
||||
// If the locale is not loaded, load it and retry once.
|
||||
if (!retry && err instanceof LocaleNotLoadedError) {
|
||||
log(
|
||||
'Error loading emoji %s for locale %s, loading locale and retrying.',
|
||||
state.code,
|
||||
code,
|
||||
locale,
|
||||
);
|
||||
const { importEmojiData } = await import('./loader');
|
||||
|
|
|
|||
6
app/javascript/mastodon/utils/objects.ts
Normal file
6
app/javascript/mastodon/utils/objects.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export type ValidObjectKey = string | number | symbol;
|
||||
export type RecordObject = Record<ValidObjectKey, unknown>;
|
||||
|
||||
export function isRecord(value: unknown): value is RecordObject {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user