11import { join } from 'node:path' ;
2- import { ELEMENTS_SITE_URL } from '../utils/env.js' ;
32import { siteData } from '../../index.11tydata.js' ;
43
54export 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
1034const 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
116140function 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 ( / (?: l a n g u a g e | l a n g ) = [ " ' ] ( [ ^ " ' ] + ) [ " ' ] / g) ] . forEach ( match => {
276+ const language = normalizeLanguage ( match [ 1 ] ) ;
277+ if ( language ) languages . add ( language ) ;
278+ } ) ;
279+
280+ [ ...pageText . matchAll ( / c l a s s = [ " ' ] [ ^ " ' ] * l a n g u a g e - ( [ a - z 0 - 9 + - ] + ) [ ^ " ' ] * [ " ' ] / gi) ] . forEach ( match => {
281+ const language = normalizeLanguage ( match [ 1 ] ) ;
282+ if ( language ) languages . add ( language ) ;
283+ } ) ;
284+
285+ [ ...pageText . matchAll ( / ` ` ` ( [ a - z 0 - 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