Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions 404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script>
// GitHub Pages SPA redirect script
// This script captures the current URL and redirects to index.html
// preserving the path for the router to handle

const pathSegments = window.location.pathname.slice(1).split('/').filter(Boolean);
const redirectPath = pathSegments.length > 0
? '/?p=/' + pathSegments.join('/')
: '/';
Comment on lines +10 to +13
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redirect logic constructs the path with string concatenation which could lead to double slashes. When pathSegments is empty but the original path had a trailing slash, or when joining segments that might contain edge cases. Consider using a more robust path construction approach or add a comment explaining the expected input format.

Suggested change
const pathSegments = window.location.pathname.slice(1).split('/').filter(Boolean);
const redirectPath = pathSegments.length > 0
? '/?p=/' + pathSegments.join('/')
: '/';
const pathSegments = window.location.pathname
.slice(1)
.split('/')
.filter(Boolean);
function buildRedirectPath(segments) {
if (!segments || segments.length === 0) {
return '/';
}
// Normalize segments to avoid accidental double slashes
const normalized = segments
.map(function (segment) {
return segment.replace(/^\/+|\/+$/g, '');
})
.filter(Boolean)
.join('/');
return normalized ? '/?p=/' + normalized : '/';
}
const redirectPath = buildRedirectPath(pathSegments);

Copilot uses AI. Check for mistakes.

window.location.replace(
window.location.origin + redirectPath + window.location.search + window.location.hash
);
</script>
</head>
<body>
<!-- This page is used to redirect all routes to index.html for GitHub Pages -->
</body>
</html>
39 changes: 24 additions & 15 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Abytegray - Personal Blog</title>
<script>
// GitHub Pages SPA support: restore path from query parameter
(function() {
const query = window.location.search;
if (query.startsWith('?p=')) {
const path = query.slice(3);
window.history.replaceState({}, '', path + window.location.hash);
}
})();
</script>
<style>
:root {
--border-color: #e0e0e0;
Expand Down Expand Up @@ -42,21 +52,20 @@
</head>
<body>
<main>
<h1>Welcome to Abytegray</h1>

<app-card
title="Web Components"
content="This is a custom web component built with vanilla TypeScript and CSS modules.">
</app-card>

<app-card
title="No Framework Required"
content="Using native browser APIs with shadow DOM for encapsulation.">
</app-card>

<app-card title="Custom Content">
<p style="margin: 1rem 0 0 0; color: #999;">This content comes from a slot!</p>
</app-card>
<embla-router>
<!-- Base route (home page) -->
<embla-route path="/" component="page-home" title="Home - Abytegray" base></embla-route>

<!-- Static routes -->
<embla-route path="/about" component="page-about" title="About - Abytegray"></embla-route>
<embla-route path="/articles" component="page-articles" title="Articles - Abytegray"></embla-route>

<!-- Dynamic route with parameter -->
<embla-route path="/article/:id" component="page-article" title="Article - Abytegray"></embla-route>

<!-- 404 Not Found route -->
<embla-route component="page-notfound" title="Page Not Found - Abytegray" notfound></embla-route>
</embla-router>
</main>

<script type="module" src="/src/index.ts"></script>
Expand Down
35 changes: 35 additions & 0 deletions src/components/page-about/page-about.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { h } from 'embla';

class AboutPage extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}

connectedCallback() {
this.render();
}

render() {
if (this.shadowRoot) {
this.shadowRoot.innerHTML = '';

const content = (
<div>
<h1>About</h1>
<p>This is a demonstration of the Embla router with web components.</p>

<nav style={{ margin: '2rem 0' }}>
<embla-link href="/">Home</embla-link>
{' | '}
<embla-link href="/articles">Articles</embla-link>
</nav>
</div>
);

this.shadowRoot.appendChild(content);
}
}
}

customElements.define('page-about', AboutPage);
55 changes: 55 additions & 0 deletions src/components/page-article/page-article.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { h } from 'embla';

class ArticlePage extends HTMLElement {
static get observedAttributes() {
return ['id'];
}

constructor() {
super();
this.attachShadow({ mode: 'open' });
}

connectedCallback() {
this.render();
}

attributeChangedCallback(_name: string, oldValue: string, newValue: string) {
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter '_name' is prefixed with underscore to indicate it's unused, but it's included in the observedAttributes list. Consider renaming to 'name' if it's meaningful, or document why it's intentionally unused.

Suggested change
attributeChangedCallback(_name: string, oldValue: string, newValue: string) {
attributeChangedCallback(name: string, oldValue: string, newValue: string) {

Copilot uses AI. Check for mistakes.
if (oldValue !== newValue) {
this.render();
}
}

render() {
const articleId = this.getAttribute('id') || 'unknown';

if (this.shadowRoot) {
this.shadowRoot.innerHTML = '';

const content = (
<div>
<h1>Article {articleId}</h1>
<p>This is article number {articleId}. The ID was extracted from the URL route parameter.</p>

<div style={{ margin: '1rem 0', padding: '1rem', background: '#f0f0f0', borderRadius: '4px' }}>
<strong>Route Parameter Demo:</strong>
<p>URL pattern: <code>/article/:id</code></p>
<p>Current ID: <code>{articleId}</code></p>
</div>

<nav style={{ margin: '2rem 0' }}>
<embla-link href="/">Home</embla-link>
{' | '}
<embla-link href="/articles">All Articles</embla-link>
{' | '}
<embla-link href="/about">About</embla-link>
</nav>
</div>
);

this.shadowRoot.appendChild(content);
}
}
}

customElements.define('page-article', ArticlePage);
41 changes: 41 additions & 0 deletions src/components/page-articles/page-articles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { h } from 'embla';

class ArticlesPage extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}

connectedCallback() {
this.render();
}

render() {
if (this.shadowRoot) {
this.shadowRoot.innerHTML = '';

const content = (
<div>
<h1>Articles</h1>
<p>Browse all articles:</p>

<ul style={{ margin: '1rem 0', padding: '0 0 0 1.5rem' }}>
<li><embla-link href="/article/1">Article 1: Getting Started</embla-link></li>
<li><embla-link href="/article/2">Article 2: Advanced Topics</embla-link></li>
<li><embla-link href="/article/123">Article 123: Router Demo</embla-link></li>
</ul>

<nav style={{ margin: '2rem 0' }}>
<embla-link href="/">Home</embla-link>
{' | '}
<embla-link href="/about">About</embla-link>
</nav>
</div>
);

this.shadowRoot.appendChild(content);
}
}
}

customElements.define('page-articles', ArticlesPage);
37 changes: 37 additions & 0 deletions src/components/page-home/page-home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { h } from 'embla';

class HomePage extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}

connectedCallback() {
this.render();
}

render() {
if (this.shadowRoot) {
this.shadowRoot.innerHTML = '';

const content = (
<div>
<h1>Home Page</h1>
<p>Welcome to Abytegray - A modern web component blog</p>

<nav style={{ margin: '2rem 0' }}>
<embla-link href="/about">About</embla-link>
{' | '}
<embla-link href="/articles">Articles</embla-link>
{' | '}
<embla-link href="/article/123">Sample Article</embla-link>
</nav>
</div>
);

this.shadowRoot.appendChild(content);
}
}
}

customElements.define('page-home', HomePage);
33 changes: 33 additions & 0 deletions src/components/page-notfound/page-notfound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { h } from 'embla';

class NotFoundPage extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}

connectedCallback() {
this.render();
}

render() {
if (this.shadowRoot) {
this.shadowRoot.innerHTML = '';

const content = (
<div>
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>

<nav style={{ margin: '2rem 0' }}>
<embla-link href="/">Go Home</embla-link>
</nav>
</div>
);

this.shadowRoot.appendChild(content);
}
}
}

customElements.define('page-notfound', NotFoundPage);
12 changes: 11 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
// Import reset CSS from embla (optional)
import 'embla/reset.css';

// Import router components from embla
import 'embla';

// Import and register all components
import './components/app-card/app-card.tsx';

// Import page components
import './components/page-home/page-home.tsx';
import './components/page-about/page-about.tsx';
import './components/page-articles/page-articles.tsx';
import './components/page-article/page-article.tsx';
import './components/page-notfound/page-notfound.tsx';

// Initialize the application
console.log('Application initialized');
console.log('Application initialized with router');
Loading