fix(site): preserve casing for code identifiers in doc titles and OG images (#1347)

This commit is contained in:
Darius Cepulis
2026-04-15 11:56:23 -05:00
committed by GitHub
parent 961766d81d
commit 34c3907bcc
4 changed files with 56 additions and 9 deletions
@@ -15,7 +15,7 @@ import DocsLayout from '@/layouts/Docs.astro';
import type { SupportedFramework } from '@/types/docs';
import { SUPPORTED_FRAMEWORKS } from '@/types/docs';
import { filterSidebar, getAdjacentGuides, getAllGuideSlugs, getSectionsForGuide } from '@/utils/docs/sidebar';
import { getDocTitle } from '@/utils/docs/title';
import { getDocTitle, isCodeIdentifier } from '@/utils/docs/title';
import { createTechArticleSchema } from '@/utils/jsonLd/schemas';
export async function getStaticPaths() {
@@ -91,9 +91,6 @@ const jsonLdSchema = createTechArticleSchema({
readingTime: remarkPluginFrontmatter.readingTimeMinutes,
articleSection: sections.length > 0 ? sections.join(' > ') : undefined,
});
// Leaving this in in case we change our minds
// const isCamelOrPascalCase = (str: string) => /[a-z][A-Z]/.test(str);
---
<DocsLayout doc={doc} framework={framework} slug={slug}>
@@ -148,8 +145,7 @@ const jsonLdSchema = createTechArticleSchema({
as="h1"
class={clsx(
"mt-0 mb-4 text-h25 @lg:text-h2",
// leaving this in in case we change our minds
// isCamelOrPascalCase(getDocTitle(doc, framework)) && "normal-case",
isCodeIdentifier(getDocTitle(doc, framework)) && "normal-case",
)}
>
{getDocTitle(doc, framework)}
+38 -1
View File
@@ -1,6 +1,6 @@
import type { CollectionEntry } from 'astro:content';
import { describe, expect, it } from 'vitest';
import { getDocTitle } from '../title';
import { getDocTitle, isCodeIdentifier } from '../title';
describe('getDocTitle', () => {
// Mock fixtures
@@ -128,3 +128,40 @@ describe('getDocTitle', () => {
});
});
});
describe('isCodeIdentifier', () => {
it('should detect PascalCase', () => {
expect(isCodeIdentifier('PlaybackRateButton')).toBe(true);
expect(isCodeIdentifier('MuteButton')).toBe(true);
expect(isCodeIdentifier('TimeDisplay')).toBe(true);
});
it('should detect camelCase', () => {
expect(isCodeIdentifier('usePlayerContext')).toBe(true);
expect(isCodeIdentifier('createPlayer')).toBe(true);
expect(isCodeIdentifier('playbackRate')).toBe(true);
});
it('should detect kebab-case', () => {
expect(isCodeIdentifier('playback-rate-button')).toBe(true);
expect(isCodeIdentifier('mute-button')).toBe(true);
expect(isCodeIdentifier('video-player')).toBe(true);
});
it('should not match natural-language titles', () => {
expect(isCodeIdentifier('Getting Started')).toBe(false);
expect(isCodeIdentifier('Installation')).toBe(false);
expect(isCodeIdentifier('Basic Concepts')).toBe(false);
expect(isCodeIdentifier('How to Use')).toBe(false);
});
it('should not match single lowercase words', () => {
expect(isCodeIdentifier('player')).toBe(false);
expect(isCodeIdentifier('installation')).toBe(false);
});
it('should not match single PascalCase words', () => {
expect(isCodeIdentifier('Player')).toBe(false);
expect(isCodeIdentifier('Slider')).toBe(false);
});
});
+11
View File
@@ -12,3 +12,14 @@ import type { SupportedFramework } from '@/types/docs';
export function getDocTitle(doc: CollectionEntry<'docs'>, framework: SupportedFramework): string {
return doc.data.frameworkTitle?.[framework] ?? doc.data.title;
}
const CAMEL_OR_PASCAL_CASE = /[a-z][A-Z]/;
const KEBAB_CASE = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
/**
* Detect whether a title is a code identifier that should preserve its casing
* instead of being uppercased. Matches PascalCase, camelCase, and kebab-case.
*/
export function isCodeIdentifier(str: string): boolean {
return CAMEL_OR_PASCAL_CASE.test(str) || KEBAB_CASE.test(str);
}
+5 -2
View File
@@ -1,6 +1,8 @@
import { Resvg } from '@resvg/resvg-js';
import satori from 'satori';
import { isCodeIdentifier } from '@/utils/docs/title';
// ---------------------------------------------------------------------------
// Configuration — tune these values to adjust OG image appearance
// ---------------------------------------------------------------------------
@@ -121,8 +123,9 @@ export async function renderOgImage(options: { title?: string; size: OgSize }):
const { width, height, topMargin } = SIZES[size];
const fontData = await loadFont();
// Uppercase, then truncate if needed
let displayTitle = title?.toUpperCase();
// Uppercase (unless the title is a code identifier like PlaybackRateButton),
// then truncate if needed
let displayTitle = title && isCodeIdentifier(title) ? title : title?.toUpperCase();
if (displayTitle && displayTitle.length > MAX_CHAR_LIMIT) {
const truncated = displayTitle.slice(0, MAX_CHAR_LIMIT);
const lastSpace = truncated.lastIndexOf(' ');