mirror of
https://github.com/mastodon/mastodon.git
synced 2025-10-05 16:42:47 +00:00
Refactor emoji GIF animation (#36165)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
Chromatic / Run Chromatic (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
This commit is contained in:
parent
24ddf80ff7
commit
6bd90940b6
|
@ -14,7 +14,10 @@ export const DisplayNameWithoutDomain: FC<
|
||||||
ComponentPropsWithoutRef<'span'>
|
ComponentPropsWithoutRef<'span'>
|
||||||
> = ({ account, className, children, ...props }) => {
|
> = ({ account, className, children, ...props }) => {
|
||||||
return (
|
return (
|
||||||
<span {...props} className={classNames('display-name', className)}>
|
<span
|
||||||
|
{...props}
|
||||||
|
className={classNames('display-name animate-parent', className)}
|
||||||
|
>
|
||||||
<bdi>
|
<bdi>
|
||||||
{account ? (
|
{account ? (
|
||||||
<EmojiHTML
|
<EmojiHTML
|
||||||
|
|
|
@ -140,32 +140,6 @@ class StatusContent extends PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseEnter = ({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-original');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseLeave = ({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-static');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this._updateStatusLinks();
|
this._updateStatusLinks();
|
||||||
}
|
}
|
||||||
|
@ -257,7 +231,13 @@ class StatusContent extends PureComponent {
|
||||||
if (this.props.onClick) {
|
if (this.props.onClick) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classNames} ref={this.setRef} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
<div
|
||||||
|
className={classNames}
|
||||||
|
ref={this.setRef}
|
||||||
|
onMouseDown={this.handleMouseDown}
|
||||||
|
onMouseUp={this.handleMouseUp}
|
||||||
|
key='status-content'
|
||||||
|
>
|
||||||
<EmojiHTML
|
<EmojiHTML
|
||||||
className='status__content__text status__content__text--visible translate'
|
className='status__content__text status__content__text--visible translate'
|
||||||
lang={language}
|
lang={language}
|
||||||
|
@ -274,7 +254,7 @@ class StatusContent extends PureComponent {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className={classNames} ref={this.setRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
<div className={classNames} ref={this.setRef}>
|
||||||
<EmojiHTML
|
<EmojiHTML
|
||||||
className='status__content__text status__content__text--visible translate'
|
className='status__content__text status__content__text--visible translate'
|
||||||
lang={language}
|
lang={language}
|
||||||
|
|
|
@ -379,36 +379,6 @@ export const AccountHeader: React.FC<{
|
||||||
});
|
});
|
||||||
}, [account]);
|
}, [account]);
|
||||||
|
|
||||||
const handleMouseEnter = useCallback(
|
|
||||||
({ currentTarget }: React.MouseEvent) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTarget
|
|
||||||
.querySelectorAll<HTMLImageElement>('.custom-emoji')
|
|
||||||
.forEach((emoji) => {
|
|
||||||
emoji.src = emoji.getAttribute('data-original') ?? '';
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleMouseLeave = useCallback(
|
|
||||||
({ currentTarget }: React.MouseEvent) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTarget
|
|
||||||
.querySelectorAll<HTMLImageElement>('.custom-emoji')
|
|
||||||
.forEach((emoji) => {
|
|
||||||
emoji.src = emoji.getAttribute('data-static') ?? '';
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const suspended = account?.suspended;
|
const suspended = account?.suspended;
|
||||||
const isRemote = account?.acct !== account?.username;
|
const isRemote = account?.acct !== account?.username;
|
||||||
const remoteDomain = isRemote ? account?.acct.split('@')[1] : null;
|
const remoteDomain = isRemote ? account?.acct.split('@')[1] : null;
|
||||||
|
@ -808,11 +778,9 @@ export const AccountHeader: React.FC<{
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classNames('account__header', {
|
className={classNames('account__header animate-parent', {
|
||||||
inactive: !!account.moved,
|
inactive: !!account.moved,
|
||||||
})}
|
})}
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
>
|
||||||
{!(suspended || hidden || account.moved) &&
|
{!(suspended || hidden || account.moved) &&
|
||||||
relationship?.requested_by && (
|
relationship?.requested_by && (
|
||||||
|
|
|
@ -23,7 +23,6 @@ import { IconButton } from 'mastodon/components/icon_button';
|
||||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||||
import StatusContent from 'mastodon/components/status_content';
|
import StatusContent from 'mastodon/components/status_content';
|
||||||
import { Dropdown } from 'mastodon/components/dropdown_menu';
|
import { Dropdown } from 'mastodon/components/dropdown_menu';
|
||||||
import { autoPlayGif } from 'mastodon/initial_state';
|
|
||||||
import { makeGetStatus } from 'mastodon/selectors';
|
import { makeGetStatus } from 'mastodon/selectors';
|
||||||
import { LinkedDisplayName } from '@/mastodon/components/display_name';
|
import { LinkedDisplayName } from '@/mastodon/components/display_name';
|
||||||
|
|
||||||
|
@ -57,32 +56,6 @@ export const Conversation = ({ conversation, scrollKey }) => {
|
||||||
const lastStatus = useSelector(state => getStatus(state, { id: lastStatusId }));
|
const lastStatus = useSelector(state => getStatus(state, { id: lastStatusId }));
|
||||||
const accounts = useSelector(state => getAccounts(state, accountIds));
|
const accounts = useSelector(state => getAccounts(state, accountIds));
|
||||||
|
|
||||||
const handleMouseEnter = useCallback(({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-original');
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleMouseLeave = useCallback(({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-static');
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
if (unread) {
|
if (unread) {
|
||||||
dispatch(markConversationRead(id));
|
dispatch(markConversationRead(id));
|
||||||
|
@ -163,7 +136,7 @@ export const Conversation = ({ conversation, scrollKey }) => {
|
||||||
{unread && <span className='conversation__unread' />} <RelativeTimestamp timestamp={lastStatus.get('created_at')} />
|
{unread && <span className='conversation__unread' />} <RelativeTimestamp timestamp={lastStatus.get('created_at')} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='conversation__content__names' onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
<div className='conversation__content__names animate-parent' onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||||
<FormattedMessage id='conversation.with' defaultMessage='With {names}' values={{ names: <span>{names}</span> }} />
|
<FormattedMessage id='conversation.with' defaultMessage='With {names}' values={{ names: <span>{names}</span> }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import type { MouseEventHandler } from 'react';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||||
|
@ -44,39 +43,6 @@ export const AccountCard: React.FC<{ accountId: string }> = ({ accountId }) => {
|
||||||
const account = useAppSelector((s) => getAccount(s, accountId));
|
const account = useAppSelector((s) => getAccount(s, accountId));
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const handleMouseEnter = useCallback<MouseEventHandler>(
|
|
||||||
({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const emojis =
|
|
||||||
currentTarget.querySelectorAll<HTMLImageElement>('.custom-emoji');
|
|
||||||
|
|
||||||
emojis.forEach((emoji) => {
|
|
||||||
const original = emoji.getAttribute('data-original');
|
|
||||||
if (original) emoji.src = original;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleMouseLeave = useCallback<MouseEventHandler>(
|
|
||||||
({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis =
|
|
||||||
currentTarget.querySelectorAll<HTMLImageElement>('.custom-emoji');
|
|
||||||
|
|
||||||
emojis.forEach((emoji) => {
|
|
||||||
const staticUrl = emoji.getAttribute('data-static');
|
|
||||||
if (staticUrl) emoji.src = staticUrl;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleFollow = useCallback(() => {
|
const handleFollow = useCallback(() => {
|
||||||
if (!account) return;
|
if (!account) return;
|
||||||
|
|
||||||
|
@ -185,9 +151,7 @@ export const AccountCard: React.FC<{ accountId: string }> = ({ accountId }) => {
|
||||||
|
|
||||||
{account.get('note').length > 0 && (
|
{account.get('note').length > 0 && (
|
||||||
<div
|
<div
|
||||||
className='account-card__bio translate'
|
className='account-card__bio translate animate-parent'
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
|
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import type { ComponentPropsWithoutRef, ElementType } from 'react';
|
import type { ComponentPropsWithoutRef, ElementType } from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
|
import { isModernEmojiEnabled } from '@/mastodon/utils/environment';
|
||||||
|
|
||||||
import { useEmojify } from './hooks';
|
import { useEmojify } from './hooks';
|
||||||
|
@ -7,12 +9,13 @@ import type { CustomEmojiMapArg } from './types';
|
||||||
|
|
||||||
type EmojiHTMLProps<Element extends ElementType = 'div'> = Omit<
|
type EmojiHTMLProps<Element extends ElementType = 'div'> = Omit<
|
||||||
ComponentPropsWithoutRef<Element>,
|
ComponentPropsWithoutRef<Element>,
|
||||||
'dangerouslySetInnerHTML'
|
'dangerouslySetInnerHTML' | 'className'
|
||||||
> & {
|
> & {
|
||||||
htmlString: string;
|
htmlString: string;
|
||||||
extraEmojis?: CustomEmojiMapArg;
|
extraEmojis?: CustomEmojiMapArg;
|
||||||
as?: Element;
|
as?: Element;
|
||||||
shallow?: boolean;
|
shallow?: boolean;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModernEmojiHTML = ({
|
export const ModernEmojiHTML = ({
|
||||||
|
@ -20,6 +23,7 @@ export const ModernEmojiHTML = ({
|
||||||
htmlString,
|
htmlString,
|
||||||
as: Wrapper = 'div', // Rename for syntax highlighting
|
as: Wrapper = 'div', // Rename for syntax highlighting
|
||||||
shallow,
|
shallow,
|
||||||
|
className = '',
|
||||||
...props
|
...props
|
||||||
}: EmojiHTMLProps<ElementType>) => {
|
}: EmojiHTMLProps<ElementType>) => {
|
||||||
const emojifiedHtml = useEmojify({
|
const emojifiedHtml = useEmojify({
|
||||||
|
@ -33,7 +37,11 @@ export const ModernEmojiHTML = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper {...props} dangerouslySetInnerHTML={{ __html: emojifiedHtml }} />
|
<Wrapper
|
||||||
|
{...props}
|
||||||
|
className={classNames(className, 'animate-parent')}
|
||||||
|
dangerouslySetInnerHTML={{ __html: emojifiedHtml }}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,7 +51,13 @@ export const EmojiHTML = <Element extends ElementType>(
|
||||||
if (isModernEmojiEnabled()) {
|
if (isModernEmojiEnabled()) {
|
||||||
return <ModernEmojiHTML {...props} />;
|
return <ModernEmojiHTML {...props} />;
|
||||||
}
|
}
|
||||||
const { as: asElement, htmlString, extraEmojis, ...rest } = props;
|
const { as: asElement, htmlString, extraEmojis, className, ...rest } = props;
|
||||||
const Wrapper = asElement ?? 'div';
|
const Wrapper = asElement ?? 'div';
|
||||||
return <Wrapper {...rest} dangerouslySetInnerHTML={{ __html: htmlString }} />;
|
return (
|
||||||
|
<Wrapper
|
||||||
|
{...rest}
|
||||||
|
dangerouslySetInnerHTML={{ __html: htmlString }}
|
||||||
|
className={classNames(className, 'animate-parent')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
61
app/javascript/mastodon/features/emoji/handlers.ts
Normal file
61
app/javascript/mastodon/features/emoji/handlers.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
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<HTMLImageElement>('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;
|
||||||
|
}
|
|
@ -111,42 +111,14 @@ class ContentWithRouter extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMouseEnter = ({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-original');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseLeave = ({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-static');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { announcement } = this.props;
|
const { announcement } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='announcements__item__content translate'
|
className='announcements__item__content translate animate-parent'
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
dangerouslySetInnerHTML={{ __html: announcement.get('contentHtml') }}
|
dangerouslySetInnerHTML={{ __html: announcement.get('contentHtml') }}
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -238,9 +210,21 @@ class Reaction extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<animated.button className={classNames('reactions-bar__item', { active: reaction.get('me') })} onClick={this.handleClick} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} title={`:${shortCode}:`} style={this.props.style}>
|
<animated.button
|
||||||
<span className='reactions-bar__item__emoji'><Emoji hovered={this.state.hovered} emoji={reaction.get('name')} emojiMap={this.props.emojiMap} /></span>
|
className={classNames('reactions-bar__item', { active: reaction.get('me') })}
|
||||||
<span className='reactions-bar__item__count'><AnimatedNumber value={reaction.get('count')} /></span>
|
onClick={this.handleClick}
|
||||||
|
title={`:${shortCode}:`}
|
||||||
|
style={this.props.style}
|
||||||
|
// This does not use animate-parent as this component is directly rendered by React.
|
||||||
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
onMouseLeave={this.handleMouseLeave}
|
||||||
|
>
|
||||||
|
<span className='reactions-bar__item__emoji'>
|
||||||
|
<Emoji hovered={this.state.hovered} emoji={reaction.get('name')} emojiMap={this.props.emojiMap} />
|
||||||
|
</span>
|
||||||
|
<span className='reactions-bar__item__count'>
|
||||||
|
<AnimatedNumber value={reaction.get('count')} />
|
||||||
|
</span>
|
||||||
</animated.button>
|
</animated.button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,32 +76,6 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({
|
||||||
[clickCoordinatesRef, statusId, account, history],
|
[clickCoordinatesRef, statusId, account, history],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleMouseEnter = useCallback<React.MouseEventHandler<HTMLDivElement>>(
|
|
||||||
({ currentTarget }) => {
|
|
||||||
const emojis =
|
|
||||||
currentTarget.querySelectorAll<HTMLImageElement>('.custom-emoji');
|
|
||||||
|
|
||||||
for (const emoji of emojis) {
|
|
||||||
const newSrc = emoji.getAttribute('data-original');
|
|
||||||
if (newSrc) emoji.src = newSrc;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleMouseLeave = useCallback<React.MouseEventHandler<HTMLDivElement>>(
|
|
||||||
({ currentTarget }) => {
|
|
||||||
const emojis =
|
|
||||||
currentTarget.querySelectorAll<HTMLImageElement>('.custom-emoji');
|
|
||||||
|
|
||||||
for (const emoji of emojis) {
|
|
||||||
const newSrc = emoji.getAttribute('data-static');
|
|
||||||
if (newSrc) emoji.src = newSrc;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleContentWarningClick = useCallback(() => {
|
const handleContentWarningClick = useCallback(() => {
|
||||||
dispatch(toggleStatusSpoilers(statusId));
|
dispatch(toggleStatusSpoilers(statusId));
|
||||||
}, [dispatch, statusId]);
|
}, [dispatch, statusId]);
|
||||||
|
@ -123,13 +97,11 @@ export const EmbeddedStatus: React.FC<{ statusId: string }> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='notification-group__embedded-status'
|
className='notification-group__embedded-status animate-parent'
|
||||||
role='button'
|
role='button'
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onMouseUp={handleMouseUp}
|
onMouseUp={handleMouseUp}
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
>
|
||||||
<div className='notification-group__embedded-status__account'>
|
<div className='notification-group__embedded-status__account'>
|
||||||
<Avatar account={account} size={16} />
|
<Avatar account={account} size={16} />
|
||||||
|
|
|
@ -22,11 +22,12 @@ import { identityContextPropShape, withIdentity } from 'mastodon/identity_contex
|
||||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||||
|
|
||||||
|
import { handleAnimateGif } from '../emoji/handlers';
|
||||||
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
|
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
|
||||||
import { clearHeight } from '../../actions/height_cache';
|
import { clearHeight } from '../../actions/height_cache';
|
||||||
import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server';
|
import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server';
|
||||||
import { expandHomeTimeline } from '../../actions/timelines';
|
import { expandHomeTimeline } from '../../actions/timelines';
|
||||||
import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding, disableHoverCards } from '../../initial_state';
|
import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding, disableHoverCards, autoPlayGif } from '../../initial_state';
|
||||||
|
|
||||||
import BundleColumnError from './components/bundle_column_error';
|
import BundleColumnError from './components/bundle_column_error';
|
||||||
import { NavigationBar } from './components/navigation_bar';
|
import { NavigationBar } from './components/navigation_bar';
|
||||||
|
@ -379,6 +380,11 @@ class UI extends PureComponent {
|
||||||
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
|
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
|
||||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
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('dragenter', this.handleDragEnter, false);
|
||||||
document.addEventListener('dragover', this.handleDragOver, false);
|
document.addEventListener('dragover', this.handleDragOver, false);
|
||||||
document.addEventListener('drop', this.handleDrop, false);
|
document.addEventListener('drop', this.handleDrop, false);
|
||||||
|
@ -404,6 +410,8 @@ class UI extends PureComponent {
|
||||||
window.removeEventListener('blur', this.handleWindowBlur);
|
window.removeEventListener('blur', this.handleWindowBlur);
|
||||||
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
|
window.removeEventListener('mouseover', handleAnimateGif);
|
||||||
|
window.removeEventListener('mouseout', handleAnimateGif);
|
||||||
|
|
||||||
document.removeEventListener('dragenter', this.handleDragEnter);
|
document.removeEventListener('dragenter', this.handleDragEnter);
|
||||||
document.removeEventListener('dragover', this.handleDragOver);
|
document.removeEventListener('dragover', this.handleDragOver);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user