diff --git a/ui/v2.5/src/components/Dialogs/SubmitDraft.tsx b/ui/v2.5/src/components/Dialogs/SubmitDraft.tsx index 58740eb47d0..03d41063138 100644 --- a/ui/v2.5/src/components/Dialogs/SubmitDraft.tsx +++ b/ui/v2.5/src/components/Dialogs/SubmitDraft.tsx @@ -9,6 +9,7 @@ import { ModalComponent } from "src/components/Shared/Modal"; import { getStashboxBase } from "src/utils/stashbox"; import { FormattedMessage, useIntl } from "react-intl"; import { faPaperPlane } from "@fortawesome/free-solid-svg-icons"; +import { ExternalLink } from "../Shared/ExternalLink"; interface IProps { type: "scene" | "performer"; @@ -108,12 +109,12 @@ export const SubmitStashBoxDraft: React.FC = ({
- + - +
); diff --git a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx index 3d3e1d11a38..15f2c6843f4 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx @@ -38,6 +38,7 @@ import { DetailImage } from "src/components/Shared/DetailImage"; import { useRatingKeybinds } from "src/hooks/keybinds"; import { useLoadStickyHeader } from "src/hooks/detailsPanel"; import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; +import { ExternalLink } from "src/components/Shared/ExternalLink"; interface IProps { movie: GQL.MovieDataFragment; @@ -274,15 +275,13 @@ const MoviePage: React.FC = ({ movie }) => { const renderClickableIcons = () => ( {movie.url && ( - )} diff --git a/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx b/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx index d0bc485e55d..b9d0d7ef7aa 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/MovieDetailsPanel.tsx @@ -3,6 +3,7 @@ import { useIntl } from "react-intl"; import * as GQL from "src/core/generated-graphql"; import TextUtils from "src/utils/text"; import { DetailItem } from "src/components/Shared/DetailItem"; +import { Link } from "react-router-dom"; interface IMovieDetailsPanel { movie: GQL.MovieDataFragment; @@ -34,9 +35,9 @@ export const MovieDetailsPanel: React.FC = ({ id="studio" value={ movie.studio?.id ? ( - + {movie.studio?.name} - + ) : ( "" ) diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index 09772776889..f6e7852d398 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -45,6 +45,7 @@ import { useRatingKeybinds } from "src/hooks/keybinds"; import { DetailImage } from "src/components/Shared/DetailImage"; import { useLoadStickyHeader } from "src/hooks/detailsPanel"; import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; +import { ExternalLink } from "src/components/Shared/ExternalLink"; interface IProps { performer: GQL.PerformerDataFragment; @@ -493,57 +494,50 @@ const PerformerPage: React.FC = ({ performer, tabKey }) => { {performer.url && ( - )} {(urls ?? []).map((url, index) => ( - ))} {performer.twitter && ( - )} {performer.instagram && ( - )} diff --git a/ui/v2.5/src/components/Performers/PerformerSelect.tsx b/ui/v2.5/src/components/Performers/PerformerSelect.tsx index 5a1632ef9ad..0076cb61394 100644 --- a/ui/v2.5/src/components/Performers/PerformerSelect.tsx +++ b/ui/v2.5/src/components/Performers/PerformerSelect.tsx @@ -25,6 +25,7 @@ import { Option as SelectOption, } from "../Shared/FilterSelect"; import { useCompare } from "src/hooks/state"; +import { Link } from "react-router-dom"; export type SelectObject = { id: string; @@ -86,10 +87,9 @@ export const PerformerSelect: React.FC< ...optionProps, children: ( - - + {name} {object.disambiguation && ( {` (${object.disambiguation})`} diff --git a/ui/v2.5/src/components/Performers/styles.scss b/ui/v2.5/src/components/Performers/styles.scss index 1686c03acc2..05b75501502 100644 --- a/ui/v2.5/src/components/Performers/styles.scss +++ b/ui/v2.5/src/components/Performers/styles.scss @@ -27,22 +27,9 @@ color: #ff7373; } - .link { - color: rgb(191, 204, 214); - } - .instagram { color: pink; } - - .icon-link { - padding: 0; - - a { - display: inline-block; - padding: $btn-padding-y $btn-padding-x; - } - } } .rating-number .form-control { diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx index 6da4c735ad6..a601b298ca4 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx @@ -85,7 +85,7 @@ const FileInfoPanel: React.FC = ( url={NavUtils.makeScenesPHashMatchUrl(phash?.value)} target="_self" truncate - trusted + internal /> { { id: "config.about.stash_home" }, { url: ( - + GitHub - + ), } )} @@ -131,13 +128,9 @@ export const SettingsAboutPanel: React.FC = () => { { id: "config.about.stash_wiki" }, { url: ( - + Documentation - + ), } )} @@ -147,13 +140,9 @@ export const SettingsAboutPanel: React.FC = () => { { id: "config.about.stash_discord" }, { url: ( - + Discord - + ), } )} @@ -163,13 +152,9 @@ export const SettingsAboutPanel: React.FC = () => { { id: "config.about.stash_open_collective" }, { url: ( - + Open Collective - + ), } )} diff --git a/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx b/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx index 76c72968f95..4a8e55a601b 100644 --- a/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsLibraryPanel.tsx @@ -7,6 +7,7 @@ import { BooleanSetting, StringListSetting, StringSetting } from "./Inputs"; import { useSettings } from "./context"; import { useIntl } from "react-intl"; import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; +import { ExternalLink } from "../Shared/ExternalLink"; export const SettingsLibraryPanel: React.FC = () => { const intl = useIntl(); @@ -76,13 +77,9 @@ export const SettingsLibraryPanel: React.FC = () => { {intl.formatMessage({ id: "config.general.excluded_video_patterns_desc", })} - + - + } value={general.excludes ?? undefined} @@ -98,13 +95,9 @@ export const SettingsLibraryPanel: React.FC = () => { {intl.formatMessage({ id: "config.general.excluded_image_gallery_patterns_desc", })} - + - + } value={general.imageExcludes ?? undefined} diff --git a/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx b/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx index 849476aa870..102d236f1ed 100644 --- a/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx @@ -26,6 +26,7 @@ import { AvailablePluginPackages, InstalledPluginPackages, } from "./PluginPackageManager"; +import { ExternalLink } from "../Shared/ExternalLink"; interface IPluginSettingProps { pluginID: string; @@ -97,15 +98,12 @@ export const SettingsPluginsPanel: React.FC = () => { function renderLink(url?: string) { if (url) { return ( - ); } diff --git a/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx b/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx index 9aef6942bb3..304b83d75c1 100644 --- a/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsScrapingPanel.tsx @@ -23,6 +23,7 @@ import { AvailableScraperPackages, InstalledScraperPackages, } from "./ScraperPackageManager"; +import { ExternalLink } from "../Shared/ExternalLink"; interface IURLList { urls: string[]; @@ -42,16 +43,7 @@ const URLList: React.FC = ({ urls }) => { const sanitised = TextUtils.sanitiseURL(url); const siteURL = linkSite(sanitised!); - return ( - - {sanitised} - - ); + return {sanitised}; } } diff --git a/ui/v2.5/src/components/Settings/styles.scss b/ui/v2.5/src/components/Settings/styles.scss index 965c9dcfce3..46c86986f7a 100644 --- a/ui/v2.5/src/components/Settings/styles.scss +++ b/ui/v2.5/src/components/Settings/styles.scss @@ -67,7 +67,7 @@ min-width: 100px; text-align: right; - button { + .btn { margin: 0.25rem; } } diff --git a/ui/v2.5/src/components/Setup/Migrate.tsx b/ui/v2.5/src/components/Setup/Migrate.tsx index 9f27d780509..23fc0b520f0 100644 --- a/ui/v2.5/src/components/Setup/Migrate.tsx +++ b/ui/v2.5/src/components/Setup/Migrate.tsx @@ -5,6 +5,7 @@ import { useHistory } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; import { useSystemStatus, mutateMigrate } from "src/core/StashService"; import { migrationNotes } from "src/docs/en/MigrationNotes"; +import { ExternalLink } from "../Shared/ExternalLink"; import { LoadingIndicator } from "../Shared/LoadingIndicator"; import { MarkdownPage } from "../Shared/MarkdownPage"; @@ -35,18 +36,12 @@ export const Migrate: React.FC = () => { : ""; const discordLink = ( - - Discord - + Discord ); const githubLink = ( - + - + ); useEffect(() => { diff --git a/ui/v2.5/src/components/Setup/Setup.tsx b/ui/v2.5/src/components/Setup/Setup.tsx index d14323f9fe1..f63aaeaf35c 100644 --- a/ui/v2.5/src/components/Setup/Setup.tsx +++ b/ui/v2.5/src/components/Setup/Setup.tsx @@ -27,6 +27,7 @@ import { faQuestionCircle, } from "@fortawesome/free-solid-svg-icons"; import { releaseNotes } from "src/docs/en/ReleaseNotes"; +import { ExternalLink } from "../Shared/ExternalLink"; export const Setup: React.FC = () => { const { configuration, loading: configLoading } = @@ -103,18 +104,12 @@ export const Setup: React.FC = () => { }, [configuration]); const discordLink = ( - - Discord - + Discord ); const githubLink = ( - + - + ); function onConfigLocationChosen(inWorkDir: boolean) { @@ -825,14 +820,9 @@ export const Setup: React.FC = () => { id="setup.success.open_collective" values={{ open_collective_link: ( - - {" "} - OpenCollective{" "} - + + Open Collective + ), }} /> diff --git a/ui/v2.5/src/components/Shared/ExternalLink.tsx b/ui/v2.5/src/components/Shared/ExternalLink.tsx new file mode 100644 index 00000000000..e01572d22c3 --- /dev/null +++ b/ui/v2.5/src/components/Shared/ExternalLink.tsx @@ -0,0 +1,5 @@ +type IExternalLinkProps = JSX.IntrinsicElements["a"]; + +export const ExternalLink: React.FC = (props) => { + return ; +}; diff --git a/ui/v2.5/src/components/Shared/StashID.tsx b/ui/v2.5/src/components/Shared/StashID.tsx index f44fbb64251..14bcef6882c 100644 --- a/ui/v2.5/src/components/Shared/StashID.tsx +++ b/ui/v2.5/src/components/Shared/StashID.tsx @@ -2,6 +2,7 @@ import React, { useMemo } from "react"; import { StashId } from "src/core/generated-graphql"; import { ConfigurationContext } from "src/hooks/Config"; import { getStashboxBase } from "src/utils/stashbox"; +import { ExternalLink } from "./ExternalLink"; export type LinkType = "performers" | "scenes" | "studios"; @@ -26,9 +27,7 @@ export const StashIDPill: React.FC<{ return ( {endpointName} - - {stash_id} - + {stash_id} ); }; diff --git a/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx b/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx index 40375d918e7..d4701d33bbb 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx @@ -45,6 +45,7 @@ import { DetailImage } from "src/components/Shared/DetailImage"; import { useRatingKeybinds } from "src/hooks/keybinds"; import { useLoadStickyHeader } from "src/hooks/detailsPanel"; import { useScrollToTopOnMount } from "src/hooks/scrollToTop"; +import { ExternalLink } from "src/components/Shared/ExternalLink"; interface IProps { studio: GQL.StudioDataFragment; @@ -285,15 +286,13 @@ const StudioPage: React.FC = ({ studio, tabKey }) => { const renderClickableIcons = () => ( {studio.url && ( - )} diff --git a/ui/v2.5/src/components/Studios/StudioDetails/StudioDetailsPanel.tsx b/ui/v2.5/src/components/Studios/StudioDetails/StudioDetailsPanel.tsx index b5d86bfd71a..a6c5126cb6b 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/StudioDetailsPanel.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/StudioDetailsPanel.tsx @@ -2,6 +2,7 @@ import React from "react"; import * as GQL from "src/core/generated-graphql"; import { DetailItem } from "src/components/Shared/DetailItem"; import { StashIDPill } from "src/components/Shared/StashID"; +import { Link } from "react-router-dom"; interface IStudioDetailsPanel { studio: GQL.StudioDataFragment; @@ -51,9 +52,9 @@ export const StudioDetailsPanel: React.FC = ({ id="parent_studios" value={ studio.parent_studio?.name ? ( - + {studio.parent_studio.name} - + ) : ( "" ) diff --git a/ui/v2.5/src/components/Tagger/PerformerModal.tsx b/ui/v2.5/src/components/Tagger/PerformerModal.tsx index 2fbdd3f2355..68b7dc8df98 100755 --- a/ui/v2.5/src/components/Tagger/PerformerModal.tsx +++ b/ui/v2.5/src/components/Tagger/PerformerModal.tsx @@ -18,6 +18,7 @@ import { faExternalLinkAlt, faTimes, } from "@fortawesome/free-solid-svg-icons"; +import { ExternalLink } from "../Shared/ExternalLink"; interface IPerformerModalProps { performer: GQL.ScrapedScenePerformerDataFragment; @@ -82,12 +83,14 @@ const PerformerModal: React.FC = ({ [name]: !excluded[name], }); - const renderField = ( + function maybeRenderField( name: string, text: string | null | undefined, truncate: boolean = true - ) => - text && ( + ) { + if (!text) return; + + return (
{!create && ( @@ -112,11 +115,72 @@ const PerformerModal: React.FC = ({ )}
); + } + + function maybeRenderImage() { + if (!images.length) return; + + return ( +
+
+ {!create && ( + + )} + handleLoad(imageIndex)} + onError={handleError} + /> + {imageState === "loading" && ( + + )} + {imageState === "error" && ( +
+ Error loading image. +
+ )} +
+
+ +
+ Select performer image +
+ {imageIndex + 1} of {images.length} +
+ +
+
+ ); + } - const base = endpoint?.match(/https?:\/\/.*?\//)?.[0]; - const link = base - ? `${base}performers/${performer.remote_site_id}` - : undefined; + function maybeRenderStashBoxLink() { + const base = endpoint?.match(/https?:\/\/.*?\//)?.[0]; + if (!base) return; + + return ( +
+ + + + +
+ ); + } function onSaveClicked() { if (!performer.name) { @@ -201,89 +265,37 @@ const PerformerModal: React.FC = ({ >
- {renderField("name", performer.name)} - {renderField("disambiguation", performer.disambiguation)} - {renderField("aliases", performer.aliases)} - {renderField( + {maybeRenderField("name", performer.name)} + {maybeRenderField("disambiguation", performer.disambiguation)} + {maybeRenderField("aliases", performer.aliases)} + {maybeRenderField( "gender", performer.gender ? intl.formatMessage({ id: "gender_types." + performer.gender }) : "" )} - {renderField("birthdate", performer.birthdate)} - {renderField("death_date", performer.death_date)} - {renderField("ethnicity", performer.ethnicity)} - {renderField("country", getCountryByISO(performer.country))} - {renderField("hair_color", performer.hair_color)} - {renderField("eye_color", performer.eye_color)} - {renderField("height", performer.height)} - {renderField("weight", performer.weight)} - {renderField("measurements", performer.measurements)} + {maybeRenderField("birthdate", performer.birthdate)} + {maybeRenderField("death_date", performer.death_date)} + {maybeRenderField("ethnicity", performer.ethnicity)} + {maybeRenderField("country", getCountryByISO(performer.country))} + {maybeRenderField("hair_color", performer.hair_color)} + {maybeRenderField("eye_color", performer.eye_color)} + {maybeRenderField("height", performer.height)} + {maybeRenderField("weight", performer.weight)} + {maybeRenderField("measurements", performer.measurements)} {performer?.gender !== GQL.GenderEnum.Male && - renderField("fake_tits", performer.fake_tits)} - {renderField("career_length", performer.career_length)} - {renderField("tattoos", performer.tattoos, false)} - {renderField("piercings", performer.piercings, false)} - {renderField("weight", performer.weight, false)} - {renderField("details", performer.details)} - {renderField("url", performer.url)} - {renderField("twitter", performer.twitter)} - {renderField("instagram", performer.instagram)} - {link && ( -
- - - - -
- )} + maybeRenderField("fake_tits", performer.fake_tits)} + {maybeRenderField("career_length", performer.career_length)} + {maybeRenderField("tattoos", performer.tattoos, false)} + {maybeRenderField("piercings", performer.piercings, false)} + {maybeRenderField("weight", performer.weight, false)} + {maybeRenderField("details", performer.details)} + {maybeRenderField("url", performer.url)} + {maybeRenderField("twitter", performer.twitter)} + {maybeRenderField("instagram", performer.instagram)} + {maybeRenderStashBoxLink()}
- {images.length > 0 && ( -
-
- {!create && ( - - )} - handleLoad(imageIndex)} - onError={handleError} - /> - {imageState === "loading" && ( - - )} - {imageState === "error" && ( -
- Error loading image. -
- )} -
-
- -
- Select performer image -
- {imageIndex + 1} of {images.length} -
- -
-
- )} + {maybeRenderImage()}
); diff --git a/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx b/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx index c6fb168224d..6ba6734c86f 100755 --- a/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx +++ b/ui/v2.5/src/components/Tagger/performers/PerformerTagger.tsx @@ -26,6 +26,7 @@ import PerformerModal from "../PerformerModal"; import { useUpdatePerformer } from "../queries"; import { faStar, faTags } from "@fortawesome/free-solid-svg-icons"; import { mergeStashIDs } from "src/utils/stashbox"; +import { ExternalLink } from "src/components/Shared/ExternalLink"; type JobFragment = Pick< GQL.Job, @@ -466,14 +467,12 @@ const PerformerTaggerList: React.FC = ({ if (stashID !== undefined) { const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0]; const link = base ? ( - {stashID.stash_id} - + ) : (
{stashID.stash_id}
); diff --git a/ui/v2.5/src/components/Tagger/scenes/PerformerResult.tsx b/ui/v2.5/src/components/Tagger/scenes/PerformerResult.tsx index 27fe9bd69f5..25f0ca88f73 100755 --- a/ui/v2.5/src/components/Tagger/scenes/PerformerResult.tsx +++ b/ui/v2.5/src/components/Tagger/scenes/PerformerResult.tsx @@ -12,6 +12,7 @@ import { PerformerSelect, } from "src/components/Performers/PerformerSelect"; import { getStashboxBase } from "src/utils/stashbox"; +import { ExternalLink } from "src/components/Shared/ExternalLink"; interface IPerformerName { performer: GQL.ScrapedPerformer | Performer; @@ -26,9 +27,7 @@ const PerformerName: React.FC = ({ }) => { const name = baseURL && id ? ( - - {performer.name} - + {performer.name} ) : ( performer.name ); diff --git a/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx b/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx index 1f891c8e14b..f2f20f8aa44 100755 --- a/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx +++ b/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx @@ -24,6 +24,7 @@ import StudioResult from "./StudioResult"; import { useInitialState } from "src/hooks/state"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { getStashboxBase } from "src/utils/stashbox"; +import { ExternalLink } from "src/components/Shared/ExternalLink"; const getDurationStatus = ( scene: IScrapedScene, @@ -488,14 +489,9 @@ const StashSearchResult: React.FC = ({ const url = scene.urls?.length ? scene.urls[0] : null; const sceneTitleEl = url ? ( - + - + ) : ( ); @@ -592,9 +588,7 @@ const StashSearchResult: React.FC = ({ > {scene.urls.map((url) => (
- - {url} - + {url}
))} @@ -626,9 +620,9 @@ const StashSearchResult: React.FC = ({ exclude={excludedFields[fields.stash_ids]} setExclude={(v) => setExcludedField(fields.stash_ids, v)} > - + {scene.remote_site_id} - +
); diff --git a/ui/v2.5/src/components/Tagger/scenes/StudioModal.tsx b/ui/v2.5/src/components/Tagger/scenes/StudioModal.tsx index 8af089360c8..b57c796ab28 100644 --- a/ui/v2.5/src/components/Tagger/scenes/StudioModal.tsx +++ b/ui/v2.5/src/components/Tagger/scenes/StudioModal.tsx @@ -15,6 +15,7 @@ import { import { Button, Form } from "react-bootstrap"; import { TruncatedText } from "src/components/Shared/TruncatedText"; import { excludeFields } from "src/utils/data"; +import { ExternalLink } from "src/components/Shared/ExternalLink"; interface IStudioDetailsProps { studio: GQL.ScrapedSceneStudioDataFragment; @@ -83,15 +84,15 @@ const StudioDetails: React.FC = ({ ); } - function maybeRenderLink() { + function maybeRenderStashBoxLink() { if (!link) return; return (
- + - +
); } @@ -104,7 +105,7 @@ const StudioDetails: React.FC = ({ {maybeRenderField("name", studio.name, !isNew)} {maybeRenderField("url", studio.url)} {maybeRenderField("parent_studio", studio.parent?.name, false)} - {maybeRenderLink()} + {maybeRenderStashBoxLink()} diff --git a/ui/v2.5/src/components/Tagger/scenes/StudioResult.tsx b/ui/v2.5/src/components/Tagger/scenes/StudioResult.tsx index 56b72d9f9ee..4d099d2a6e2 100755 --- a/ui/v2.5/src/components/Tagger/scenes/StudioResult.tsx +++ b/ui/v2.5/src/components/Tagger/scenes/StudioResult.tsx @@ -11,6 +11,7 @@ import * as GQL from "src/core/generated-graphql"; import { OptionalField } from "../IncludeButton"; import { faSave } from "@fortawesome/free-solid-svg-icons"; import { getStashboxBase } from "src/utils/stashbox"; +import { ExternalLink } from "src/components/Shared/ExternalLink"; interface IStudioName { studio: GQL.ScrapedStudio | GQL.SlimStudioDataFragment; @@ -21,9 +22,7 @@ interface IStudioName { const StudioName: React.FC = ({ studio, id, baseURL }) => { const name = baseURL && id ? ( - - {studio.name} - + {studio.name} ) : ( studio.name ); diff --git a/ui/v2.5/src/components/Tagger/scenes/TaggerScene.tsx b/ui/v2.5/src/components/Tagger/scenes/TaggerScene.tsx index e854e84c228..01cad14787a 100644 --- a/ui/v2.5/src/components/Tagger/scenes/TaggerScene.tsx +++ b/ui/v2.5/src/components/Tagger/scenes/TaggerScene.tsx @@ -18,6 +18,7 @@ import { faImage, } from "@fortawesome/free-solid-svg-icons"; import { objectPath, objectTitle } from "src/core/files"; +import { ExternalLink } from "src/components/Shared/ExternalLink"; interface ITaggerSceneDetails { scene: GQL.SlimSceneDataFragment; @@ -174,15 +175,13 @@ export const TaggerScene: React.FC> = ({ const stashLinks = scene.stash_ids.map((stashID) => { const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0]; const link = base ? ( - {stashID.stash_id} - + ) : (
{stashID.stash_id}
); diff --git a/ui/v2.5/src/components/Tagger/studios/StudioTagger.tsx b/ui/v2.5/src/components/Tagger/studios/StudioTagger.tsx index b0499b8ab90..f4b08cb1ad0 100644 --- a/ui/v2.5/src/components/Tagger/studios/StudioTagger.tsx +++ b/ui/v2.5/src/components/Tagger/studios/StudioTagger.tsx @@ -27,6 +27,7 @@ import StudioModal from "../scenes/StudioModal"; import { useUpdateStudio } from "../queries"; import { apolloError } from "src/utils"; import { faStar, faTags } from "@fortawesome/free-solid-svg-icons"; +import { ExternalLink } from "src/components/Shared/ExternalLink"; type JobFragment = Pick< GQL.Job, @@ -515,14 +516,12 @@ const StudioTaggerList: React.FC = ({ if (stashID !== undefined) { const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0]; const link = base ? ( - {stashID.stash_id} - + ) : (
{stashID.stash_id}
); diff --git a/ui/v2.5/src/index.scss b/ui/v2.5/src/index.scss index 4103224e51d..60ec8bbf163 100755 --- a/ui/v2.5/src/index.scss +++ b/ui/v2.5/src/index.scss @@ -205,6 +205,15 @@ dd { } } +.btn.link { + color: $link-color; + + &:hover:not(:disabled), + &:active:not(:disabled) { + color: $link-color; + } +} + .detail-header.edit { background-color: unset; overflow: visible; @@ -691,12 +700,11 @@ div.dropdown-menu { flex-wrap: wrap; row-gap: 10px; - /* stylelint-disable */ .badge { - white-space: normal !important; margin: unset; + // stylelint-disable declaration-no-important + white-space: normal !important; } - /* stylelint-enable */ } .tag-item { diff --git a/ui/v2.5/src/utils/field.tsx b/ui/v2.5/src/utils/field.tsx index 26ef6d86ccc..c4a55d475de 100644 --- a/ui/v2.5/src/utils/field.tsx +++ b/ui/v2.5/src/utils/field.tsx @@ -1,5 +1,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; +import { Link } from "react-router-dom"; +import { ExternalLink } from "src/components/Shared/ExternalLink"; import { TruncatedText } from "src/components/Shared/TruncatedText"; interface ITextField { @@ -44,8 +46,8 @@ interface IURLField { url?: string | null; truncate?: boolean | null; target?: string; - // use for internal links - trusted?: boolean; + // an internal link (uses ) + internal?: boolean; } export const URLField: React.FC = ({ @@ -55,11 +57,10 @@ export const URLField: React.FC = ({ url, abbr, truncate, - children, - target, - trusted, + target = "_blank", + internal, }) => { - if (!value && !children) { + if (!value) { return null; } @@ -67,26 +68,30 @@ export const URLField: React.FC = ({ <>{id ? : name}: ); - const rel = !trusted ? "noopener noreferrer" : undefined; + function maybeRenderUrl() { + if (!url) return; + + const children = truncate ? : value; + + if (internal) { + return ( + + {children} + + ); + } else { + return ( + + {children} + + ); + } + } return ( <>
{abbr ? {message} : message}
-
- {url ? ( - - {value ? ( - truncate ? ( - - ) : ( - value - ) - ) : ( - children - )} - - ) : undefined} -
+
{maybeRenderUrl()}
); }; @@ -98,8 +103,8 @@ interface IURLsField { urls?: string[] | null; truncate?: boolean | null; target?: string; - // use for internal links - trusted?: boolean; + // an internal link (uses ) + internal?: boolean; } export const URLsField: React.FC = ({ @@ -108,11 +113,10 @@ export const URLsField: React.FC = ({ urls, abbr, truncate, - target, - trusted, + target = "_blank", + internal, }) => { - const values = urls ?? []; - if (!values.length) { + if (!urls || !urls.length) { return null; } @@ -120,19 +124,33 @@ export const URLsField: React.FC = ({ <>{id ? : name}: ); - const rel = !trusted ? "noopener noreferrer" : undefined; + const renderUrls = () => { + return urls.map((url, i) => { + if (!url) return; + + const children = truncate ? : url; + + if (internal) { + return ( + + {children} + + ); + } else { + return ( + + {children} + + ); + } + }); + }; return ( <>
{abbr ? {message} : message}
-
- {values.map((url, i) => ( - - {truncate ? : url} - - ))} -
+
{renderUrls()}
);