Skip to content

Commit 5451f15

Browse files
committed
feat: implement createLink utility for consistent URL handling across components
1 parent 4d701ff commit 5451f15

12 files changed

Lines changed: 83 additions & 21 deletions

File tree

src/template/src/components/Breadcrumbs.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import { getConfigFile } from '../utils/config';
3+
import { createLink } from '../utils/url';
34
45
const { pathname } = Astro.url;
56
const config = await getConfigFile();
@@ -63,7 +64,7 @@ if (!found && currentSlug) {
6364
{item.label}
6465
</span>
6566
) : item.href ? (
66-
<a href={item.href} class="hover:text-foreground transition-colors duration-200">
67+
<a href={createLink(item.href)} class="hover:text-foreground transition-colors duration-200">
6768
{item.label}
6869
</a>
6970
) : (

src/template/src/components/Footer.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import { getConfigFile } from '../utils/config';
3+
import { createLink } from '../utils/url';
34
45
const config = await getConfigFile();
56
const { footer } = config;
@@ -16,7 +17,7 @@ const { footer } = config;
1617
{section.items.map((item) => (
1718
<li>
1819
<a
19-
href={item.href}
20+
href={createLink(item.href)}
2021
class="text-sm text-muted-foreground hover:text-foreground transition-colors"
2122
>
2223
{item.label}

src/template/src/components/Header.astro

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import { getConfigFile } from '../utils/config';
3+
import { createLink } from '../utils/url';
34
import ThemeToggle from './ThemeToggle.astro';
45
56
const config = await getConfigFile();
@@ -23,7 +24,7 @@ const { navbar } = config.navigation;
2324

2425
<!-- Logo -->
2526
<div class="flex items-center gap-4">
26-
<a href={config.branding.logo.href} class="flex items-center gap-3 hover:opacity-80 transition-opacity">
27+
<a href={createLink(config.branding.logo.href)} class="flex items-center gap-3 hover:opacity-80 transition-opacity">
2728
<span class="text-2xl">📚</span>
2829
<h1 class="text-lg font-bold text-foreground m-0 tracking-tight hidden sm:block">
2930
{config.metadata.name}
@@ -35,7 +36,7 @@ const { navbar } = config.navigation;
3536
<nav class="hidden md:flex items-center gap-6">
3637
{navbar.links?.map((link) => (
3738
<a
38-
href={link.href}
39+
href={createLink(link.href)}
3940
class="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
4041
target={link.href.startsWith('http') ? '_blank' : undefined}
4142
rel={link.href.startsWith('http') ? 'noopener noreferrer' : undefined}
@@ -64,7 +65,7 @@ const { navbar } = config.navigation;
6465
<ThemeToggle />
6566

6667
{navbar.cta && (
67-
<a href={navbar.cta.href} class="cta-button hidden sm:inline-flex">
68+
<a href={createLink(navbar.cta.href)} class="cta-button hidden sm:inline-flex">
6869
{navbar.cta.label}
6970
</a>
7071
)}

src/template/src/components/MobileNav.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* Triggered by hamburger menu in Header
55
*/
66
import { getConfigFile } from '../utils/config';
7+
import { createLink } from '../utils/url';
78
89
const config = await getConfigFile();
910
const { sidebar } = config.navigation;
@@ -29,7 +30,7 @@ const { sidebar } = config.navigation;
2930
<ul class="mobile-nav-list">
3031
{group.items?.map((item) => (
3132
<li>
32-
<a href={`/${item.slug}`} class="mobile-nav-link">
33+
<a href={createLink(item.href || `/${item.slug}`)} class="mobile-nav-link">
3334
{item.label}
3435
</a>
3536
</li>

src/template/src/components/PrevNextNav.astro

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import { getConfigFile } from '../utils/config';
3+
import { createLink } from '../utils/url';
34
45
const { pathname } = Astro.url;
56
const config = await getConfigFile();
@@ -37,7 +38,7 @@ const nextPage = currentIndex < allPages.length - 1 ? allPages[currentIndex + 1]
3738
<nav class="prev-next-nav" aria-label="Page navigation">
3839
<div class="prev-next-container">
3940
{prevPage ? (
40-
<a href={prevPage.href} class="prev-next-link prev">
41+
<a href={createLink(prevPage.href)} class="prev-next-link prev">
4142
<span class="prev-next-direction">
4243
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
4344
<path d="m15 18-6-6 6-6"/>
@@ -51,7 +52,7 @@ const nextPage = currentIndex < allPages.length - 1 ? allPages[currentIndex + 1]
5152
)}
5253

5354
{nextPage ? (
54-
<a href={nextPage.href} class="prev-next-link next">
55+
<a href={createLink(nextPage.href)} class="prev-next-link next">
5556
<span class="prev-next-direction">
5657
Next
5758
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">

src/template/src/components/SearchModal.astro

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* SearchModal - ⌘K / Ctrl+K Search Modal
44
* Uses Pagefind for static search (must build first to index content)
55
*/
6+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
67
---
78

89
<div id="search-modal" class="search-modal" role="dialog" aria-modal="true" aria-labelledby="search-title">
@@ -119,7 +120,7 @@
119120
}
120121
</style>
121122

122-
<script>
123+
<script define:vars={{ base }}>
123124
let pagefind: any = null;
124125
let selectedIndex = 0;
125126
let results: any[] = [];
@@ -164,12 +165,17 @@
164165
return;
165166
}
166167

167-
resultsContainer.innerHTML = searchResults.map((result, i) => `
168-
<a href="${result.url}" class="search-result-item ${i === selectedIndex ? 'selected' : ''}" data-index="${i}">
168+
resultsContainer.innerHTML = searchResults.map((result, i) => {
169+
let url = result.url;
170+
if (base && !url.startsWith(base) && !url.startsWith('http')) {
171+
url = base + url;
172+
}
173+
return `
174+
<a href="${url}" class="search-result-item ${i === selectedIndex ? 'selected' : ''}" data-index="${i}">
169175
<div class="search-result-title">${result.meta?.title || result.url}</div>
170176
<div class="search-result-excerpt">${result.excerpt || ''}</div>
171177
</a>
172-
`).join('');
178+
`}).join('');
173179

174180
// Add click handlers
175181
resultsContainer.querySelectorAll('.search-result-item').forEach((item) => {

src/template/src/components/SidebarGroup.astro

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import type { SidebarGroup } from '../types/config';
3+
import { createLink, isLinkActive } from '../utils/url';
34
45
interface Props {
56
group: SidebarGroup;
@@ -13,9 +14,8 @@ const groupId = group.label.toLowerCase().replace(/\s+/g, '-');
1314
1415
const isActive = (item: any) => {
1516
const itemPath = item.href || `/${item.slug}`;
16-
const normalizedPath = currentPath.replace(/\/$/, '');
17-
const normalizedItemPath = itemPath.replace(/\/$/, '');
18-
return normalizedPath === normalizedItemPath || normalizedPath === `${normalizedItemPath}.html`;
17+
const fullItemPath = createLink(itemPath);
18+
return isLinkActive(fullItemPath, currentPath);
1919
};
2020
2121
// Check if any item in this group is active (for auto-expand)
@@ -33,11 +33,12 @@ const hasActiveItem = group.items?.some((item) => isActive(item)) ?? false;
3333
<ul class="sidebar-group-items list-none m-0 p-0 space-y-1 overflow-hidden transition-all duration-200">
3434
{group.items?.map((item) => {
3535
const itemHref = item.href || `/${item.slug}`;
36+
const fullHref = createLink(itemHref);
3637
const active = isActive(item);
3738
return (
3839
<li>
3940
<a
40-
href={itemHref}
41+
href={fullHref}
4142
class={active ? 'sidebar-link sidebar-link-active' : 'sidebar-link'}
4243
aria-current={active ? 'page' : undefined}
4344
>

src/template/src/components/landing/DocsHero.astro

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
---
2+
import { createLink } from '../../utils/url';
3+
24
interface Props {
35
title: string;
46
subtitle?: string;
@@ -33,13 +35,13 @@ const { title, subtitle, image, primaryCTA, secondaryCTA, version } = Astro.prop
3335

3436
<div class="flex flex-col sm:flex-row gap-6 justify-center items-center w-full sm:w-auto">
3537
{primaryCTA && (
36-
<a href={primaryCTA.link} class="group relative inline-flex items-center justify-center px-8 py-4 text-lg font-medium text-white transition-all duration-200 bg-primary rounded-full hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary shadow-lg shadow-primary/25 hover:shadow-xl hover:shadow-primary/30 hover:-translate-y-0.5">
38+
<a href={createLink(primaryCTA.link)} class="group relative inline-flex items-center justify-center px-8 py-4 text-lg font-medium text-white transition-all duration-200 bg-primary rounded-full hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary shadow-lg shadow-primary/25 hover:shadow-xl hover:shadow-primary/30 hover:-translate-y-0.5">
3739
{primaryCTA.text}
3840
<svg class="w-5 h-5 ml-2 -mr-1 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"></path></svg>
3941
</a>
4042
)}
4143
{secondaryCTA && (
42-
<a href={secondaryCTA.link} class="inline-flex items-center justify-center px-8 py-4 text-lg font-medium text-foreground transition-all duration-200 bg-card border border-border rounded-full hover:bg-muted hover:text-foreground focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-border shadow-sm hover:shadow-md">
44+
<a href={createLink(secondaryCTA.link)} class="inline-flex items-center justify-center px-8 py-4 text-lg font-medium text-foreground transition-all duration-200 bg-card border border-border rounded-full hover:bg-muted hover:text-foreground focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-border shadow-sm hover:shadow-md">
4345
{secondaryCTA.text}
4446
</a>
4547
)}

src/template/src/components/mdx/Card.astro

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
---
2+
import { createLink } from '../../utils/url';
3+
24
interface Props {
35
title?: string;
46
href?: string;
@@ -8,10 +10,11 @@ interface Props {
810
const { title, href, icon } = Astro.props;
911
1012
const Tag = href ? 'a' : 'div';
13+
const link = href ? createLink(href) : undefined;
1114
---
1215

1316
<Tag
14-
href={href}
17+
href={link}
1518
class={`
1619
block not-prose p-6 rounded border border-border bg-card
1720
transition-all duration-300 hover:border-ring hover:shadow-lg

src/template/src/layouts/Layout.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Sidebar from '../components/Sidebar.astro';
44
import MobileNav from '../components/MobileNav.astro';
55
import SearchModal from '../components/SearchModal.astro';
66
import { getConfigFile } from '../utils/config';
7+
import { createLink } from '../utils/url';
78
import '../styles/global.css';
89
910
interface Props {
@@ -28,7 +29,7 @@ const config = await getConfigFile();
2829
<meta property="og:description" content={description || config.metadata.description} />
2930
<meta name="twitter:card" content="summary_large_image" />
3031
<meta name="twitter:creator" content={config.seo.twitterHandle} />
31-
<link rel="icon" href={config.branding.favicon} />
32+
<link rel="icon" href={createLink(config.branding.favicon)} />
3233

3334
<link rel="preconnect" href="https://fonts.googleapis.com" />
3435
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

0 commit comments

Comments
 (0)