Skip to content

Commit efb89f7

Browse files
committed
chore(docs): metadata structure fixes
Signed-off-by: Cory Rylan <crylan@nvidia.com>
1 parent 5eb909d commit efb89f7

6 files changed

Lines changed: 708 additions & 35 deletions

File tree

projects/site/package.json

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"command": "rm -rf dist .11ty-vite && pnpm exec eleventy",
5050
"files": [
5151
"src",
52+
"!src/**/*.test.js",
5253
"!src/**/*.test.lighthouse.ts",
5354
"public",
5455
"!public/llms.txt",
@@ -196,16 +197,13 @@
196197
]
197198
},
198199
"test": {
199-
"command": "vitest run --config=vitest.config.ts",
200+
"command": "vitest run src/_11ty/layouts/metadata.test.ts",
200201
"files": [
201-
"src/**/*.test.ts",
202202
"src/_11ty/layouts/metadata.js",
203+
"src/_11ty/layouts/metadata.test.ts",
203204
"vitest.config.ts"
204205
],
205-
"output": [],
206-
"dependencies": [
207-
"../internals/vite:ci"
208-
]
206+
"output": []
209207
},
210208
"test:lighthouse": {
211209
"command": "playwright-lock 'vitest run --config=vitest.lighthouse.ts'",

projects/site/src/_11ty/layouts/common.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const renderBaseHead = data => {
2828
<meta property="og:type" content="${ogType}">
2929
<link rel="icon" href="/favicon.svg">
3030
<meta name="google-site-verification" content="pqQ1zOnKkqdZ2Lm0H8qIQx3q1x6Q7ghbumSrwzF_KSY" />
31+
<meta name="msvalidate.01" content="590EBF38676B077801C72048F4D8B5CB" />
3132
${renderJsonLd(data, meta)}
3233
${renderGlobalsScript(data)}
3334
<style>
@@ -103,6 +104,13 @@ export const renderBaseHead = data => {
103104
}
104105
globalThis.document.documentElement.removeAttribute('no-js');
105106
</script>
107+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-K5TKPS8VHN"></script>
108+
<script>
109+
window.dataLayer = window.dataLayer || [];
110+
function gtag(){dataLayer.push(arguments);}
111+
gtag('js', new Date());
112+
gtag('config', 'G-K5TKPS8VHN');
113+
</script>
106114
`;
107115
};
108116

@@ -344,6 +352,7 @@ export const renderDocsNav = data => /* html */ `
344352
<a href="docs/internal/guidelines/agent-harness/">Internal Guidelines</a>
345353
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/agent-harness/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/agent-harness/">Agent Harness</a></nve-tree-node>
346354
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/agent-tooling/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/agent-tooling/">Agent Tooling</a></nve-tree-node>
355+
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/agent-ownership/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/agent-ownership/">Agent Ownership</a></nve-tree-node>
347356
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/documentation/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/documentation/">Documentation</a></nve-tree-node>
348357
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/examples/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/examples/">Examples</a></nve-tree-node>
349358
<nve-tree-node ${data.page.url.includes('/docs/internal/guidelines/typescript/') ? 'highlighted selected' : ''}><a href="docs/internal/guidelines/typescript/">TypeScript</a></nve-tree-node>

projects/site/src/_11ty/layouts/metadata.js

Lines changed: 205 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,35 @@
11
import { join } from 'node:path';
2-
import { ELEMENTS_SITE_URL } from '../utils/env.js';
32
import { siteData } from '../../index.11tydata.js';
43

54
export const BASE_URL = join('/', process.env.PAGES_BASE_URL ?? '', '/');
65

7-
const SITE_ORIGIN = ELEMENTS_SITE_URL.replace(/\/$/, '');
8-
const PATH_PREFIX = BASE_URL.replace(/\/$/, '');
6+
const SITE_URL = 'https://nvidia.github.io/elements';
7+
export const SOFTWARE_ID = `${SITE_URL}/#software`;
8+
const SOFTWARE_URL = `${SITE_URL}/`;
9+
const CODE_SAMPLE_ROUTES = ['/docs/cli/', '/docs/code/', '/docs/integrations/', '/docs/lint/', '/docs/mcp/'];
10+
11+
const LANGUAGE_NAMES = {
12+
bash: 'Shell',
13+
css: 'CSS',
14+
go: 'Go',
15+
html: 'HTML',
16+
javascript: 'JavaScript',
17+
js: 'JavaScript',
18+
json: 'JSON',
19+
markdown: 'Markdown',
20+
md: 'Markdown',
21+
python: 'Python',
22+
shell: 'Shell',
23+
sh: 'Shell',
24+
toml: 'TOML',
25+
ts: 'TypeScript',
26+
tsx: 'TypeScript',
27+
typescript: 'TypeScript',
28+
xml: 'XML',
29+
yaml: 'YAML',
30+
yml: 'YAML',
31+
zsh: 'Shell'
32+
};
933

1034
const BREADCRUMB_TERMS = {
1135
api: 'API',
@@ -108,43 +132,87 @@ export function resolvePageMeta(data) {
108132
description = `Documentation for ${rawTitle} in NVIDIA Elements, the framework-agnostic design system for AI/ML factories.`;
109133
}
110134

111-
const canonicalUrl = `${SITE_ORIGIN}${PATH_PREFIX}${url}`;
112-
const ogImage = `${SITE_ORIGIN}${PATH_PREFIX}/favicon.svg`;
135+
const canonicalUrl = `${SITE_URL}${url}`;
136+
const ogImage = `${SITE_URL}/favicon.svg`;
113137
return { title, description, canonicalUrl, ogImage, url };
114138
}
115139

116140
function jsonLdEncode(value) {
117141
return JSON.stringify(value).replace(/<\//g, '<\\/');
118142
}
119143

120-
export function renderJsonLd(data, meta) {
121-
const isDocs = meta.url.startsWith('/docs/');
122-
const articleType = isDocs ? 'TechArticle' : 'WebPage';
123-
const date = data.page?.date instanceof Date ? data.page.date.toISOString() : new Date().toISOString();
124-
const generatedUrls = new Set(data.collections?.all?.map(entry => entry.url).filter(Boolean));
144+
function isApiReferencePage(data, meta) {
145+
return Boolean(
146+
data.tag ||
147+
data.isApiTab ||
148+
meta.url.startsWith('/docs/api-design/') ||
149+
meta.url.startsWith('/docs/cli/') ||
150+
meta.url.startsWith('/docs/integrations/') ||
151+
meta.url.startsWith('/docs/lint/') ||
152+
meta.url.startsWith('/docs/mcp/')
153+
);
154+
}
155+
156+
function normalizeDate(value) {
157+
if (value instanceof Date) return value.toISOString();
158+
if (typeof value !== 'string') return null;
159+
160+
const date = new Date(value);
161+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
162+
}
163+
164+
function getContentDates(data) {
165+
return {
166+
datePublished: normalizeDate(data.datePublished ?? data.published ?? data.git?.created ?? data.git?.createdTime),
167+
dateModified: normalizeDate(data.dateModified ?? data.modified ?? data.git?.modified ?? data.git?.modifiedTime)
168+
};
169+
}
125170

171+
function getArticle(data, meta) {
172+
const isDocs = meta.url.startsWith('/docs/');
173+
const isApiReference = isApiReferencePage(data, meta);
174+
const element = findElementByTag(data.tag ?? data.component?.data?.tag);
175+
const dates = getContentDates(data);
126176
const article = {
127-
'@context': 'https://schema.org',
128-
'@type': articleType,
177+
'@id': meta.canonicalUrl,
178+
'@type': isApiReference ? 'APIReference' : isDocs ? 'TechArticle' : 'WebPage',
129179
headline: meta.title,
130180
description: meta.description,
131181
url: meta.canonicalUrl,
132182
mainEntityOfPage: meta.canonicalUrl,
133183
inLanguage: 'en',
134184
image: meta.ogImage,
135-
datePublished: date,
136-
dateModified: date,
137-
publisher: { '@type': 'Organization', name: 'NVIDIA', url: SITE_ORIGIN },
185+
publisher: { '@type': 'Organization', name: 'NVIDIA', url: SITE_URL },
138186
author: { '@type': 'Organization', name: 'NVIDIA' }
139187
};
140188

189+
if (isDocs || meta.url === '/') {
190+
article.about = { '@id': SOFTWARE_ID };
191+
}
192+
193+
if (isApiReference && element?.manifest?.tagName) {
194+
article.programmingModel = 'Web Components';
195+
article.targetPlatform = 'Web';
196+
197+
if (element?.version) {
198+
article.assemblyVersion = element.version;
199+
}
200+
}
201+
202+
if (dates.datePublished) article.datePublished = dates.datePublished;
203+
if (dates.dateModified) article.dateModified = dates.dateModified;
204+
205+
return article;
206+
}
207+
208+
function getBreadcrumb(data, meta) {
209+
const generatedUrls = new Set(data.collections?.all?.map(entry => entry.url).filter(Boolean));
141210
const segments = meta.url.split('/').filter(Boolean);
142-
const articleScript = `<script type="application/ld+json">${jsonLdEncode(article)}</script>`;
143211
if (segments.length === 0) {
144-
return articleScript;
212+
return null;
145213
}
146214

147-
const itemListElement = [{ '@type': 'ListItem', position: 1, name: 'Home', item: `${SITE_ORIGIN}${PATH_PREFIX}/` }];
215+
const itemListElement = [{ '@type': 'ListItem', position: 1, name: 'Home', item: `${SITE_URL}/` }];
148216
let cumulative = '';
149217
segments.forEach((seg, i) => {
150218
cumulative += `/${seg}`;
@@ -155,14 +223,129 @@ export function renderJsonLd(data, meta) {
155223
'@type': 'ListItem',
156224
position: itemListElement.length + 1,
157225
name: titleCaseSegment(seg),
158-
item: `${SITE_ORIGIN}${PATH_PREFIX}${item}`
226+
item: `${SITE_URL}${item}`
159227
});
160228
});
161229

162-
const breadcrumb = {
163-
'@context': 'https://schema.org',
230+
return {
231+
'@id': `${meta.canonicalUrl}#breadcrumb`,
164232
'@type': 'BreadcrumbList',
165233
itemListElement
166234
};
167-
return `${articleScript}\n <script type="application/ld+json">${jsonLdEncode(breadcrumb)}</script>`;
235+
}
236+
237+
function getSoftwareApplication(description) {
238+
return {
239+
'@id': SOFTWARE_ID,
240+
'@type': 'SoftwareApplication',
241+
name: 'NVIDIA Elements',
242+
description,
243+
url: SOFTWARE_URL,
244+
applicationCategory: 'DeveloperApplication',
245+
operatingSystem: 'Any',
246+
runtimePlatform: 'Web',
247+
softwareHelp: SOFTWARE_URL
248+
};
249+
}
250+
251+
function getPageText(data) {
252+
return [data.content, data.rawInput, data.templateContent].filter(value => typeof value === 'string').join('\n');
253+
}
254+
255+
function normalizeLanguage(language) {
256+
if (typeof language !== 'string') return null;
257+
return LANGUAGE_NAMES[language.toLowerCase()] ?? null;
258+
}
259+
260+
function getProgrammingLanguages(data) {
261+
const languages = new Set();
262+
const declared = data.programmingLanguage ?? data.programmingLanguages;
263+
const declaredLanguages = Array.isArray(declared) ? declared : [declared].filter(Boolean);
264+
declaredLanguages
265+
.map(String)
266+
.map(normalizeLanguage)
267+
.filter(Boolean)
268+
.forEach(language => languages.add(language));
269+
270+
if (data.isExamplesTab) {
271+
languages.add('HTML');
272+
}
273+
274+
const pageText = getPageText(data);
275+
[...pageText.matchAll(/(?:language|lang)=["']([^"']+)["']/g)].forEach(match => {
276+
const language = normalizeLanguage(match[1]);
277+
if (language) languages.add(language);
278+
});
279+
280+
[...pageText.matchAll(/class=["'][^"']*language-([a-z0-9+-]+)[^"']*["']/gi)].forEach(match => {
281+
const language = normalizeLanguage(match[1]);
282+
if (language) languages.add(language);
283+
});
284+
285+
[...pageText.matchAll(/```([a-z0-9+-]+)/gi)].forEach(match => {
286+
const language = normalizeLanguage(match[1]);
287+
if (language) languages.add(language);
288+
});
289+
290+
return [...languages];
291+
}
292+
293+
function hasVisibleCode(data) {
294+
return getProgrammingLanguages(data).length > 0;
295+
}
296+
297+
function shouldEmitSoftwareSourceCode(data, meta) {
298+
if (data.structuredData?.sourceCode === false) return false;
299+
if (data.structuredData?.sourceCode === true) return true;
300+
if (data.isExamplesTab) return true;
301+
if (!hasVisibleCode(data)) return false;
302+
return CODE_SAMPLE_ROUTES.some(route => meta.url.startsWith(route));
303+
}
304+
305+
function getCodeSampleType(data, meta) {
306+
if (data.structuredData?.codeSampleType) return data.structuredData.codeSampleType;
307+
if (meta.url.startsWith('/starters/')) return 'template';
308+
if (data.isExamplesTab || meta.url.includes('/examples/')) return 'full solution';
309+
return 'code snippet';
310+
}
311+
312+
function getRuntimePlatform(meta) {
313+
return meta.url.startsWith('/docs/cli/') || meta.url.startsWith('/docs/mcp/') ? 'Native binary' : 'Web';
314+
}
315+
316+
function getSoftwareSourceCode(data, meta) {
317+
if (!shouldEmitSoftwareSourceCode(data, meta)) return null;
318+
319+
const programmingLanguage = getProgrammingLanguages(data);
320+
if (!programmingLanguage.length) return null;
321+
322+
return {
323+
'@id': `${meta.canonicalUrl}#source-code`,
324+
'@type': 'SoftwareSourceCode',
325+
name: meta.title,
326+
description: meta.description,
327+
url: meta.canonicalUrl,
328+
codeSampleType: getCodeSampleType(data, meta),
329+
programmingLanguage: programmingLanguage.length === 1 ? programmingLanguage[0] : programmingLanguage,
330+
runtimePlatform: getRuntimePlatform(meta),
331+
targetProduct: { '@id': SOFTWARE_ID, '@type': 'SoftwareApplication' }
332+
};
333+
}
334+
335+
export function renderJsonLd(data, meta) {
336+
const article = getArticle(data, meta);
337+
const breadcrumb = getBreadcrumb(data, meta);
338+
const sourceCode = getSoftwareSourceCode(data, meta);
339+
const graph = [
340+
article,
341+
...(breadcrumb ? [breadcrumb] : []),
342+
...(meta.url === '/' ? [getSoftwareApplication(meta.description)] : []),
343+
...(sourceCode ? [sourceCode] : [])
344+
];
345+
346+
if (sourceCode) {
347+
article.hasPart = { '@id': sourceCode['@id'] };
348+
}
349+
350+
return `<script type="application/ld+json">${jsonLdEncode({ '@context': 'https://schema.org', '@graph': graph })}</script>`;
168351
}

0 commit comments

Comments
 (0)