feat: fix panel persist over server rendering, etc

This commit is contained in:
2024-02-22 23:05:02 +07:00
parent 2c9faa332a
commit efe51a9b5e
27 changed files with 962 additions and 150 deletions
+8 -11
View File
@@ -1,16 +1,12 @@
import { type Request, type Response, Router } from "express";
import { getFileExt } from "~/lib/utils";
import db from "~/server/db";
import { file } from "~/server/db/schema/file";
import { and, eq, isNull } from "drizzle-orm";
import { serveHtml } from "./serve-html";
import { Mime } from "mime/lite";
import standardTypes from "mime/types/standard.js";
import otherTypes from "mime/types/other.js";
import { serveJs } from "./serve-js";
import { type Request, type Response, Router } from "express";
const mime = new Mime(standardTypes, otherTypes);
mime.define({ "text/javascript": ["jsx", "tsx"] }, true);
import { getMimeType } from "~/server/lib/mime";
import { postcss } from "./postcss";
const get = async (req: Request, res: Response) => {
const { slug, ...pathParams } = req.params as any;
@@ -35,10 +31,11 @@ const get = async (req: Request, res: Response) => {
content = await serveJs(fileData);
}
res.setHeader(
"Content-Type",
mime.getType(fileData.filename) || "application/octet-stream"
);
if (["css"].includes(ext)) {
content = await postcss(fileData);
}
res.setHeader("Content-Type", getMimeType(fileData.filename));
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
+34
View File
@@ -0,0 +1,34 @@
import postcssPlugin from "postcss";
import tailwindcss from "tailwindcss";
import cssnano from "cssnano";
import { fileExists, getProjectDir } from "~/server/lib/utils";
import { FileSchema } from "~/server/db/schema/file";
import { unpackProject } from "~/server/lib/unpack-project";
export const postcss = async (fileData: FileSchema) => {
const content = fileData.content || "";
const projectDir = getProjectDir();
if (!fileExists(projectDir)) {
return content;
}
try {
await unpackProject({ ext: "ts,tsx,js,jsx,html" });
const result = await postcssPlugin([
tailwindcss({
content: [projectDir + "/**/*.{ts,tsx,js,jsx,html}"],
}),
cssnano({
preset: ["default", { discardComments: { removeAll: true } }],
}),
]).process(content, {
from: undefined,
});
return result.css;
} catch (err) {
return content;
}
};
+8 -7
View File
@@ -2,6 +2,8 @@ import db from "~/server/db";
import { FileSchema, file } from "~/server/db/schema/file";
import { and, eq, isNull } from "drizzle-orm";
const preventHtmlDirectAccess = `<script>if (window === window.parent) {window.location.href = '/';}</script>`;
export const serveHtml = async (fileData: FileSchema) => {
const layout = await db.query.file.findFirst({
where: and(
@@ -14,15 +16,16 @@ export const serveHtml = async (fileData: FileSchema) => {
});
let content = fileData.content || "";
if (!layout?.content) {
return content;
if (layout?.content != null) {
content = layout.content.replace("{CONTENT}", content);
}
content = layout.content.replace("{CONTENT}", content);
const bodyOpeningTagIdx = content.indexOf("<body");
const firstScriptTagIdx = content.indexOf("<script", bodyOpeningTagIdx);
const injectScripts = ['<script src="/js/hook-console.js"></script>'];
const injectScripts = [
'<script src="/js/hook-console.js"></script>',
preventHtmlDirectAccess,
];
const importMaps = [
{ name: "react", url: "https://esm.sh/[email protected]" },
@@ -45,7 +48,5 @@ export const serveHtml = async (fileData: FileSchema) => {
content.substring(firstScriptTagIdx);
}
// content = content.replace(/ {2}|\r\n|\n|\r/gm, "");
return content;
};
+2 -5
View File
@@ -1,9 +1,6 @@
import { CreateExpressContextOptions } from "@trpc/server/adapters/express";
import { Request } from "express";
export const createContext = async ({
req,
res,
}: CreateExpressContextOptions) => {
export const createContext = async ({ req }: { req: Request }) => {
return {};
};
+6 -1
View File
@@ -1,7 +1,12 @@
import { createContext } from "./context";
import { appRouter } from "../../routers/_app";
import { createCallerFactory } from ".";
import { PageContext } from "vike/types";
const trpcServer = createCallerFactory(appRouter)(createContext);
const trpcServer = async (ctx: PageContext) => {
const createCaller = createCallerFactory(appRouter);
const context = await createContext({ req: ctx.req });
return createCaller(context);
};
export default trpcServer;
+104 -29
View File
@@ -12,6 +12,52 @@ const main = async () => {
})
.returning();
// await db
// .insert(file)
// .values([
// {
// userId: adminUser.id,
// path: "index.html",
// filename: "index.html",
// content: '<p class="text-lg text-red-500">Hello world!</p>',
// },
// {
// userId: adminUser.id,
// path: "styles.css",
// filename: "styles.css",
// content: "body { padding: 16px; }",
// },
// {
// userId: adminUser.id,
// path: "script.js",
// filename: "script.js",
// content: "console.log('hello world!');",
// },
// {
// userId: adminUser.id,
// path: "_layout.html",
// filename: "_layout.html",
// content: `<!DOCTYPE html>
// <html lang="en">
// <head>
// <meta charset="UTF-8">
// <meta name="viewport" content="width=device-width, initial-scale=1.0">
// <title>Document</title>
// <link rel="stylesheet" href="styles.css">
// <script src="https://cdn.tailwindcss.com"></script>
// </head>
// <body>
// {CONTENT}
// <script src="script.js" type="module" defer></script>
// </body>
// </html>`,
// },
// ])
// .execute();
// react template
await db
.insert(file)
.values([
@@ -19,40 +65,69 @@ const main = async () => {
userId: adminUser.id,
path: "index.html",
filename: "index.html",
content: '<p class="text-lg text-red-500">Hello world!</p>',
content: `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>React + Tailwind Template</title>
<link rel="stylesheet" href="globals.css" />
</head>
<body>
<div id="app"></div>
<script src="index.jsx" type="module" defer></script>
</body>
</html>
`,
},
{
userId: adminUser.id,
path: "styles.css",
filename: "styles.css",
content: "body { padding: 16px; }",
},
{
userId: adminUser.id,
path: "script.js",
filename: "script.js",
content: "console.log('hello world!');",
},
{
userId: adminUser.id,
path: "_layout.html",
filename: "_layout.html",
content: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
path: "globals.css",
filename: "globals.css",
content: `@tailwind base;
@tailwind components;
@tailwind utilities;
<link rel="stylesheet" href="styles.css">
body {
@apply p-4;
}
`,
},
{
userId: adminUser.id,
path: "index.jsx",
filename: "index.jsx",
content: `import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
{CONTENT}
<script src="script.js" type="module" defer></script>
</body>
</html>`,
const root = createRoot(document.getElementById("app"));
root.render(<App />);
`,
},
{
userId: adminUser.id,
path: "App.jsx",
filename: "App.jsx",
isPinned: true,
content: `import React from "react";
const App = () => {
return (
<div>
<h1 class="text-xl font-medium text-blue-500">
React + Tailwind Template!
</h1>
<p>Open App.jsx to edit this text.</p>
</div>
);
};
export default App;
`,
},
])
.execute();
+4 -1
View File
@@ -1,6 +1,7 @@
import express from "express";
import { renderPage } from "vike/server";
import { IS_DEV } from "./lib/consts";
import cookieParser from "cookie-parser";
import api from "./api";
async function createServer() {
@@ -22,11 +23,13 @@ async function createServer() {
app.use(express.static(root + "/dist/client"));
}
app.use(cookieParser());
app.use("/api", api);
app.use("*", async (req, res, next) => {
const url = req.originalUrl;
const pageContext = {};
const pageContext = { req, cookies: req.cookies };
const ctx = await renderPage({ urlOriginal: url, ...pageContext });
const { httpResponse } = ctx;
+11
View File
@@ -0,0 +1,11 @@
import { Mime } from "mime/lite";
import standardTypes from "mime/types/standard.js";
import otherTypes from "mime/types/other.js";
const mime = new Mime(standardTypes, otherTypes);
mime.define({ "text/javascript": ["jsx", "tsx"] }, true);
export const getMimeType = (
ext: string,
defaultMime: string = "application/octet-stream"
) => mime.getType(ext) || defaultMime;
+50
View File
@@ -0,0 +1,50 @@
import fs from "node:fs/promises";
import path from "node:path";
import { and, eq, isNull } from "drizzle-orm";
import db from "../db";
import { file } from "../db/schema/file";
import { fileExists, getProjectDir } from "./utils";
import { getFileExt } from "~/lib/utils";
type UnpackProjectOptions = {
ext: string;
};
export const unpackProject = async (
opt: Partial<UnpackProjectOptions> = {}
) => {
const files = await db.query.file.findMany({
where: and(
eq(file.isDirectory, false),
eq(file.isFile, false),
isNull(file.deletedAt)
),
});
const projectDir = getProjectDir();
if (!fileExists(projectDir)) {
await fs.mkdir(projectDir, { recursive: true });
}
for (const file of files) {
const ext = getFileExt(file.filename);
// skip file if not in included extension list
if (opt.ext && opt.ext.length > 0 && !opt.ext.split(",").includes(ext)) {
continue;
}
const fpath = path.resolve(
projectDir,
file.path.replace(/(\.{2})(\/+)/g, "")
);
const dir = path.dirname(fpath);
if (!fileExists(dir)) {
await fs.mkdir(dir, { recursive: true });
}
await fs.writeFile(fpath, file.content || "");
}
return projectDir;
};
+15
View File
@@ -0,0 +1,15 @@
import fs from "node:fs";
import path from "node:path";
export const fileExists = (path: string) => {
try {
fs.accessSync(path, fs.constants.F_OK);
return true;
} catch (e) {
return false;
}
};
export const getProjectDir = () => {
return path.resolve(process.cwd(), "storage/tmp/project1");
};
+1 -1
View File
@@ -21,7 +21,7 @@ const fileRouter = router({
opt?.isPinned ? eq(file.isPinned, true) : undefined
),
orderBy: [desc(file.isDirectory), asc(file.filename)],
columns: { content: false },
columns: !file.isPinned ? { content: true } : undefined,
});
return files;