Skip to content

Commit

Permalink
Merge pull request #2761 from ClearlyClaire/glitch-soc/merge-upstream
Browse files Browse the repository at this point in the history
Merge upstream changes up to d3f5042
  • Loading branch information
ClearlyClaire authored Jul 1, 2024
2 parents 6d6064d + 4d4f7ed commit 71375e9
Show file tree
Hide file tree
Showing 44 changed files with 983 additions and 492 deletions.
12 changes: 6 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,16 @@ GEM
attr_required (1.0.2)
awrence (1.2.1)
aws-eventstream (1.3.0)
aws-partitions (1.947.0)
aws-sdk-core (3.199.0)
aws-partitions (1.949.0)
aws-sdk-core (3.200.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.87.0)
aws-sdk-core (~> 3, >= 3.199.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.154.0)
aws-sdk-s3 (1.155.0)
aws-sdk-core (~> 3, >= 3.199.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
Expand Down Expand Up @@ -696,7 +696,7 @@ GEM
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.3.0)
rexml (3.3.1)
strscan
rotp (6.3.0)
rouge (4.2.1)
Expand Down Expand Up @@ -751,7 +751,7 @@ GEM
rubocop-performance (1.21.1)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.25.0)
rubocop-rails (2.25.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
Expand Down Expand Up @@ -833,7 +833,7 @@ GEM
unicode-display_width (>= 1.1.1, < 3)
terrapin (1.0.1)
climate_control
test-prof (1.3.3)
test-prof (1.3.3.1)
thor (1.3.1)
tilt (2.3.0)
timeout (0.4.1)
Expand Down
1 change: 1 addition & 0 deletions app/javascript/flavours/glitch/actions/timelines.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export const expandAccountTimeline = (accountId, { maxId, withReplies, t
export const expandAccountFeaturedTimeline = (accountId, { tagged } = {}) => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, tagged });
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
export const expandLinkTimeline = (url, { maxId } = {}, done = noOp) => expandTimeline(`link:${url}`, `/api/v1/timelines/link`, { url, max_id: maxId }, done);
export const expandHashtagTimeline = (hashtag, { maxId, tags, local } = {}, done = noOp) => {
return expandTimeline(`hashtag:${hashtag}${local ? ':local' : ''}`, `/api/v1/timelines/tag/${hashtag}`, {
max_id: maxId,
Expand Down
1 change: 1 addition & 0 deletions app/javascript/flavours/glitch/api_types/statuses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface ApiPreviewCardJSON {
type: string;
author_name: string;
author_url: string;
author_account?: ApiAccountJSON;
provider_name: string;
provider_url: string;
html: string;
Expand Down
35 changes: 23 additions & 12 deletions app/javascript/flavours/glitch/components/follow_button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback, useEffect } from 'react';

import { useIntl, defineMessages } from 'react-intl';
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';

import { useIdentity } from '@/flavours/glitch/identity_context';
import {
Expand All @@ -18,15 +18,11 @@ const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
follow: { id: 'account.follow', defaultMessage: 'Follow' },
followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' },
cancel_follow_request: {
id: 'account.cancel_follow_request',
defaultMessage: 'Withdraw follow request',
},
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
});

export const FollowButton: React.FC<{
accountId: string;
accountId?: string;
}> = ({ accountId }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
Expand All @@ -35,7 +31,7 @@ export const FollowButton: React.FC<{
accountId ? state.accounts.get(accountId) : undefined,
);
const relationship = useAppSelector((state) =>
state.relationships.get(accountId),
accountId ? state.relationships.get(accountId) : undefined,
);
const following = relationship?.following || relationship?.requested;

Expand Down Expand Up @@ -64,11 +60,28 @@ export const FollowButton: React.FC<{
if (accountId === me) {
return;
} else if (relationship.following || relationship.requested) {
dispatch(unfollowAccount(accountId));
dispatch(
openModal({
modalType: 'CONFIRM',
modalProps: {
message: (
<FormattedMessage
id='confirmations.unfollow.message'
defaultMessage='Are you sure you want to unfollow {name}?'
values={{ name: <strong>@{account?.acct}</strong> }}
/>
),
confirm: intl.formatMessage(messages.unfollow),
onConfirm: () => {
dispatch(unfollowAccount(accountId));
},
},
}),
);
} else {
dispatch(followAccount(accountId));
}
}, [dispatch, accountId, relationship, account, signedIn]);
}, [dispatch, intl, accountId, relationship, account, signedIn]);

let label;

Expand All @@ -78,11 +91,9 @@ export const FollowButton: React.FC<{
label = intl.formatMessage(messages.edit_profile);
} else if (!relationship) {
label = <LoadingIndicator />;
} else if (relationship.requested) {
label = intl.formatMessage(messages.cancel_follow_request);
} else if (!relationship.following && relationship.followed_by) {
label = intl.formatMessage(messages.followBack);
} else if (relationship.following) {
} else if (relationship.following || relationship.requested) {
label = intl.formatMessage(messages.unfollow);
} else {
label = intl.formatMessage(messages.follow);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';

export const HoverCardAccount = forwardRef<
HTMLDivElement,
{ accountId: string }
{ accountId?: string }
>(({ accountId }, ref) => {
const dispatch = useAppDispatch();

Expand Down
145 changes: 102 additions & 43 deletions app/javascript/flavours/glitch/components/hover_card_controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { HoverCardAccount } from 'flavours/glitch/components/hover_card_account'
import { useTimeout } from 'flavours/glitch/hooks/useTimeout';

const offset = [-12, 4] as OffsetValue;
const enterDelay = 650;
const leaveDelay = 250;
const enterDelay = 750;
const leaveDelay = 150;
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;

const isHoverCardAnchor = (element: HTMLElement) =>
Expand All @@ -23,78 +23,137 @@ export const HoverCardController: React.FC = () => {
const [open, setOpen] = useState(false);
const [accountId, setAccountId] = useState<string | undefined>();
const [anchor, setAnchor] = useState<HTMLElement | null>(null);
const cardRef = useRef<HTMLDivElement>(null);
const cardRef = useRef<HTMLDivElement | null>(null);
const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout();
const [setEnterTimeout, cancelEnterTimeout] = useTimeout();
const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout();
const [setScrollTimeout] = useTimeout();
const location = useLocation();

const handleAnchorMouseEnter = useCallback(
(e: MouseEvent) => {
const handleClose = useCallback(() => {
cancelEnterTimeout();
cancelLeaveTimeout();
setOpen(false);
setAnchor(null);
}, [cancelEnterTimeout, cancelLeaveTimeout, setOpen, setAnchor]);

useEffect(() => {
handleClose();
}, [handleClose, location]);

useEffect(() => {
let isScrolling = false;
let currentAnchor: HTMLElement | null = null;

const open = (target: HTMLElement) => {
target.setAttribute('aria-describedby', 'hover-card');
setOpen(true);
setAnchor(target);
setAccountId(target.getAttribute('data-hover-card-account') ?? undefined);
};

const close = () => {
currentAnchor?.removeAttribute('aria-describedby');
currentAnchor = null;
setOpen(false);
setAnchor(null);
setAccountId(undefined);
};

const handleMouseEnter = (e: MouseEvent) => {
const { target } = e;

if (target instanceof HTMLElement && isHoverCardAnchor(target)) {
// We've exited the window
if (!(target instanceof HTMLElement)) {
close();
return;
}

// We've entered an anchor
if (!isScrolling && isHoverCardAnchor(target)) {
cancelLeaveTimeout();

currentAnchor?.removeAttribute('aria-describedby');
currentAnchor = target;

setEnterTimeout(() => {
target.setAttribute('aria-describedby', 'hover-card');
setAnchor(target);
setOpen(true);
setAccountId(
target.getAttribute('data-hover-card-account') ?? undefined,
);
open(target);
}, enterDelay);
}

if (target === cardRef.current?.parentNode) {
// We've entered the hover card
if (
!isScrolling &&
(target === currentAnchor || target === cardRef.current)
) {
cancelLeaveTimeout();
}
},
[cancelLeaveTimeout, setEnterTimeout, setOpen, setAccountId, setAnchor],
);
};

const handleAnchorMouseLeave = useCallback(
(e: MouseEvent) => {
if (e.target === anchor || e.target === cardRef.current?.parentNode) {
const handleMouseLeave = (e: MouseEvent) => {
if (!currentAnchor) {
return;
}

if (e.target === currentAnchor || e.target === cardRef.current) {
cancelEnterTimeout();

setLeaveTimeout(() => {
anchor?.removeAttribute('aria-describedby');
setOpen(false);
setAnchor(null);
close();
}, leaveDelay);
}
},
[cancelEnterTimeout, setLeaveTimeout, setOpen, setAnchor, anchor],
);
};

const handleClose = useCallback(() => {
cancelEnterTimeout();
cancelLeaveTimeout();
setOpen(false);
setAnchor(null);
}, [cancelEnterTimeout, cancelLeaveTimeout, setOpen, setAnchor]);
const handleScrollEnd = () => {
isScrolling = false;
};

useEffect(() => {
handleClose();
}, [handleClose, location]);
const handleScroll = () => {
isScrolling = true;
cancelEnterTimeout();
setScrollTimeout(handleScrollEnd, 100);
};

useEffect(() => {
document.body.addEventListener('mouseenter', handleAnchorMouseEnter, {
const handleMouseMove = () => {
delayEnterTimeout(enterDelay);
};

document.body.addEventListener('mouseenter', handleMouseEnter, {
passive: true,
capture: true,
});

document.body.addEventListener('mousemove', handleMouseMove, {
passive: true,
capture: false,
});

document.body.addEventListener('mouseleave', handleMouseLeave, {
passive: true,
capture: true,
});
document.body.addEventListener('mouseleave', handleAnchorMouseLeave, {

document.addEventListener('scroll', handleScroll, {
passive: true,
capture: true,
});

return () => {
document.body.removeEventListener('mouseenter', handleAnchorMouseEnter);
document.body.removeEventListener('mouseleave', handleAnchorMouseLeave);
document.body.removeEventListener('mouseenter', handleMouseEnter);
document.body.removeEventListener('mousemove', handleMouseMove);
document.body.removeEventListener('mouseleave', handleMouseLeave);
document.removeEventListener('scroll', handleScroll);
};
}, [handleAnchorMouseEnter, handleAnchorMouseLeave]);

if (!accountId) return null;
}, [
setEnterTimeout,
setLeaveTimeout,
setScrollTimeout,
cancelEnterTimeout,
cancelLeaveTimeout,
delayEnterTimeout,
setOpen,
setAccountId,
setAnchor,
]);

return (
<Overlay
Expand Down
1 change: 1 addition & 0 deletions app/javascript/flavours/glitch/components/status_list.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default class StatusList extends ImmutablePureComponent {
withCounters: PropTypes.bool,
timelineId: PropTypes.string.isRequired,
lastId: PropTypes.string,
bindToDocument: PropTypes.bool,
regex: PropTypes.string,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { makeGetAccount, getAccountHidden } from '../../../selectors';
import Header from '../components/header';

const messages = defineMessages({
cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' },
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
});
Expand All @@ -43,7 +42,7 @@ const makeMapStateToProps = () => {
const mapDispatchToProps = (dispatch, { intl }) => ({

onFollow (account) {
if (account.getIn(['relationship', 'following'])) {
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
Expand All @@ -52,15 +51,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
},
}));
} else if (account.getIn(['relationship', 'requested'])) {
dispatch(openModal({
modalType: 'CONFIRM',
modalProps: {
message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
},
}));
} else {
dispatch(followAccount(account.get('id')));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
menu.push({ text: intl.formatMessage(messages.delete), action: handleDelete });

const names = accounts.map(a => (
<Permalink to={`/@${a.get('acct')}`} href={a.get('url')} key={a.get('id')} title={a.get('acct')}>
<Permalink to={`/@${a.get('acct')}`} href={a.get('url')} key={a.get('id')} data-hover-card-account={a.get('id')}>
<bdi>
<strong
className='display-name__html'
Expand Down
Loading

0 comments on commit 71375e9

Please sign in to comment.