You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
4.8 KiB

Bringing Hometown up to date with mastodon/mastodon (#1371) This is a massive PR, so apologies for the poor performance viewing it! This merges in the content of Mastodon 4.3.0. *This PR isn't ready to deploy to production*, and I don't recommend trying it in a production environment. Since I started by merging 4.3.0, this will likely have reverted at least some of the content of the post-4.3.0 security patches in the 4.2.x series; those will be brought back as I merge in 4.4.0 and 4.5.0. I haven't yet tested this PR, so it may not actually run without additional tweaks. I'm putting it up now for discussion and so multiple people can test if we want. --- Here's a few notes on things I ran into and thoughts I had while working on it: * We may want to look at `server_banner.jsx,` where I ended up undoing the v3-style rewrite due to the new react component being used. I'm not sure what the current goal for it is. * I may not have 100% merged in all content from `status_action_bar` correctly. * There's a number of places where I updated the links for remote usernames, and they should be tested to make sure they work. They're all `<Permalink>` elements. I have in my notes that `status_content` might need double-checking. * I may also not have gotten the spoiler display right in `status_content`. * It looks like previous versions of Hometown had customizations to the `media_item` display, not all of which was preserved as-is due to surrounding rewrites. I'm not sure what the vision is and could use another set of eyes. * The character count rendering clashed with the newly-introduced handling of a character count variable upstream, and I'm not sure if the version I've got here 100% works or not. * The navigation bar was completely rewritten and no longer has the components Hometown was customizing; did I properly catch any replacements? Did we handle all links to accounts to make sure they go to the remote instance? * Polls: I reverted the single/multiple choice toggle because upstream has an actual proper UI for this that should be integrated in one of the versions we’re merging in. Please double-check that the current version looks fine to you. * The font icons will need double-checking. Hometown made some custom use of the font-awesome icons, while Mastodon 4.3.0 replaced font-awesome with something else. I may not have caught all places that font-awesome icons were being used. * We’ll want to make sure the post display is correct, including Hometown customizations, because the previous static post page has been replaced with the React one. --------- Co-authored-by: Claire <claire.github-309c@sitedethib.com> Co-authored-by: Matt Jankowski <matt@jankowski.online> Co-authored-by: Renaud Chaput <renchap@gmail.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: David Roetzel <david@roetzel.de> Co-authored-by: Emelia Smith <ThisIsMissEm@users.noreply.github.com> Co-authored-by: Jeong Arm <kjwonmail@gmail.com> Co-authored-by: Christian Schmidt <github@chsc.dk> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Actions <noreply@github.com> Co-authored-by: Eugen Rochko <eugen@zeonfederated.com> Co-authored-by: Darius Kazemi <darius.kazemi@gmail.com> Co-authored-by: diondiondion <mail@diondiondion.com> Co-authored-by: Echo <ChaosExAnima@users.noreply.github.com> Co-authored-by: Shugo Maeda <shugo.maeda@gmail.com> Co-authored-by: emilweth <7402764+emilweth@users.noreply.github.com> Co-authored-by: Shlee <github@shl.ee> Co-authored-by: Joshua Rogers <MegaManSec@users.noreply.github.com> Co-authored-by: Jessica Stokes <hello@jessicastokes.net> Co-authored-by: PGray <77597544+PGrayCS@users.noreply.github.com>
2 months ago
import { useEffect, useRef, useState, useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import Overlay from 'react-overlays/Overlay';
import type {
OffsetValue,
UsePopperOptions,
} from 'react-overlays/esm/usePopper';
import { HoverCardAccount } from 'mastodon/components/hover_card_account';
import { useTimeout } from 'mastodon/hooks/useTimeout';
const offset = [-12, 4] as OffsetValue;
const enterDelay = 750;
const leaveDelay = 150;
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
const isHoverCardAnchor = (element: HTMLElement) =>
element.matches('[data-hover-card-account]');
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>(null);
const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout();
const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout();
const [setScrollTimeout] = useTimeout();
const location = useLocation();
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;
let currentTitle: string | 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;
// 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;
currentTitle = target.getAttribute('title');
target.removeAttribute('title');
setEnterTimeout(() => {
open(target);
}, enterDelay);
}
// We've entered the hover card
if (
!isScrolling &&
(target === currentAnchor || target === cardRef.current)
) {
cancelLeaveTimeout();
}
};
const handleMouseLeave = (e: MouseEvent) => {
const { target } = e;
if (!currentAnchor) {
return;
}
if (
currentTitle &&
target instanceof HTMLElement &&
target === currentAnchor
)
target.setAttribute('title', currentTitle);
if (target === currentAnchor || target === cardRef.current) {
cancelEnterTimeout();
setLeaveTimeout(() => {
close();
}, leaveDelay);
}
};
const handleScrollEnd = () => {
isScrolling = false;
};
const handleScroll = () => {
isScrolling = true;
cancelEnterTimeout();
setScrollTimeout(handleScrollEnd, 100);
};
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.addEventListener('scroll', handleScroll, {
passive: true,
capture: true,
});
return () => {
document.body.removeEventListener('mouseenter', handleMouseEnter);
document.body.removeEventListener('mousemove', handleMouseMove);
document.body.removeEventListener('mouseleave', handleMouseLeave);
document.removeEventListener('scroll', handleScroll);
};
}, [
setEnterTimeout,
setLeaveTimeout,
setScrollTimeout,
cancelEnterTimeout,
cancelLeaveTimeout,
delayEnterTimeout,
setOpen,
setAccountId,
setAnchor,
]);
return (
<Overlay
rootClose
onHide={handleClose}
show={open}
target={anchor}
placement='bottom-start'
flip
offset={offset}
popperConfig={popperConfig}
>
{({ props }) => (
<div {...props} className='hover-card-controller'>
<HoverCardAccount accountId={accountId} ref={cardRef} />
</div>
)}
</Overlay>
);
};