parent
8e40b8599b
commit
830585e2ba
15 changed files with 5371 additions and 15 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -11,3 +11,7 @@ Thumbs.db
|
|||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
node_modules/
|
||||
dist/
|
||||
.astro/
|
||||
|
|
|
|||
23
AGENTS.md
23
AGENTS.md
|
|
@ -6,8 +6,8 @@ explicitly says a change must preserve a URL.
|
|||
|
||||
## What This Is
|
||||
|
||||
This is a plain static website made from HTML, CSS, JavaScript, images, PDFs,
|
||||
and other static assets.
|
||||
This is an Astro-built static website. The current source content is exported
|
||||
HTML plus CSS, JavaScript, images, PDFs, and other static assets.
|
||||
|
||||
## Working Rules
|
||||
|
||||
|
|
@ -27,8 +27,11 @@ and other static assets.
|
|||
| `website/index.html` | Home page |
|
||||
| `website/about.html` | About page |
|
||||
| `website/contact.html` | Contact page with the form |
|
||||
| `website/*.html` | All other static pages, archives, categories, tags, and posts |
|
||||
| `website/PAGES.md` | Map from old folder paths to new flat filenames |
|
||||
| `website/` | Source HTML pages, blog posts, speech archives, categories, and tags |
|
||||
| `website/PAGES.md` | Map from old folder paths to current HTML paths |
|
||||
| `src/pages/` | Astro endpoints that generate static output from `website/` |
|
||||
| `src/components/` | Astro components for shared layout migration |
|
||||
| `dist/` | Generated static output from `npm run build` |
|
||||
| `css/` | Site-level stylesheets |
|
||||
| `js/` | Site-level scripts |
|
||||
| `assets/` | Images, PDFs, fonts, theme files, uploads, and static vendor files |
|
||||
|
|
@ -36,6 +39,18 @@ and other static assets.
|
|||
|
||||
## Local Preview
|
||||
|
||||
For Astro development:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
For the Nginx preview:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
|
|
|||
19
README.md
19
README.md
|
|
@ -1,10 +1,13 @@
|
|||
# familyfedie-website
|
||||
|
||||
Local static website files.
|
||||
Local static website files built with Astro.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `website/` - top-level static pages
|
||||
- `src/pages/` - Astro endpoints that generate static HTML from `website/`
|
||||
- `src/components/` - Astro components for the shared layout migration path
|
||||
- `dist/` - generated static output from `npm run build`
|
||||
- `website/index.html` - homepage
|
||||
- `website/about.html` - about page
|
||||
- `website/contact.html` - contact page with the form
|
||||
|
|
@ -23,6 +26,12 @@ Local static website files.
|
|||
|
||||
## Maintenance
|
||||
|
||||
Run Astro locally while editing:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
After changing shared navigation, sidebar, or footer markup in
|
||||
`scripts/components.mjs`, run:
|
||||
|
||||
|
|
@ -32,6 +41,14 @@ npm run render:layout
|
|||
|
||||
## Local Preview
|
||||
|
||||
Build the Astro output first:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then serve `dist/` with Docker:
|
||||
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
|
|
|||
9
astro.config.mjs
Normal file
9
astro.config.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { defineConfig } from "astro/config";
|
||||
|
||||
export default defineConfig({
|
||||
output: "static",
|
||||
outDir: "dist",
|
||||
build: {
|
||||
format: "file",
|
||||
},
|
||||
});
|
||||
|
|
@ -3,7 +3,7 @@ server {
|
|||
listen [::]:80;
|
||||
|
||||
server_name _;
|
||||
root /usr/share/nginx/html/website;
|
||||
root /usr/share/nginx/html/dist;
|
||||
index index.html;
|
||||
|
||||
location ~ /\. {
|
||||
|
|
@ -15,17 +15,17 @@ server {
|
|||
}
|
||||
|
||||
location /assets/ {
|
||||
alias /usr/share/nginx/html/assets/;
|
||||
alias /usr/share/nginx/html/dist/assets/;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location /css/ {
|
||||
alias /usr/share/nginx/html/css/;
|
||||
alias /usr/share/nginx/html/dist/css/;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location /js/ {
|
||||
alias /usr/share/nginx/html/js/;
|
||||
alias /usr/share/nginx/html/dist/js/;
|
||||
try_files $uri =404;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Site Structure
|
||||
|
||||
This is a local static site. Top-level pages are kept in `website/`, while blog
|
||||
posts and speech archive pages are grouped into dedicated folders.
|
||||
This is a local static site built with Astro. The current exported HTML source
|
||||
is kept in `website/`, while Astro emits the served site into `dist/`.
|
||||
|
||||
## Root
|
||||
|
||||
|
|
@ -16,6 +16,11 @@ posts and speech archive pages are grouped into dedicated folders.
|
|||
- `website/speeches/rev-dr-sun-myung-moon/*.html` - yearly speech archive pages
|
||||
- `website/speeches/categories/*.html` - speech category archive pages
|
||||
- `website/PAGES.md` - map of old folder paths to current HTML paths
|
||||
- `src/pages/index.html.ts` - Astro endpoint for the home page
|
||||
- `src/pages/[...route].ts` - Astro endpoint for all other existing HTML pages
|
||||
- `src/lib/static-pages.ts` - maps `website/` files to Astro static routes
|
||||
- `src/components/` - Astro shared layout components for ongoing migration
|
||||
- `dist/` - generated site output, ignored by git
|
||||
- `css/` - site-level stylesheets
|
||||
- `js/` - site-level scripts
|
||||
- `assets/` - images, PDFs, fonts, theme files, uploads, and vendor files
|
||||
|
|
@ -23,14 +28,19 @@ posts and speech archive pages are grouped into dedicated folders.
|
|||
- `scripts/components.mjs` - shared nav/sidebar/footer components
|
||||
- `scripts/render-shared-layout.mjs` - renders shared layout components into pages
|
||||
- `scripts/organize-content.mjs` - organizes exported blog and speech pages
|
||||
- `scripts/copy-static-assets.mjs` - copies `assets/`, `css/`, and `js/` into `dist/`
|
||||
- `docker/` and `compose.yaml` - local static server preview
|
||||
- `.forgejo/` - deployment automation
|
||||
|
||||
## Shared Layout
|
||||
|
||||
The site remains static HTML, but common navigation, sidebar, and footer markup
|
||||
is generated from `scripts/components.mjs`. Run `npm run render:layout` after
|
||||
editing those components.
|
||||
The current source pages remain static HTML, but common navigation, sidebar, and
|
||||
footer markup is generated from `scripts/components.mjs`. Run
|
||||
`npm run render:layout` after editing those components.
|
||||
|
||||
Astro components in `src/components/` are available for the next migration step:
|
||||
converting individual pages from exported HTML into `.astro` or Markdown content
|
||||
while reusing the same layout structure.
|
||||
|
||||
## Asset Layout
|
||||
|
||||
|
|
@ -45,6 +55,19 @@ editing those components.
|
|||
Run:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then open Astro's local URL, usually:
|
||||
|
||||
```text
|
||||
http://localhost:4321
|
||||
```
|
||||
|
||||
For the Nginx preview, build first:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
docker compose up
|
||||
```
|
||||
|
||||
|
|
|
|||
5083
package-lock.json
generated
Normal file
5083
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,6 +2,12 @@
|
|||
"scripts": {
|
||||
"organize": "node scripts/organize-content.mjs",
|
||||
"render:layout": "node scripts/render-shared-layout.mjs",
|
||||
"build": "npm run organize && npm run render:layout"
|
||||
"prepare:source": "npm run organize && npm run render:layout",
|
||||
"dev": "npm run prepare:source && astro dev --host 0.0.0.0",
|
||||
"build": "npm run prepare:source && astro build && node scripts/copy-static-assets.mjs",
|
||||
"preview": "astro preview --host 0.0.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^6.4.6"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
scripts/copy-static-assets.mjs
Normal file
18
scripts/copy-static-assets.mjs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
const distRoot = path.join(repoRoot, "dist");
|
||||
const staticDirs = ["assets", "css", "js"];
|
||||
|
||||
function copyDir(source, destination) {
|
||||
fs.rmSync(destination, { recursive: true, force: true });
|
||||
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
||||
fs.cpSync(source, destination, { recursive: true });
|
||||
}
|
||||
|
||||
for (const dir of staticDirs) {
|
||||
copyDir(path.join(repoRoot, dir), path.join(distRoot, dir));
|
||||
}
|
||||
|
||||
console.log(`Copied ${staticDirs.join(", ")} into dist/.`);
|
||||
18
src/components/SiteFooter.astro
Normal file
18
src/components/SiteFooter.astro
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<footer id="footer" role="contentinfo">
|
||||
<div id="colophon"></div>
|
||||
|
||||
<div id="footer2">
|
||||
<div id="footer2-inner">
|
||||
<div id="site-copyright">
|
||||
<b>Family Federation for World Peace and Unification (FFWPU) Ireland</b>
|
||||
<br />
|
||||
Charity no. CHY 6071 All Rights Reserved 2013
|
||||
<br />
|
||||
<a href="/speeches/" target="_blank">Speeches</a>
|
||||
</div>
|
||||
<div style="text-align:center;padding:5px 0 2px;text-transform:uppercase;font-size:12px;margin:1em auto 0;">
|
||||
Family Federation for World Peace and Unification Ireland
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
70
src/components/SiteNav.astro
Normal file
70
src/components/SiteNav.astro
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
const { pathname = "/" } = Astro.props;
|
||||
|
||||
const navItems = [
|
||||
{ href: "/index.html", label: "Home", match: ["/index.html", "/"] },
|
||||
{
|
||||
href: "/about.html",
|
||||
label: "About Us",
|
||||
match: ["/about.html", "/the-founders.html", "/about-us-organizations.html"],
|
||||
children: [
|
||||
{ href: "/the-founders.html", label: "The Founders" },
|
||||
{ href: "/about-us-organizations.html", label: "Organizations" },
|
||||
],
|
||||
},
|
||||
{ href: "/videos.html", label: "Videos", match: ["/videos.html"] },
|
||||
{
|
||||
href: "/events.html",
|
||||
label: "Events",
|
||||
match: ["/events.html", "/services.html"],
|
||||
children: [{ href: "/services.html", label: "Sunday Services" }],
|
||||
},
|
||||
{ href: "/speeches/", label: "Speeches", match: ["/speeches/"] },
|
||||
{ href: "/contact.html", label: "Contact", match: ["/contact.html"] },
|
||||
];
|
||||
|
||||
function isCurrent(item) {
|
||||
return item.match.some((match) => {
|
||||
if (match === "/") {
|
||||
return pathname === "/";
|
||||
}
|
||||
if (match.endsWith("/")) {
|
||||
return pathname.startsWith(match);
|
||||
}
|
||||
return pathname === match;
|
||||
});
|
||||
}
|
||||
---
|
||||
|
||||
<nav id="access" class="jssafe" role="navigation">
|
||||
<div class="skip-link screen-reader-text"><a href="#content" title="Skip to content">Skip to content</a></div>
|
||||
<div class="menu">
|
||||
<ul id="prime_nav" class="menu">
|
||||
{
|
||||
navItems.map((item, index) => {
|
||||
const current = isCurrent(item);
|
||||
return (
|
||||
<li class:list={["menu-item", item.children && "menu-item-has-children", current && "current-menu-item current_page_item", `menu-item-${index + 1}`]}>
|
||||
<a href={item.href} aria-current={current ? "page" : undefined}>
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
{
|
||||
item.children && (
|
||||
<ul class="sub-menu">
|
||||
{item.children.map((child, childIndex) => (
|
||||
<li class={`menu-item menu-item-${index + 1}-${childIndex + 1}`}>
|
||||
<a href={child.href}>
|
||||
<span>{child.label}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
26
src/components/SiteSidebar.astro
Normal file
26
src/components/SiteSidebar.astro
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
const sidebarItems = [
|
||||
{ href: "/the-founders.html", label: "The Founders" },
|
||||
{ href: "/services.html", label: "Sunday Services" },
|
||||
{ href: "/contact.html", label: "Contact" },
|
||||
{ href: "/speeches/", label: "Speeches" },
|
||||
];
|
||||
---
|
||||
|
||||
<div id="secondary" class="widget-area sidey" role="complementary">
|
||||
<ul class="xoxo">
|
||||
<li id="nav_menu-2" class="widget-container widget_nav_menu">
|
||||
<div class="menu-menu-3-side-bar-menu-container">
|
||||
<ul id="menu-menu-3-side-bar-menu" class="menu">
|
||||
{
|
||||
sidebarItems.map((item, index) => (
|
||||
<li class={`menu-item menu-item-${index + 1}`}>
|
||||
<a href={item.href}>{item.label}</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
32
src/lib/static-pages.ts
Normal file
32
src/lib/static-pages.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
export const repoRoot = process.cwd();
|
||||
export const websiteRoot = path.join(repoRoot, "website");
|
||||
|
||||
export function walkHtmlFiles(dir = websiteRoot): string[] {
|
||||
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
return walkHtmlFiles(fullPath);
|
||||
}
|
||||
return entry.isFile() && entry.name.endsWith(".html") ? [fullPath] : [];
|
||||
});
|
||||
}
|
||||
|
||||
export function relativeWebsitePath(filePath: string): string {
|
||||
return path.relative(websiteRoot, filePath).split(path.sep).join("/");
|
||||
}
|
||||
|
||||
export function routeFromRelativePath(relativePath: string): string {
|
||||
if (relativePath === "index.html") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
export function sourcePathFromRoute(route = ""): string {
|
||||
const relativePath = route === "" ? "index.html" : route.endsWith(".html") ? route : `${route}/index.html`;
|
||||
return path.join(websiteRoot, relativePath);
|
||||
}
|
||||
23
src/pages/[...route].ts
Normal file
23
src/pages/[...route].ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import fs from "node:fs/promises";
|
||||
import { relativeWebsitePath, routeFromRelativePath, sourcePathFromRoute, walkHtmlFiles } from "../lib/static-pages";
|
||||
|
||||
export function getStaticPaths() {
|
||||
return walkHtmlFiles()
|
||||
.map(relativeWebsitePath)
|
||||
.filter((relativePath) => relativePath !== "index.html")
|
||||
.map((relativePath) => ({
|
||||
params: {
|
||||
route: routeFromRelativePath(relativePath),
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
export async function GET({ params }: { params: { route?: string } }) {
|
||||
const html = await fs.readFile(sourcePathFromRoute(params.route), "utf8");
|
||||
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"content-type": "text/html; charset=utf-8",
|
||||
},
|
||||
});
|
||||
}
|
||||
12
src/pages/index.html.ts
Normal file
12
src/pages/index.html.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import fs from "node:fs/promises";
|
||||
import { sourcePathFromRoute } from "../lib/static-pages";
|
||||
|
||||
export async function GET() {
|
||||
const html = await fs.readFile(sourcePathFromRoute(), "utf8");
|
||||
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"content-type": "text/html; charset=utf-8",
|
||||
},
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue