Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions docs/references/api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,49 +20,41 @@ Complete API documentation for all Temporal SDKs and server APIs.
href: "https://dotnet.temporal.io/api/",
title: ".NET SDK API",
description: "Complete .NET SDK API documentation with all namespaces, classes, and methods.",
external: true,
},
{
href: "https://pkg.go.dev/go.temporal.io/sdk",
title: "Go SDK API",
description: "Complete Go SDK API documentation on pkg.go.dev with all packages, types, and methods.",
external: true,
},
{
href: "https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/index.html",
title: "Java SDK API",
description: "Complete Java SDK API documentation on javadoc.io with all classes, interfaces, and annotations.",
external: true,
},
{
href: "https://php.temporal.io/namespaces/temporal.html",
title: "PHP SDK API",
description: "Complete PHP SDK API documentation with all namespaces, classes, and interfaces.",
external: true,
},
{
href: "https://python.temporal.io/",
title: "Python SDK API",
description: "Complete Python SDK API documentation with all modules, classes, and functions.",
external: true,
},
{
href: "https://ruby.temporal.io/",
title: "Ruby SDK API",
description: "Complete Ruby SDK API documentation with all modules, classes, and methods.",
external: true,
},
{
href: "https://docs.rs/temporalio-sdk/latest/temporalio_sdk/",
title: "Rust SDK API",
description: "Complete Rust SDK API documentation on docs.rs with all modules, structs, and associated functions/methods.",
external: true,
},
{
href: "https://typescript.temporal.io",
title: "TypeScript SDK API",
description: "Complete TypeScript SDK API documentation with all interfaces, types, and namespaces.",
external: true,
},
]} />

Expand Down
2 changes: 0 additions & 2 deletions docs/security.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ Find security information for your Temporal deployment, whether you're using Tem
href: "https://trust.temporal.io",
title: "Company Security",
description: "Learn about Temporal Technologies' general security practices, compliance certifications, and organizational security measures.",
external: true,
},
{
href: "/evaluate/cloud/security",
Expand All @@ -34,6 +33,5 @@ Find security information for your Temporal deployment, whether you're using Tem
href: "https://temporal.io/pages/cloud-security-white-paper",
title: "Temporal Cloud Security Whitepaper",
description: "Learn how Temporal Cloud provides provable security by design - orchestrating encrypted workflows without ever accessing your sensitive data.",
external: true,
},
]} />
5 changes: 3 additions & 2 deletions src/components/PatternCards.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import { isExternalHref } from '@site/src/utils/links';

type PatternCardItem = {
href: string;
title: string;
description: string;
external?: boolean;
icon?: string;
};

Expand All @@ -31,7 +31,8 @@ export default function PatternCards({ items, className }: PatternCardsProps) {
key={item.href}
to={item.href}
className="pattern-card"
{...(item.external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}
target={isExternalHref(item.href) ? '_blank' : undefined}
rel={isExternalHref(item.href) ? 'noopener noreferrer' : undefined}
>
<div className="pattern-content">
{item.icon ? (
Expand Down
91 changes: 91 additions & 0 deletions src/components/TemporalNavbarLink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import { isRegexpStringMatch } from '@docusaurus/theme-common';
import IconExternalLink from '@theme/Icon/ExternalLink';
import { isExternalHref } from '@site/src/utils/links';

/**
* Registered as the 'default' navbar item type (see NavbarItem/ComponentTypes),
* replacing every plain navbar link/label item — top-level and dropdown
* children alike. Docusaurus's own default opens every absolute URL in a new
* tab and shows the external-link icon for it, including links to other
* temporal.io properties (learn.temporal.io, community.temporal.io, etc.).
* This reimplements that rendering (mirroring NavbarItem/DefaultNavbarItem +
* NavbarItem/NavbarNavLink) so both the tab behavior and the icon use the
* temporal.io-aware check instead. It's also the place to add any future
* top-nav item behavior, since it's ours to extend.
*/
export default function TemporalNavbarLink({
mobile = false,
position, // consumed only so it isn't spread onto the DOM
isDropdownItem = false,
className,
activeClassName,
activeBasePath,
activeBaseRegex,
to,
href,
label,
html,
prependBaseUrlToHref,
...props
}) {
const toUrl = useBaseUrl(to);
const activeBaseUrl = useBaseUrl(activeBasePath);
const normalizedHref = useBaseUrl(href, { forcePrependBaseUrl: true });
const external = isExternalHref(href ?? to);

const linkClassName = clsx(
isDropdownItem ? 'dropdown__link' : mobile ? 'menu__link' : 'navbar__item navbar__link',
className,
);
const resolvedActiveClassName =
activeClassName ?? (mobile ? 'menu__link--active' : 'navbar__link--active');

const content = html ? (
<span dangerouslySetInnerHTML={{ __html: html }} />
) : (
<>
{label}
{external && <IconExternalLink {...(isDropdownItem && { width: 12, height: 12 })} />}
</>
);

const link = href ? (
<Link
className={linkClassName}
href={prependBaseUrlToHref ? normalizedHref : href}
{...props}
target={external ? '_blank' : undefined}
rel={external ? 'noopener noreferrer' : undefined}
>
{content}
</Link>
) : (
<Link
className={linkClassName}
to={toUrl}
isNavLink
activeClassName={resolvedActiveClassName}
{...((activeBasePath || activeBaseRegex) && {
isActive: (_match, location) =>
activeBaseRegex
? isRegexpStringMatch(activeBaseRegex, location.pathname)
: location.pathname.startsWith(activeBaseUrl),
})}
{...props}
>
{content}
</Link>
);

if (isDropdownItem) {
return <li>{link}</li>;
}
if (mobile) {
return <li className="menu__list-item">{link}</li>;
}
return link;
}
25 changes: 5 additions & 20 deletions src/components/elements/GridCard/GridCard.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
import React from 'react';
import Link from '@docusaurus/Link';
import clsx from 'clsx';
import { isExternalHref } from '@site/src/utils/links';
import styles from './GridCard.module.css';

function isExternal(href: string): boolean {
return href.startsWith('http://') || href.startsWith('https://');
}

// A link to temporal.io itself (e.g. the Code Exchange) still opens in a new
// tab like any other external href, but doesn't get the external-link icon —
// it isn't "leaving Temporal" the way a partner's docs site is.
function isOffTemporalDomain(href: string): boolean {
try {
const { hostname } = new URL(href);
return hostname !== 'temporal.io' && !hostname.endsWith('.temporal.io');
} catch {
return true;
}
}

function ExternalLinkIcon() {
return (
<svg
Expand Down Expand Up @@ -55,19 +40,19 @@ export type GridCardProps = {
* component so the two stay visually identical instead of drifting apart.
*/
export default function GridCard({ title, description, href, tags = [], icon, analyticsId }: GridCardProps) {
const external = isExternal(href);
const showExternalIcon = external && isOffTemporalDomain(href);
const external = isExternalHref(href);
return (
<Link
to={href}
className={clsx('grid-card', styles.card)}
{...(external ? { target: '_blank', rel: 'noopener noreferrer' } : {})}
target={external ? '_blank' : undefined}
rel={external ? 'noopener noreferrer' : undefined}
{...(analyticsId ? { 'data-analytics-id': analyticsId, 'data-analytics-action': 'click' } : {})}
>
<div className={styles.cardHeader}>
<h3 className={styles.cardName}>
{title}
{showExternalIcon && <ExternalLinkIcon />}
{external && <ExternalLinkIcon />}
</h3>
</div>
<p className={styles.cardDescription}>{description}</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Link from '@docusaurus/Link';
import SdkSvg from '../../SdkSvgs/SdkSvg';
import { SDKS } from '../../../../constants/sdks';
import sdkVersions from '../../../../data/sdk-versions.json';
import { isExternalHref } from '@site/src/utils/links';
import styles from './sdk-overview-cards.module.css';

export const SdkOverviewCards = () => {
Expand All @@ -35,8 +36,8 @@ export const SdkOverviewCards = () => {
<Link
to={apiReferenceHref}
className={styles.link}
target="_blank"
rel="noopener noreferrer"
target={isExternalHref(apiReferenceHref) ? '_blank' : undefined}
rel={isExternalHref(apiReferenceHref) ? 'noopener noreferrer' : undefined}
>
API reference
</Link>
Expand Down
31 changes: 31 additions & 0 deletions src/theme/Footer/LinkItem/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import IconExternalLink from '@theme/Icon/ExternalLink';
import { isExternalHref } from '@site/src/utils/links';

// Swizzled (not wrapped) because the default Footer/LinkItem decides the
// external-link icon from Docusaurus's own protocol-only "external" check,
// which isn't exposed as a prop — so wrapping it can't fix the icon to match
// the temporal.io-aware target/rel decision below. This otherwise mirrors the
// original implementation.
export default function FooterLinkItem({ item }) {
const { to, href, label, prependBaseUrlToHref, className, ...props } = item;
const toUrl = useBaseUrl(to);
const normalizedHref = useBaseUrl(href, { forcePrependBaseUrl: true });
const external = isExternalHref(to ?? href);

return (
<Link
className={clsx('footer__link-item', className)}
{...(href ? { href: prependBaseUrlToHref ? normalizedHref : href } : { to: toUrl })}
{...props}
target={external ? '_blank' : undefined}
rel={external ? 'noopener noreferrer' : undefined}
>
{label}
{external && <IconExternalLink />}
</Link>
);
}
47 changes: 47 additions & 0 deletions src/theme/Footer/Logo/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import { useBaseUrlUtils } from '@docusaurus/useBaseUrl';
import ThemedImage from '@theme/ThemedImage';
import { isExternalHref } from '@site/src/utils/links';
import styles from './styles.module.css';

function LogoImage({ logo }) {
const { withBaseUrl } = useBaseUrlUtils();
const sources = {
light: withBaseUrl(logo.src),
dark: withBaseUrl(logo.srcDark ?? logo.src),
};
return (
<ThemedImage
className={clsx('footer__logo', logo.className)}
alt={logo.alt}
sources={sources}
width={logo.width}
height={logo.height}
style={logo.style}
/>
);
}

// Swizzled (not wrapped) because the default only ever overrides `target`,
// leaving Link's default rel="noopener noreferrer" in place even when the
// link — https://temporal.io — doesn't leave the temporal.io family and
// doesn't open a new tab. Otherwise mirrors the original implementation.
export default function FooterLogo({ logo }) {
if (!logo.href) {
return <LogoImage logo={logo} />;
}

const external = isExternalHref(logo.href);
return (
<Link
href={logo.href}
className={styles.footerLogoLink}
target={external ? '_blank' : undefined}
rel={external ? 'noopener noreferrer' : undefined}
>
<LogoImage logo={logo} />
</Link>
);
}
9 changes: 9 additions & 0 deletions src/theme/Footer/Logo/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.footerLogoLink {
opacity: 0.5;
transition: opacity var(--ifm-transition-fast)
var(--ifm-transition-timing-default);
}

.footerLogoLink:hover {
opacity: 1;
}
21 changes: 21 additions & 0 deletions src/theme/Logo/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import OriginalLogo from '@theme-original/Logo';
import { useThemeConfig } from '@docusaurus/theme-common';
import { isExternalHref } from '@site/src/utils/links';

// Used by Navbar/Logo (Footer/Logo is a separate component and already
// behaves correctly). The default only sets target/rel when navbar.logo.target
// is explicitly configured, so it falls through to Docusaurus's own default
// of opening every absolute URL in a new tab — including the logo's link to
// temporal.io itself. Cancel that for the temporal.io family.
export default function Logo(props) {
const {
navbar: { logo },
} = useThemeConfig();

return isExternalHref(logo?.href) ? (
<OriginalLogo {...props} />
) : (
<OriginalLogo {...props} target={undefined} rel={undefined} />
);
}
15 changes: 15 additions & 0 deletions src/theme/MDXComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import TabItem from '@theme/TabItem';
import LLMActions from '@site/src/components/LLMActions/LLMActions';
import ZoomableImage from '@site/src/components/elements/Images/ZoomableImage';
import NoZoom from '@site/src/components/elements/Images/NoZoom';
import { isExternalHref } from '@site/src/utils/links';
import styles from '@site/src/theme/DocItem/Content/styles.module.css';

function H1WithLLMActions(props: React.ComponentProps<'h1'>): JSX.Element {
Expand All @@ -23,11 +24,25 @@ function H1WithLLMActions(props: React.ComponentProps<'h1'>): JSX.Element {
);
}

// The default <a> (@theme/MDXComponents/A) opens every absolute URL in a new
// tab, including links to other temporal.io properties. Cancel target/rel for
// the temporal.io family so prose links behave like they do on temporal.io,
// while keeping everything else (footnote anchor styling, etc.) unchanged.
const DefaultA = MDXComponents.a;
function A(props: React.ComponentProps<typeof DefaultA> & { href?: string }): JSX.Element {
return isExternalHref(props.href) ? (
<DefaultA {...props} />
) : (
<DefaultA {...props} target={undefined} rel={undefined} />
);
}

export default {
...MDXComponents,
Tabs,
TabItem,
NoZoom,
h1: H1WithLLMActions,
img: ZoomableImage,
a: A,
};
Loading
Loading