fix: PageHead header alignment, analytics proxy, and infra improvements (#64)

🤖 Pull request created from `various-fixes` containing PageHead
breadcrumbs and navigation alignment fixes, Umami analytics proxy, and
infrastructure updates.
This commit is contained in:
deytenit authored and GitHub committed 2026-10-24 16:52:49 +03:00
1 parent ff5a50bad8
commit 9ee672504f
110 files changed
+2649 -1760

No files matched your search

@@ -1,5 +1,3 @@
import { createHighlighter } from 'shiki';
import type { Highlighter } from 'shiki';
/**
@@ -77,14 +75,16 @@ class CodeHighlighter {
return this.initializingPromise;
}
this.initializingPromise = createHighlighter({
themes: ['github-dark', 'github-light'],
langs: SUPPORTED_LANGUAGES,
}).then((highlighter) => {
this.initializingPromise = (async () => {
const { createHighlighter } = await import('shiki');
const highlighter = await createHighlighter({
themes: ['github-dark', 'github-light'],
langs: SUPPORTED_LANGUAGES,
});
this.highlighter = highlighter;
this.initializingPromise = null;
return highlighter;
});
})();
return this.initializingPromise;
}
+79 -2
View File
@@ -2,7 +2,7 @@ import React from 'react';
import { vi } from 'vitest';
import { Link } from './Link';
import { render, screen } from '../../lib/testing';
import { render, screen, fireEvent } from '../../lib/testing';
describe('components/Link', () => {
beforeEach(() => {
@@ -66,6 +66,83 @@ describe('components/Link', () => {
</Link>
);
const link = screen.getByRole('link');
expect(link).toHaveClass('opacity-0');
expect(link).toHaveStyle({ animationDelay: '500ms', animationFillMode: 'both' });
expect(link).toHaveClass('animate-in');
});
it('obfuscates mailto link until user interaction', () => {
render(
<Link isObfuscated href="mailto:personal@ermnvldmr.com">
personal@ermnvldmr.com
</Link>
);
const link = screen.getByRole('link', { name: 'personal@ermnvldmr.com' });
expect(link).toHaveAttribute('href', '#');
expect(link).toHaveAttribute('data-user', 'personal');
expect(link).toHaveAttribute('data-domain', 'ermnvldmr.com');
fireEvent.pointerEnter(link);
expect(link).toHaveAttribute('href', 'mailto:personal@ermnvldmr.com');
});
it('activates obfuscated mailto link on focus and pointer down', () => {
const { rerender } = render(
<Link isObfuscated href="mailto:focus@ermnvldmr.com">
focus@ermnvldmr.com
</Link>
);
const link = screen.getByRole('link', { name: 'focus@ermnvldmr.com' });
fireEvent.focus(link);
expect(link).toHaveAttribute('href', 'mailto:focus@ermnvldmr.com');
rerender(
<Link key="pointer-down" isObfuscated href="mailto:pointer@ermnvldmr.com">
pointer@ermnvldmr.com
</Link>
);
const linkPointer = screen.getByRole('link', { name: 'pointer@ermnvldmr.com' });
fireEvent.pointerDown(linkPointer);
expect(linkPointer).toHaveAttribute('href', 'mailto:pointer@ermnvldmr.com');
});
it('handles click on unactivated and activated obfuscated mailto link', () => {
const mockLocation = { href: '' };
vi.stubGlobal('location', mockLocation);
render(
<Link isObfuscated href="mailto:click@ermnvldmr.com">
click@ermnvldmr.com
</Link>
);
const link = screen.getByRole('link', { name: 'click@ermnvldmr.com' });
fireEvent.click(link);
expect(mockLocation.href).toBe('mailto:click@ermnvldmr.com');
expect(link).toHaveAttribute('href', 'mailto:click@ermnvldmr.com');
// Click again when already activated
fireEvent.click(link);
expect(mockLocation.href).toBe('mailto:click@ermnvldmr.com');
});
it('renders custom children directly when obfuscated instead of bidi override', () => {
render(
<Link isObfuscated href="mailto:custom@ermnvldmr.com">
Send an Email
</Link>
);
const link = screen.getByRole('link', { name: 'custom@ermnvldmr.com' });
expect(link).toHaveTextContent('Send an Email');
});
it('handles obfuscated mailto without at symbol gracefully', () => {
render(
<Link isObfuscated href="mailto:plainaddress">
plainaddress
</Link>
);
const link = screen.getByRole('link', { name: 'plainaddress' });
expect(link).toHaveAttribute('data-user', '');
expect(link).toHaveAttribute('data-domain', '');
});
});
+63 -4
View File
@@ -1,6 +1,6 @@
import { localePath } from '@ermnvldmr/i18n';
import { castRef, cn, filterDataAttributes } from '@ermnvldmr/stl';
import React, { memo, useRef } from 'react';
import React, { memo, useCallback, useRef, useState } from 'react';
import { useLink } from 'react-aria';
import { Text } from '../Text/Text';
@@ -27,6 +27,8 @@ export interface LinkProps extends AriaLinkOptions, ClassNameProps, DataAttribut
color?: TextColor;
/** Delay in milliseconds before the link appears after entering the viewport. */
delay?: number;
/** Whether to obfuscate the email/link from static HTML scrapers. */
isObfuscated?: boolean;
}
/**
@@ -36,7 +38,40 @@ export interface LinkProps extends AriaLinkOptions, ClassNameProps, DataAttribut
* It uses standard design system colors (--rb-ring) and provides responsive hover/focus states.
*/
export const Link = memo(function Link(props: LinkProps) {
const { children, className, href, isExternal, type, size, color, delay, ...otherProps } = props;
const {
children,
className,
href,
isExternal,
type,
size,
color,
delay,
isObfuscated = false,
...otherProps
} = props;
const [isActivated, setIsActivated] = useState(false);
const isMailto = Boolean(isObfuscated && href?.startsWith('mailto:'));
const rawEmail = isMailto && href ? href.slice(7) : '';
const atIndex = rawEmail.indexOf('@');
const user = atIndex !== -1 ? rawEmail.slice(0, atIndex) : '';
const domain = atIndex !== -1 ? rawEmail.slice(atIndex + 1) : '';
const activate = useCallback(() => {
setIsActivated(true);
}, []);
const handleClick = useCallback(
(e: React.MouseEvent<HTMLElement>) => {
activate();
if (!isActivated && href) {
window.location.href = href;
e.preventDefault();
}
},
[activate, href, isActivated]
);
// We use a specific ref type that useLink expects.
// Since Link can be an 'a' or a 'span', we use HTMLElement as common denominator.
@@ -53,15 +88,35 @@ export const Link = memo(function Link(props: LinkProps) {
const dataAttributes = filterDataAttributes(props);
if (href) {
const resolvedHref = localePath(href);
const isAutoExternal = isExternal ?? href.startsWith('http');
const externalProps = isAutoExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {};
const resolvedHref = isMailto ? (isActivated ? href : '#') : localePath(href);
const isChildrenEmail = typeof children === 'string' && children === rawEmail;
const displayedContent =
isMailto && !isActivated && isChildrenEmail ? (
<span style={{ unicodeBidi: 'bidi-override', direction: 'rtl' }}>
{domain.split('').reverse().join('')}@{user.split('').reverse().join('')}
</span>
) : (
children
);
const obfuscationAttrs = isMailto
? {
'data-user': user,
'data-domain': domain,
'aria-label': rawEmail,
}
: {};
return (
<Text
{...linkProps}
{...externalProps}
{...dataAttributes}
{...obfuscationAttrs}
ref={ref}
as="a"
className={sharedClasses}
@@ -70,8 +125,12 @@ export const Link = memo(function Link(props: LinkProps) {
href={resolvedHref}
size={size}
type={type}
onClick={isMailto ? handleClick : undefined}
onFocus={isMailto ? activate : undefined}
onPointerDown={isMailto ? activate : undefined}
onPointerEnter={isMailto ? activate : undefined}
>
{children}
{displayedContent}
</Text>
);
}
+8 -46
View File
@@ -2,19 +2,14 @@ import React from 'react';
import { vi } from 'vitest';
import { Text } from './Text';
import { render, screen, act } from '../../lib/testing';
import { render, screen } from '../../lib/testing';
describe('components/Text', () => {
let intersectionCallback: (entries: any[]) => void;
beforeEach(() => {
vi.useFakeTimers();
// Mock IntersectionObserver
class MockIntersectionObserver {
constructor(public callback: (entries: any[]) => void) {
intersectionCallback = callback;
}
observe = vi.fn();
disconnect = vi.fn();
}
@@ -31,58 +26,25 @@ describe('components/Text', () => {
expect(screen.getByText('Test content')).toBeInTheDocument();
});
it('is immediately visible when no delay is provided', () => {
it('is immediately visible without animation when no delay is provided', () => {
render(<Text>No delay</Text>);
const el = screen.getByText('No delay');
expect(el).not.toHaveClass('opacity-0');
expect(el).not.toHaveClass('animate-in');
expect(el.style.animationDelay).toBe('');
});
it('is initially hidden when delay is provided', () => {
it('applies CSS animation classes and delay style when delay is provided', () => {
render(<Text delay={500}>With delay</Text>);
const el = screen.getByText('With delay');
expect(el).toHaveClass('opacity-0');
expect(el).toHaveClass('animate-in', 'fade-in', 'slide-in-from-bottom-2');
expect(el).toHaveStyle({ animationDelay: '500ms', animationFillMode: 'both' });
});
it('becomes visible after intersection and specified delay', () => {
render(<Text delay={500}>Delayed visible</Text>);
const el = screen.getByText('Delayed visible');
expect(el).toHaveClass('opacity-0');
// Simulate intersection
act(() => {
intersectionCallback([{ isIntersecting: true }]);
});
// Should still be hidden before delay
act(() => {
vi.advanceTimersByTime(250);
});
expect(el).toHaveClass('opacity-0');
// Should be visible after delay
act(() => {
vi.advanceTimersByTime(250);
});
expect(el).not.toHaveClass('opacity-0');
expect(el).toHaveClass('animate-in');
expect(el).toHaveClass('fade-in');
});
it('becomes visible immediately after intersection if delay is 0', () => {
it('applies CSS animation classes and zero delay when delay is 0', () => {
render(<Text delay={0}>Zero delay</Text>);
const el = screen.getByText('Zero delay');
expect(el).toHaveClass('opacity-0');
// Simulate intersection
act(() => {
intersectionCallback([{ isIntersecting: true }]);
});
expect(el).not.toHaveClass('opacity-0');
expect(el).toHaveClass('animate-in');
expect(el).toHaveStyle({ animationDelay: '0ms', animationFillMode: 'both' });
});
it('applies alignment classes', () => {
+14 -23
View File
@@ -1,5 +1,5 @@
import { cn, genericMemo, useIntersectionObserver, castMutableRef } from '@ermnvldmr/stl';
import React, { forwardRef, useState, useEffect, useRef } from 'react';
import { cn, genericMemo, castMutableRef } from '@ermnvldmr/stl';
import React, { forwardRef, useEffect, useRef } from 'react';
import {
getTypographyClassNames,
@@ -85,7 +85,6 @@ const TextComponent = forwardRef<HTMLElement, TextProps>(function Text(
},
ref
) {
const [isVisible, setIsVisible] = useState(delay === undefined);
const internalRef = useRef<HTMLElement>(null);
useEffect(() => {
@@ -97,22 +96,13 @@ const TextComponent = forwardRef<HTMLElement, TextProps>(function Text(
}
}, [ref]);
const isIntersecting = useIntersectionObserver(internalRef, {
threshold: 0.1,
once: true,
});
useEffect(() => {
if (delay !== undefined && isIntersecting) {
if (delay > 0) {
const timer = setTimeout(() => setIsVisible(true), delay);
return () => clearTimeout(timer);
} else {
setIsVisible(true);
}
}
return undefined;
}, [delay, isIntersecting]);
const delayStyle: React.CSSProperties | undefined =
delay !== undefined
? {
animationDelay: `${delay}ms`,
animationFillMode: 'both',
}
: undefined;
return (
<Component
@@ -132,13 +122,14 @@ const TextComponent = forwardRef<HTMLElement, TextProps>(function Text(
overflow,
maxLines,
}),
delay !== undefined &&
(isVisible
? 'animate-in fade-in slide-in-from-bottom-2 duration-1000 ease-out'
: 'opacity-0'),
delay !== undefined && 'animate-in fade-in slide-in-from-bottom-2 duration-1000 ease-out',
className
)}
data-testid={testId}
style={{
...delayStyle,
...props.style,
}}
type={htmlType ?? (Component === 'button' ? 'button' : undefined)}
>
{children}