feat: enable full dynamic homepage content via EmDash

- Add homepage singleton collection with 30+ editable fields
- Update index.astro to fetch from getEmDashEntry("homepage", "homepage-main")
- Update Base.astro footer tagline/copyright from EmDash

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kunthawat Greethong
2026-04-29 23:44:37 +07:00
parent 620c63951f
commit 734fad181e
3 changed files with 284 additions and 364 deletions

View File

@@ -1,5 +1,5 @@
---
import { getMenu, getEmDashCollection, getSiteSettings } from "emdash";
import { getMenu, getEmDashCollection, getSiteSettings, getEmDashEntry } from "emdash";
import {
WidgetArea,
EmDashHead,
@@ -47,6 +47,15 @@ const fullTitle = title.includes(siteTitle) ? title : `${title} — ${siteTitle}
// Fetch primary menu defined in seed
const menu = await getMenu("primary");
// Fetch homepage for footer text
let homepage;
try {
const result = await getEmDashEntry("homepage", "homepage-main");
homepage = result.entry;
} catch {
homepage = null;
}
// Fetch pages for footer
const { entries: pages } = await getEmDashCollection("pages");
@@ -148,7 +157,7 @@ const isLoggedIn = !!Astro.locals.user;
: siteTitle
}
</a>
<p class="footer-tagline">{siteTagline}</p>
<p class="footer-tagline">{homepage?.data.footer_tagline || siteTagline}</p>
</div>
<div class="footer-nav">
<h4 class="footer-heading">Navigate</h4>
@@ -195,7 +204,7 @@ const isLoggedIn = !!Astro.locals.user;
</div>
<div class="footer-bottom">
<p class="footer-copyright">
Powered by <a href="https://emdashcms.com">EmDash</a>
{homepage?.data.footer_copyright || `Powered by <a href="https://emdashcms.com">EmDash</a>`}
</p>
<div class="theme-switcher">
<button

View File

@@ -1,8 +1,19 @@
---
import { getSiteSettings } from "emdash";
import { getEmDashEntry } from "emdash";
import Base from "../layouts/Base.astro";
const settings = await getSiteSettings();
const { entry: homepage } = await getEmDashEntry("homepage", "homepage-main");
const { hero_headline, hero_subtitle, hero_button_text, hero_button_url, hero_image,
features_section_title, features_section_subtitle,
feature_1_title, feature_1_description, feature_1_icon,
feature_2_title, feature_2_description, feature_2_icon,
feature_3_title, feature_3_description, feature_3_icon,
feature_4_title, feature_4_description, feature_4_icon,
feature_5_title, feature_5_description, feature_5_icon,
feature_6_title, feature_6_description, feature_6_icon,
comparison_title, comparison_subtitle,
cta_title, cta_subtitle, cta_button_text, cta_button_url,
footer_tagline, footer_about_text, footer_copyright } = homepage.data;
---
<Base title="EmDash CMS - Self-Hosted for Astro" description="Fully self-hosted CMS for Astro. No cloud required, fully local, with admin panel, authentication, and plugin system.">
<main class="landing">
@@ -11,23 +22,20 @@ const settings = await getSiteSettings();
<div class="hero-content">
<div class="badge">Open Source • 10k+ Stars</div>
<h1 class="hero-title">
The CMS that<br />
<span class="accent">runs on your server</span>
{hero_headline.split('\n').map((line: string) => <><span>{line}</span><br /></>)}
</h1>
<p class="hero-subtitle">
EmDash is a full-stack TypeScript CMS built on Astro.
No cloud account required, no external dependencies.
Just a complete admin panel and your content.
{hero_subtitle}
</p>
<div class="hero-actions">
<a href="/_emdash/admin" class="btn btn-primary">
<a href={hero_button_url} class="btn btn-primary">
<span class="btn-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"/>
<path d="M3 9h18M9 21V9"/>
</svg>
</span>
Open Admin Panel
{hero_button_text}
</a>
<a href="https://github.com/emdash-cms/emdash" class="btn btn-secondary" target="_blank">
View on GitHub
@@ -61,8 +69,8 @@ const settings = await getSiteSettings();
<!-- Features Section -->
<section class="features" id="features">
<div class="section-header">
<h2 class="section-title">Everything you need</h2>
<p class="section-subtitle">A complete CMS without the vendor lock-in</p>
<h2 class="section-title">{features_section_title}</h2>
<p class="section-subtitle">{features_section_subtitle}</p>
</div>
<div class="features-grid">
<div class="feature-card">
@@ -73,11 +81,8 @@ const settings = await getSiteSettings();
<path d="M2 12l10 5 10-5"/>
</svg>
</div>
<h3 class="feature-title">Fully Self-Hosted</h3>
<p class="feature-desc">
SQLite, D1, Turso, or PostgreSQL. Your data stays on your servers.
No cloud account required.
</p>
<h3 class="feature-title">{feature_1_title}</h3>
<p class="feature-desc">{feature_1_description}</p>
</div>
<div class="feature-card">
<div class="feature-icon">
@@ -86,11 +91,8 @@ const settings = await getSiteSettings();
<path d="M3 9h18M9 21V9"/>
</svg>
</div>
<h3 class="feature-title">Admin Panel</h3>
<p class="feature-desc">
Visual schema builder, media library, navigation menus.
Full admin at /_emdash/admin
</p>
<h3 class="feature-title">{feature_2_title}</h3>
<p class="feature-desc">{feature_2_description}</p>
</div>
<div class="feature-card">
<div class="feature-icon">
@@ -98,11 +100,8 @@ const settings = await getSiteSettings();
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
</svg>
</div>
<h3 class="feature-title">Passkey Auth</h3>
<p class="feature-desc">
WebAuthn passkey-first authentication with OAuth and magic link fallbacks.
Role-based access control.
</p>
<h3 class="feature-title">{feature_3_title}</h3>
<p class="feature-desc">{feature_3_description}</p>
</div>
<div class="feature-card">
<div class="feature-icon">
@@ -111,11 +110,8 @@ const settings = await getSiteSettings();
<path d="M12 6v6l4 2"/>
</svg>
</div>
<h3 class="feature-title">Built-in MCP</h3>
<p class="feature-desc">
Model Context Protocol server for AI tools.
Claude and ChatGPT can interact with your site directly.
</p>
<h3 class="feature-title">{feature_4_title}</h3>
<p class="feature-desc">{feature_4_description}</p>
</div>
<div class="feature-card">
<div class="feature-icon">
@@ -124,11 +120,8 @@ const settings = await getSiteSettings();
<polyline points="8 6 2 12 8 18"/>
</svg>
</div>
<h3 class="feature-title">Plugin System</h3>
<p class="feature-desc">
Sandboxed plugins on Cloudflare Workers.
Define capabilities, run safely in isolation.
</p>
<h3 class="feature-title">{feature_5_title}</h3>
<p class="feature-desc">{feature_5_description}</p>
</div>
<div class="feature-card">
<div class="feature-icon">
@@ -139,11 +132,8 @@ const settings = await getSiteSettings();
<line x1="16" y1="17" x2="8" y2="17"/>
</svg>
</div>
<h3 class="feature-title">WordPress Import</h3>
<p class="feature-desc">
Import posts, pages, media, and taxonomies from WXR exports,
REST API, or WordPress.com.
</p>
<h3 class="feature-title">{feature_6_title}</h3>
<p class="feature-desc">{feature_6_description}</p>
</div>
</div>
</section>
@@ -151,8 +141,8 @@ const settings = await getSiteSettings();
<!-- Comparison Section -->
<section class="comparison">
<div class="section-header">
<h2 class="section-title">EmDash vs Tina CMS</h2>
<p class="section-subtitle">Both work with Astro, but differ in approach</p>
<h2 class="section-title">{comparison_title}</h2>
<p class="section-subtitle">{comparison_subtitle}</p>
</div>
<div class="comparison-table">
<div class="comparison-header">
@@ -196,14 +186,10 @@ const settings = await getSiteSettings();
<!-- CTA Section -->
<section class="cta">
<div class="cta-content">
<h2 class="cta-title">Ready to get started?</h2>
<p class="cta-subtitle">
Clone the template, run bootstrap, and you're ready to build.
</p>
<h2 class="cta-title">{cta_title}</h2>
<p class="cta-subtitle">{cta_subtitle}</p>
<div class="cta-actions">
<a href="/_emdash/admin" class="btn btn-primary btn-large">
Try Admin Panel
</a>
<a href={cta_button_url} class="btn btn-primary btn-large">{cta_button_text}</a>
</div>
</div>
</section>