diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc579f00f..46d0cc257 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -400,7 +400,7 @@ importers: version: link:../styles '@vitest/browser': specifier: 'catalog:' - version: 4.1.7(vite@8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1)(yaml@2.8.3))(vitest@4.1.7) + version: 4.1.7(vite@8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.15))(terser@5.47.1)(yaml@2.8.3))(vitest@4.1.7) '@vitest/coverage-istanbul': specifier: 'catalog:' version: 4.1.7(vitest@4.1.7) @@ -424,10 +424,10 @@ importers: version: 6.0.3 vite: specifier: 'catalog:' - version: 8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1)(yaml@2.8.3) + version: 8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.15))(terser@5.47.1)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-istanbul@4.1.7)(jsdom@27.1.0)(vite@8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1)(yaml@2.8.3)) + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-istanbul@4.1.7)(jsdom@27.1.0)(vite@8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.15))(terser@5.47.1)(yaml@2.8.3)) projects/core: dependencies: @@ -1208,7 +1208,7 @@ importers: version: 3.1.5 '@11ty/eleventy-plugin-vite': specifier: 'catalog:' - version: 8.0.0(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.15))(terser@5.47.1)(yaml@2.8.3) + version: 8.0.0(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1)(yaml@2.8.3) '@eslint/js': specifier: 'catalog:' version: 10.0.1(eslint@10.4.0(jiti@2.6.1)) @@ -1287,6 +1287,9 @@ importers: pagefind: specifier: 1.3.0 version: 1.3.0 + parse5: + specifier: 7.1.2 + version: 7.1.2 playwright: specifier: 'catalog:' version: 1.59.1 @@ -1301,10 +1304,10 @@ importers: version: 6.0.3 vite: specifier: 'catalog:' - version: 8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.15))(terser@5.47.1)(yaml@2.8.3) + version: 8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-istanbul@4.1.7)(jsdom@27.1.0)(vite@8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.15))(terser@5.47.1)(yaml@2.8.3)) + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-istanbul@4.1.7)(jsdom@27.1.0)(vite@8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1)(yaml@2.8.3)) projects/starters: dependencies: @@ -14151,6 +14154,24 @@ snapshots: dependencies: prismjs: 1.30.0 + '@11ty/eleventy-plugin-vite@8.0.0(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1)(yaml@2.8.3)': + dependencies: + '@11ty/eleventy-utils': 2.0.7 + vite: 8.0.13(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1)(yaml@2.8.3) + transitivePeerDependencies: + - '@types/node' + - '@vitejs/devtools' + - esbuild + - jiti + - less + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + '@11ty/eleventy-plugin-vite@8.0.0(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.15))(terser@5.47.1)(yaml@2.8.3)': dependencies: '@11ty/eleventy-utils': 2.0.7 @@ -18688,6 +18709,7 @@ snapshots: - msw - utf-8-validate - vite + optional: true '@vitest/browser@4.1.7(vite@8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.15))(terser@5.47.1)(yaml@2.8.3))(vitest@4.1.7)': dependencies: @@ -18718,7 +18740,7 @@ snapshots: magicast: 0.5.3 obug: 2.1.1 tinyrainbow: 3.1.0 - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.6.2)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-istanbul@4.1.7)(jsdom@27.1.0)(vite@8.0.14(@types/node@25.6.2)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.15))(terser@5.47.1)(yaml@2.8.3)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@25.7.0)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-istanbul@4.1.7)(jsdom@27.1.0)(vite@8.0.14(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1)(yaml@2.8.3)) transitivePeerDependencies: - supports-color @@ -26869,6 +26891,23 @@ snapshots: terser: 5.47.1 yaml: 2.8.3 + vite@8.0.13(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.14))(terser@5.47.1)(yaml@2.8.3): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.14 + rolldown: 1.0.1 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 25.7.0 + esbuild: 0.28.0 + fsevents: 2.3.3 + jiti: 2.6.1 + sass: 1.97.3 + sugarss: 5.0.1(postcss@8.5.14) + terser: 5.47.1 + yaml: 2.8.3 + vite@8.0.13(@types/node@25.7.0)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.97.3)(sugarss@5.0.1(postcss@8.5.15))(terser@5.47.1)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 diff --git a/projects/core/src/tabs/tabs.examples.ts b/projects/core/src/tabs/tabs.examples.ts index e513bd90f..6c4217898 100644 --- a/projects/core/src/tabs/tabs.examples.ts +++ b/projects/core/src/tabs/tabs.examples.ts @@ -145,13 +145,13 @@ export const Links = { render: () => html` - Tab 1 + Tab 1 - Tab 2 + Tab 2 - Tab 3 + Tab 3 ` }; diff --git a/projects/site/eleventy.config.js b/projects/site/eleventy.config.js index ad65bb706..a69bd8219 100644 --- a/projects/site/eleventy.config.js +++ b/projects/site/eleventy.config.js @@ -1,6 +1,6 @@ // @ts-check -import { EleventyRenderPlugin, IdAttributePlugin } from '@11ty/eleventy'; +import { EleventyRenderPlugin, HtmlBasePlugin, IdAttributePlugin } from '@11ty/eleventy'; import EleventyPluginVite from '@11ty/eleventy-plugin-vite'; import litPlugin from '@lit-labs/eleventy-plugin-lit'; @@ -19,6 +19,7 @@ import { llmsTxtPlugin } from './src/_11ty/plugins/llms-txt.js'; import { sitemapPlugin } from './src/_11ty/plugins/sitemap-xml.js'; import { elementLoaderTransform } from './src/_11ty/transforms/element-loader.js'; import { anchorGeneratorTransform } from './src/_11ty/transforms/anchor-generator.js'; +import { siteUrlsTransform } from './src/_11ty/transforms/site-urls.js'; import { htmlMinifyTransform } from './src/_11ty/transforms/html-minify.js'; import { envReplaceTransform } from './src/_11ty/transforms/env-replace.js'; import { @@ -42,6 +43,7 @@ import { svgLogoShortcode, svgLogosShortcode } from './src/_11ty/shortcodes/svg- import { tokensShortcode } from './src/_11ty/shortcodes/tokens.js'; import markdown from './src/_11ty/libraries/markdown.js'; import { ApiService } from '@internals/metadata'; +import { ELEMENTS_SITE_ORIGIN } from './src/_11ty/utils/site-url.js'; const apis = await ApiService.getData(); @@ -88,6 +90,8 @@ const entrypoints = [ * Sets up plugins, transforms, collections, and build options */ export default function (eleventyConfig) { + eleventyConfig.pathPrefix = BASE_URL; + // Add core 11ty plugins eleventyConfig.addPlugin(EleventyRenderPlugin); eleventyConfig.addPlugin(IdAttributePlugin, { checkDuplicates: false }); @@ -154,18 +158,6 @@ export default function (eleventyConfig) { } }); - // Configure server options for development - eleventyConfig.setServerOptions({ - onRequest: { - '/': () => ({ - status: 307, - headers: { - Location: BASE_URL - } - }) - } - }); - // Add search plugin for documentation search functionality if (process.env.ELEVENTY_RUN_MODE === 'build') { eleventyConfig.addPlugin(searchPlugin, { @@ -209,6 +201,10 @@ export default function (eleventyConfig) { eleventyConfig.addTransform('env-replace', envReplaceTransform); eleventyConfig.addTransform('element-loader', elementLoaderTransform); eleventyConfig.addTransform('anchor-generator', anchorGeneratorTransform); + eleventyConfig.addPlugin(HtmlBasePlugin, { + baseHref: process.env.ELEVENTY_RUN_MODE === 'build' ? ELEMENTS_SITE_ORIGIN : '/' + }); + eleventyConfig.addTransform('site-urls', siteUrlsTransform); if (process.env.ELEVENTY_RUN_MODE === 'build') { eleventyConfig.addTransform('html-minify', htmlMinifyTransform); @@ -263,6 +259,7 @@ export default function (eleventyConfig) { input: 'src', output: 'dist', layouts: '_11ty/layouts' - } + }, + pathPrefix: BASE_URL }; } diff --git a/projects/site/package.json b/projects/site/package.json index 6d5a7bd4d..72dc24446 100644 --- a/projects/site/package.json +++ b/projects/site/package.json @@ -70,6 +70,10 @@ "ELEMENTS_SITE_URL": { "external": true, "default": "https://nvidia.github.io" + }, + "LOCAL_PREVIEW": { + "external": true, + "default": "false" } } }, @@ -197,14 +201,11 @@ ] }, "test": { - "command": "vitest run src/_11ty/layouts/metadata.test.ts src/_11ty/shortcodes/api.test.ts", + "command": "vitest run src/_11ty/layouts/metadata.test.ts src/_11ty/layouts/links.test.ts src/_11ty/transforms/site-urls.test.ts src/_11ty/shortcodes/api.test.ts", "files": [ - "src/docs/**/*.md", - "src/_11ty/layouts/common.js", - "src/_11ty/layouts/metadata.js", - "src/_11ty/layouts/metadata.test.ts", - "src/_11ty/shortcodes/api.js", - "src/_11ty/shortcodes/api.test.ts", + "src/**/*.js", + "src/**/*.md", + "src/**/*.ts", "vitest.config.ts" ], "output": [] @@ -233,42 +234,43 @@ } }, "dependencies": { + "3d-force-graph": "1.79.0", "chart.js": "4.5.1", - "lit": "catalog:", - "3d-force-graph": "1.79.0" + "lit": "catalog:" }, "devDependencies": { "@11ty/eleventy": "catalog:", "@11ty/eleventy-plugin-vite": "catalog:", "@eslint/js": "catalog:", - "@lit-labs/eleventy-plugin-lit": "catalog:", - "@lit-labs/ssr-client": "catalog:", "@internals/eslint": "workspace:*", "@internals/metadata": "workspace:*", - "@internals/tools": "workspace:*", "@internals/patterns": "workspace:*", + "@internals/tools": "workspace:*", "@internals/vite": "workspace:*", + "@lit-labs/eleventy-plugin-lit": "catalog:", + "@lit-labs/ssr-client": "catalog:", "@nvidia-elements/code": "workspace:*", + "@nvidia-elements/core": "workspace:*", "@nvidia-elements/forms": "workspace:*", "@nvidia-elements/lint": "workspace:*", "@nvidia-elements/markdown": "workspace:*", "@nvidia-elements/media": "workspace:*", - "@nvidia-elements/core": "workspace:*", "@nvidia-elements/monaco": "workspace:*", "@nvidia-elements/styles": "workspace:*", "@nvidia-elements/themes": "workspace:*", "@vitest/coverage-istanbul": "catalog:", "compare-versions": "6.1.1", "eslint": "catalog:", - "stylelint": "catalog:", - "stylelint-config-standard": "catalog:", "html-minifier-next": "5.2.2", "lighthouse": "catalog:", "lightningcss": "1.30.2", "markdown-it": "catalog:", "markdown-it-link-attributes": "catalog:", "pagefind": "1.3.0", + "parse5": "7.1.2", "playwright": "catalog:", + "stylelint": "catalog:", + "stylelint-config-standard": "catalog:", "typescript": "catalog:", "vite": "catalog:", "vitest": "catalog:" diff --git a/projects/site/src/_11ty/layouts/common.js b/projects/site/src/_11ty/layouts/common.js index 4f6475ef0..ea1c749ee 100644 --- a/projects/site/src/_11ty/layouts/common.js +++ b/projects/site/src/_11ty/layouts/common.js @@ -132,267 +132,267 @@ export const renderBaseHead = data => { export const renderDocsNav = data => /* html */ ` - Getting Started - Getting Started - Installation - CLI - MCP - Skills - Lint + Getting Started + Getting Started + Installation + CLI + MCP + Skills + Lint - Integrations - Getting Started - Angular - Bundles - Custom Elements - Golang - Hugo - Import Maps - Lit - Lit Library - MCP Apps - NextJS - Nuxt - Preact - React - SolidJS - Svelte - TypeScript - Vue + Integrations + Getting Started + Angular + Bundles + Custom Elements + Golang + Hugo + Import Maps + Lit + Lit Library + MCP Apps + NextJS + Nuxt + Preact + React + SolidJS + Svelte + TypeScript + Vue - About - Changelog - Metrics - Support - Accessibility - Contributions - Requests - Migration + About + Changelog + Metrics + Support + Accessibility + Contributions + Requests + Migration - Foundations - Overview - Typography - Iconography + Foundations + Overview + Typography + Iconography - Themes - Design Tokens - Size & Space - Objects - Interactions - Support - Status - Color - Animation - Fonts - Layers - Custom + Themes + Design Tokens + Size & Space + Objects + Interactions + Support + Status + Color + Animation + Fonts + Layers + Custom - Layout - Horizontal - Vertical - Grid + Layout + Horizontal + Vertical + Grid - Popovers - i18n - Visualization - View Transitions + Popovers + i18n + Visualization + View Transitions - Elements - Accordion - Alert - Avatar - Badge - Breadcrumb - Button - Button Group - Card - Chat Message - Checkbox - Color - Combobox - Copy Button + Elements + Accordion + Alert + Avatar + Badge + Breadcrumb + Button + Button Group + Card + Chat Message + Checkbox + Color + Combobox + Copy Button - Datagrid - Integrations - Column Action - Column Alignment - Column Fixed - Column width - Container - Card - Display Settings - Footer - Heatmap - Keynav - Multi Select - Pagination - Panel Detail - Panel Grid - Performance - Placeholder - Row Action - Row Groups - Row Sort - Scroll Height - Single Select - Stripe + Datagrid + Integrations + Column Action + Column Alignment + Column Fixed + Column width + Container + Card + Display Settings + Footer + Heatmap + Keynav + Multi Select + Pagination + Panel Detail + Panel Grid + Performance + Placeholder + Row Action + Row Groups + Row Sort + Scroll Height + Single Select + Stripe - Date - Datetime - Dialog - Divider - Dot - Drawer - Dropdown - Dropdown Group - Dropzone - File - Format Datetime - Format Number - Format Relative Time + Date + Datetime + Dialog + Divider + Dot + Drawer + Dropdown + Dropdown Group + Dropzone + File + Format Datetime + Format Number + Format Relative Time - Forms - Validation - Actions - Control + Forms + Validation + Actions + Control - Icon - Icon Button - Input - Input Group - Logo - Menu - Month - Notification - Page - Page Header - Page Loader - Pagination - Progressive Filter Chip - Progress Bar - Progress Ring - Password - Preferences Input - Pulse - Radio - Range - Resize Handle - Search - Select - Skeleton - Sort Button - Sparkline - Star Rating - Steps - Switch - Tabs - Tag - Textarea - Time - Toast - Toggletip - Toolbar - Tooltip - Tree - Week + Icon + Icon Button + Input + Input Group + Logo + Menu + Month + Notification + Page + Page Header + Page Loader + Pagination + Progressive Filter Chip + Progress Bar + Progress Ring + Password + Preferences Input + Pulse + Radio + Range + Resize Handle + Search + Select + Skeleton + Sort Button + Sparkline + Star Rating + Steps + Switch + Tabs + Tag + Textarea + Time + Toast + Toggletip + Toolbar + Tooltip + Tree + Week - Patterns - Authentication - Browse - Chat - Dashboard - Editor - Empty States - Heatmap - Keyboard Shortcut - Logging - Media - Navigation - Onboarding - Panel - Responsive - Search - Subheader - Trend + Patterns + Authentication + Browse + Chat + Dashboard + Editor + Empty States + Heatmap + Keyboard Shortcut + Logging + Media + Navigation + Onboarding + Panel + Responsive + Search + Subheader + Trend - Code - Codeblock + Code + Codeblock - Monaco - Input - Diff Input - Editor - Diff Editor - Problems + Monaco + Input + Diff Input + Editor + Diff Editor + Problems - Markdown - Markdown - CSS Utility + Markdown + Markdown + CSS Utility - Labs + Labs - Responsive Layout - Viewport - Container - Patterns + Responsive Layout + Viewport + Container + Patterns - Forms + Forms - API Design - Properties & Attributes - Slots - Registration - CustomEvents - Stateless - Composition - Styles - Packaging - Glossary - Logs + API Design + Properties & Attributes + Slots + Registration + CustomEvents + Stateless + Composition + Styles + Packaging + Glossary + Logs - Internal Guidelines - Agent Harness - Agent Tooling - Agent Ownership - Documentation - Examples - TypeScript - Testing - Unit Testing - Accessibility Testing - Lighthouse Testing - SSR Testing - Visual Testing - Troubleshooting - Component Creation + Internal Guidelines + Agent Harness + Agent Tooling + Agent Ownership + Documentation + Examples + TypeScript + Testing + Unit Testing + Accessibility Testing + Lighthouse Testing + SSR Testing + Visual Testing + Troubleshooting + Component Creation - Internal Examples - All Examples + Internal Examples + All Examples `; @@ -432,10 +432,10 @@ export function renderBasePageHeader(data) { return /* html */ ` NV - Elements - Catalog + Elements + Catalog ${ELEMENTS_PLAYGROUND_BASE_URL ? /* html */ `Playground` : ''} - Starters + Starters Repo System Themes diff --git a/projects/site/src/_11ty/layouts/docs.11ty.js b/projects/site/src/_11ty/layouts/docs.11ty.js index d1f4ffbab..cde9a4c31 100644 --- a/projects/site/src/_11ty/layouts/docs.11ty.js +++ b/projects/site/src/_11ty/layouts/docs.11ty.js @@ -88,15 +88,15 @@ export async function render(data) { - Overview + Overview - API + API ${ data.hideExamplesTab ? '' : ` - Examples + Examples ` } diff --git a/projects/site/src/_11ty/layouts/links.test.ts b/projects/site/src/_11ty/layouts/links.test.ts new file mode 100644 index 000000000..c8b789179 --- /dev/null +++ b/projects/site/src/_11ty/layouts/links.test.ts @@ -0,0 +1,46 @@ +import { readdir, readFile } from 'node:fs/promises'; +import { relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const RELATIVE_INTERNAL_LINK_PATTERN = /(?:href=["']|]\()(?:\.\/)?(?:docs|examples|starters)\//; +const BASE_PREFIXED_INTERNAL_LINK_PATTERN = /(?:href=["']|]\()\/elements\/(?:docs|examples|starters)\//; +const JS_RELATIVE_INTERNAL_LINK_PATTERN = /\bhref:\s*['"](?:\.\/)?(?:docs|examples|starters)\//; +const JS_BASE_PREFIXED_INTERNAL_LINK_PATTERN = /\bhref:\s*['"]\/elements\/(?:docs|examples|starters)\//; + +async function getAuthoredFiles(dir: URL): Promise { + const entries = await readdir(dir, { withFileTypes: true }); + const nestedFiles = await Promise.all( + entries.map(entry => { + const path = new URL(`${entry.name}${entry.isDirectory() ? '/' : ''}`, dir); + if (entry.isDirectory()) return getAuthoredFiles(path); + return /\.(?:11ty\.)?(?:js|md|ts)$/.test(entry.name) && !entry.name.includes('.test.') ? [path] : []; + }) + ); + + return nestedFiles.flat(); +} + +describe('docs links', () => { + it('should use root-relative paths for authored site links', async () => { + const files = await getAuthoredFiles(new URL('../../', import.meta.url)); + const relativeLinks = ( + await Promise.all( + files.map(async file => { + const content = await readFile(file, 'utf8'); + const hasInvalidLink = + RELATIVE_INTERNAL_LINK_PATTERN.test(content) || + BASE_PREFIXED_INTERNAL_LINK_PATTERN.test(content) || + JS_RELATIVE_INTERNAL_LINK_PATTERN.test(content) || + JS_BASE_PREFIXED_INTERNAL_LINK_PATTERN.test(content); + + if (!hasInvalidLink) return null; + + return relative(process.cwd(), fileURLToPath(file)); + }) + ) + ).filter(value => value !== null); + + expect(relativeLinks).toEqual([]); + }); +}); diff --git a/projects/site/src/_11ty/layouts/metadata.js b/projects/site/src/_11ty/layouts/metadata.js index 2b03932f1..78f080274 100644 --- a/projects/site/src/_11ty/layouts/metadata.js +++ b/projects/site/src/_11ty/layouts/metadata.js @@ -1,9 +1,9 @@ -import { join } from 'node:path'; import { siteData } from '../../index.11tydata.js'; +import { BASE_URL, DEPLOYED_SITE_URL } from '../utils/site-url.js'; -export const BASE_URL = join('/', process.env.PAGES_BASE_URL ?? '', '/'); +export { BASE_URL }; -const SITE_URL = 'https://nvidia.github.io/elements'; +const SITE_URL = DEPLOYED_SITE_URL; const ORGANIZATION_URL = 'https://www.nvidia.com/'; const REPOSITORY_URL = 'https://github.com/NVIDIA/elements'; const SOFTWARE_URL = `${SITE_URL}/`; diff --git a/projects/site/src/_11ty/layouts/page.11ty.js b/projects/site/src/_11ty/layouts/page.11ty.js index 62ddf091a..d4fe73b77 100644 --- a/projects/site/src/_11ty/layouts/page.11ty.js +++ b/projects/site/src/_11ty/layouts/page.11ty.js @@ -19,10 +19,10 @@ export function render(data) { NV - Elements - Catalog + Elements + Catalog ${ELEMENTS_PLAYGROUND_BASE_URL ? /* html */ `Playground` : ''} - Starters + Starters Repo System Themes diff --git a/projects/site/src/_11ty/plugins/llms-txt.js b/projects/site/src/_11ty/plugins/llms-txt.js index eed6c65c6..7f2044eb9 100644 --- a/projects/site/src/_11ty/plugins/llms-txt.js +++ b/projects/site/src/_11ty/plugins/llms-txt.js @@ -3,12 +3,10 @@ import markdownIt from 'markdown-it'; import { ApiService } from '@internals/tools/api'; import { ExamplesService } from '@internals/tools/examples'; import { skills } from '@internals/tools/skills'; -import { BASE_URL } from '../layouts/metadata.js'; -import { ELEMENTS_PAGES_BASE_URL, ELEMENTS_SITE_URL } from '../utils/env.js'; +import { DEPLOYED_SITE_URL } from '../utils/site-url.js'; +import { siteUrlsTransform } from '../transforms/site-urls.js'; -const SITE_ORIGIN = ELEMENTS_SITE_URL.replace(/\/$/, ''); -const PATH_PREFIX = BASE_URL.replace(/\/$/, ''); -const BASE = (ELEMENTS_PAGES_BASE_URL || `${SITE_ORIGIN}${PATH_PREFIX}`).replace(/\/$/, ''); +const BASE = DEPLOYED_SITE_URL; const md = markdownIt({ html: true, linkify: true, breaks: false }); const relativeMarkdownUrlPattern = /\b(href|src)="((?!(?:[a-z][a-z\d+.-]*:|\/\/))[^"]+?)\.md(?=")/gi; @@ -36,14 +34,16 @@ function getMarkdownMeta(markdown) { }; } -async function writeDoc(path, markdown) { +async function writeDoc(path, markdown, transformHtml = html => html) { const meta = getMarkdownMeta(markdown); const htmlUrl = `${path.replace('./.11ty-vite/public', BASE)}.html`; const nav = path.endsWith('context/index') ? '' : ``; const renderedMarkdown = md.render(markdown).replace(relativeMarkdownUrlPattern, '$1="$2.html'); - const html = `${escapeAttr(meta.title)} | NVIDIA Elements context${nav}
${renderedMarkdown}
`; + const html = await transformHtml( + `${escapeAttr(meta.title)} | NVIDIA Elements context${nav}
${renderedMarkdown}
` + ); await fsp.writeFile(`${path}.md`, markdown, 'utf-8'); await fsp.writeFile(`${path}.html`, html, 'utf-8'); } @@ -51,6 +51,9 @@ async function writeDoc(path, markdown) { // https://llmstxt.org export function llmsTxtPlugin(eleventyConfig) { eleventyConfig.on('eleventy.after', async () => { + const transformContextHtml = html => siteUrlsTransform.call({ page: { url: '/' } }, html, 'context/index.html'); + const writeContextDoc = (path, markdown) => writeDoc(path, markdown, transformContextHtml); + await fsp.mkdir('./.11ty-vite/public/context/skills/', { recursive: true }); await fsp.mkdir('./.11ty-vite/public/context/api/', { recursive: true }); await fsp.mkdir('./.11ty-vite/public/context/api/icons/', { recursive: true }); @@ -58,25 +61,25 @@ export function llmsTxtPlugin(eleventyConfig) { await fsp.mkdir('./.11ty-vite/public/context/examples/', { recursive: true }); const skillsContent = `# Skills\n\nList of all available skills and context fragments.\n\n${skills.map(s => `- [${s.name}](${BASE}/context/skills/${s.name}.md): ${s.description}`).join('\n')}`; - await writeDoc('./.11ty-vite/public/context/skills/index', skillsContent); + await writeContextDoc('./.11ty-vite/public/context/skills/index', skillsContent); const skillMarkdown = []; for (const { name, context } of skills) { skillMarkdown.push(context); - await writeDoc(`./.11ty-vite/public/context/skills/${name}`, context); + await writeContextDoc(`./.11ty-vite/public/context/skills/${name}`, context); } const cliReadme = await fsp.readFile('../cli/README.md', 'utf-8'); const lintReadme = await fsp.readFile('../lint/README.md', 'utf-8'); - await writeDoc('./.11ty-vite/public/context/cli', cliReadme); - await writeDoc('./.11ty-vite/public/context/lint', lintReadme); + await writeContextDoc('./.11ty-vite/public/context/cli', cliReadme); + await writeContextDoc('./.11ty-vite/public/context/lint', lintReadme); const { elements, attributes } = await ApiService.list({ format: 'json' }); const apis = [...elements, ...attributes]; const apiMarkdown = await Promise.all( apis.map(async e => { const api = String(await ApiService.get({ names: e.name, format: 'markdown' })); - await writeDoc(`./.11ty-vite/public/context/api/${e.name.replace(/^nve-/, '')}`, api); + await writeContextDoc(`./.11ty-vite/public/context/api/${e.name.replace(/^nve-/, '')}`, api); return api; }) ); @@ -85,27 +88,27 @@ export function llmsTxtPlugin(eleventyConfig) { ${elements.map(e => `- [${e.name.replace(/^nve-/, '')}](${BASE}/context/api/${e.name.replace(/^nve-/, '')}.md): ${e.description}`).join('\n')} ${attributes.map(e => `- [${e.name.replace(/^nve-/, '')} (attribute utility)](${BASE}/context/api/${e.name.replace(/^nve-/, '')}.md): ${e.description}`).join('\n')}`; - await writeDoc(`./.11ty-vite/public/context/api/index`, apiContent); + await writeContextDoc(`./.11ty-vite/public/context/api/index`, apiContent); const icons = await ApiService.iconsList({ format: 'markdown' }); const iconsContent = `## Icons\n\nList of all available icon names for nve-icon and nve-icon-button.\n\n${icons}`; - await writeDoc(`./.11ty-vite/public/context/api/icons/index`, iconsContent); + await writeContextDoc(`./.11ty-vite/public/context/api/icons/index`, iconsContent); const tokens = await ApiService.tokensList({ format: 'markdown' }); const tokensContent = `## Tokens\n\nList of all available semantic CSS custom properties / design tokens for theming.\n\n${tokens}`; - await writeDoc(`./.11ty-vite/public/context/api/tokens/index`, tokensContent); + await writeContextDoc(`./.11ty-vite/public/context/api/tokens/index`, tokensContent); const examples = await ExamplesService.list({ format: 'json' }); const exampleMarkdown = await Promise.all( examples.map(async ({ id }) => { const example = String(await ExamplesService.get({ id, format: 'markdown' })); - await writeDoc(`./.11ty-vite/public/context/examples/${id}`, example); + await writeContextDoc(`./.11ty-vite/public/context/examples/${id}`, example); return example; }) ); const examplesContent = `# Examples\n\nList of all available UI patterns and example templates.\n\n${examples.map(({ id, summary }) => `- [${id.replace('patterns-', '').replace('elements-', '')}](${BASE}/context/examples/${id}.md): ${summary}`).join('\n')}`; - await writeDoc(`./.11ty-vite/public/context/examples/index`, examplesContent); + await writeContextDoc(`./.11ty-vite/public/context/examples/index`, examplesContent); const content = `# Elements @@ -124,7 +127,7 @@ Elements ships Web Components/HTML custom elements (\`nve-*\`), design tokens, C For the complete documentation archive in a large single file, use [llms-full.txt](${BASE}/llms-full.txt). Intended for offline use or content vectorization. `; - await writeDoc('./.11ty-vite/public/context/index', content); + await writeContextDoc('./.11ty-vite/public/context/index', content); await fsp.writeFile('./.11ty-vite/public/llms.txt', content, 'utf-8'); const fullContent = [ diff --git a/projects/site/src/_11ty/plugins/sitemap-xml.js b/projects/site/src/_11ty/plugins/sitemap-xml.js index 152c6cdfb..63ed19919 100644 --- a/projects/site/src/_11ty/plugins/sitemap-xml.js +++ b/projects/site/src/_11ty/plugins/sitemap-xml.js @@ -1,9 +1,6 @@ import { promises as fsp } from 'node:fs'; -import { BASE_URL } from '../layouts/metadata.js'; -import { ELEMENTS_SITE_URL } from '../utils/env.js'; +import { getSiteUrl } from '../utils/site-url.js'; -const SITE_ORIGIN = ELEMENTS_SITE_URL.replace(/\/$/, ''); -const PATH_PREFIX = BASE_URL.replace(/\/$/, ''); const EXCLUDED_PREFIXES = ['/docs/changelog/', '/docs/metrics/', '/examples/', '/404']; const ROBOTS_NOINDEX = /]*name=["']robots["'][^>]*content=["'][^"']*\bnoindex\b/i; @@ -23,7 +20,7 @@ export function sitemapPlugin(eleventyConfig) { eleventyConfig.on('eleventy.after', async ({ results } = {}) => { const urls = [...new Set((results ?? []).filter(isPublishableResult).map(result => result.url))].sort(); const entries = urls.map(url => { - const loc = `${SITE_ORIGIN}${PATH_PREFIX}${url}`; + const loc = getSiteUrl(url); return ['', `${loc}`, ''].join('\n'); }); diff --git a/projects/site/src/_11ty/shortcodes/index.js b/projects/site/src/_11ty/shortcodes/index.js index 95d4c4c89..053e7434c 100644 --- a/projects/site/src/_11ty/shortcodes/index.js +++ b/projects/site/src/_11ty/shortcodes/index.js @@ -31,9 +31,9 @@ ${examples.find(s => s.name === 'Default' && s.element === tag)?.template} \`\`\`
- CLI + CLI / - MCP + MCP
diff --git a/projects/site/src/_11ty/templates/api.js b/projects/site/src/_11ty/templates/api.js index 13b95f93f..7a73c13e6 100644 --- a/projects/site/src/_11ty/templates/api.js +++ b/projects/site/src/_11ty/templates/api.js @@ -189,7 +189,7 @@ export function badgeStatus(status, container = '', content = '') { return /* html */ ` - ${status ? status : 'unknown'}:  ${content} + ${status ? status : 'unknown'}:  ${content} `; } @@ -224,7 +224,7 @@ export function badgeCoverage(value, container = '', content = '') { return /* html */ ` - ${content}${formattedValue} + ${content}${formattedValue} `; } @@ -250,7 +250,7 @@ export function badgeBundle(value, container = '', content = '') { return /* html */ ` - ${content}${value}${value ? 'kb' : ''} + ${content}${value}${value ? 'kb' : ''} `; } @@ -345,7 +345,7 @@ export function elementStatus(tag) {
Robust unit test coverages - Passed API Review + Passed API Review Passed Designer VQA Review Included in library package
diff --git a/projects/site/src/_11ty/transforms/anchor-generator.js b/projects/site/src/_11ty/transforms/anchor-generator.js index 7a42e7546..9f9fa8329 100644 --- a/projects/site/src/_11ty/transforms/anchor-generator.js +++ b/projects/site/src/_11ty/transforms/anchor-generator.js @@ -1,3 +1,5 @@ +import { getSiteHref } from '../utils/site-url.js'; + /** * Transform that generates a list of anchor links for all heading tags in the content. * Creates a side navigation menu from h2 and h3 tags that have IDs. @@ -25,15 +27,15 @@ export async function anchorGeneratorTransform(content) { !this.page.url.includes('integrations') && !this.page.url.includes('changelog') && headings.length > 2 ? /* html */ ` ` : ''; diff --git a/projects/site/src/_11ty/transforms/site-urls.js b/projects/site/src/_11ty/transforms/site-urls.js new file mode 100644 index 000000000..aec552d0e --- /dev/null +++ b/projects/site/src/_11ty/transforms/site-urls.js @@ -0,0 +1,135 @@ +/* eslint-env node */ +/* global process */ + +import { parse, parseFragment, serialize } from 'parse5'; + +import { + DEPLOYED_SITE_URL, + ELEMENTS_SITE_ORIGIN, + getSitePath as getBaseFreeSitePath, + getSiteHref, + getSiteUrl +} from '../utils/site-url.js'; + +const URL_ATTRIBUTES = new Set(['href', 'src']); +const SKIPPED_CHILD_TAGS = new Set(['script', 'style', 'template']); +const ROOT_RELATIVE_SEGMENT_PATTERN = /^\.?\/?(?:docs|examples|starters|static|_internal|local-bundles)(?:\/|$)/; +const ROOT_RELATIVE_FILE_PATTERN = /^\.?\/?(?:favicon\.svg|install\.(?:sh|cmd))(?:[?#]|$)/; +const SCHEME_PATTERN = /^[a-z][a-z\d+.-]*:/i; +const DEPLOYED_SITE_ORIGIN = new URL(DEPLOYED_SITE_URL).origin; +const IS_FULL_DOCUMENT_PATTERN = /]/i; + +function isHtmlOutput(outputPath, content) { + if (outputPath) return outputPath.endsWith('.html'); + + return IS_FULL_DOCUMENT_PATTERN.test(content); +} + +function isSameSiteUrl(url) { + return url.origin === ELEMENTS_SITE_ORIGIN || url.origin === DEPLOYED_SITE_ORIGIN; +} + +function getRootRelativeSitePath(value) { + if (value === '.' || value === './') return '/'; + + const path = value.replace(/^\.\//, ''); + + if (ROOT_RELATIVE_SEGMENT_PATTERN.test(path) || ROOT_RELATIVE_FILE_PATTERN.test(path)) return `/${path}`; + + return null; +} + +function getSitePathCandidate(value) { + const url = value.trim(); + + if (!url || url.startsWith('#') || url.startsWith('//')) return null; + + if (SCHEME_PATTERN.test(url)) { + if (!url.startsWith('http://') && !url.startsWith('https://')) return null; + if (!URL.canParse(url)) return null; + + const parsedUrl = new URL(url); + if (!isSameSiteUrl(parsedUrl)) return null; + + return `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`; + } + + if (url.startsWith('/')) return url; + + return getRootRelativeSitePath(url); +} + +function resolveSiteUrl(value) { + const sitePath = getSitePathCandidate(value); + + if (!sitePath) return value; + if (process.env.ELEVENTY_RUN_MODE === 'build') return getSiteHref(sitePath); + + return getBaseFreeSitePath(sitePath); +} + +function getAttribute(node, name) { + if (!Array.isArray(node.attrs)) return undefined; + + return node.attrs.find(attribute => attribute.name === name); +} + +function isModuleScript(node) { + return getAttribute(node, 'type')?.value.trim().toLowerCase() === 'module'; +} + +function resolveSourceModuleUrl(value) { + const sitePath = getSitePathCandidate(value); + + if (!sitePath) return value; + + return getBaseFreeSitePath(sitePath); +} + +function transformSourceModuleAttribute(node) { + if (node.nodeName !== 'script' || !isModuleScript(node)) return false; + + const src = getAttribute(node, 'src'); + if (!src) return false; + + const value = resolveSourceModuleUrl(src.value); + const changed = value !== src.value; + src.value = value; + + return changed; +} + +function transformAttributes(node) { + if (!Array.isArray(node.attrs)) return false; + + return node.attrs + .filter(attribute => URL_ATTRIBUTES.has(attribute.name)) + .map(attribute => { + const value = resolveSiteUrl(attribute.value); + const changed = value !== attribute.value; + attribute.value = value; + + return changed; + }) + .some(Boolean); +} + +function transformNode(node) { + if (node.nodeName === 'script') return transformSourceModuleAttribute(node); + + if (SKIPPED_CHILD_TAGS.has(node.nodeName)) return false; + + const changed = transformAttributes(node); + + return (node.childNodes ?? []).map(transformNode).some(Boolean) || changed; +} + +export async function siteUrlsTransform(content, outputPath) { + if (!isHtmlOutput(outputPath ?? this.page?.outputPath, content)) return content; + + const document = IS_FULL_DOCUMENT_PATTERN.test(content) ? parse(content) : parseFragment(content); + + if (!transformNode(document)) return content; + + return serialize(document); +} diff --git a/projects/site/src/_11ty/transforms/site-urls.test.ts b/projects/site/src/_11ty/transforms/site-urls.test.ts new file mode 100644 index 000000000..968219876 --- /dev/null +++ b/projects/site/src/_11ty/transforms/site-urls.test.ts @@ -0,0 +1,174 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +interface TransformContext { + page: { + url: string; + outputPath?: string; + }; +} + +interface ImportTransformOptions { + localPreview?: boolean; + pagesBaseUrl?: string; +} + +async function importTransform(runMode: 'build' | 'serve', options: ImportTransformOptions = {}) { + vi.resetModules(); + vi.stubEnv('ELEVENTY_RUN_MODE', runMode); + vi.stubEnv('LOCAL_PREVIEW', options.localPreview ? 'true' : 'false'); + vi.stubEnv('ELEMENTS_SITE_URL', 'https://nvidia.github.io'); + vi.stubEnv('PAGES_BASE_URL', options.pagesBaseUrl ?? '/elements/'); + + return import('./site-urls.js'); +} + +function createContext(url = '/docs/integrations/', outputPath = 'index.html'): TransformContext { + return { + page: { + url, + outputPath + } + }; +} + +afterEach(() => { + vi.unstubAllEnvs(); +}); + +describe('siteUrlsTransform', () => { + it('should fully qualify same-site urls in build output', async () => { + const { siteUrlsTransform } = await importTransform('build'); + const html = ` +CLI +MCP +Metrics +API Design +Home +Root +Angular logo +`; + + const result = await siteUrlsTransform.call(createContext(), html, 'index.html'); + + expect(result).toContain('href="https://nvidia.github.io/elements/docs/cli/"'); + expect(result).toContain('href="https://nvidia.github.io/elements/docs/mcp/#skills"'); + expect(result).toContain('href="https://nvidia.github.io/elements/docs/metrics/"'); + expect(result).toContain('href="https://nvidia.github.io/elements/docs/api-design/"'); + expect(result).toContain('href="https://nvidia.github.io/elements/"'); + expect(result).toContain('src="https://nvidia.github.io/elements/static/images/integrations/angular.svg"'); + }); + + it('should keep local same-site urls root-relative outside build output', async () => { + const { siteUrlsTransform } = await importTransform('serve'); + const html = ` +Metrics +CLI +Angular logo +`; + + const result = await siteUrlsTransform.call(createContext(), html, 'index.html'); + + expect(result).toContain('href="/docs/metrics/"'); + expect(result).toContain('href="/docs/cli/"'); + expect(result).toContain('src="/static/images/integrations/angular.svg"'); + }); + + it('should keep pages preview urls local when building for local preview', async () => { + const { siteUrlsTransform } = await importTransform('build', { + localPreview: true, + pagesBaseUrl: '/elements/preview/' + }); + const html = ` + +CLI +Angular logo +`; + + const result = await siteUrlsTransform.call(createContext(), html, 'index.html'); + + expect(result).toContain('href="/elements/preview/"'); + expect(result).toContain('href="/elements/preview/docs/cli/"'); + expect(result).toContain('src="/elements/preview/static/images/integrations/angular.svg"'); + }); + + it('should preserve source module urls when building production output', async () => { + const { siteUrlsTransform } = await importTransform('build'); + const html = ` + +Angular logo +`; + + const result = await siteUrlsTransform.call(createContext(), html, 'index.html'); + + expect(result).toContain('src="/404/index.ts"'); + expect(result).toContain('src="https://nvidia.github.io/elements/static/images/integrations/angular.svg"'); + }); + + it('should preserve source module urls when building for local preview', async () => { + const { siteUrlsTransform } = await importTransform('build', { + localPreview: true, + pagesBaseUrl: '/elements/preview/' + }); + const html = ` + +Angular logo +`; + + const result = await siteUrlsTransform.call(createContext(), html, 'index.html'); + + expect(result).toContain('src="/404/index.ts"'); + expect(result).toContain('src="/elements/preview/static/images/integrations/angular.svg"'); + }); + + it('should preserve external protocol and fragment urls', async () => { + const { siteUrlsTransform } = await importTransform('build'); + const html = ` +External +Protocol +Email +Phone +Data +Blob +JavaScript +Fragment + +`; + + const result = await siteUrlsTransform.call(createContext(), html, 'index.html'); + + expect(result).toContain('href="https://example.com/docs/"'); + expect(result).toContain('href="//example.com/docs/"'); + expect(result).toContain('href="mailto:docs@nvidia.com"'); + expect(result).toContain('href="tel:5555555555"'); + expect(result).toContain('href="data:text/plain,docs"'); + expect(result).toContain('href="blob:https://example.com/id"'); + expect(result).toContain('href="javascript:void(0)"'); + expect(result).toContain('href="#section"'); + expect(result).toContain('href="#icon"'); + }); + + it('should not rewrite urls inside template content', async () => { + const { siteUrlsTransform } = await importTransform('build'); + const html = ''; + + const result = await siteUrlsTransform.call(createContext(), html, 'index.html'); + + expect(result).toBe(html); + }); + + it('should return non-html output unchanged', async () => { + const { siteUrlsTransform } = await importTransform('build'); + const html = 'CLI'; + + await expect(siteUrlsTransform.call(createContext(), html, 'index.xml')).resolves.toBe(html); + }); + + it('should use the Eleventy page output path when the output path argument is omitted', async () => { + const { siteUrlsTransform } = await importTransform('serve'); + const html = 'CLI'; + + const result = await siteUrlsTransform.call(createContext('/docs/integrations/', 'dist/index.html'), html); + + expect(result).toContain('href="/docs/cli/"'); + }); +}); diff --git a/projects/site/src/_11ty/utils/site-url.js b/projects/site/src/_11ty/utils/site-url.js new file mode 100644 index 000000000..5e2594c56 --- /dev/null +++ b/projects/site/src/_11ty/utils/site-url.js @@ -0,0 +1,87 @@ +/* eslint-env node */ +/* global process */ + +import { ELEMENTS_PAGES_BASE_URL, ELEMENTS_SITE_URL } from './env.js'; + +export const ELEMENTS_SITE_ORIGIN = ELEMENTS_SITE_URL.replace(/\/+$/, ''); + +export const BASE_URL = normalizeBasePath(process.env.PAGES_BASE_URL); +export const DEPLOYED_SITE_URL = getDeployedSiteUrl(); +export const IS_LOCAL_PREVIEW = process.env.LOCAL_PREVIEW === 'true'; +const BASE_PATH = BASE_URL === '/' ? '' : BASE_URL.replace(/\/$/, ''); + +export function normalizeBasePath(value) { + const path = String(value ?? '') + .trim() + .replace(/^\/+|\/+$/g, ''); + + return path ? `/${path}/` : '/'; +} + +export function getDeployedSiteUrl() { + const pagesBaseUrl = ELEMENTS_PAGES_BASE_URL.trim().replace(/\/+$/, ''); + + if (pagesBaseUrl) return pagesBaseUrl; + if (BASE_URL === '/') return ELEMENTS_SITE_ORIGIN; + + return `${ELEMENTS_SITE_ORIGIN}${BASE_URL.replace(/\/$/, '')}`; +} + +function normalizeSitePath(path) { + const value = String(path ?? '/').trim(); + if (!value) return '/'; + return value.startsWith('/') ? value : `/${value}`; +} + +function removeDuplicateBasePath(pathname) { + if (!BASE_PATH) return pathname; + + const duplicateBasePath = `${BASE_PATH}${BASE_PATH}`; + + if (pathname === duplicateBasePath) return BASE_PATH; + if (pathname.startsWith(`${duplicateBasePath}/`)) return `${BASE_PATH}${pathname.slice(duplicateBasePath.length)}`; + + return pathname; +} + +function addBasePath(path) { + const url = new URL(normalizeSitePath(path), ELEMENTS_SITE_ORIGIN); + url.pathname = removeDuplicateBasePath(url.pathname); + + if (!BASE_PATH || url.pathname === BASE_PATH || url.pathname.startsWith(`${BASE_PATH}/`)) { + return `${url.pathname}${url.search}${url.hash}`; + } + + const pathname = url.pathname === '/' ? `${BASE_PATH}/` : `${BASE_PATH}${url.pathname}`; + return `${pathname}${url.search}${url.hash}`; +} + +function removeBasePath(path) { + const url = new URL(normalizeSitePath(path), ELEMENTS_SITE_ORIGIN); + url.pathname = removeDuplicateBasePath(url.pathname); + + if (BASE_PATH && url.pathname === BASE_PATH) { + return `/${url.search}${url.hash}`; + } + + if (BASE_PATH && url.pathname.startsWith(`${BASE_PATH}/`)) { + return `${url.pathname.slice(BASE_PATH.length)}${url.search}${url.hash}`; + } + + return `${url.pathname}${url.search}${url.hash}`; +} + +export function getSiteHref(path = '/') { + if (process.env.ELEVENTY_RUN_MODE === 'build' && !IS_LOCAL_PREVIEW) return getSiteUrl(path); + return addBasePath(path); +} + +export function getSitePath(path = '/') { + return removeBasePath(path); +} + +export function getSiteUrl(path = '/') { + const normalizedPath = removeBasePath(path); + + return `${DEPLOYED_SITE_URL}${normalizedPath}`; +} diff --git a/projects/site/src/_internal/metrics-carousel/metrics-carousel.ts b/projects/site/src/_internal/metrics-carousel/metrics-carousel.ts index fab17702d..df2249002 100644 --- a/projects/site/src/_internal/metrics-carousel/metrics-carousel.ts +++ b/projects/site/src/_internal/metrics-carousel/metrics-carousel.ts @@ -198,55 +198,55 @@ export class MetricsCarousel extends LitElement { return [ { - href: '/elements/docs/metrics/', + href: '/docs/metrics/', title: 'Available Components', label: 'Browse our component offerings', metricCount: totalElements }, { - href: '/elements/docs/metrics/', + href: '/docs/metrics/', title: 'Total Web Components', label: 'Browse our component offerings', metricCount: totalElements }, { - href: '/elements/docs/metrics/testing-and-performance/', + href: '/docs/metrics/testing-and-performance/', title: 'Total Automated Tests', label: 'View automated test results', metricCount: totalAutomatedTests }, { - href: '/elements/docs/metrics/testing-and-performance/', + href: '/docs/metrics/testing-and-performance/', title: 'Unit Tests', label: 'View our unit test suite', metricCount: totalUnitTests }, { - href: '/elements/docs/metrics/testing-and-performance/', + href: '/docs/metrics/testing-and-performance/', title: '% Test Coverage', label: 'View our test coverage', metricCount: `${Math.round(elementsTestCoverage)}%` }, { - href: '/elements/docs/metrics/testing-and-performance/', + href: '/docs/metrics/testing-and-performance/', title: 'Axe Tests', label: 'View Axe accessibility test results', metricCount: totalAxeTests }, { - href: '/elements/docs/metrics/testing-and-performance/', + href: '/docs/metrics/testing-and-performance/', title: 'Lighthouse Test Suites', label: 'View Lighthouse test results', metricCount: totalLighthouseTests }, { - href: '/elements/docs/metrics/testing-and-performance/', + href: '/docs/metrics/testing-and-performance/', title: 'Visual Regression Tests', label: 'View visual test results', metricCount: totalVisualTests }, { - href: '/elements/docs/metrics/testing-and-performance/', + href: '/docs/metrics/testing-and-performance/', title: 'SSR Tests', label: 'View SSR test results', metricCount: totalSsrTests diff --git a/projects/site/src/docs/about/contributions.md b/projects/site/src/docs/about/contributions.md index 2f6b5df82..92b6a0941 100644 --- a/projects/site/src/docs/about/contributions.md +++ b/projects/site/src/docs/about/contributions.md @@ -59,9 +59,9 @@ The main focus is to enable you to deliver value to your users. 2. **Design Review**: The initial MVP design passes review and is ready for implementation. -3. **Implementation**: Provide a API proposal spec on the component adhering to the [coding and API guidelines](./docs/api-design/) +3. **Implementation**: Provide a API proposal spec on the component adhering to the [coding and API guidelines](/docs/api-design/) -4. **Code and Design Quality**: New components should maintain a high standard of code quality and adhere to the [coding and API guidelines](./docs/api-design/) +4. **Code and Design Quality**: New components should maintain a high standard of code quality and adhere to the [coding and API guidelines](/docs/api-design/) 5. **Unit Testing**: As with other contributions, all new components should have appropriate unit tests to verify their functionality and performance. diff --git a/projects/site/src/docs/about/migration.md b/projects/site/src/docs/about/migration.md index 7ba9f53de..14727ae9f 100644 --- a/projects/site/src/docs/about/migration.md +++ b/projects/site/src/docs/about/migration.md @@ -89,7 +89,7 @@ Replace import paths throughout your source code: ## Deprecations & Removals -The following are the active deprecations. Each next major release removes the prior deprecations. Read more about the versioning and deprecation cycle policy. +The following are the active deprecations. Each next major release removes the prior deprecations. Read more about the versioning and deprecation cycle policy. ### Logo removed diff --git a/projects/site/src/docs/about/support.md b/projects/site/src/docs/about/support.md index 051618f80..1fcac383e 100644 --- a/projects/site/src/docs/about/support.md +++ b/projects/site/src/docs/about/support.md @@ -21,35 +21,35 @@ ## Frameworks -Elements [supports a wide variety](https://custom-elements-everywhere.com) of JavaScript frameworks and libraries as well as vanilla JS. Read more at the [installation](./docs/about/installation/) page. +Elements [supports a wide variety](https://custom-elements-everywhere.com) of JavaScript frameworks and libraries as well as vanilla JS. Read more at the [installation](/docs/about/installation/) page.
- {% svg-logo 'typescript' '18' %} TypeScript + {% svg-logo 'typescript' '18' %} TypeScript {% svg-logo 'lit' '20' %} Lit - {% svg-logo 'angular' '20' %} Angular + {% svg-logo 'angular' '20' %} Angular - {% svg-logo 'vue' '20' %} Vue + {% svg-logo 'vue' '20' %} Vue - {% svg-logo 'preact' '20' %} Preact + {% svg-logo 'preact' '20' %} Preact - {% svg-logo 'nextjs' '20' %} NextJS + {% svg-logo 'nextjs' '20' %} NextJS - {% svg-logo 'react' '20' %} React + {% svg-logo 'react' '20' %} React - {% svg-logo 'solidjs' '18' %} SolidJS + {% svg-logo 'solidjs' '18' %} SolidJS - {% svg-logo 'javascript' '18' %} JavaScript + {% svg-logo 'javascript' '18' %} JavaScript
@@ -71,7 +71,7 @@ import '@nvidia-elements/core/polyfills'; ## Versioning -The Elements package follows [semantic versioning](https://semver.org/) and is now in its stable 1.x release cycle. You can find changes in the [Changelog](./docs/changelog/). A debug utility is available +The Elements package follows [semantic versioning](https://semver.org/) and is now in its stable 1.x release cycle. You can find changes in the [Changelog](/docs/changelog/). A debug utility is available on the global window object to help identify the active versions in use at runtime. This log lists all the registered elements and active versions. - API Breaking changes are at most once per three months. diff --git a/projects/site/src/docs/api-design/logs.md b/projects/site/src/docs/api-design/logs.md index 4fe40f10b..e3465c6e9 100644 --- a/projects/site/src/docs/api-design/logs.md +++ b/projects/site/src/docs/api-design/logs.md @@ -14,7 +14,7 @@ This document describes the runtime logs and warnings from the Element libraries This warning appears when the application bundles or imports many versions of Elements or its dependencies within the same runtime. This can create unexpected compatibility issues and bug at runtime. To resolve, ensure dependencies are up to date and list their dependencies or peer dependencies if they internally depend on Elements. -Read more about [library packaging best practices](docs/api-design/packaging/). +Read more about [library packaging best practices](/docs/api-design/packaging/). ## Excessive Instance Limit @@ -24,8 +24,8 @@ To resolve this warning: - Consider reusing existing elements instead of creating new ones - Use virtualization or pagination of elements -- [Data Grid Performance Documentation](docs/elements/data-grid/performance/) -- [Dynamic Tree Documentation](docs/elements/tree/#dynamic-tree) +- [Data Grid Performance Documentation](/docs/elements/data-grid/performance/) +- [Dynamic Tree Documentation](/docs/elements/tree/#dynamic-tree) ## Invalid Parent diff --git a/projects/site/src/docs/api-design/packaging.md b/projects/site/src/docs/api-design/packaging.md index 6175e0369..41a679e36 100644 --- a/projects/site/src/docs/api-design/packaging.md +++ b/projects/site/src/docs/api-design/packaging.md @@ -95,7 +95,7 @@ The `package.json` should use a `sideEffects` array that lists registration and This enables tools like Webpack and Rollup to preserve explicit registration entrypoints while still tree-shaking side-effect-free component modules. -🎓 Learn about Lit Library integration +🎓 Learn about Lit Library integration 🎓 Learn: scoped element registry diff --git a/projects/site/src/docs/changelog/changelog.11ty.js b/projects/site/src/docs/changelog/changelog.11ty.js index 6546ee031..211a6636b 100644 --- a/projects/site/src/docs/changelog/changelog.11ty.js +++ b/projects/site/src/docs/changelog/changelog.11ty.js @@ -30,7 +30,7 @@ export function render(data) { # Changelog - ${data.changelog.title} - Changelogs + Changelogs ${data.changelog.title} diff --git a/projects/site/src/docs/changelog/index.11ty.js b/projects/site/src/docs/changelog/index.11ty.js index c88a7f0e2..8739809fe 100644 --- a/projects/site/src/docs/changelog/index.11ty.js +++ b/projects/site/src/docs/changelog/index.11ty.js @@ -23,7 +23,7 @@ export function render() { ${changelogs .map( changelog => /* html */ ` - +
diff --git a/projects/site/src/docs/elements/dialog.md b/projects/site/src/docs/elements/dialog.md index 72d4856fc..1e6497929 100644 --- a/projects/site/src/docs/elements/dialog.md +++ b/projects/site/src/docs/elements/dialog.md @@ -9,7 +9,7 @@ ## Installation -Learn more about native [Popover APIs](docs/foundations/popovers/). +Learn more about native [Popover APIs](/docs/foundations/popovers/). {% install 'nve-dialog' %} diff --git a/projects/site/src/docs/elements/drawer.md b/projects/site/src/docs/elements/drawer.md index 15af6821b..85fe1a7d6 100644 --- a/projects/site/src/docs/elements/drawer.md +++ b/projects/site/src/docs/elements/drawer.md @@ -9,7 +9,7 @@ ## Installation -Learn more about native [Popover APIs](docs/foundations/popovers/). +Learn more about native [Popover APIs](/docs/foundations/popovers/). {% install 'nve-drawer' %} diff --git a/projects/site/src/docs/elements/dropdown.md b/projects/site/src/docs/elements/dropdown.md index 570358385..a39fb275e 100644 --- a/projects/site/src/docs/elements/dropdown.md +++ b/projects/site/src/docs/elements/dropdown.md @@ -9,7 +9,7 @@ ## Installation -Learn more about native [Popover APIs](docs/foundations/popovers/). +Learn more about native [Popover APIs](/docs/foundations/popovers/). {% install 'nve-dropdown' %} diff --git a/projects/site/src/docs/elements/forms/index.md b/projects/site/src/docs/elements/forms/index.md index 699ca44ba..f02e919e9 100644 --- a/projects/site/src/docs/elements/forms/index.md +++ b/projects/site/src/docs/elements/forms/index.md @@ -36,11 +36,11 @@ See the links below for specific integration patterns for the following framewor
- {% svg-logo 'lit' '20' %} Lit Integration + {% svg-logo 'lit' '20' %} Lit Integration - {% svg-logo 'angular' '20' %} Angular Integration + {% svg-logo 'angular' '20' %} Angular Integration
diff --git a/projects/site/src/docs/elements/icon.md b/projects/site/src/docs/elements/icon.md index 674d33845..f8568f155 100644 --- a/projects/site/src/docs/elements/icon.md +++ b/projects/site/src/docs/elements/icon.md @@ -14,7 +14,7 @@ The Iconography system exposes an SVG based icon library to the `nve-icon` element. -See the searchable [Interactive Icon Catalog](./docs/foundations/iconography/) +See the searchable [Interactive Icon Catalog](/docs/foundations/iconography/) diff --git a/projects/site/src/docs/elements/notification.md b/projects/site/src/docs/elements/notification.md index 187cdbd88..611880fe7 100644 --- a/projects/site/src/docs/elements/notification.md +++ b/projects/site/src/docs/elements/notification.md @@ -9,7 +9,7 @@ ## Installation -Learn more about native [Popover APIs](docs/foundations/popovers/). +Learn more about native [Popover APIs](/docs/foundations/popovers/). {% install 'nve-notification' %} diff --git a/projects/site/src/docs/elements/page.md b/projects/site/src/docs/elements/page.md index 651eb5fa4..e768dd37e 100644 --- a/projects/site/src/docs/elements/page.md +++ b/projects/site/src/docs/elements/page.md @@ -9,7 +9,7 @@ ## Installation -To enable smooth transitions between page view, see the [View Transition API](./docs/foundations/view-transitions/) documentation. +To enable smooth transitions between page view, see the [View Transition API](/docs/foundations/view-transitions/) documentation. {% install 'nve-page' %} diff --git a/projects/site/src/docs/elements/toast.md b/projects/site/src/docs/elements/toast.md index 399277da4..f6a44f0e1 100644 --- a/projects/site/src/docs/elements/toast.md +++ b/projects/site/src/docs/elements/toast.md @@ -8,7 +8,7 @@ ## Installation -Learn more about native [Popover APIs](docs/foundations/popovers/). +Learn more about native [Popover APIs](/docs/foundations/popovers/). {% install 'nve-toast' %} diff --git a/projects/site/src/docs/elements/toggletip.md b/projects/site/src/docs/elements/toggletip.md index 8cac032a0..1cc65f02c 100644 --- a/projects/site/src/docs/elements/toggletip.md +++ b/projects/site/src/docs/elements/toggletip.md @@ -9,7 +9,7 @@ ## Installation -Learn more about native [Popover APIs](docs/foundations/popovers/). +Learn more about native [Popover APIs](/docs/foundations/popovers/). {% install 'nve-toggletip' %} diff --git a/projects/site/src/docs/elements/tooltip.md b/projects/site/src/docs/elements/tooltip.md index f34342d27..acff441fd 100644 --- a/projects/site/src/docs/elements/tooltip.md +++ b/projects/site/src/docs/elements/tooltip.md @@ -8,7 +8,7 @@ ## Installation -Learn more about native [Popover APIs](docs/foundations/popovers/). +Learn more about native [Popover APIs](/docs/foundations/popovers/). {% install 'nve-tooltip' %} diff --git a/projects/site/src/docs/foundations/iconography.md b/projects/site/src/docs/foundations/iconography.md index 3846e8359..e9476a1c5 100644 --- a/projects/site/src/docs/foundations/iconography.md +++ b/projects/site/src/docs/foundations/iconography.md @@ -10,7 +10,7 @@ The Iconography system builds on exposing an SVG based icon library to a the `` element. -[Icon Element Documentation](./docs/elements/icon/) +[Icon Element Documentation](/docs/elements/icon/) diff --git a/projects/site/src/docs/foundations/index.md b/projects/site/src/docs/foundations/index.md index 73b86ddcc..88c86cc01 100644 --- a/projects/site/src/docs/foundations/index.md +++ b/projects/site/src/docs/foundations/index.md @@ -21,7 +21,7 @@ Foundations are the shared visual system behind every NVIDIA Elements interface. Typography, iconography, color, themes, layout, motion, and internationalization combine into a single, consistent design language so teams and AI Agents across AI/ML infrastructure, robotics, and autonomous vehicles build tools efficiently without rebuilding the basics. Each foundation stays framework-agnostic and composes with the component library, CLI, and MCP tooling.