Added homepage and moved old homepage to aboutme page

This commit is contained in:
becarta
2025-03-03 23:23:14 +01:00
parent 2e4284cba3
commit 9c61657071
16 changed files with 1769 additions and 533 deletions

View File

@@ -1,5 +1,6 @@
---
import { Icon } from 'astro-icon/components';
import { supportedLanguages } from '~/i18n/translations';
interface Props {
currentLang: string;
@@ -7,8 +8,6 @@ interface Props {
const { currentLang } = Astro.props;
import { supportedLanguages } from '~/i18n/translations';
type SupportedLanguage = typeof supportedLanguages[number];
const languages = [
@@ -121,13 +120,13 @@ const currentLanguage = languages.find(lang => lang.code === currentLang) || lan
}
</style>
<script>
<script define:vars={{ supportedLanguages }}>
function setupLanguageDropdown() {
const button = document.querySelector<HTMLButtonElement>('#menu-button');
const menu = document.querySelector<HTMLDivElement>('#language-menu');
const chevronIcon = document.querySelector<HTMLElement>('#chevron-icon');
const selectedLanguageText = document.querySelector<HTMLElement>('#selected-language');
const languageButtons = document.querySelectorAll<HTMLButtonElement>('[data-lang-code]');
const button = document.querySelector('#menu-button');
const menu = document.querySelector('#language-menu');
const chevronIcon = document.querySelector('#chevron-icon');
const selectedLanguageText = document.querySelector('#selected-language');
const languageButtons = document.querySelectorAll('[data-lang-code]');
if (!button || !menu || !chevronIcon || !selectedLanguageText) {
return;
@@ -184,7 +183,7 @@ const currentLanguage = languages.find(lang => lang.code === currentLang) || lan
closeMenu();
// Toggle menu
button.addEventListener('click', (e: MouseEvent) => {
button.addEventListener('click', (e) => {
e.stopPropagation();
if (isOpen) {
closeMenu();
@@ -193,7 +192,7 @@ const currentLanguage = languages.find(lang => lang.code === currentLang) || lan
// Focus the first menu item for better keyboard navigation
const firstMenuItem = menu.querySelector('button[role="menuitem"]');
if (firstMenuItem) {
(firstMenuItem as HTMLElement).focus();
firstMenuItem.focus();
}
}
});
@@ -205,7 +204,7 @@ const currentLanguage = languages.find(lang => lang.code === currentLang) || lan
if (!langCode) return;
// Update button text and icon
const langName = langButton.textContent?.trim();
const langName = langButton.textContent ? langButton.textContent.trim() : '';
const flagIcon = langButton.querySelector('svg');
if (langName && flagIcon) {
selectedLanguageText.textContent = langName;
@@ -218,27 +217,84 @@ const currentLanguage = languages.find(lang => lang.code === currentLang) || lan
// Close menu
closeMenu();
// Get current path and redirect
const currentPath = window.location.pathname.replace(/\/$/, '');
// Get current URL information
const currentUrl = new URL(window.location.href);
const currentPath = currentUrl.pathname.replace(/\/$/, '');
const currentHash = currentUrl.hash;
const pathSegments = currentPath.split('/').filter(Boolean);
const isBlogPost = pathSegments.length > 0 && !['en', 'nl', 'de'].includes(pathSegments[0]);
const pathWithoutLang = pathSegments.length > 1 ? `/${pathSegments.slice(1).join('/')}`.replace(/\/$/, '') : '';
// Redirect to new language path
window.location.href = isBlogPost ? `/${langCode}` : `/${langCode}${pathWithoutLang || ''}`;
// Check if we're on a language-specific path
const isLangPath = supportedLanguages.includes(pathSegments[0]);
// Get the previous language code
const previousLangCode = isLangPath ? pathSegments[0] : 'en';
// Extract the page path without language
let pagePath = '';
if (isLangPath && pathSegments.length > 1) {
// If we're on a language-specific path, get everything after the language code
pagePath = `/${pathSegments.slice(1).join('/')}`;
} else if (!isLangPath && pathSegments.length > 0) {
// If we're not on a language-specific path, use the current path
pagePath = `/${pathSegments.join('/')}`;
}
// Handle special case for root path
const isRootPath = pathSegments.length === 0 || (isLangPath && pathSegments.length === 1);
// Construct the new URL
let newUrl = isRootPath ? `/${langCode}` : `/${langCode}${pagePath}`;
// Clean up any potential double slashes
newUrl = newUrl.replace(/\/+/g, '/');
// Append hash fragment if it exists
if (currentHash) {
newUrl += currentHash;
}
// Store the language preference in localStorage and cookies
if (window.languageUtils) {
window.languageUtils.storeLanguagePreference(langCode);
} else {
// Fallback if languageUtils is not available
localStorage.setItem('preferredLanguage', langCode);
// Also set a cookie for server-side detection
const expirationDate = new Date();
expirationDate.setFullYear(expirationDate.getFullYear() + 1);
document.cookie = `preferredLanguage=${langCode}; expires=${expirationDate.toUTCString()}; path=/; SameSite=Lax`;
}
// Dispatch the language changed event
const reloadEvent = new CustomEvent('languageChanged', {
detail: {
langCode,
previousLangCode,
path: newUrl,
willReload: true
}
});
document.dispatchEvent(reloadEvent);
// Construct the full URL
const newFullUrl = `${window.location.origin}${newUrl}`;
// Reload the page to ensure all content is updated to the new language
window.location.href = newFullUrl;
});
});
// Close when clicking outside
document.addEventListener('click', (e: MouseEvent) => {
const target = e.target as HTMLElement;
document.addEventListener('click', (e) => {
const target = e.target;
if (isOpen && !menu.contains(target) && !button.contains(target)) {
closeMenu();
}
});
// Handle keyboard navigation
document.addEventListener('keydown', (e: KeyboardEvent) => {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && isOpen) {
closeMenu();
button.focus();
@@ -257,7 +313,7 @@ const currentLanguage = languages.find(lang => lang.code === currentLang) || lan
newIndex = currentIndex > 0 ? currentIndex - 1 : menuItems.length - 1;
}
(menuItems[newIndex] as HTMLElement).focus();
menuItems[newIndex].focus();
}
});
}
@@ -271,4 +327,10 @@ const currentLanguage = languages.find(lang => lang.code === currentLang) || lan
// Re-run setup when the page content is updated (e.g., after navigation)
document.addEventListener('astro:page-load', setupLanguageDropdown);
// Listen for popstate events (browser back/forward buttons)
window.addEventListener('popstate', (_event) => {
// No need to manually update anything here as the browser will
// automatically load the correct URL, and Astro will handle the rendering
});
</script>

View File

@@ -0,0 +1,112 @@
---
// This component handles the synchronization between localStorage and cookies
// for language persistence across page loads and navigation
// Extend Window interface to include languageUtils
declare global {
interface Window {
languageUtils?: {
getStoredLanguage: () => string | null;
storeLanguagePreference: (langCode: string) => void;
};
}
}
---
<script>
function setupLanguagePersistence() {
// Function to get language from localStorage
function getStoredLanguage() {
return localStorage.getItem('preferredLanguage');
}
// Function to set a cookie with the language preference
function setLanguageCookie(langCode) {
// Set cookie with a long expiration (1 year)
const expirationDate = new Date();
expirationDate.setFullYear(expirationDate.getFullYear() + 1);
document.cookie = `preferredLanguage=${langCode}; expires=${expirationDate.toUTCString()}; path=/; SameSite=Lax`;
}
// Function to get language from cookie
function getLanguageFromCookie() {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.startsWith('preferredLanguage=')) {
return cookie.substring('preferredLanguage='.length);
}
}
return null;
}
// Function to get language from URL
function getLanguageFromURL() {
const pathSegments = window.location.pathname.split('/').filter(Boolean);
const supportedLanguages = ['en', 'nl', 'de', 'fr'];
if (pathSegments.length > 0 && supportedLanguages.includes(pathSegments[0])) {
return pathSegments[0];
}
return null;
}
// On page load, sync language between URL, localStorage and cookie
const urlLanguage = getLanguageFromURL();
const storedLanguage = getStoredLanguage();
const cookieLanguage = getLanguageFromCookie();
// URL language takes precedence if it exists
if (urlLanguage) {
// If URL has a language, make sure localStorage and cookie match it
if (!storedLanguage || storedLanguage !== urlLanguage) {
localStorage.setItem('preferredLanguage', urlLanguage);
}
if (!cookieLanguage || cookieLanguage !== urlLanguage) {
setLanguageCookie(urlLanguage);
}
} else if (storedLanguage && !cookieLanguage) {
// If language is in localStorage but not in cookie, update cookie
setLanguageCookie(storedLanguage);
} else if (!storedLanguage && cookieLanguage) {
// If language is in cookie but not in localStorage, update localStorage
localStorage.setItem('preferredLanguage', cookieLanguage);
} else if (storedLanguage && cookieLanguage && storedLanguage !== cookieLanguage) {
// If both exist but are different, prefer localStorage
setLanguageCookie(storedLanguage);
}
// Listen for language changes and update the cookie
document.addEventListener('languageChanged', (event: CustomEvent) => {
if (event.detail && event.detail.langCode) {
setLanguageCookie(event.detail.langCode);
localStorage.setItem('preferredLanguage', event.detail.langCode);
}
});
// When localStorage changes (e.g., from another tab), update the cookie
window.addEventListener('storage', (event) => {
if (event.key === 'preferredLanguage' && event.newValue) {
setLanguageCookie(event.newValue);
}
});
// Make language utility functions available globally
window.languageUtils = {
getStoredLanguage: getStoredLanguage,
storeLanguagePreference: (langCode) => {
localStorage.setItem('preferredLanguage', langCode);
setLanguageCookie(langCode);
}
};
}
// Run setup when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupLanguagePersistence);
} else {
setupLanguagePersistence();
}
// Re-run setup when the page content is updated (e.g., after navigation)
document.addEventListener('astro:page-load', setupLanguagePersistence);
</script>

View File

@@ -159,6 +159,205 @@ import { UI } from 'astrowind:config';
onLoad();
onPageShow();
});
// Handle smooth scrolling for anchor links across all pages
function setupSmoothScrolling() {
// Handle links that start with # (pure anchor links)
document.querySelectorAll('a[href^="#"]:not([href="#"])').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
window.scrollTo({
top: targetElement.offsetTop - 50, // Offset for header
behavior: 'smooth'
});
}
});
});
// Handle links that contain # but don't start with it (page + anchor)
document.querySelectorAll('a[href*="#"]:not([href^="#"])').forEach(anchor => {
anchor.addEventListener('click', function (e) {
const href = this.getAttribute('href');
const isHashLink = this.getAttribute('data-hash-link') === 'true';
// Check if this is a link to the current page
// First, extract the path part (before the hash)
const hrefPath = href.split('#')[0];
const currentPath = window.location.pathname;
// Consider it's the current page if:
// 1. The path matches exactly
// 2. The href is just a hash (like '/#services')
// 3. The href path is '/' and we're on the homepage
const isCurrentPage =
currentPath === hrefPath ||
hrefPath === '' ||
(hrefPath === '/' && (currentPath === '/' || currentPath.endsWith('/index.html')));
// For hash links, we want to update the URL and scroll to the element
if (isHashLink || isCurrentPage) {
e.preventDefault();
const hashIndex = href.indexOf('#');
if (hashIndex !== -1) {
const targetId = href.substring(hashIndex + 1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// Update the URL with the hash fragment
history.pushState(null, null, href);
window.scrollTo({
top: targetElement.offsetTop - 50, // Offset for header
behavior: 'smooth'
});
} else {
// If the target element doesn't exist on the current page, navigate to the page
window.location.href = href;
}
} else {
// If there's no hash fragment, just navigate to the page
window.location.href = href;
}
}
});
});
}
// Handle language changes and hash navigation
function setupLanguageNavigation() {
// Initialize language preference from localStorage or default to 'en'
function getStoredLanguage() {
return localStorage.getItem('preferredLanguage') || 'en';
}
// Store language preference in localStorage
function storeLanguagePreference(langCode) {
localStorage.setItem('preferredLanguage', langCode);
console.log('Language preference stored:', langCode);
}
// Function to update URLs with the current language
function updateUrlsWithLanguage() {
const currentLang = getStoredLanguage();
const supportedLanguages = ['en', 'nl', 'de', 'fr'];
// Update all internal links to include the language prefix
document.querySelectorAll('a[href^="/"]:not([href^="//"])').forEach(link => {
const href = link.getAttribute('href');
if (!href) return;
// Skip hash-only links (e.g., "#services")
if (href.startsWith('#')) {
return;
}
// Extract hash fragment if present
let hashFragment = '';
let pathWithoutHash = href;
if (href.includes('#')) {
const parts = href.split('#');
pathWithoutHash = parts[0];
hashFragment = '#' + parts[1];
}
// Parse the URL path (without hash) to check for existing language code
const pathSegments = pathWithoutHash.split('/').filter(Boolean);
const hasLanguagePrefix = pathSegments.length > 0 && supportedLanguages.includes(pathSegments[0]);
// If it already has a language prefix but it's different from the current language,
// update it to the current language
if (hasLanguagePrefix && pathSegments[0] !== currentLang) {
// Replace the existing language prefix with the current one
pathSegments[0] = currentLang;
const newPath = '/' + pathSegments.join('/');
// Set the new href with the hash fragment (if any)
link.setAttribute('href', newPath + hashFragment);
return;
}
// If it doesn't have a language prefix, add the current language
if (!hasLanguagePrefix) {
// Create the new path with the language prefix
const newPath = pathWithoutHash === '/' ?
`/${currentLang}` :
`/${currentLang}${pathWithoutHash}`;
// Set the new href with the hash fragment (if any)
link.setAttribute('href', newPath + hashFragment);
}
});
}
// Listen for the custom languageChanged event
document.addEventListener('languageChanged', (event) => {
// Store the selected language in localStorage
if (event.detail && event.detail.langCode) {
storeLanguagePreference(event.detail.langCode);
console.log('Language changed:', event.detail);
// Always update all internal links with the new language
// regardless of whether we're doing a full page reload
updateUrlsWithLanguage();
}
});
// Process links when the page loads
updateUrlsWithLanguage();
// Process links after client-side navigation
document.addEventListener('astro:page-load', () => {
// Short delay to ensure DOM is fully updated
setTimeout(updateUrlsWithLanguage, 0);
});
// Also update links when the DOM content is loaded
document.addEventListener('DOMContentLoaded', updateUrlsWithLanguage);
// Check for hash in URL on page load and scroll to it
function scrollToHashOnLoad() {
if (window.location.hash) {
const targetId = window.location.hash.substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// Use setTimeout to ensure the page has fully loaded
setTimeout(() => {
window.scrollTo({
top: targetElement.offsetTop - 50, // Offset for header
behavior: 'smooth'
});
}, 100);
}
}
}
scrollToHashOnLoad();
// Make language functions available globally
window.languageUtils = {
getStoredLanguage,
storeLanguagePreference
};
}
document.addEventListener('DOMContentLoaded', () => {
setupSmoothScrolling();
setupLanguageNavigation();
});
// Re-attach event listeners after page transitions
document.addEventListener('astro:after-swap', () => {
setupSmoothScrolling();
setupLanguageNavigation();
});
</script>
<script is:inline>

View File

@@ -2,6 +2,7 @@
import { Icon } from 'astro-icon/components';
import { SITE } from 'astrowind:config';
import { getHomePermalink } from '~/utils/permalinks';
import { getFooterData } from '~/navigation';
interface Link {
text?: string;
@@ -17,13 +18,50 @@ interface Links {
export interface Props {
links?: Array<Links>;
secondaryLinks: Array<Link>;
socialLinks: Array<Link>;
secondaryLinks?: Array<Link>;
socialLinks?: Array<Link>;
footNote?: string;
theme?: string;
}
const { socialLinks = [], theme = 'light' } = Astro.props;
import { supportedLanguages } from '~/i18n/translations';
// Define the type for supported languages
type SupportedLanguage = typeof supportedLanguages[number];
// Get current language from URL
const currentPath = `/${Astro.url.pathname.replace(/^\/+|\/+$/g, '')}`;
const pathSegments = currentPath.split('/').filter(Boolean);
// Check for language in URL path
let currentLang = pathSegments[0] && supportedLanguages.includes(pathSegments[0] as SupportedLanguage)
? pathSegments[0] as SupportedLanguage
: null;
// If no language in URL, check cookies
if (!currentLang) {
const cookies = Astro.request.headers.get('cookie') || '';
const cookieLanguage = cookies.split(';')
.map(cookie => cookie.trim())
.find(cookie => cookie.startsWith('preferredLanguage='))
?.split('=')[1];
if (cookieLanguage && supportedLanguages.includes(cookieLanguage as SupportedLanguage)) {
currentLang = cookieLanguage as SupportedLanguage;
} else {
// Default to English if no language is found
currentLang = 'en';
}
}
// Get translated footer data
const footerData = getFooterData(currentLang);
const {
secondaryLinks = footerData.secondaryLinks,
socialLinks = footerData.socialLinks,
theme = 'light'
} = Astro.props;
---
<footer class:list={[{ dark: theme === 'dark' }, 'relative border-t border-gray-200 dark:border-slate-800 not-prose']}>
@@ -38,13 +76,17 @@ const { socialLinks = [], theme = 'light' } = Astro.props;
<!-- Site Title with Terms & Privacy Links -->
<div class="flex flex-col items-start space-y-2">
<!-- Site Title -->
<a class="inline-block font-bold text-xl" href={getHomePermalink()}>
<a class="inline-block font-bold text-xl" href={getHomePermalink(currentLang)}>
{SITE?.name}
</a>
<!-- Terms & Privacy Policy Links -->
<div class="flex items-center space-x-4 text-sm text-muted">
{secondaryLinks.map(({ text, href }) => (
<a class="hover:text-gray-700 dark:hover:text-gray-200 transition duration-150 ease-in-out" href={href}>
{text}
</a>
))}
</div>
</div>

View File

@@ -8,6 +8,7 @@ import LanguageDropdown from '~/components/LanguageDropdown.astro';
import { getHomePermalink } from '~/utils/permalinks';
import { trimSlash, getAsset } from '~/utils/permalinks';
import { getHeaderData } from '~/navigation';
interface Link {
text?: string;
@@ -18,6 +19,7 @@ interface Link {
interface MenuLink extends Link {
links?: Array<MenuLink>;
isHashLink?: boolean;
}
export interface Props {
@@ -31,9 +33,42 @@ export interface Props {
position?: string;
}
import { supportedLanguages } from '~/i18n/translations';
// Get current language from URL
const currentPath = `/${trimSlash(new URL(Astro.url).pathname)}`;
const pathSegments = currentPath.split('/').filter(Boolean);
// Define the type for supported languages
type SupportedLanguage = typeof supportedLanguages[number];
// Check for language in URL path
let currentLang = pathSegments[0] && supportedLanguages.includes(pathSegments[0] as SupportedLanguage)
? pathSegments[0] as SupportedLanguage
: null;
// If no language in URL, check cookies
if (!currentLang) {
const cookies = Astro.request.headers.get('cookie') || '';
const cookieLanguage = cookies.split(';')
.map(cookie => cookie.trim())
.find(cookie => cookie.startsWith('preferredLanguage='))
?.split('=')[1];
if (cookieLanguage && supportedLanguages.includes(cookieLanguage as SupportedLanguage)) {
currentLang = cookieLanguage as SupportedLanguage;
} else {
// Default to English if no language is found
currentLang = 'en';
}
}
// Get translated header data
const headerData = getHeaderData(currentLang);
const {
id = 'header',
links = [],
links = headerData.links,
isSticky = false,
isDark = false,
isFullWidth = false,
@@ -41,8 +76,6 @@ const {
showRssFeed = false,
position = 'center',
} = Astro.props;
const currentPath = `/${trimSlash(new URL(Astro.url).pathname)}`;
---
<header
@@ -107,7 +140,7 @@ const currentPath = `/${trimSlash(new URL(Astro.url).pathname)}`;
<Icon name="tabler:chevron-down" class="w-3.5 h-3.5 ml-0.5 rtl:ml-0 rtl:mr-0.5 hidden md:inline" />
</button>
<ul class="dropdown-menu md:backdrop-blur-md dark:md:bg-dark rounded md:absolute pl-4 md:pl-0 md:hidden font-medium md:bg-white/90 md:min-w-[200px] drop-shadow-xl">
{links.map(({ text: text2, href: href2 }) => (
{links.map(({ text: text2, href: href2, isHashLink }) => (
<li>
<a
class:list={[
@@ -115,6 +148,7 @@ const currentPath = `/${trimSlash(new URL(Astro.url).pathname)}`;
{ 'aw-link-active': href2 === currentPath },
]}
href={href2}
data-hash-link={isHashLink ? 'true' : undefined}
>
{text2}
</a>
@@ -129,6 +163,7 @@ const currentPath = `/${trimSlash(new URL(Astro.url).pathname)}`;
{ 'aw-link-active': href === currentPath },
]}
href={href}
data-hash-link={href?.includes('#') ? 'true' : undefined}
>
{text}
</a>