fix: typescript support fix, add some params to editor, etc

This commit is contained in:
Khairul Hidayat 2024-02-28 22:32:31 +00:00
parent 7bdd995394
commit 543260479a
16 changed files with 136 additions and 34 deletions

View File

@ -130,13 +130,18 @@ function getLangMetadata(lang: string) {
case "js": case "js":
case "ts": case "ts":
case "tsx": case "tsx":
const isTypescript = ["tsx", "ts"].includes(lang);
extensions = [ extensions = [
javascript({ javascript({
jsx: ["jsx", "tsx"].includes(lang), jsx: ["jsx", "tsx"].includes(lang),
typescript: ["tsx", "ts"].includes(lang), typescript: isTypescript,
}), }),
]; ];
formatter = ["babel", prettierBabelPlugin, prettierPluginEstree]; formatter = [
isTypescript ? "babel-ts" : "babel",
prettierBabelPlugin,
prettierPluginEstree,
];
break; break;
} }

View File

@ -26,6 +26,7 @@
"@types/cookie-parser": "^1.4.6", "@types/cookie-parser": "^1.4.6",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.5", "@types/jsonwebtoken": "^9.0.5",
"@types/morgan": "^1.9.9",
"@types/node": "^20.11.19", "@types/node": "^20.11.19",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/react": "^18.2.57", "@types/react": "^18.2.57",
@ -74,6 +75,7 @@
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lucide-react": "^0.331.0", "lucide-react": "^0.331.0",
"mime": "^4.0.1", "mime": "^4.0.1",
"morgan": "^1.10.0",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"postcss": "^8", "postcss": "^8",

View File

@ -15,20 +15,23 @@ import { Data } from "./+data";
const ViewProjectPage = () => { const ViewProjectPage = () => {
const { project } = useData<Data>(); const { project } = useData<Data>();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const isCompact = !!( const isCompact = Boolean(searchParams.get("compact"));
searchParams.get("compact") || searchParams.get("embed") const isEmbed = Boolean(searchParams.get("embed"));
); const hidePreview = searchParams.get("preview") === "0";
const previewUrl = getPreviewUrl(project, "index.html"); const previewUrl = getPreviewUrl(project, "index.html");
return ( return (
<ProjectContext.Provider value={{ project, isCompact }}> <ProjectContext.Provider value={{ project, isCompact, isEmbed }}>
<ResizablePanelGroup <ResizablePanelGroup
autoSaveId="main-panel" autoSaveId="main-panel"
direction={{ sm: "vertical", md: "horizontal" }} direction={{ sm: "vertical", md: "horizontal" }}
className={cn("w-full !h-dvh bg-slate-600", !isCompact ? "md:p-4" : "")} className={cn(
"w-full !h-dvh bg-slate-600",
!isCompact && !isEmbed ? "md:p-4" : ""
)}
> >
<ResizablePanel <ResizablePanel
defaultSize={60} defaultSize={hidePreview ? 100 : 60}
collapsible collapsible
collapsedSize={0} collapsedSize={0}
minSize={30} minSize={30}
@ -38,13 +41,14 @@ const ViewProjectPage = () => {
<ResizableHandle <ResizableHandle
withHandle withHandle
className={ className={
!isCompact !isCompact && !isEmbed
? "bg-slate-800 md:bg-transparent hover:bg-slate-500 transition-colors md:mx-1 w-2 md:data-[panel-group-direction=vertical]:h-2 md:rounded-lg" ? "bg-slate-800 md:bg-transparent hover:bg-slate-500 transition-colors md:mx-1 w-2 md:data-[panel-group-direction=vertical]:h-2 md:rounded-lg"
: "bg-slate-800" : "bg-slate-800"
} }
/> />
<WebPreview <WebPreview
defaultSize={40} defaultSize={40}
defaultCollapsed={hidePreview}
collapsible collapsible
collapsedSize={0} collapsedSize={0}
minSize={10} minSize={10}

View File

@ -86,6 +86,7 @@ const APIManager = () => {
const Actions = ({ stats }: any) => { const Actions = ({ stats }: any) => {
const { project } = useProjectContext(); const { project } = useProjectContext();
const restart = useMutation({ const restart = useMutation({
mutationFn: () => { mutationFn: () => {
return api(`/sandbox/${project.slug}/restart`, { method: "POST" }); return api(`/sandbox/${project.slug}/restart`, { method: "POST" });

View File

@ -73,10 +73,10 @@ const Editor = () => {
} }
}, [openedFilesData.data]); }, [openedFilesData.data]);
useEffect(() => { // useEffect(() => {
// start API sandbox // // start API sandbox
api(`/sandbox/${project.slug}/start`, { method: "POST" }).catch(() => {}); // api(`/sandbox/${project.slug}/start`, { method: "POST" }).catch(() => {});
}, [project]); // }, [project]);
const onOpenFile = useCallback( const onOpenFile = useCallback(
(fileId: number, autoSwitchTab = true) => { (fileId: number, autoSwitchTab = true) => {
@ -154,17 +154,18 @@ const Editor = () => {
}); });
} }
tabs.push({ // tabs.push({
title: "API", // title: "API",
icon: <FiServer />, // icon: <FiServer />,
render: () => <APIManager />, // render: () => <APIManager />,
locked: true, // locked: true,
}); // });
return tabs; return tabs;
}, [curOpenFiles, openedFiles, breakpoint]); }, [curOpenFiles, openedFiles, breakpoint]);
const PanelComponent = !projectCtx.isCompact ? Panel : "div"; const PanelComponent =
!projectCtx.isCompact || !projectCtx.isEmbed ? Panel : "div";
return ( return (
<EditorContext.Provider <EditorContext.Provider
@ -181,8 +182,8 @@ const Editor = () => {
className="flex-1 order-2 md:order-1" className="flex-1 order-2 md:order-1"
> >
<Sidebar <Sidebar
defaultSize={{ sm: 50, md: 25 }} defaultSize={{ md: 50, lg: 25 }}
defaultCollapsed={{ sm: true, md: false }} defaultCollapsed={{ md: true, lg: false }}
minSize={10} minSize={10}
collapsible collapsible
collapsedSize={0} collapsedSize={0}
@ -190,7 +191,7 @@ const Editor = () => {
<ResizableHandle className="w-0" /> <ResizableHandle className="w-0" />
<ResizablePanel defaultSize={{ sm: 100, md: 75 }}> <ResizablePanel defaultSize={{ md: 100, lg: 75 }}>
<ResizablePanelGroup autoSaveId="code-editor" direction="vertical"> <ResizablePanelGroup autoSaveId="code-editor" direction="vertical">
<ResizablePanel defaultSize={{ sm: 100, md: 80 }} minSize={20}> <ResizablePanel defaultSize={{ sm: 100, md: 80 }} minSize={20}>
<Tabs <Tabs

View File

@ -56,7 +56,7 @@ const WebPreview = ({ url, ...props }: WebPreviewProps) => {
refresh(); refresh();
}, [refresh, togglePanel]); }, [refresh, togglePanel]);
const PanelComponent = !project.isCompact ? Panel : "div"; const PanelComponent = !project.isCompact || !project.isEmbed ? Panel : "div";
return ( return (
<ResizablePanel <ResizablePanel
@ -89,7 +89,7 @@ const WebPreview = ({ url, ...props }: WebPreviewProps) => {
id="web-preview" id="web-preview"
ref={frameRef} ref={frameRef}
className="border-none w-full flex-1 overflow-hidden bg-white" className="border-none w-full flex-1 overflow-hidden bg-white"
sandbox="allow-scripts" sandbox="allow-scripts allow-forms"
/> />
) : null} ) : null}
</PanelComponent> </PanelComponent>

View File

@ -4,6 +4,7 @@ import type { ProjectSchema } from "~/server/db/schema/project";
type TProjectContext = { type TProjectContext = {
project: ProjectSchema; project: ProjectSchema;
isCompact?: boolean; isCompact?: boolean;
isEmbed?: boolean;
}; };
const ProjectContext = createContext<TProjectContext | null>(null); const ProjectContext = createContext<TProjectContext | null>(null);

48
pnpm-lock.yaml generated
View File

@ -110,6 +110,9 @@ dependencies:
mime: mime:
specifier: ^4.0.1 specifier: ^4.0.1
version: 4.0.1 version: 4.0.1
morgan:
specifier: ^1.10.0
version: 1.10.0
node-fetch: node-fetch:
specifier: ^3.3.2 specifier: ^3.3.2
version: 3.3.2 version: 3.3.2
@ -178,6 +181,9 @@ devDependencies:
'@types/jsonwebtoken': '@types/jsonwebtoken':
specifier: ^9.0.5 specifier: ^9.0.5
version: 9.0.5 version: 9.0.5
'@types/morgan':
specifier: ^1.9.9
version: 1.9.9
'@types/node': '@types/node':
specifier: ^20.11.19 specifier: ^20.11.19
version: 20.11.19 version: 20.11.19
@ -2338,6 +2344,12 @@ packages:
resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==}
dev: true dev: true
/@types/morgan@1.9.9:
resolution: {integrity: sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==}
dependencies:
'@types/node': 20.11.19
dev: true
/@types/node@20.11.19: /@types/node@20.11.19:
resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==} resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==}
dependencies: dependencies:
@ -2703,6 +2715,13 @@ packages:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: false dev: false
/basic-auth@2.0.1:
resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==}
engines: {node: '>= 0.8'}
dependencies:
safe-buffer: 5.1.2
dev: false
/basic-ftp@5.0.4: /basic-ftp@5.0.4:
resolution: {integrity: sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==} resolution: {integrity: sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
@ -4846,6 +4865,19 @@ packages:
hasBin: true hasBin: true
dev: false dev: false
/morgan@1.10.0:
resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==}
engines: {node: '>= 0.8.0'}
dependencies:
basic-auth: 2.0.1
debug: 2.6.9
depd: 2.0.0
on-finished: 2.3.0
on-headers: 1.0.2
transitivePeerDependencies:
- supports-color
dev: false
/mrmime@2.0.0: /mrmime@2.0.0:
resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -5022,6 +5054,13 @@ packages:
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
dev: false dev: false
/on-finished@2.3.0:
resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
engines: {node: '>= 0.8'}
dependencies:
ee-first: 1.1.1
dev: false
/on-finished@2.4.1: /on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -5029,6 +5068,11 @@ packages:
ee-first: 1.1.1 ee-first: 1.1.1
dev: false dev: false
/on-headers@1.0.2:
resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
engines: {node: '>= 0.8'}
dev: false
/once@1.4.0: /once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies: dependencies:
@ -5883,6 +5927,10 @@ packages:
dependencies: dependencies:
queue-microtask: 1.2.3 queue-microtask: 1.2.3
/safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: false
/safe-buffer@5.2.1: /safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}

View File

@ -3,11 +3,12 @@ import preview from "./preview";
import trpc from "./trpc/handler"; import trpc from "./trpc/handler";
import { thumbnail } from "./thumbnail"; import { thumbnail } from "./thumbnail";
import sandbox from "./sandbox"; import sandbox from "./sandbox";
import { nocache } from "../middlewares/nocache";
const api = Router(); const api = Router();
api.use("/trpc", trpc); api.use("/trpc", trpc);
api.use("/preview", preview); api.use("/preview", nocache, preview);
api.use("/sandbox", sandbox); api.use("/sandbox", sandbox);
api.get("/thumbnail/:slug", thumbnail); api.get("/thumbnail/:slug", thumbnail);

View File

@ -1,3 +1,4 @@
import { getFileExt } from "~/lib/utils";
import { FileSchema } from "~/server/db/schema/file"; import { FileSchema } from "~/server/db/schema/file";
import { ProjectSettingsSchema } from "~/server/db/schema/project"; import { ProjectSettingsSchema } from "~/server/db/schema/project";
import { transformJs } from "~/server/lib/transform-js"; import { transformJs } from "~/server/lib/transform-js";
@ -10,7 +11,9 @@ export const serveJs = async (
// transpile to es5 // transpile to es5
if (cfg?.transpiler === "swc") { if (cfg?.transpiler === "swc") {
content = await transformJs(content); const ext = getFileExt(file.filename);
const typescript = ["ts", "tsx"].includes(ext);
content = await transformJs(content, typescript ? "ts" : "js");
} }
return content; return content;

View File

@ -5,7 +5,6 @@ import { IS_DEV } from "./lib/consts";
import cookieParser from "cookie-parser"; import cookieParser from "cookie-parser";
import api from "./api"; import api from "./api";
import { authMiddleware } from "./middlewares/auth"; import { authMiddleware } from "./middlewares/auth";
import fetch from "node-fetch";
async function createServer() { async function createServer() {
const app = express(); const app = express();
@ -24,8 +23,12 @@ async function createServer() {
} else { } else {
// serve client assets // serve client assets
app.use(express.static(root + "/dist/client")); app.use(express.static(root + "/dist/client"));
const { default: morgan } = await import("morgan");
app.use(morgan("combined"));
} }
app.set("etag", false);
app.use(cookieParser()); app.use(cookieParser());
app.use(authMiddleware); app.use(authMiddleware);
@ -42,9 +45,11 @@ async function createServer() {
} }
const { body, statusCode, headers, earlyHints } = httpResponse; const { body, statusCode, headers, earlyHints } = httpResponse;
if (res.writeEarlyHints) {
res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) }); // FIXME: SSL cloudflare gak bisa digunakan kalo ada ini..
} // if (res.writeEarlyHints) {
// res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) });
// }
headers.forEach(([name, value]) => res.setHeader(name, value)); headers.forEach(([name, value]) => res.setHeader(name, value));
res.status(statusCode).send(body); res.status(statusCode).send(body);

View File

@ -3,7 +3,7 @@ import standardTypes from "mime/types/standard.js";
import otherTypes from "mime/types/other.js"; import otherTypes from "mime/types/other.js";
const mime = new Mime(standardTypes, otherTypes); const mime = new Mime(standardTypes, otherTypes);
mime.define({ "text/javascript": ["jsx", "tsx"] }, true); mime.define({ "text/javascript": ["jsx", "tsx", "ts"] }, true);
export const getMimeType = ( export const getMimeType = (
ext: string, ext: string,

View File

@ -1,12 +1,13 @@
import * as swc from "@swc/core"; import * as swc from "@swc/core";
export const transformJs = async (code: string) => { export const transformJs = async (code: string, type: "js" | "ts" = "js") => {
try { try {
const result = await swc.transform(code, { const result = await swc.transform(code, {
jsc: { jsc: {
parser: { parser: {
syntax: type === "js" ? "ecmascript" : "typescript",
jsx: true, jsx: true,
syntax: "ecmascript", tsx: true,
}, },
target: "es5", target: "es5",
}, },
@ -14,6 +15,7 @@ export const transformJs = async (code: string) => {
return result.code; return result.code;
} catch (err) { } catch (err) {
// console.log(err);
return code; return code;
} }
}; };

View File

@ -17,6 +17,7 @@ export const unpackProject = async (
) => { ) => {
const files = await db.query.file.findMany({ const files = await db.query.file.findMany({
where: and( where: and(
eq(file.projectId, projectData.id),
eq(file.isDirectory, false), eq(file.isDirectory, false),
eq(file.isFile, false), eq(file.isFile, false),
isNull(file.deletedAt) isNull(file.deletedAt)

View File

@ -0,0 +1,12 @@
import { NextFunction, Request, Response } from "express";
export const nocache = (_req: Request, res: Response, next: NextFunction) => {
res.setHeader("Surrogate-Control", "no-store");
res.setHeader(
"Cache-Control",
"no-store, no-cache, must-revalidate, proxy-revalidate"
);
res.setHeader("Expires", "0");
next();
};

View File

@ -116,6 +116,17 @@ const projectRouter = router({
const projectId = projectData.id; const projectId = projectData.id;
if (input.forkFromId) { if (input.forkFromId) {
const forkProject = await tx.query.project.findFirst({
where: and(
eq(project.id, input.forkFromId),
isNull(project.deletedAt)
),
});
if (!forkProject) {
throw new Error("Fork Project not found!");
}
const forkFiles = await tx.query.file.findMany({ const forkFiles = await tx.query.file.findMany({
where: and( where: and(
eq(file.projectId, input.forkFromId), eq(file.projectId, input.forkFromId),
@ -132,6 +143,11 @@ const projectRouter = router({
await tx await tx
.insert(file) .insert(file)
.values(forkFiles.map((file) => ({ ...file, projectId }))); .values(forkFiles.map((file) => ({ ...file, projectId })));
await tx
.update(project)
.set({ settings: forkProject.settings })
.where(eq(project.id, projectData.id));
} else { } else {
await tx.insert(file).values([ await tx.insert(file).values([
{ {