import fs from "node:fs"; import type { IncomingMessage, ServerResponse } from "node:http"; import path from "node:path"; import type { Connect, Plugin, ViteDevServer } from "vite"; import { defineConfig, type MarkdownOptions } from "vitepress"; const host = process.env.TAURI_DEV_HOST; // https://vitepress.dev/reference/site-config export default defineConfig({ title: "ko-grammar", description: "Korean grammar edited", lang: "ko-KR", themeConfig: { socialLinks: [ { icon: "github", link: "https://git.polv.cc/polv/ko-grammar" }, ], outline: { level: "deep", }, }, markdown: { config(md) { const mdInline: MarkdownOptions["config"] = (md) => { const className = "md"; md.core.ruler.after("inline", "inline_class_md", (state: any) => { const tokens = state.tokens; const tagPattern = new RegExp( `class=(?:\"|')([^\"']*\\b${escapeReg(className)}\\b[^\"']*)(?:\"|')`, "i", ); for (let i = 0; i < tokens.length; i++) { const t = tokens[i]; if (!/^(html_inline|html_block)$/.test(t.type)) continue; const html = t.content; // match tags with class including the target class, capture inner content (non-greedy) const pattern = new RegExp( `<([a-zA-Z0-9-]+)(\\b[^>]*)?>([\\s\\S]*?)<\\/\\1>`, "gi", ); let lastIndex = 0; let out = ""; let m; while ((m = pattern.exec(html)) !== null) { const [full, tagName, attrs = "", inner] = m; const idx = m.index; if (idx > lastIndex) out += html.slice(lastIndex, idx); if (tagPattern.test(attrs)) { // render inner as inline markdown out += `<${tagName}${attrs}>${md.renderInline(inner)}`; } else { out += full; } lastIndex = idx + full.length; } if (lastIndex === 0) continue; if (lastIndex < html.length) out += html.slice(lastIndex); t.content = out; } }); function escapeReg(s: string) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } }; md.use(mdInline); }, }, vite: { plugins: [audioProxyPlugin() as any], envDir: process.cwd(), ssr: { noExternal: [], external: ["pdfjs-dist"], // don't bundle it for SSR — leave it browser-only }, clearScreen: false, server: { /** * Allow Windows Firewall via Admin PowerShell with * * ``` * New-NetFirewallRule -DisplayName "Vitepress Server" -Direction Inbound -Protocol TCP -LocalPort 5173 -Action Allow * ``` */ port: 5173, strictPort: true, host: host || false, watch: { // tell vite to ignore watching `src-tauri` ignored: ["**/src-tauri/**"], }, hmr: host ? { protocol: "ws", host, port: 5174, } : undefined, }, // Env variables starting with the item of `envPrefix` will be exposed in tauri's source code through `import.meta.env`. envPrefix: ["VITE_", "TAURI_ENV_*"], build: { // Tauri uses Chromium on Windows and WebKit on macOS and Linux target: process.env.TAURI_ENV_PLATFORM == "windows" ? "chrome105" : "safari13", // don't minify for debug builds minify: !process.env.TAURI_ENV_DEBUG ? "esbuild" : false, // produce sourcemaps for debug builds sourcemap: !!process.env.TAURI_ENV_DEBUG, }, }, }); function audioProxyPlugin(): Plugin { return { name: "audio-proxy", configureServer(server: ViteDevServer) { // Vite dev server middlewares are connect-style: (req, res, next) server.middlewares.use(async (req, res, next) => { const reqUrl = req.url; if (!reqUrl || !reqUrl.startsWith("/uploads/")) return next(); if (!reqUrl.endsWith(".mp3")) return next(); try { await proxyAudio(req, res, next); } catch (e) { console.error(e, reqUrl); next(); } }); }, }; } async function proxyAudio( req: Connect.IncomingMessage, res: ServerResponse, next: Connect.NextFunction, ) { const url = new URL(`https://www.howtostudykorean.com/wp-content${req.url}`); const segs = req.url!.slice(1).split("/"); const filename = segs.pop()!; const saveroot = "docs/public"; const saveto = path.join(saveroot, ...segs, filename); if (fs.existsSync(saveto)) { return next(); } fs.mkdirSync(path.join(saveroot, ...segs), { recursive: true }); const r = await fetch(url, { redirect: "follow" }); if (!r.ok) { console.error(r); return next(); } fs.writeFileSync(saveto, Buffer.from(await r.arrayBuffer())); // console.log(fs.statSync(saveto)); console.log("saved to", saveto); res.setHeader("Content-Type", "audio/mpeg"); fs.createReadStream(saveto).pipe(res); }