diff --git a/src/test/TestApp.tsx b/src/test/TestApp.tsx index 88b81919..922845a8 100644 --- a/src/test/TestApp.tsx +++ b/src/test/TestApp.tsx @@ -70,6 +70,7 @@ export function TestApp({ children }: TestAppProps) { faviconUrl: 'https://fetch.ditto.pub/favicon/{hostname}', linkPreviewUrl: 'https://fetch.ditto.pub/link/{url}', corsProxy: 'https://proxy.shakespeare.diy/?url={href}', + magicMouse: false, contentWarningPolicy: 'blur', }; diff --git a/vite.config.ts b/vite.config.ts index 44d935d2..fd473f45 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import react from "@vitejs/plugin-react-swc"; +import type { Plugin } from "vite"; import { defineConfig } from "vitest/config"; import { DittoConfigSchema } from "./src/lib/schemas"; @@ -27,7 +28,70 @@ function loadDittoConfig(): object | undefined { return result; } +/** + * Copy all files from `src` into `dest`, overwriting existing files. + * Recursively handles subdirectories. + */ +function copyDirSync(src: string, dest: string): void { + fs.mkdirSync(dest, { recursive: true }); + for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + if (entry.isDirectory()) { + copyDirSync(srcPath, destPath); + } else { + fs.copyFileSync(srcPath, destPath); + } + } +} + +/** + * Vite plugin that merges an external public directory on top of the default one. + * Set the PUBLIC_DIR env var to a directory path. Files in that directory take + * precedence over files in the built-in `public/` directory. + * + * - In build mode, files are copied into the output after the default public dir. + * - In dev mode, the external directory is served with higher priority. + */ +function mergePublicDir(externalDir: string): Plugin { + const resolved = path.resolve(externalDir); + + return { + name: "ditto:merge-public-dir", + + configureServer(server) { + // Serve files from the external public dir before the default public dir. + server.middlewares.use((req, res, next) => { + if (!req.url) return next(); + + const urlPath = decodeURIComponent(new URL(req.url, "http://localhost").pathname); + const filePath = path.join(resolved, urlPath); + + try { + const stat = fs.statSync(filePath); + if (stat.isFile()) { + // Let Vite's static middleware handle it by pointing to the file. + const stream = fs.createReadStream(filePath); + stream.pipe(res); + return; + } + } catch { + // File not found in external dir — fall through to default public dir + } + + next(); + }); + }, + + writeBundle(options) { + const outDir = options.dir ?? path.resolve("dist"); + copyDirSync(resolved, outDir); + }, + }; +} + const dittoConfig = loadDittoConfig(); +const publicDir = process.env.PUBLIC_DIR; // https://vitejs.dev/config/ export default defineConfig(() => ({ @@ -37,6 +101,7 @@ export default defineConfig(() => ({ }, plugins: [ react(), + ...(publicDir ? [mergePublicDir(publicDir)] : []), ], define: { __DITTO_CONFIG__: JSON.stringify(dittoConfig ?? null),