198 lines
5.7 KiB
TypeScript
198 lines
5.7 KiB
TypeScript
import fs from "node:fs";
|
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
import https from "node:https";
|
|
import path from "node:path";
|
|
import { Connect, Plugin, ViteDevServer } from "vite";
|
|
import { defineConfig, 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)}</${tagName}>`;
|
|
} 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((req, res, next) => {
|
|
const reqUrl = req.url;
|
|
if (!reqUrl || !reqUrl.startsWith("/uploads/")) return next();
|
|
if (!reqUrl.endsWith(".mp3")) return next();
|
|
|
|
proxyAudio(req, res, next);
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
function proxyAudio(
|
|
req: Connect.IncomingMessage,
|
|
res: ServerResponse<IncomingMessage>,
|
|
next: Connect.NextFunction,
|
|
) {
|
|
const url = new URL(
|
|
`https://www.howtostudykorean.com/wp-content/uploads${req.url}`,
|
|
);
|
|
|
|
// Forward common headers (important for Range streaming)
|
|
const headers = {
|
|
...req.headers,
|
|
host: url.host,
|
|
range: req.headers["range"],
|
|
};
|
|
|
|
const segs = req.url!.slice(1).split("/");
|
|
const filename = segs.pop()!;
|
|
const saveroot = "docs/public";
|
|
const savetemp = path.join(saveroot, ...segs, filename + ".temp");
|
|
const saveto = path.join(saveroot, ...segs, filename);
|
|
|
|
if (fs.existsSync(saveto)) {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
fs.mkdirSync(path.join(saveroot, ...segs), { recursive: true });
|
|
const fileStream = fs.createWriteStream(savetemp);
|
|
|
|
const upstreamReq = https.request(
|
|
{
|
|
protocol: url.protocol,
|
|
hostname: url.hostname,
|
|
port: url.port || undefined,
|
|
path: url.pathname + url.search,
|
|
method: req.method,
|
|
headers,
|
|
},
|
|
(upstreamRes) => {
|
|
// Pass status (200 or 206 for Range)
|
|
res.writeHead(upstreamRes.statusCode || 200, upstreamRes.headers);
|
|
|
|
// Stream through
|
|
upstreamRes.pipe(res);
|
|
upstreamRes.pipe(fileStream);
|
|
},
|
|
);
|
|
|
|
fileStream.on("finish", () => {
|
|
fs.renameSync(savetemp, saveto);
|
|
console.log("saved to", saveto);
|
|
});
|
|
|
|
fileStream.on("error", (err) => {
|
|
console.error("write failed:", err);
|
|
});
|
|
|
|
upstreamReq.on("error", (err) => {
|
|
res.statusCode = 502;
|
|
res.end(`Bad Gateway: ${err.message}`);
|
|
});
|
|
|
|
req.pipe(upstreamReq);
|
|
}
|