Patchable ExternalLinkButtons component (#5727)

* Patchable ExternalLinkButtons component
* added fontAwesomeBrands
* use ExternalLinkButtons on groups page
This commit is contained in:
CJ
2025-03-16 19:20:39 -05:00
committed by GitHub
parent bc923929bb
commit 4d61c88661
5 changed files with 74 additions and 63 deletions

View File

@@ -28,7 +28,7 @@ import { DetailImage } from "src/components/Shared/DetailImage";
import { useRatingKeybinds } from "src/hooks/keybinds"; import { useRatingKeybinds } from "src/hooks/keybinds";
import { useLoadStickyHeader } from "src/hooks/detailsPanel"; import { useLoadStickyHeader } from "src/hooks/detailsPanel";
import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; import { useScrollToTopOnMount } from "src/hooks/scrollToTop";
import { ExternalLinksButton } from "src/components/Shared/ExternalLinksButton"; import { ExternalLinkButtons } from "src/components/Shared/ExternalLinksButton";
import { BackgroundImage } from "src/components/Shared/DetailsPage/BackgroundImage"; import { BackgroundImage } from "src/components/Shared/DetailsPage/BackgroundImage";
import { DetailTitle } from "src/components/Shared/DetailsPage/DetailTitle"; import { DetailTitle } from "src/components/Shared/DetailsPage/DetailTitle";
import { ExpandCollapseButton } from "src/components/Shared/CollapseButton"; import { ExpandCollapseButton } from "src/components/Shared/CollapseButton";
@@ -374,7 +374,7 @@ const GroupPage: React.FC<IProps> = ({ group, tabKey }) => {
/> />
)} )}
<span className="name-icons"> <span className="name-icons">
<ExternalLinksButton urls={group.urls} /> <ExternalLinkButtons urls={group.urls ?? undefined} />
</span> </span>
</DetailTitle> </DetailTitle>

View File

@@ -6,73 +6,76 @@ import { IconDefinition, faLink } from "@fortawesome/free-solid-svg-icons";
import { useMemo } from "react"; import { useMemo } from "react";
import { faInstagram, faTwitter } from "@fortawesome/free-brands-svg-icons"; import { faInstagram, faTwitter } from "@fortawesome/free-brands-svg-icons";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { PatchComponent } from "src/patch";
export const ExternalLinksButton: React.FC<{ export const ExternalLinksButton: React.FC<{
icon?: IconDefinition; icon?: IconDefinition;
urls: string[]; urls: string[];
className?: string; className?: string;
}> = ({ urls, icon = faLink, className = "" }) => { }> = PatchComponent(
if (!urls.length) { "ExternalLinksButton",
return null; ({ urls, icon = faLink, className = "" }) => {
} if (!urls.length) {
return null;
const Menu = () =>
ReactDOM.createPortal(
<Dropdown.Menu>
{urls.map((url) => (
<Dropdown.Item
key={url}
as={ExternalLink}
href={TextUtils.sanitiseURL(url)}
title={url}
>
{url}
</Dropdown.Item>
))}
</Dropdown.Menu>,
document.body
);
return (
<Dropdown className="external-links-button">
<Dropdown.Toggle as={Button} className={`minimal link ${className}`}>
<Icon icon={icon} />
</Dropdown.Toggle>
<Menu />
</Dropdown>
);
};
export const ExternalLinkButtons: React.FC<{ urls: string[] | undefined }> = ({
urls,
}) => {
const urlSpecs = useMemo(() => {
if (!urls?.length) {
return [];
} }
const twitter = urls.filter((u) => const Menu = () =>
u.match(/https?:\/\/(?:www\.)?(?:twitter|x).com\//) ReactDOM.createPortal(
); <Dropdown.Menu>
const instagram = urls.filter((u) => {urls.map((url) => (
u.match(/https?:\/\/(?:www\.)?instagram.com\//) <Dropdown.Item
); key={url}
const others = urls.filter( as={ExternalLink}
(u) => !twitter.includes(u) && !instagram.includes(u) href={TextUtils.sanitiseURL(url)}
); title={url}
>
{url}
</Dropdown.Item>
))}
</Dropdown.Menu>,
document.body
);
return [ return (
{ icon: faLink, className: "", urls: others }, <Dropdown className="external-links-button">
{ icon: faTwitter, className: "twitter", urls: twitter }, <Dropdown.Toggle as={Button} className={`minimal link ${className}`}>
{ icon: faInstagram, className: "instagram", urls: instagram }, <Icon icon={icon} />
]; </Dropdown.Toggle>
}, [urls]); <Menu />
</Dropdown>
);
}
);
return ( export const ExternalLinkButtons: React.FC<{ urls: string[] | undefined }> =
<> PatchComponent("ExternalLinkButtons", ({ urls }) => {
{urlSpecs.map((spec, i) => ( const urlSpecs = useMemo(() => {
<ExternalLinksButton key={i} {...spec} /> if (!urls?.length) {
))} return [];
</> }
);
}; const twitter = urls.filter((u) =>
u.match(/https?:\/\/(?:www\.)?(?:twitter|x).com\//)
);
const instagram = urls.filter((u) =>
u.match(/https?:\/\/(?:www\.)?instagram.com\//)
);
const others = urls.filter(
(u) => !twitter.includes(u) && !instagram.includes(u)
);
return [
{ icon: faLink, className: "", urls: others },
{ icon: faTwitter, className: "twitter", urls: twitter },
{ icon: faInstagram, className: "instagram", urls: instagram },
];
}, [urls]);
return (
<>
{urlSpecs.map((spec, i) => (
<ExternalLinksButton key={i} {...spec} />
))}
</>
);
});

View File

@@ -29,6 +29,7 @@ This namespace contains the generated graphql client interface. This is a low-le
- `Intl` - `Intl`
- `FontAwesomeRegular` - `FontAwesomeRegular`
- `FontAwesomeSolid` - `FontAwesomeSolid`
- `FontAwesomeBrands`
- `Mousetrap` - `Mousetrap`
- `MousetrapPause` - `MousetrapPause`
- `ReactSelect` - `ReactSelect`
@@ -147,6 +148,8 @@ Returns `void`.
- `ConstantSetting` - `ConstantSetting`
- `CountrySelect` - `CountrySelect`
- `DateInput` - `DateInput`
- `ExternalLinkButtons`
- `ExternalLinksButton`
- `FolderSelect` - `FolderSelect`
- `FrontPage` - `FrontPage`
- `GalleryIDSelect` - `GalleryIDSelect`

View File

@@ -614,6 +614,7 @@ declare namespace PluginApi {
const Bootstrap: typeof import("react-bootstrap"); const Bootstrap: typeof import("react-bootstrap");
const FontAwesomeRegular: typeof import("@fortawesome/free-regular-svg-icons"); const FontAwesomeRegular: typeof import("@fortawesome/free-regular-svg-icons");
const FontAwesomeSolid: typeof import("@fortawesome/free-solid-svg-icons"); const FontAwesomeSolid: typeof import("@fortawesome/free-solid-svg-icons");
const FontAwesomeBrands: typeof import("@fortawesome/free-brands-svg-icons");
const Intl: typeof import("react-intl"); const Intl: typeof import("react-intl");
const Mousetrap: typeof import("mousetrap"); const Mousetrap: typeof import("mousetrap");
const ReactSelect: typeof import("react-select"); const ReactSelect: typeof import("react-select");
@@ -697,6 +698,8 @@ declare namespace PluginApi {
PerformerImagesPanel: React.FC<any>; PerformerImagesPanel: React.FC<any>;
TabTitleCounter: React.FC<any>; TabTitleCounter: React.FC<any>;
PerformerCard: React.FC<any>; PerformerCard: React.FC<any>;
ExternalLinkButtons: React.FC<any>;
ExternalLinksButton: React.FC<any>;
"PerformerCard.Popovers": React.FC<any>; "PerformerCard.Popovers": React.FC<any>;
"PerformerCard.Details": React.FC<any>; "PerformerCard.Details": React.FC<any>;
"PerformerCard.Overlays": React.FC<any>; "PerformerCard.Overlays": React.FC<any>;

View File

@@ -11,6 +11,7 @@ import * as Bootstrap from "react-bootstrap";
import * as Intl from "react-intl"; import * as Intl from "react-intl";
import * as FontAwesomeSolid from "@fortawesome/free-solid-svg-icons"; import * as FontAwesomeSolid from "@fortawesome/free-solid-svg-icons";
import * as FontAwesomeRegular from "@fortawesome/free-regular-svg-icons"; import * as FontAwesomeRegular from "@fortawesome/free-regular-svg-icons";
import * as FontAwesomeBrands from "@fortawesome/free-brands-svg-icons";
import * as ReactSelect from "react-select"; import * as ReactSelect from "react-select";
import { useSpriteInfo } from "./hooks/sprite"; import { useSpriteInfo } from "./hooks/sprite";
import { useToast } from "./hooks/Toast"; import { useToast } from "./hooks/Toast";
@@ -72,6 +73,7 @@ export const PluginApi = {
Intl, Intl,
FontAwesomeRegular, FontAwesomeRegular,
FontAwesomeSolid, FontAwesomeSolid,
FontAwesomeBrands,
Mousetrap, Mousetrap,
MousetrapPause, MousetrapPause,
ReactSelect, ReactSelect,