Merge remote-tracking branch 'origin/develop' into ts
This commit is contained in:
commit
a16230780e
|
@ -7,7 +7,7 @@ import type { AxiosInstance, AxiosResponse } from 'axios';
|
||||||
const api = jest.requireActual('../api') as Record<string, Function>;
|
const api = jest.requireActual('../api') as Record<string, Function>;
|
||||||
let mocks: Array<Function> = [];
|
let mocks: Array<Function> = [];
|
||||||
|
|
||||||
export const __stub = (func: Function) => mocks.push(func);
|
export const __stub = (func: (mock: MockAdapter) => void) => mocks.push(func);
|
||||||
export const __clear = (): Function[] => mocks = [];
|
export const __clear = (): Function[] => mocks = [];
|
||||||
|
|
||||||
const setupMock = (axios: AxiosInstance) => {
|
const setupMock = (axios: AxiosInstance) => {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
const STATUS_HOVER_CARD_OPEN = 'STATUS_HOVER_CARD_OPEN';
|
||||||
|
const STATUS_HOVER_CARD_UPDATE = 'STATUS_HOVER_CARD_UPDATE';
|
||||||
|
const STATUS_HOVER_CARD_CLOSE = 'STATUS_HOVER_CARD_CLOSE';
|
||||||
|
|
||||||
|
const openStatusHoverCard = (ref: React.MutableRefObject<HTMLDivElement>, statusId: string) => ({
|
||||||
|
type: STATUS_HOVER_CARD_OPEN,
|
||||||
|
ref,
|
||||||
|
statusId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateStatusHoverCard = () => ({
|
||||||
|
type: STATUS_HOVER_CARD_UPDATE,
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeStatusHoverCard = (force = false) => ({
|
||||||
|
type: STATUS_HOVER_CARD_CLOSE,
|
||||||
|
force,
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
STATUS_HOVER_CARD_OPEN,
|
||||||
|
STATUS_HOVER_CARD_UPDATE,
|
||||||
|
STATUS_HOVER_CARD_CLOSE,
|
||||||
|
openStatusHoverCard,
|
||||||
|
updateStatusHoverCard,
|
||||||
|
closeStatusHoverCard,
|
||||||
|
};
|
|
@ -13,6 +13,8 @@ import * as BuildConfig from 'soapbox/build_config';
|
||||||
import { RootState } from 'soapbox/store';
|
import { RootState } from 'soapbox/store';
|
||||||
import { getAccessToken, getAppToken, isURL, parseBaseURL } from 'soapbox/utils/auth';
|
import { getAccessToken, getAppToken, isURL, parseBaseURL } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
import type MockAdapter from 'axios-mock-adapter';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Parse Link headers, mostly for pagination.
|
Parse Link headers, mostly for pagination.
|
||||||
@see {@link https://www.npmjs.com/package/http-link-header}
|
@see {@link https://www.npmjs.com/package/http-link-header}
|
||||||
|
@ -90,3 +92,7 @@ export default (getState: () => RootState, authType: string = 'user'): AxiosInst
|
||||||
|
|
||||||
return baseClient(accessToken, baseURL);
|
return baseClient(accessToken, baseURL);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The Jest mock exports these, so they're needed for TypeScript.
|
||||||
|
export const __stub = (_func: (mock: MockAdapter) => void) => 0;
|
||||||
|
export const __clear = (): Function[] => [];
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
openStatusHoverCard,
|
||||||
|
closeStatusHoverCard,
|
||||||
|
} from 'soapbox/actions/status-hover-card';
|
||||||
|
import { isMobile } from 'soapbox/is_mobile';
|
||||||
|
|
||||||
|
const showStatusHoverCard = debounce((dispatch, ref, statusId) => {
|
||||||
|
dispatch(openStatusHoverCard(ref, statusId));
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
interface IHoverStatusWrapper {
|
||||||
|
statusId: any,
|
||||||
|
inline: boolean,
|
||||||
|
className?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Makes a status hover card appear when the wrapped element is hovered. */
|
||||||
|
export const HoverStatusWrapper: React.FC<IHoverStatusWrapper> = ({ statusId, children, inline = false, className }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const Elem: keyof JSX.IntrinsicElements = inline ? 'span' : 'div';
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
if (!isMobile(window.innerWidth)) {
|
||||||
|
showStatusHoverCard(dispatch, ref, statusId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
showStatusHoverCard.cancel();
|
||||||
|
setTimeout(() => dispatch(closeStatusHoverCard()), 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
showStatusHoverCard.cancel();
|
||||||
|
dispatch(closeStatusHoverCard(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Elem
|
||||||
|
ref={ref}
|
||||||
|
className={classNames('hover-status-wrapper', className)}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Elem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { HoverStatusWrapper as default, showStatusHoverCard };
|
|
@ -0,0 +1,102 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
|
import { usePopper } from 'react-popper';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
import {
|
||||||
|
closeStatusHoverCard,
|
||||||
|
updateStatusHoverCard,
|
||||||
|
} from 'soapbox/actions/status-hover-card';
|
||||||
|
import { fetchStatus } from 'soapbox/actions/statuses';
|
||||||
|
import StatusContainer from 'soapbox/containers/status_container';
|
||||||
|
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
import { showStatusHoverCard } from './hover-status-wrapper';
|
||||||
|
import { Card, CardBody } from './ui';
|
||||||
|
|
||||||
|
interface IStatusHoverCard {
|
||||||
|
visible: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Popup status preview that appears when hovering reply to */
|
||||||
|
export const StatusHoverCard: React.FC<IStatusHoverCard> = ({ visible = true }) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const [popperElement, setPopperElement] = useState<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const statusId: string | undefined = useAppSelector(state => state.status_hover_card.statusId || undefined);
|
||||||
|
const status = useAppSelector(state => state.statuses.get(statusId!));
|
||||||
|
const targetRef = useAppSelector(state => state.status_hover_card.ref?.current);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (statusId && !status) {
|
||||||
|
dispatch(fetchStatus(statusId));
|
||||||
|
}
|
||||||
|
}, [statusId, status]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlisten = history.listen(() => {
|
||||||
|
showStatusHoverCard.cancel();
|
||||||
|
dispatch(closeStatusHoverCard());
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { styles, attributes } = usePopper(targetRef, popperElement, {
|
||||||
|
placement: 'top',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleMouseEnter = useCallback((): React.MouseEventHandler => {
|
||||||
|
return () => {
|
||||||
|
dispatch(updateStatusHoverCard());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleMouseLeave = useCallback((): React.MouseEventHandler => {
|
||||||
|
return () => {
|
||||||
|
dispatch(closeStatusHoverCard(true));
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!statusId) return null;
|
||||||
|
|
||||||
|
const renderStatus = (statusId: string) => {
|
||||||
|
return (
|
||||||
|
// @ts-ignore
|
||||||
|
<StatusContainer
|
||||||
|
key={statusId}
|
||||||
|
id={statusId}
|
||||||
|
hoverable={false}
|
||||||
|
hideActionBar
|
||||||
|
muted
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
'absolute transition-opacity w-[500px] z-50 top-0 left-0': true,
|
||||||
|
'opacity-100': visible,
|
||||||
|
'opacity-0 pointer-events-none': !visible,
|
||||||
|
})}
|
||||||
|
ref={setPopperElement}
|
||||||
|
style={styles.popper}
|
||||||
|
{...attributes.popper}
|
||||||
|
onMouseEnter={handleMouseEnter()}
|
||||||
|
onMouseLeave={handleMouseLeave()}
|
||||||
|
>
|
||||||
|
<Card className='relative'>
|
||||||
|
<CardBody>
|
||||||
|
{renderStatus(statusId)}
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatusHoverCard;
|
|
@ -3,6 +3,7 @@ import { FormattedList, FormattedMessage } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { openModal } from 'soapbox/actions/modals';
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
|
import HoverStatusWrapper from 'soapbox/components/hover-status-wrapper';
|
||||||
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
|
import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper';
|
||||||
import { useAppDispatch } from 'soapbox/hooks';
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
@ -10,9 +11,10 @@ import type { Account, Status } from 'soapbox/types/entities';
|
||||||
|
|
||||||
interface IStatusReplyMentions {
|
interface IStatusReplyMentions {
|
||||||
status: Status,
|
status: Status,
|
||||||
|
hoverable?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status }) => {
|
const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status, hoverable = true }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const handleOpenMentionsModal: React.MouseEventHandler<HTMLSpanElement> = (e) => {
|
const handleOpenMentionsModal: React.MouseEventHandler<HTMLSpanElement> = (e) => {
|
||||||
|
@ -46,11 +48,21 @@ const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The typical case with a reply-to and a list of mentions.
|
// The typical case with a reply-to and a list of mentions.
|
||||||
const accounts = to.slice(0, 2).map(account => (
|
const accounts = to.slice(0, 2).map(account => {
|
||||||
<HoverRefWrapper key={account.id} accountId={account.id} inline>
|
const link = (
|
||||||
<Link to={`/@${account.acct}`} className='reply-mentions__account'>@{account.username}</Link>
|
<Link to={`/@${account.acct}`} className='reply-mentions__account'>@{account.username}</Link>
|
||||||
</HoverRefWrapper>
|
);
|
||||||
)).toArray();
|
|
||||||
|
if (hoverable) {
|
||||||
|
return (
|
||||||
|
<HoverRefWrapper key={account.id} accountId={account.id} inline>
|
||||||
|
{link}
|
||||||
|
</HoverRefWrapper>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
}).toArray();
|
||||||
|
|
||||||
if (to.size > 2) {
|
if (to.size > 2) {
|
||||||
accounts.push(
|
accounts.push(
|
||||||
|
@ -64,9 +76,26 @@ const StatusReplyMentions: React.FC<IStatusReplyMentions> = ({ status }) => {
|
||||||
<div className='reply-mentions'>
|
<div className='reply-mentions'>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='reply_mentions.reply'
|
id='reply_mentions.reply'
|
||||||
defaultMessage='Replying to {accounts}'
|
defaultMessage='<hover>Replying to</hover> {accounts}'
|
||||||
values={{
|
values={{
|
||||||
accounts: <FormattedList type='conjunction' value={accounts} />,
|
accounts: <FormattedList type='conjunction' value={accounts} />,
|
||||||
|
hover: (children: React.ReactNode) => {
|
||||||
|
if (hoverable) {
|
||||||
|
return (
|
||||||
|
<HoverStatusWrapper statusId={status.in_reply_to_id} inline>
|
||||||
|
<span
|
||||||
|
key='hoverstatus'
|
||||||
|
className='hover:underline cursor-pointer'
|
||||||
|
role='presentation'
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
</HoverStatusWrapper>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -93,6 +93,8 @@ interface IStatus extends RouteComponentProps {
|
||||||
history: History,
|
history: History,
|
||||||
featured?: boolean,
|
featured?: boolean,
|
||||||
withDismiss?: boolean,
|
withDismiss?: boolean,
|
||||||
|
hideActionBar?: boolean,
|
||||||
|
hoverable?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IStatusState {
|
interface IStatusState {
|
||||||
|
@ -105,6 +107,7 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
focusable: true,
|
focusable: true,
|
||||||
|
hoverable: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
didShowCard = false;
|
didShowCard = false;
|
||||||
|
@ -480,6 +483,7 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
||||||
action={reblogElement}
|
action={reblogElement}
|
||||||
hideActions={!reblogElement}
|
hideActions={!reblogElement}
|
||||||
showEdit={!!status.edited_at}
|
showEdit={!!status.edited_at}
|
||||||
|
showProfileHoverCard={this.props.hoverable}
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
</div>
|
</div>
|
||||||
|
@ -491,7 +495,10 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<StatusReplyMentions status={this._properStatus()} />
|
<StatusReplyMentions
|
||||||
|
status={this._properStatus()}
|
||||||
|
hoverable={this.props.hoverable}
|
||||||
|
/>
|
||||||
|
|
||||||
<StatusContent
|
<StatusContent
|
||||||
status={status}
|
status={status}
|
||||||
|
@ -512,14 +519,16 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
||||||
{poll}
|
{poll}
|
||||||
{quote}
|
{quote}
|
||||||
|
|
||||||
<StatusActionBar
|
{!this.props.hideActionBar && (
|
||||||
status={status}
|
<StatusActionBar
|
||||||
// @ts-ignore what?
|
status={status}
|
||||||
account={account}
|
// @ts-ignore what?
|
||||||
emojiSelectorFocused={this.state.emojiSelectorFocused}
|
account={account}
|
||||||
handleEmojiSelectorUnfocus={this.handleEmojiSelectorUnfocus}
|
emojiSelectorFocused={this.state.emojiSelectorFocused}
|
||||||
{...other}
|
handleEmojiSelectorUnfocus={this.handleEmojiSelectorUnfocus}
|
||||||
/>
|
{...other}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -102,6 +102,7 @@ import {
|
||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
UploadArea,
|
UploadArea,
|
||||||
ProfileHoverCard,
|
ProfileHoverCard,
|
||||||
|
StatusHoverCard,
|
||||||
Share,
|
Share,
|
||||||
NewStatus,
|
NewStatus,
|
||||||
IntentionalError,
|
IntentionalError,
|
||||||
|
@ -698,6 +699,10 @@ const UI: React.FC = ({ children }) => {
|
||||||
<BundleContainer fetchComponent={ProfileHoverCard}>
|
<BundleContainer fetchComponent={ProfileHoverCard}>
|
||||||
{Component => <Component />}
|
{Component => <Component />}
|
||||||
</BundleContainer>
|
</BundleContainer>
|
||||||
|
|
||||||
|
<BundleContainer fetchComponent={StatusHoverCard}>
|
||||||
|
{Component => <Component />}
|
||||||
|
</BundleContainer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
|
|
|
@ -406,6 +406,10 @@ export function ProfileHoverCard() {
|
||||||
return import(/* webpackChunkName: "features/ui" */'soapbox/components/profile-hover-card');
|
return import(/* webpackChunkName: "features/ui" */'soapbox/components/profile-hover-card');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function StatusHoverCard() {
|
||||||
|
return import(/* webpackChunkName: "features/ui" */'soapbox/components/status-hover-card');
|
||||||
|
}
|
||||||
|
|
||||||
export function CryptoDonate() {
|
export function CryptoDonate() {
|
||||||
return import(/* webpackChunkName: "features/crypto_donate" */'../../crypto_donate');
|
return import(/* webpackChunkName: "features/crypto_donate" */'../../crypto_donate');
|
||||||
}
|
}
|
||||||
|
|
|
@ -860,7 +860,7 @@
|
||||||
"reply_mentions.account.add": "Add to mentions",
|
"reply_mentions.account.add": "Add to mentions",
|
||||||
"reply_mentions.account.remove": "Remove from mentions",
|
"reply_mentions.account.remove": "Remove from mentions",
|
||||||
"reply_mentions.more": "{count, plural, one {einen weiteren Nutzer} other {# weitere Nutzer}}",
|
"reply_mentions.more": "{count, plural, one {einen weiteren Nutzer} other {# weitere Nutzer}}",
|
||||||
"reply_mentions.reply": "Antwort an {accounts}",
|
"reply_mentions.reply": "<hover>Antwort an</hover> {accounts}",
|
||||||
"reply_mentions.reply_empty": "Antwort auf einen Beitrag",
|
"reply_mentions.reply_empty": "Antwort auf einen Beitrag",
|
||||||
"report.block": "{target} blockieren.",
|
"report.block": "{target} blockieren.",
|
||||||
"report.block_hint": "Soll dieses Konto zusammen mit der Meldung auch gleich blockiert werden?",
|
"report.block_hint": "Soll dieses Konto zusammen mit der Meldung auch gleich blockiert werden?",
|
||||||
|
|
|
@ -1001,7 +1001,7 @@
|
||||||
"id": "reply_mentions.reply_empty"
|
"id": "reply_mentions.reply_empty"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Replying to {accounts}{more}",
|
"defaultMessage": "<hover>Replying to</hover> {accounts}{more}",
|
||||||
"id": "reply_mentions.reply"
|
"id": "reply_mentions.reply"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2470,7 +2470,7 @@
|
||||||
"id": "reply_mentions.reply_empty"
|
"id": "reply_mentions.reply_empty"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Replying to {accounts}{more}",
|
"defaultMessage": "<hover>Replying to</hover> {accounts}{more}",
|
||||||
"id": "reply_mentions.reply"
|
"id": "reply_mentions.reply"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -5609,7 +5609,7 @@
|
||||||
"id": "reply_indicator.cancel"
|
"id": "reply_indicator.cancel"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Replying to {accounts}{more}",
|
"defaultMessage": "<hover>Replying to</hover> {accounts}{more}",
|
||||||
"id": "reply_mentions.reply"
|
"id": "reply_mentions.reply"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -860,7 +860,7 @@
|
||||||
"reply_mentions.account.add": "𐑨𐑛 𐑑 𐑥𐑧𐑯𐑖𐑩𐑯𐑟",
|
"reply_mentions.account.add": "𐑨𐑛 𐑑 𐑥𐑧𐑯𐑖𐑩𐑯𐑟",
|
||||||
"reply_mentions.account.remove": "𐑮𐑦𐑥𐑵𐑝 𐑓𐑮𐑪𐑥 𐑥𐑧𐑯𐑖𐑩𐑯𐑟",
|
"reply_mentions.account.remove": "𐑮𐑦𐑥𐑵𐑝 𐑓𐑮𐑪𐑥 𐑥𐑧𐑯𐑖𐑩𐑯𐑟",
|
||||||
"reply_mentions.more": "{count} 𐑥𐑹",
|
"reply_mentions.more": "{count} 𐑥𐑹",
|
||||||
"reply_mentions.reply": "𐑮𐑦𐑐𐑤𐑲𐑦𐑙 𐑑 {accounts}",
|
"reply_mentions.reply": "<hover>𐑮𐑦𐑐𐑤𐑲𐑦𐑙 𐑑</hover> {accounts}",
|
||||||
"reply_mentions.reply_empty": "𐑮𐑦𐑐𐑤𐑲𐑦𐑙 𐑑 𐑐𐑴𐑕𐑑",
|
"reply_mentions.reply_empty": "𐑮𐑦𐑐𐑤𐑲𐑦𐑙 𐑑 𐑐𐑴𐑕𐑑",
|
||||||
"report.block": "𐑚𐑤𐑪𐑒 {target}",
|
"report.block": "𐑚𐑤𐑪𐑒 {target}",
|
||||||
"report.block_hint": "𐑛𐑵 𐑿 𐑷𐑤𐑕𐑴 𐑢𐑪𐑯𐑑 𐑑 𐑚𐑤𐑪𐑒 𐑞𐑦𐑕 𐑩𐑒𐑬𐑯𐑑?",
|
"report.block_hint": "𐑛𐑵 𐑿 𐑷𐑤𐑕𐑴 𐑢𐑪𐑯𐑑 𐑑 𐑚𐑤𐑪𐑒 𐑞𐑦𐑕 𐑩𐑒𐑬𐑯𐑑?",
|
||||||
|
|
|
@ -860,7 +860,7 @@
|
||||||
"reply_mentions.account.add": "הוסף לאזכורים",
|
"reply_mentions.account.add": "הוסף לאזכורים",
|
||||||
"reply_mentions.account.remove": "הסר מהאזכורים",
|
"reply_mentions.account.remove": "הסר מהאזכורים",
|
||||||
"reply_mentions.more": "{count} עוד",
|
"reply_mentions.more": "{count} עוד",
|
||||||
"reply_mentions.reply": "משיב ל-{accounts}",
|
"reply_mentions.reply": "<hover>משיב ל-</hover>{accounts}",
|
||||||
"reply_mentions.reply_empty": "משיב לפוסט",
|
"reply_mentions.reply_empty": "משיב לפוסט",
|
||||||
"report.block": "חסום {target}",
|
"report.block": "חסום {target}",
|
||||||
"report.block_hint": "האם גם אתה רוצה לחסום את החשבון הזה?",
|
"report.block_hint": "האם גם אתה רוצה לחסום את החשבון הזה?",
|
||||||
|
|
|
@ -789,7 +789,7 @@
|
||||||
"reply_mentions.account.add": "Bæta við í tilvísanirnar",
|
"reply_mentions.account.add": "Bæta við í tilvísanirnar",
|
||||||
"reply_mentions.account.remove": "Fjarlægja úr tilvísunum",
|
"reply_mentions.account.remove": "Fjarlægja úr tilvísunum",
|
||||||
"reply_mentions.more": "{count} fleirum",
|
"reply_mentions.more": "{count} fleirum",
|
||||||
"reply_mentions.reply": "Að svara {accounts}",
|
"reply_mentions.reply": "<hover>Að svara</hover> {accounts}",
|
||||||
"reply_mentions.reply_empty": "Að svara færslu",
|
"reply_mentions.reply_empty": "Að svara færslu",
|
||||||
"report.block": "Loka á {target}",
|
"report.block": "Loka á {target}",
|
||||||
"report.block_hint": "Viltu líka loka á þennan reikning?",
|
"report.block_hint": "Viltu líka loka á þennan reikning?",
|
||||||
|
|
|
@ -860,7 +860,7 @@
|
||||||
"reply_mentions.account.add": "Add to mentions",
|
"reply_mentions.account.add": "Add to mentions",
|
||||||
"reply_mentions.account.remove": "Remove from mentions",
|
"reply_mentions.account.remove": "Remove from mentions",
|
||||||
"reply_mentions.more": "ancora {count}",
|
"reply_mentions.more": "ancora {count}",
|
||||||
"reply_mentions.reply": "Risponde a {accounts}",
|
"reply_mentions.reply": "<hover>Risponde a</hover> {accounts}",
|
||||||
"reply_mentions.reply_empty": "Rispondendo al contenuto",
|
"reply_mentions.reply_empty": "Rispondendo al contenuto",
|
||||||
"report.block": "Blocca {target}",
|
"report.block": "Blocca {target}",
|
||||||
"report.block_hint": "Vuoi anche bloccare questa persona?",
|
"report.block_hint": "Vuoi anche bloccare questa persona?",
|
||||||
|
|
|
@ -913,7 +913,7 @@
|
||||||
"reply_mentions.account.add": "Dodaj do wspomnianych",
|
"reply_mentions.account.add": "Dodaj do wspomnianych",
|
||||||
"reply_mentions.account.remove": "Usuń z wspomnianych",
|
"reply_mentions.account.remove": "Usuń z wspomnianych",
|
||||||
"reply_mentions.more": "{count} więcej",
|
"reply_mentions.more": "{count} więcej",
|
||||||
"reply_mentions.reply": "W odpowiedzi do {accounts}",
|
"reply_mentions.reply": "<hover>W odpowiedzi do</hover> {accounts}",
|
||||||
"reply_mentions.reply_empty": "W odpowiedzi na wpis",
|
"reply_mentions.reply_empty": "W odpowiedzi na wpis",
|
||||||
"report.block": "Zablokuj {target}",
|
"report.block": "Zablokuj {target}",
|
||||||
"report.block_hint": "Czy chcesz też zablokować to konto?",
|
"report.block_hint": "Czy chcesz też zablokować to konto?",
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
import {
|
||||||
|
STATUS_HOVER_CARD_OPEN,
|
||||||
|
STATUS_HOVER_CARD_CLOSE,
|
||||||
|
STATUS_HOVER_CARD_UPDATE,
|
||||||
|
} from 'soapbox/actions/status-hover-card';
|
||||||
|
|
||||||
|
import reducer, { ReducerRecord } from '../status-hover-card';
|
||||||
|
|
||||||
|
describe(STATUS_HOVER_CARD_OPEN, () => {
|
||||||
|
it('sets the ref and statusId', () => {
|
||||||
|
const ref = { current: document.createElement('div') };
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
type: STATUS_HOVER_CARD_OPEN,
|
||||||
|
ref,
|
||||||
|
statusId: '1234',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = reducer(undefined, action);
|
||||||
|
expect(result.ref).toBe(ref);
|
||||||
|
expect(result.statusId).toBe('1234');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(STATUS_HOVER_CARD_CLOSE, () => {
|
||||||
|
it('flushes the state', () => {
|
||||||
|
const state = ReducerRecord({
|
||||||
|
ref: { current: document.createElement('div') },
|
||||||
|
statusId: '1234',
|
||||||
|
});
|
||||||
|
|
||||||
|
const action = { type: STATUS_HOVER_CARD_CLOSE };
|
||||||
|
|
||||||
|
const result = reducer(state, action);
|
||||||
|
expect(result.ref).toBe(null);
|
||||||
|
expect(result.statusId).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves the state alone if hovered', () => {
|
||||||
|
const state = ReducerRecord({
|
||||||
|
ref: { current: document.createElement('div') },
|
||||||
|
statusId: '1234',
|
||||||
|
hovered: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const action = { type: STATUS_HOVER_CARD_CLOSE };
|
||||||
|
const result = reducer(state, action);
|
||||||
|
expect(result).toEqual(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('action.force flushes the state even if hovered', () => {
|
||||||
|
const state = ReducerRecord({
|
||||||
|
ref: { current: document.createElement('div') },
|
||||||
|
statusId: '1234',
|
||||||
|
hovered: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const action = { type: STATUS_HOVER_CARD_CLOSE, force: true };
|
||||||
|
const result = reducer(state, action);
|
||||||
|
expect(result.ref).toBe(null);
|
||||||
|
expect(result.statusId).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(STATUS_HOVER_CARD_UPDATE, () => {
|
||||||
|
it('sets hovered', () => {
|
||||||
|
const state = ReducerRecord();
|
||||||
|
const action = { type: STATUS_HOVER_CARD_UPDATE };
|
||||||
|
const result = reducer(state, action);
|
||||||
|
expect(result.hovered).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -53,6 +53,7 @@ import security from './security';
|
||||||
import settings from './settings';
|
import settings from './settings';
|
||||||
import sidebar from './sidebar';
|
import sidebar from './sidebar';
|
||||||
import soapbox from './soapbox';
|
import soapbox from './soapbox';
|
||||||
|
import status_hover_card from './status-hover-card';
|
||||||
import status_lists from './status_lists';
|
import status_lists from './status_lists';
|
||||||
import statuses from './statuses';
|
import statuses from './statuses';
|
||||||
import suggestions from './suggestions';
|
import suggestions from './suggestions';
|
||||||
|
@ -108,6 +109,7 @@ const reducers = {
|
||||||
chat_messages,
|
chat_messages,
|
||||||
chat_message_lists,
|
chat_message_lists,
|
||||||
profile_hover_card,
|
profile_hover_card,
|
||||||
|
status_hover_card,
|
||||||
backups,
|
backups,
|
||||||
admin_log,
|
admin_log,
|
||||||
security,
|
security,
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Record as ImmutableRecord } from 'immutable';
|
||||||
|
|
||||||
|
import {
|
||||||
|
STATUS_HOVER_CARD_OPEN,
|
||||||
|
STATUS_HOVER_CARD_CLOSE,
|
||||||
|
STATUS_HOVER_CARD_UPDATE,
|
||||||
|
} from 'soapbox/actions/status-hover-card';
|
||||||
|
|
||||||
|
import type { AnyAction } from 'redux';
|
||||||
|
|
||||||
|
export const ReducerRecord = ImmutableRecord({
|
||||||
|
ref: null as React.MutableRefObject<HTMLDivElement> | null,
|
||||||
|
statusId: '',
|
||||||
|
hovered: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
type State = ReturnType<typeof ReducerRecord>;
|
||||||
|
|
||||||
|
export default function statusHoverCard(state: State = ReducerRecord(), action: AnyAction) {
|
||||||
|
switch (action.type) {
|
||||||
|
case STATUS_HOVER_CARD_OPEN:
|
||||||
|
return state.withMutations((state) => {
|
||||||
|
state.set('ref', action.ref);
|
||||||
|
state.set('statusId', action.statusId);
|
||||||
|
});
|
||||||
|
case STATUS_HOVER_CARD_UPDATE:
|
||||||
|
return state.set('hovered', true);
|
||||||
|
case STATUS_HOVER_CARD_CLOSE:
|
||||||
|
if (state.hovered === true && !action.force)
|
||||||
|
return state;
|
||||||
|
else
|
||||||
|
return ReducerRecord();
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import { danger, warn, message } from 'danger';
|
||||||
|
|
||||||
const docs = danger.git.fileMatch('docs/**/*.md');
|
const docs = danger.git.fileMatch('docs/**/*.md');
|
||||||
const app = danger.git.fileMatch('app/**/*.(js|ts|tsx)');
|
const app = danger.git.fileMatch('app/**/*.(js|ts|tsx)');
|
||||||
const tests = danger.git.fileMatch('*/__tests__/*');
|
const tests = danger.git.fileMatch('**/__tests__/**');
|
||||||
|
|
||||||
if (docs.edited) {
|
if (docs.edited) {
|
||||||
message('Thanks - We :heart: our [documentarians](http://www.writethedocs.org/)!');
|
message('Thanks - We :heart: our [documentarians](http://www.writethedocs.org/)!');
|
||||||
|
|
|
@ -92,7 +92,7 @@
|
||||||
"array-includes": "^3.0.3",
|
"array-includes": "^3.0.3",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"axios-mock-adapter": "^1.18.1",
|
"axios-mock-adapter": "^1.21.1",
|
||||||
"babel-loader": "^8.2.5",
|
"babel-loader": "^8.2.5",
|
||||||
"babel-plugin-lodash": "^3.3.4",
|
"babel-plugin-lodash": "^3.3.4",
|
||||||
"babel-plugin-preval": "^5.1.0",
|
"babel-plugin-preval": "^5.1.0",
|
||||||
|
|
14
yarn.lock
14
yarn.lock
|
@ -3435,13 +3435,12 @@ axe-core@^4.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.3.tgz#b55cd8e8ddf659fe89b064680e1c6a4dceab0325"
|
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.3.tgz#b55cd8e8ddf659fe89b064680e1c6a4dceab0325"
|
||||||
integrity sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==
|
integrity sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA==
|
||||||
|
|
||||||
axios-mock-adapter@^1.18.1:
|
axios-mock-adapter@^1.21.1:
|
||||||
version "1.20.0"
|
version "1.21.1"
|
||||||
resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.20.0.tgz#21f5b4b625306f43e8c05673616719da86e20dcb"
|
resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.21.1.tgz#efe7e46ad1b0d2acd72188afa91c5720660777b3"
|
||||||
integrity sha512-shZRhTjLP0WWdcvHKf3rH3iW9deb3UdKbdnKUoHmmsnBhVXN3sjPJM6ZvQ2r/ywgvBVQrMnjrSyQab60G1sr2w==
|
integrity sha512-Pdm7nZuhkz/DSucQ4Bbo9qVBqfm9j7ev9ycTvIXHqvAjnJEjWPHKYfTfpounVp8MwjFFSHXGS7hCkTwAswtSTA==
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-deep-equal "^3.1.3"
|
fast-deep-equal "^3.1.3"
|
||||||
is-blob "^2.1.0"
|
|
||||||
is-buffer "^2.0.5"
|
is-buffer "^2.0.5"
|
||||||
|
|
||||||
axios@^0.27.2:
|
axios@^0.27.2:
|
||||||
|
@ -6583,11 +6582,6 @@ is-binary-path@~2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
binary-extensions "^2.0.0"
|
binary-extensions "^2.0.0"
|
||||||
|
|
||||||
is-blob@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-blob/-/is-blob-2.1.0.tgz#e36cd82c90653f1e1b930f11baf9c64216a05385"
|
|
||||||
integrity sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==
|
|
||||||
|
|
||||||
is-boolean-object@^1.1.0:
|
is-boolean-object@^1.1.0:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
|
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
|
||||||
|
|
Loading…
Reference in New Issue