好的,我们可以扩展这个程序来支持自定义主题 (theme)功能。我们将添加以下功能:

  1. 用户可以创建、编辑和选择不同的主题模板
  2. 主题模板将被保存到文件系统中
  3. 用户可以在界面上切换不同的主题

让我们开始修改和添加一些文件:

  1. 首先, 创建一个新的文件 src/themes.ts 来处理主题相关的功能:
import { readFile, writeFile, readdir } from 'fs/promises';
import path from 'path';
 
const THEMES_DIR = path.join(process.cwd(), 'themes');
 
export interface Theme {
  name: string;
  content: string;
}
 
export async function getThemes(): Promise<Theme[]> {
  const files = await readdir(THEMES_DIR);
  const themes = await Promise.all(
    files.map(async (file) => {
      const content = await readFile(path.join(THEMES_DIR, file), 'utf-8');
      return { name: path.parse(file).name, content };
    })
  );
  return themes;
}
 
export async function saveTheme(name: string, content: string): Promise<void> {
  await writeFile(path.join(THEMES_DIR, `${name}.html`), content);
}
 
export async function getTheme(name: string): Promise<Theme | null> {
  try {
    const content = await readFile(path.join(THEMES_DIR, `${name}.html`), 'utf-8');
    return { name, content };
  } catch (error) {
    return null;
  }
}
  1. 修改 src/index.ts 文件, 添加新的路由来处理主题:
import { Elysia } from "elysia";
import { html } from "@elysiajs/html";
import { staticPlugin } from "@elysiajs/static";
import { layout } from "./views/layout";
import { forecast } from "./views/forecast";
import { getWeatherData } from "./weather";
import { getThemes, saveTheme, getTheme } from "./themes";
 
const app = new Elysia()
  .use(html())
  .use(staticPlugin())
  .get("/", ({ html }) => html(layout(forecast())))
  .get("/forecast/:city/:days", async ({ params, html }) => {
    const { city, days } = params;
    const weatherData = await getWeatherData(city, parseInt(days));
    return html(forecast(city, parseInt(days), weatherData));
  })
  .get("/themes", async ({ html }) => {
    const themes = await getThemes();
    return html(layout(await import("./views/themes").then(m => m.themesList(themes))));
  })
  .get("/themes/edit/:name?", async ({ params, html }) => {
    const theme = params.name ? await getTheme(params.name) : null;
    return html(layout(await import("./views/themes").then(m => m.themeEditor(theme))));
  })
  .post("/themes/save", async ({ body, html }) => {
    const { name, content } = body as { name: string; content: string };
    await saveTheme(name, content);
    return html(`<div class="text-green-500">Theme saved successfully!</div>`);
  })
  .get("/apply-theme/:name", async ({ params, html }) => {
    const theme = await getTheme(params.name);
    if (theme) {
      return html(theme.content);
    }
    return html(`<div class="text-red-500">Theme not found</div>`);
  })
  .listen(3000);
 
console.log(`Server is running at http://localhost:${app.server?.port}`);
  1. 创建一个新文件 src/views/themes.ts 来处理主题列表和编辑器的视图:
import { Theme } from "../themes";
 
export const themesList = (themes: Theme[]) => `
  <div class="mb-4">
    <h2 class="text-2xl mb-2">Themes</h2>
    <ul>
      ${themes.map(theme => `
        <li class="mb-2">
          ${theme.name}
          <button hx-get="/apply-theme/${theme.name}" hx-target="body" class="ml-2 p-1 bg-blue-500 text-white rounded">Apply</button>
          <a href="/themes/edit/${theme.name}" class="ml-2 p-1 bg-green-500 text-white rounded">Edit</a>
        </li>
      `).join('')}
    </ul>
    <a href="/themes/edit" class="mt-4 inline-block p-2 bg-purple-500 text-white rounded">Create New Theme</a>
  </div>
`;
 
export const themeEditor = (theme: Theme | null) => `
  <div class="mb-4">
    <h2 class="text-2xl mb-2">${theme ? 'Edit Theme' : 'Create New Theme'}</h2>
    <form hx-post="/themes/save" hx-swap="outerHTML">
      <div class="mb-2">
        <label for="name" class="block">Theme Name:</label>
        <input type="text" id="name" name="name" value="${theme?.name || ''}" required class="w-full p-2 border rounded">
      </div>
      <div class="mb-2">
        <label for="content" class="block">Theme Content:</label>
        <textarea id="content" name="content" rows="10" required class="w-full p-2 border rounded">${theme?.content || ''}</textarea>
      </div>
      <button type="submit" class="p-2 bg-blue-500 text-white rounded">Save Theme</button>
    </form>
  </div>
`;
  1. 修改 src/views/layout.ts 文件, 添加一个链接到主题列表:
export const layout = (content: string) => `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Weather Forecast</title>
  <script src="https://unpkg.com/htmx.org@1.9.6"></script>
  <link href="/public/styles.css" rel="stylesheet">
</head>
<body class="bg-gray-100 text-gray-800">
  <div class="container mx-auto p-4">
    <h1 class="text-3xl font-bold mb-4">Weather Forecast</h1>
    <nav class="mb-4">
      <a href="/" class="mr-4">Home</a>
      <a href="/themes" class="mr-4">Themes</a>
    </nav>
    ${content}
  </div>
</body>
</html>
`;
  1. 在项目根目录下创建一个 themes 文件夹, 并添加一个默认主题 default.html:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Weather Forecast - Default Theme</title>
  <script src="https://unpkg.com/htmx.org@1.9.6"></script>
  <style>
    body { font-family: Arial, sans-serif; background-color: #f0f0f0; }
    .container { max-width: 800px; margin: 0 auto; padding: 20px; }
    h1 { color: #333; }
    .forecast { background-color: #fff; padding: 20px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
  </style>
</head>
<body>
  <div class="container">
    <h1>Weather Forecast</h1>
    <div id="content"></div>
  </div>
  <script>
    htmx.ajax('GET', '/', '#content');
  </script>
</body>
</html>

现在, 你的应用支持自定义主题了。用户可以:

  1. 查看现有的主题列表 (/themes)
  2. 创建新主题或编辑现有主题 (/themes/edit/themes/edit/:name)
  3. 应用不同的主题 (点击主题列表中的 “Apply” 按钮)

要运行这个更新后的应用:

  1. 确保你已经创建了 themes 文件夹并添加了 default.html
  2. 运行 bun run dev
  3. 访问 http://localhost:3000

这个实现允许用户创建和编辑完整的 HTML 模板作为主题。在实际应用中, 你可能想要限制用户只能编辑特定的 CSS 样式或模板部分, 以确保应用的核心功能不会被破坏。此外, 你可能还想添加用户认证和授权, 以控制谁可以编辑和应用主题。