mirror of
https://github.com/khairul169/code-share.git
synced 2026-10-24 11:53:58 +07:00
feat: fix panel persist over server rendering, etc
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 {};
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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");
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user