parent
528acb8ac5
commit
2bbbcd625e
|
@ -569,7 +569,7 @@ const rejectEventParticipationRequestFail = (id: string, accountId: string, erro
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchEventIcs = (id: string) =>
|
const fetchEventIcs = (id: string) =>
|
||||||
(dispatch: any, getState: () => RootState) =>
|
(dispatch: AppDispatch, getState: () => RootState) =>
|
||||||
api(getState).get(`/api/v1/pleroma/events/${id}/ics`);
|
api(getState).get(`/api/v1/pleroma/events/${id}/ics`);
|
||||||
|
|
||||||
const cancelEventCompose = () => ({
|
const cancelEventCompose = () => ({
|
||||||
|
|
|
@ -2,7 +2,6 @@ import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TransitionMotion, spring } from 'react-motion';
|
import { TransitionMotion, spring } from 'react-motion';
|
||||||
|
|
||||||
import { Icon } from 'soapbox/components/ui';
|
|
||||||
import EmojiPickerDropdown from 'soapbox/features/emoji/containers/emoji-picker-dropdown-container';
|
import EmojiPickerDropdown from 'soapbox/features/emoji/containers/emoji-picker-dropdown-container';
|
||||||
import { useSettings } from 'soapbox/hooks';
|
import { useSettings } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
@ -55,7 +54,7 @@ const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, addR
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{visibleReactions.size < 8 && <EmojiPickerDropdown onPickEmoji={handleEmojiPick} button={<Icon className='h-4 w-4 text-gray-400 hover:text-gray-600 dark:hover:text-white' src={require('@tabler/icons/plus.svg')} />} />}
|
{visibleReactions.size < 8 && <EmojiPickerDropdown onPickEmoji={handleEmojiPick} />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</TransitionMotion>
|
</TransitionMotion>
|
||||||
|
|
|
@ -6,8 +6,7 @@ import { usePopper } from 'react-popper';
|
||||||
|
|
||||||
import { changeSetting } from 'soapbox/actions/settings';
|
import { changeSetting } from 'soapbox/actions/settings';
|
||||||
import { Emoji, HStack, IconButton } from 'soapbox/components/ui';
|
import { Emoji, HStack, IconButton } from 'soapbox/components/ui';
|
||||||
import { messages } from 'soapbox/features/emoji/components/emoji-picker-dropdown';
|
import { getFrequentlyUsedEmojis, messages } from 'soapbox/features/emoji/components/emoji-picker-dropdown';
|
||||||
import { getFrequentlyUsedEmojis } from 'soapbox/features/emoji/containers/emoji-picker-dropdown-container';
|
|
||||||
import { EmojiPicker as EmojiPickerAsync } from 'soapbox/features/ui/util/async-components';
|
import { EmojiPicker as EmojiPickerAsync } from 'soapbox/features/ui/util/async-components';
|
||||||
import { useAppDispatch, useAppSelector, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
@ -88,10 +87,14 @@ const EmojiSelector: React.FC<IEmojiSelector> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
if ([referenceElement, popperElement, document.querySelector('em-emoji-picker')].some(el => el?.contains(event.target as Node))) {
|
if ([referenceElement, popperElement].some(el => el?.contains(event.target as Node))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (document.querySelector('em-emoji-picker')) {
|
||||||
|
return setExpanded(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (onClose) {
|
if (onClose) {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
|
@ -145,6 +148,10 @@ const EmojiSelector: React.FC<IEmojiSelector> = ({
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => () => {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setExpanded(false);
|
setExpanded(false);
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
|
@ -103,7 +103,7 @@ const MediaItem: React.FC<IMediaItem> = ({ attachment, onOpenMedia }) => {
|
||||||
} else if (attachment.type === 'audio') {
|
} else if (attachment.type === 'audio') {
|
||||||
const remoteURL = attachment.remote_url || '';
|
const remoteURL = attachment.remote_url || '';
|
||||||
const fileExtensionLastIndex = remoteURL.lastIndexOf('.');
|
const fileExtensionLastIndex = remoteURL.lastIndexOf('.');
|
||||||
const fileExtension = remoteURL.substr(fileExtensionLastIndex + 1).toUpperCase();
|
const fileExtension = remoteURL.slice(fileExtensionLastIndex + 1).toUpperCase();
|
||||||
thumbnail = (
|
thumbnail = (
|
||||||
<div className='media-gallery__item-thumbnail'>
|
<div className='media-gallery__item-thumbnail'>
|
||||||
<span className='media-gallery__item__icons'><Icon src={require('@tabler/icons/volume.svg')} /></span>
|
<span className='media-gallery__item__icons'><Icon src={require('@tabler/icons/volume.svg')} /></span>
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
import React, { useEffect, useState, useLayoutEffect } from 'react';
|
import React, { useEffect, useState, useLayoutEffect } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { usePopper } from 'react-popper';
|
import { usePopper } from 'react-popper';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { useSettings } from 'soapbox/hooks';
|
import { useEmoji } from 'soapbox/actions/emojis';
|
||||||
|
import { changeSetting } from 'soapbox/actions/settings';
|
||||||
|
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
||||||
import { isMobile } from 'soapbox/is-mobile';
|
import { isMobile } from 'soapbox/is-mobile';
|
||||||
|
import { RootState } from 'soapbox/store';
|
||||||
|
|
||||||
import { buildCustomEmojis } from '../../emoji';
|
import { buildCustomEmojis } from '../../emoji';
|
||||||
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
||||||
|
|
||||||
import type { EmojiPick } from 'emoji-mart';
|
import type { EmojiPick } from 'emoji-mart';
|
||||||
import type { List } from 'immutable';
|
|
||||||
import type { Emoji, CustomEmoji, NativeEmoji } from 'soapbox/features/emoji';
|
import type { Emoji, CustomEmoji, NativeEmoji } from 'soapbox/features/emoji';
|
||||||
|
|
||||||
let EmojiPicker: any; // load asynchronously
|
let EmojiPicker: any; // load asynchronously
|
||||||
|
@ -43,17 +47,73 @@ export const messages = defineMessages({
|
||||||
skins_6: { id: 'emoji_button.skins_6', defaultMessage: 'Dark' },
|
skins_6: { id: 'emoji_button.skins_6', defaultMessage: 'Dark' },
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: fix types
|
export interface IEmojiPickerDropdown {
|
||||||
interface IEmojiPickerDropdown {
|
onPickEmoji?: (emoji: Emoji) => void
|
||||||
custom_emojis: List<any>
|
condensed?: boolean
|
||||||
frequentlyUsedEmojis: string[]
|
render: React.FC<{
|
||||||
intl: any
|
setPopperReference: React.Ref<HTMLButtonElement>
|
||||||
onPickEmoji: (emoji: Emoji) => void
|
title?: string
|
||||||
onSkinTone: () => void
|
visible?: boolean
|
||||||
condensed: boolean
|
loading?: boolean
|
||||||
render: any
|
handleToggle: (e: Event) => void
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const perLine = 8;
|
||||||
|
const lines = 2;
|
||||||
|
|
||||||
|
const DEFAULTS = [
|
||||||
|
'+1',
|
||||||
|
'grinning',
|
||||||
|
'kissing_heart',
|
||||||
|
'heart_eyes',
|
||||||
|
'laughing',
|
||||||
|
'stuck_out_tongue_winking_eye',
|
||||||
|
'sweat_smile',
|
||||||
|
'joy',
|
||||||
|
'yum',
|
||||||
|
'disappointed',
|
||||||
|
'thinking_face',
|
||||||
|
'weary',
|
||||||
|
'sob',
|
||||||
|
'sunglasses',
|
||||||
|
'heart',
|
||||||
|
'ok_hand',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getFrequentlyUsedEmojis = createSelector([
|
||||||
|
(state: RootState) => state.settings.get('frequentlyUsedEmojis', ImmutableMap()),
|
||||||
|
], (emojiCounters: ImmutableMap<string, number>) => {
|
||||||
|
let emojis = emojiCounters
|
||||||
|
.keySeq()
|
||||||
|
.sort((a, b) => emojiCounters.get(a)! - emojiCounters.get(b)!)
|
||||||
|
.reverse()
|
||||||
|
.slice(0, perLine * lines)
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
if (emojis.length < DEFAULTS.length) {
|
||||||
|
const uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji));
|
||||||
|
emojis = emojis.concat(uniqueDefaults.slice(0, DEFAULTS.length - emojis.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return emojis;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCustomEmojis = createSelector([
|
||||||
|
(state: RootState) => state.custom_emojis,
|
||||||
|
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
|
||||||
|
const aShort = a.get('shortcode')!.toLowerCase();
|
||||||
|
const bShort = b.get('shortcode')!.toLowerCase();
|
||||||
|
|
||||||
|
if (aShort < bShort) {
|
||||||
|
return -1;
|
||||||
|
} else if (aShort > bShort) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
// Fixes render bug where popover has a delayed position update
|
// Fixes render bug where popover has a delayed position update
|
||||||
const RenderAfter = ({ children, update }: any) => {
|
const RenderAfter = ({ children, update }: any) => {
|
||||||
const [nextTick, setNextTick] = useState(false);
|
const [nextTick, setNextTick] = useState(false);
|
||||||
|
@ -75,13 +135,17 @@ const RenderAfter = ({ children, update }: any) => {
|
||||||
|
|
||||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||||
|
|
||||||
const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ custom_emojis, frequentlyUsedEmojis, onPickEmoji, onSkinTone, condensed, render: Render }) => {
|
const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ onPickEmoji, condensed, render: Render }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const title = intl.formatMessage(messages.emoji);
|
const title = intl.formatMessage(messages.emoji);
|
||||||
const userTheme = settings.get('themeMode');
|
const userTheme = settings.get('themeMode');
|
||||||
const theme = (userTheme === 'dark' || userTheme === 'light') ? userTheme : 'auto';
|
const theme = (userTheme === 'dark' || userTheme === 'light') ? userTheme : 'auto';
|
||||||
|
|
||||||
|
const customEmojis = useAppSelector((state) => getCustomEmojis(state));
|
||||||
|
const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state));
|
||||||
|
|
||||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||||
const [popperReference, setPopperReference] = useState<HTMLButtonElement | null>(null);
|
const [popperReference, setPopperReference] = useState<HTMLButtonElement | null>(null);
|
||||||
const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null);
|
const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null);
|
||||||
|
@ -108,22 +172,34 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ custom_emojis, fr
|
||||||
const handlePick = (emoji: EmojiPick) => {
|
const handlePick = (emoji: EmojiPick) => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
|
||||||
|
let pickedEmoji: Emoji;
|
||||||
|
|
||||||
if (emoji.native) {
|
if (emoji.native) {
|
||||||
onPickEmoji({
|
pickedEmoji = {
|
||||||
id: emoji.id,
|
id: emoji.id,
|
||||||
colons: emoji.shortcodes,
|
colons: emoji.shortcodes,
|
||||||
custom: false,
|
custom: false,
|
||||||
native: emoji.native,
|
native: emoji.native,
|
||||||
unified: emoji.unified,
|
unified: emoji.unified,
|
||||||
} as NativeEmoji);
|
} as NativeEmoji;
|
||||||
} else {
|
} else {
|
||||||
onPickEmoji({
|
pickedEmoji = {
|
||||||
id: emoji.id,
|
id: emoji.id,
|
||||||
colons: emoji.shortcodes,
|
colons: emoji.shortcodes,
|
||||||
custom: true,
|
custom: true,
|
||||||
imageUrl: emoji.src,
|
imageUrl: emoji.src,
|
||||||
} as CustomEmoji);
|
} as CustomEmoji;
|
||||||
|
|
||||||
|
dispatch(useEmoji(pickedEmoji)); // eslint-disable-line react-hooks/rules-of-hooks
|
||||||
|
|
||||||
|
if (onPickEmoji) {
|
||||||
|
onPickEmoji(pickedEmoji);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSkinTone = (skinTone: string) => {
|
||||||
|
dispatch(changeSetting(['skinTone'], skinTone));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getI18n = () => {
|
const getI18n = () => {
|
||||||
|
@ -216,12 +292,12 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ custom_emojis, fr
|
||||||
<RenderAfter update={update}>
|
<RenderAfter update={update}>
|
||||||
{!loading && (
|
{!loading && (
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
custom={[{ emojis: buildCustomEmojis(custom_emojis) }]}
|
custom={[{ emojis: buildCustomEmojis(customEmojis) }]}
|
||||||
title={title}
|
title={title}
|
||||||
onEmojiSelect={handlePick}
|
onEmojiSelect={handlePick}
|
||||||
recent={frequentlyUsedEmojis}
|
recent={frequentlyUsedEmojis}
|
||||||
perLine={8}
|
perLine={8}
|
||||||
skin={onSkinTone}
|
skin={handleSkinTone}
|
||||||
emojiSize={22}
|
emojiSize={22}
|
||||||
emojiButtonSize={34}
|
emojiButtonSize={34}
|
||||||
set='twitter'
|
set='twitter'
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
import { IconButton } from 'soapbox/components/ui';
|
|
||||||
|
|
||||||
import { useEmoji } from '../../../actions/emojis';
|
|
||||||
import { getSettings, changeSetting } from '../../../actions/settings';
|
|
||||||
import EmojiPickerDropdown from '../components/emoji-picker-dropdown';
|
|
||||||
|
|
||||||
const perLine = 8;
|
|
||||||
const lines = 2;
|
|
||||||
|
|
||||||
const DEFAULTS = [
|
|
||||||
'+1',
|
|
||||||
'grinning',
|
|
||||||
'kissing_heart',
|
|
||||||
'heart_eyes',
|
|
||||||
'laughing',
|
|
||||||
'stuck_out_tongue_winking_eye',
|
|
||||||
'sweat_smile',
|
|
||||||
'joy',
|
|
||||||
'yum',
|
|
||||||
'disappointed',
|
|
||||||
'thinking_face',
|
|
||||||
'weary',
|
|
||||||
'sob',
|
|
||||||
'sunglasses',
|
|
||||||
'heart',
|
|
||||||
'ok_hand',
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getFrequentlyUsedEmojis = createSelector([
|
|
||||||
state => state.settings.get('frequentlyUsedEmojis', ImmutableMap()),
|
|
||||||
], emojiCounters => {
|
|
||||||
let emojis = emojiCounters
|
|
||||||
.keySeq()
|
|
||||||
.sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b))
|
|
||||||
.reverse()
|
|
||||||
.slice(0, perLine * lines)
|
|
||||||
.toArray();
|
|
||||||
|
|
||||||
if (emojis.length < DEFAULTS.length) {
|
|
||||||
const uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji));
|
|
||||||
emojis = emojis.concat(uniqueDefaults.slice(0, DEFAULTS.length - emojis.length));
|
|
||||||
}
|
|
||||||
|
|
||||||
return emojis;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getCustomEmojis = createSelector([
|
|
||||||
state => state.custom_emojis,
|
|
||||||
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
|
|
||||||
const aShort = a.get('shortcode').toLowerCase();
|
|
||||||
const bShort = b.get('shortcode').toLowerCase();
|
|
||||||
|
|
||||||
if (aShort < bShort) {
|
|
||||||
return -1;
|
|
||||||
} else if (aShort > bShort) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
custom_emojis: getCustomEmojis(state),
|
|
||||||
skinTone: getSettings(state).get('skinTone'),
|
|
||||||
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
|
|
||||||
onSkinTone: skinTone => {
|
|
||||||
dispatch(changeSetting(['skinTone'], skinTone));
|
|
||||||
},
|
|
||||||
|
|
||||||
onPickEmoji: emoji => {
|
|
||||||
dispatch(useEmoji(emoji)); // eslint-disable-line react-hooks/rules-of-hooks
|
|
||||||
|
|
||||||
if (onPickEmoji) {
|
|
||||||
onPickEmoji(emoji);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const Container = connect(mapStateToProps, mapDispatchToProps)(EmojiPickerDropdown);
|
|
||||||
|
|
||||||
const EmojiPickerDropdownWrapper = (props) => {
|
|
||||||
return (
|
|
||||||
<Container
|
|
||||||
render={
|
|
||||||
({ setPopperReference, title, visible, loading, handleToggle }) => (
|
|
||||||
<IconButton
|
|
||||||
className={classNames({
|
|
||||||
'text-gray-400 hover:text-gray-600': true,
|
|
||||||
'pulse-loading': visible && loading,
|
|
||||||
})}
|
|
||||||
ref={setPopperReference}
|
|
||||||
src={require('@tabler/icons/mood-happy.svg')}
|
|
||||||
title={title}
|
|
||||||
aria-label={title}
|
|
||||||
aria-expanded={visible}
|
|
||||||
role='button'
|
|
||||||
onClick={handleToggle}
|
|
||||||
onKeyDown={handleToggle}
|
|
||||||
tabIndex={0}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default EmojiPickerDropdownWrapper;
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { IconButton } from 'soapbox/components/ui';
|
||||||
|
|
||||||
|
import EmojiPickerDropdown, { IEmojiPickerDropdown } from '../components/emoji-picker-dropdown';
|
||||||
|
|
||||||
|
const EmojiPickerDropdownWrapper = (props: Omit<IEmojiPickerDropdown, 'render'>) => {
|
||||||
|
return (
|
||||||
|
<EmojiPickerDropdown
|
||||||
|
render={
|
||||||
|
({ setPopperReference, title, visible, loading, handleToggle }: any) => (
|
||||||
|
<IconButton
|
||||||
|
className={clsx({
|
||||||
|
'text-gray-400 hover:text-gray-600': true,
|
||||||
|
'pulse-loading': visible && loading,
|
||||||
|
})}
|
||||||
|
ref={setPopperReference}
|
||||||
|
src={require('@tabler/icons/mood-happy.svg')}
|
||||||
|
title={title}
|
||||||
|
aria-label={title}
|
||||||
|
aria-expanded={visible}
|
||||||
|
role='button'
|
||||||
|
onClick={handleToggle}
|
||||||
|
onKeyDown={handleToggle}
|
||||||
|
tabIndex={0}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default EmojiPickerDropdownWrapper;
|
|
@ -8,8 +8,8 @@ function replaceAll(str: string, find: string, replace: string) {
|
||||||
|
|
||||||
interface UnicodeMap {
|
interface UnicodeMap {
|
||||||
[s: string]: {
|
[s: string]: {
|
||||||
unified: string,
|
unified: string
|
||||||
shortcode: string,
|
shortcode: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ const search = (str: string, { maxResults = 5, custom }: searchOptions = {}, cus
|
||||||
.flatMap(id => {
|
.flatMap(id => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (id[0] === 'c') {
|
if (id[0] === 'c') {
|
||||||
const { shortcode, static_url } = custom_emojis.get((id as string).substr(1)).toJS();
|
const { shortcode, static_url } = custom_emojis.get((id as string).slice(1)).toJS();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: shortcode,
|
id: shortcode,
|
||||||
|
@ -51,10 +51,10 @@ const search = (str: string, { maxResults = 5, custom }: searchOptions = {}, cus
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { skins } = data.emojis[(id as string).substr(1)];
|
const { skins } = data.emojis[(id as string).slice(1)];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: (id as string).substr(1),
|
id: (id as string).slice(1),
|
||||||
colons: ':' + id + ':',
|
colons: ':' + id + ':',
|
||||||
unified: skins[0].unified,
|
unified: skins[0].unified,
|
||||||
native: skins[0].native,
|
native: skins[0].native,
|
||||||
|
|
|
@ -118,7 +118,7 @@ describe('statuses reducer', () => {
|
||||||
const status = require('soapbox/__fixtures__/status-custom-emoji.json');
|
const status = require('soapbox/__fixtures__/status-custom-emoji.json');
|
||||||
const action = { type: STATUS_IMPORT, status };
|
const action = { type: STATUS_IMPORT, status };
|
||||||
|
|
||||||
const expected = 'Hello <img draggable="false" class="emojione custom-emoji" alt=":ablobcathyper:" title=":ablobcathyper:" src="https://gleasonator.com/emoji/blobcat/ablobcathyper.png" data-original="https://gleasonator.com/emoji/blobcat/ablobcathyper.png" data-static="https://gleasonator.com/emoji/blobcat/ablobcathyper.png"> <img draggable="false" class="emojione custom-emoji" alt=":ageblobcat:" title=":ageblobcat:" src="https://gleasonator.com/emoji/blobcat/ageblobcat.png" data-original="https://gleasonator.com/emoji/blobcat/ageblobcat.png" data-static="https://gleasonator.com/emoji/blobcat/ageblobcat.png"> <img draggable="false" class="emojione" alt="😂" title=":joy:" src="/packs/emoji/1f602.svg"> world <img draggable="false" class="emojione" alt="😋" title=":yum:" src="/packs/emoji/1f60b.svg"> test <img draggable="false" class="emojione custom-emoji" alt=":blobcatphoto:" title=":blobcatphoto:" src="https://gleasonator.com/emoji/blobcat/blobcatphoto.png" data-original="https://gleasonator.com/emoji/blobcat/blobcatphoto.png" data-static="https://gleasonator.com/emoji/blobcat/blobcatphoto.png">';
|
const expected = 'Hello <img draggable="false" class="emojione" alt=":ablobcathyper:" title=":ablobcathyper:" src="https://gleasonator.com/emoji/blobcat/ablobcathyper.png"> <img draggable="false" class="emojione" alt=":ageblobcat:" title=":ageblobcat:" src="https://gleasonator.com/emoji/blobcat/ageblobcat.png"> <img draggable="false" class="emojione" alt="😂" title=":joy:" src="/packs/emoji/1f602.svg"> world <img draggable="false" class="emojione" alt="😋" title=":yum:" src="/packs/emoji/1f60b.svg"> test <img draggable="false" class="emojione" alt=":blobcatphoto:" title=":blobcatphoto:" src="https://gleasonator.com/emoji/blobcat/blobcatphoto.png">';
|
||||||
|
|
||||||
const result = reducer(undefined, action).getIn(['AGm7uC9DaAIGUa4KYK', 'contentHtml']);
|
const result = reducer(undefined, action).getIn(['AGm7uC9DaAIGUa4KYK', 'contentHtml']);
|
||||||
expect(result).toBe(expected);
|
expect(result).toBe(expected);
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { CUSTOM_EMOJIS_FETCH_SUCCESS } from '../actions/custom-emojis';
|
||||||
import type { AnyAction } from 'redux';
|
import type { AnyAction } from 'redux';
|
||||||
import type { APIEntity } from 'soapbox/types/entities';
|
import type { APIEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
const initialState = ImmutableList();
|
const initialState = ImmutableList<ImmutableMap<string, string>>();
|
||||||
|
|
||||||
// Populate custom emojis for composer autosuggest
|
// Populate custom emojis for composer autosuggest
|
||||||
const autosuggestPopulate = (emojis: ImmutableList<ImmutableMap<string, string>>) => {
|
const autosuggestPopulate = (emojis: ImmutableList<ImmutableMap<string, string>>) => {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
|
|
||||||
declare module 'emoji-mart' {
|
declare module 'emoji-mart' {
|
||||||
export interface NativeEmoji {
|
export interface NativeEmoji {
|
||||||
unified: string,
|
unified: string
|
||||||
native: string,
|
native: string
|
||||||
x: number,
|
x: number
|
||||||
y: number,
|
y: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomEmoji {
|
export interface CustomEmoji {
|
||||||
|
@ -12,40 +12,40 @@ declare module 'emoji-mart' {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Emoji<T> {
|
export interface Emoji<T> {
|
||||||
id: string,
|
id: string
|
||||||
name: string,
|
name: string
|
||||||
keywords: string[],
|
keywords: string[]
|
||||||
skins: T[],
|
skins: T[]
|
||||||
version?: number,
|
version?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmojiPick {
|
export interface EmojiPick {
|
||||||
id: string,
|
id: string
|
||||||
name: string,
|
name: string
|
||||||
native?: string,
|
native?: string
|
||||||
unified?: string,
|
unified?: string
|
||||||
keywords: string[],
|
keywords: string[]
|
||||||
shortcodes: string,
|
shortcodes: string
|
||||||
emoticons: string[],
|
emoticons: string[]
|
||||||
src?: string,
|
src?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PickerProps {
|
export interface PickerProps {
|
||||||
custom?: { emojis: Emoji<CustomEmoji> }[],
|
custom?: { emojis: Emoji<CustomEmoji> }[]
|
||||||
set?: string,
|
set?: string
|
||||||
title?: string,
|
title?: string
|
||||||
theme?: string,
|
theme?: string
|
||||||
onEmojiSelect?: (emoji: EmojiPick) => void,
|
onEmojiSelect?: (emoji: EmojiPick) => void
|
||||||
recent?: any,
|
recent?: any
|
||||||
skin?: any,
|
skin?: any
|
||||||
perLine?: number,
|
perLine?: number
|
||||||
emojiSize?: number,
|
emojiSize?: number
|
||||||
emojiButtonSize?: number,
|
emojiButtonSize?: number
|
||||||
navPosition?: string,
|
navPosition?: string
|
||||||
autoFocus?: boolean,
|
autoFocus?: boolean
|
||||||
i18n?: any,
|
i18n?: any
|
||||||
getImageURL: (set: string, name: string) => string,
|
getImageURL: (set: string, name: string) => string
|
||||||
getSpritesheetURL: (set: string) => string,
|
getSpritesheetURL: (set: string) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Picker {
|
export class Picker {
|
||||||
|
@ -58,10 +58,10 @@ declare module 'emoji-mart' {
|
||||||
|
|
||||||
declare module '@emoji-mart/data/sets/14/twitter.json' {
|
declare module '@emoji-mart/data/sets/14/twitter.json' {
|
||||||
export interface NativeEmoji {
|
export interface NativeEmoji {
|
||||||
unified: string,
|
unified: string
|
||||||
native: string,
|
native: string
|
||||||
x: number,
|
x: number
|
||||||
y: number,
|
y: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomEmoji {
|
export interface CustomEmoji {
|
||||||
|
@ -69,36 +69,36 @@ declare module '@emoji-mart/data/sets/14/twitter.json' {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Emoji<T> {
|
export interface Emoji<T> {
|
||||||
id: string,
|
id: string
|
||||||
name: string,
|
name: string
|
||||||
keywords: string[],
|
keywords: string[]
|
||||||
skins: T[],
|
skins: T[]
|
||||||
version?: number,
|
version?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmojiCategory {
|
export interface EmojiCategory {
|
||||||
id: string,
|
id: string
|
||||||
emojis: string[],
|
emojis: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmojiMap {
|
export interface EmojiMap {
|
||||||
[s: string]: Emoji<NativeEmoji>,
|
[s: string]: Emoji<NativeEmoji>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmojiAlias {
|
export interface EmojiAlias {
|
||||||
[s: string]: string,
|
[s: string]: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmojiSheet {
|
export interface EmojiSheet {
|
||||||
cols: number,
|
cols: number
|
||||||
rows: number,
|
rows: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmojiData {
|
export interface EmojiData {
|
||||||
categories: EmojiCategory[],
|
categories: EmojiCategory[]
|
||||||
emojis: EmojiMap,
|
emojis: EmojiMap
|
||||||
aliases: EmojiAlias,
|
aliases: EmojiAlias
|
||||||
sheet: EmojiSheet,
|
sheet: EmojiSheet
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: EmojiData;
|
const data: EmojiData;
|
||||||
|
|
Loading…
Reference in New Issue