Opening a channel kept every key of the page under it (`?q=music&sort=oldest&chan=…`, a sort the channel did not show), and the channel's own tab, sort and content types lived in its state alone, so a reload or a bookmark lost them. Now the channel's address is `channelUrl`: the shell's keys and `chan`, shared by `openChannel` and the channel link's href. The page writes its view (`chtab`, `chsort`, `chtypes`, lib/channelPageUrlState) and reopens it; these are shell keys, cleared by a page switch and by closing it in place. While a channel stands over a page, no module store writes its keys (`urlPage`); Back finds them on the page's own entry. A link written by an older build opens without the page's keys.
53 lines
2.3 KiB
TypeScript
53 lines
2.3 KiB
TypeScript
import { forwardRef, type AnchorHTMLAttributes } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { isPlainLeftClick } from "../../lib/nav";
|
|
import { channelUrl } from "../../lib/urlState";
|
|
|
|
// A channel name that is a REAL link. The player showed it as a `<button>`, so the browser's own
|
|
// gestures did nothing on it: middle-click and Ctrl/Cmd-click opened no tab, on a thing that looks
|
|
// exactly like a link (the YouTube icon right next to it IS an anchor and does open one). The video
|
|
// card's channel name had the same `<button>`, so it renders through here too.
|
|
//
|
|
// The href is the same URL `openChannel` pushes (`channelUrl`: the channel over the current page,
|
|
// without the page's own keys) — so a new tab lands where the in-app navigation would have. A plain left click is still handled in-app, because
|
|
// the SPA path does more than navigate (the player has to close first and reclaim its own
|
|
// Back-to-close history entry; see lib/history.closeOverlaysThenNavigate).
|
|
type Props = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href" | "onClick" | "title"> & {
|
|
channelId: string;
|
|
title: string | null | undefined;
|
|
/** The in-app navigation, run only for a plain left click. */
|
|
onOpenInApp: () => void;
|
|
/** The native tooltip (`title` is taken by the channel's name). */
|
|
tooltip?: string;
|
|
/** Keep EVERY click on the link — the browser's own gestures too — from reaching a clickable
|
|
* ancestor (the video card's row). */
|
|
stopPropagation?: boolean;
|
|
};
|
|
|
|
const ChannelNameLink = forwardRef<HTMLAnchorElement, Props>(function ChannelNameLink(
|
|
{ channelId, title, onOpenInApp, tooltip, stopPropagation, ...rest },
|
|
ref,
|
|
) {
|
|
const { t } = useTranslation();
|
|
return (
|
|
<a
|
|
{...rest}
|
|
ref={ref}
|
|
href={channelUrl(channelId)}
|
|
title={tooltip}
|
|
onClick={(e) => {
|
|
if (stopPropagation) e.stopPropagation();
|
|
if (!isPlainLeftClick(e)) return; // let the browser open its tab/window
|
|
e.preventDefault();
|
|
onOpenInApp();
|
|
}}
|
|
>
|
|
{/* A channel first seen through a live search can have no title yet. An empty anchor would
|
|
still be in the tab order, announced by nothing but its href. */}
|
|
{title || t("player.channel")}
|
|
</a>
|
|
);
|
|
});
|
|
|
|
export default ChannelNameLink;
|