mirror of
https://github.com/khairul169/code-share.git
synced 2025-04-28 16:49:36 +07:00
Merge pull request #1 from khairul169/rebuild
feat: rebuild app using vike
This commit is contained in:
commit
2c9faa332a
@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
43
.gitignore
vendored
43
.gitignore
vendored
@ -1,42 +1,5 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
storage/**/*
|
||||
!.gitkeep
|
||||
|
||||
.env
|
||||
dist/
|
||||
|
23
.swcrc
Normal file
23
.swcrc
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "ecmascript",
|
||||
"jsx": false,
|
||||
"dynamicImport": false,
|
||||
"privateMethod": false,
|
||||
"functionBind": false,
|
||||
"exportDefaultFrom": false,
|
||||
"exportNamespaceFrom": false,
|
||||
"decorators": false,
|
||||
"decoratorsBeforeExport": false,
|
||||
"topLevelAwait": false,
|
||||
"importMeta": false
|
||||
},
|
||||
"target": "es2020",
|
||||
"loose": false,
|
||||
"externalHelpers": false,
|
||||
"keepClassNames": false
|
||||
},
|
||||
"minify": false
|
||||
}
|
36
README.md
36
README.md
@ -1,36 +0,0 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
@ -5,13 +5,13 @@
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/app/globals.css",
|
||||
"css": "renderer/globals.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": false,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
"components": "~/components",
|
||||
"utils": "~/lib/utils"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
45
components/containers/error-boundary.tsx
Normal file
45
components/containers/error-boundary.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
|
||||
type ErrorBoundaryProps = {
|
||||
fallback?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
type ErrorBoundaryState = {
|
||||
error?: Error | null;
|
||||
};
|
||||
|
||||
class ErrorBoundary extends React.Component<
|
||||
ErrorBoundaryProps,
|
||||
ErrorBoundaryState
|
||||
> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: unknown) {
|
||||
return { error };
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, fallback } = this.props;
|
||||
const { error } = this.state;
|
||||
|
||||
if (error) {
|
||||
if (!fallback) {
|
||||
return (
|
||||
<div className="p-4 text-sm">
|
||||
<p>An error occured!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
@ -1,6 +1,6 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { cn } from "~/lib/utils";
|
||||
import React, { forwardRef } from "react";
|
||||
import { IconType } from "react-icons/lib";
|
||||
|
@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300",
|
@ -17,8 +17,8 @@ import prettierCssPlugin from "prettier/plugins/postcss";
|
||||
import prettierBabelPlugin from "prettier/plugins/babel";
|
||||
import * as prettierPluginEstree from "prettier/plugins/estree";
|
||||
import { abbreviationTracker } from "@emmetio/codemirror6-plugin";
|
||||
import { useDebounce } from "@/hooks/useDebounce";
|
||||
import useCommandKey from "@/hooks/useCommandKey";
|
||||
import { useDebounce } from "~/hooks/useDebounce";
|
||||
import useCommandKey from "~/hooks/useCommandKey";
|
||||
|
||||
type Props = {
|
||||
lang?: string;
|
@ -4,7 +4,7 @@ import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
|
@ -4,7 +4,7 @@ import * as React from "react";
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||
import { Check, ChevronRight, Circle } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FileSchema } from "@/server/db/schema/file";
|
||||
import { FileSchema } from "~/server/db/schema/file";
|
||||
import { ComponentProps } from "react";
|
||||
import { FiFile, FiFolder } from "react-icons/fi";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useForm } from "@/hooks/useForm";
|
||||
import { useForm } from "~/hooks/useForm";
|
||||
import { FieldValues } from "react-hook-form";
|
||||
import React from "react";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
@ -17,9 +17,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input }
|
||||
export { Input };
|
@ -1,4 +1,4 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from "~/lib/utils";
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
@ -3,7 +3,7 @@
|
||||
import { GripVertical } from "lucide-react";
|
||||
import * as ResizablePrimitive from "react-resizable-panels";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const ResizablePanelGroup = ({
|
||||
className,
|
12
components/ui/spinner.tsx
Normal file
12
components/ui/spinner.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
type Spinner = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Spinner = ({ className }: Spinner) => {
|
||||
return <Loader2 className={cn("size-8 animate-spin", className)} />;
|
||||
};
|
||||
|
||||
export default Spinner;
|
@ -1,4 +1,4 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from "~/lib/utils";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import ActionButton from "./action-button";
|
||||
import { FiX } from "react-icons/fi";
|
@ -1,11 +1,10 @@
|
||||
import path from "node:path";
|
||||
import type { Config } from "drizzle-kit";
|
||||
|
||||
export default {
|
||||
schema: "./src/server/db/schema/_schema.ts",
|
||||
out: "./src/server/db/drizzle",
|
||||
schema: "./server/db/schema/_schema.ts",
|
||||
out: "./server/db/drizzle",
|
||||
driver: "better-sqlite",
|
||||
dbCredentials: {
|
||||
url: path.join(process.cwd(), "storage/database.db"),
|
||||
url: "./storage/database.db",
|
||||
},
|
||||
} satisfies Config;
|
||||
|
2
lib/consts.ts
Normal file
2
lib/consts.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const BASE_URL =
|
||||
typeof window !== "undefined" ? location.protocol + "//" + location.host : "";
|
@ -1,5 +1,5 @@
|
||||
import { createTRPCReact } from "@trpc/react-query";
|
||||
import type { AppRouter } from "@/server/routers/_app";
|
||||
import type { AppRouter } from "~/server/routers/_app";
|
||||
|
||||
export const getBaseUrl = () => {
|
||||
if (typeof window !== "undefined") return "";
|
@ -1,4 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
|
||||
export default nextConfig;
|
71
package.json
71
package.json
@ -1,24 +1,41 @@
|
||||
{
|
||||
"name": "code-share",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"name": "vike",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "server/index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently --kill-others \"next dev\" \"npm run transformer:dev\"",
|
||||
"build": "next build && npm run transformer:build",
|
||||
"start": "concurrently --kill-others \"next start\" \"npm run transformer:start\"",
|
||||
"lint": "next lint",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "tsx watch --ignore *.mjs server/index.ts",
|
||||
"start": "NODE_ENV=production tsx server/index.ts",
|
||||
"build": "vite build",
|
||||
"generate": "drizzle-kit generate:sqlite",
|
||||
"drop": "drizzle-kit drop",
|
||||
"push": "drizzle-kit push:sqlite",
|
||||
"migrate": "tsx src/server/db/migrate.ts",
|
||||
"seed": "tsx src/server/db/seed.ts",
|
||||
"reset": "rm -f storage/database.db && npm run push && npm run seed",
|
||||
"transformer:start": "node dist/transformer/server.js",
|
||||
"transformer:dev": "tsx --watch src/server/transformer/server.ts",
|
||||
"transformer:build": "tsc --project tsconfig-server.json"
|
||||
"migrate": "tsx server/db/migrate.ts",
|
||||
"seed": "tsx server/db/seed.ts",
|
||||
"reset": "rm -f storage/database.db && npm run push && npm run seed"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@swc/cli": "^0.3.9",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.11.19",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/react": "^18.2.57",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"drizzle-kit": "^0.20.14",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"@codemirror/lang-css": "^6.2.1",
|
||||
"@codemirror/lang-html": "^6.4.8",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
@ -32,49 +49,33 @@
|
||||
"@swc/core": "^1.4.2",
|
||||
"@tanstack/react-query": "^5.21.7",
|
||||
"@trpc/client": "11.0.0-next-beta.289",
|
||||
"@trpc/next": "11.0.0-next-beta.289",
|
||||
"@trpc/react-query": "11.0.0-next-beta.289",
|
||||
"@trpc/server": "11.0.0-next-beta.289",
|
||||
"@types/express": "^4.17.21",
|
||||
"@uiw/codemirror-theme-tokyo-night": "^4.21.22",
|
||||
"@uiw/react-codemirror": "^4.21.22",
|
||||
"bcrypt": "^5.1.1",
|
||||
"better-sqlite3": "^9.4.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"console-feed": "^3.5.0",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"drizzle-orm": "^0.29.3",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
"express": "^4.18.2",
|
||||
"lucide-react": "^0.331.0",
|
||||
"mime": "^4.0.1",
|
||||
"next": "14.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"prettier": "^3.2.5",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.50.1",
|
||||
"react-icons": "^5.0.1",
|
||||
"react-resizable-panels": "^2.0.9",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"usehooks-ts": "^2.14.0",
|
||||
"vike": "^0.4.162",
|
||||
"zod": "^3.22.4",
|
||||
"zustand": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/better-sqlite3": "^7.6.9",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"concurrently": "^8.2.2",
|
||||
"drizzle-kit": "^0.20.14",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.1.0",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
17
pages/@id/+Page.tsx
Normal file
17
pages/@id/+Page.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { useData } from "~/renderer/hooks";
|
||||
import { Data } from "./+data";
|
||||
import Link from "~/renderer/link";
|
||||
|
||||
const ViewPostPage = () => {
|
||||
const { post } = useData<Data>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Link href="/">Go Back</Link>
|
||||
<h1>{post.title}</h1>
|
||||
<p>{post.body}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewPostPage;
|
12
pages/@id/+data.ts
Normal file
12
pages/@id/+data.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { PageContext } from "vike/types";
|
||||
|
||||
export const data = async (ctx: PageContext) => {
|
||||
const id = ctx.routeParams?.id;
|
||||
const post = await fetch(
|
||||
"https://jsonplaceholder.typicode.com/posts/" + id
|
||||
).then((response) => response.json());
|
||||
|
||||
return { post, title: post?.title };
|
||||
};
|
||||
|
||||
export type Data = Awaited<ReturnType<typeof data>>;
|
20
pages/_error/+Page.tsx
Normal file
20
pages/_error/+Page.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { usePageContext } from "~/renderer/context";
|
||||
|
||||
const Page = () => {
|
||||
const pageContext = usePageContext();
|
||||
let { abortReason } = pageContext;
|
||||
|
||||
if (!abortReason) {
|
||||
abortReason = pageContext.is404
|
||||
? "Page not found."
|
||||
: "Something went wrong.";
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p style={{ fontSize: "1.3em" }}>{abortReason}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
24
pages/index/+Page.tsx
Normal file
24
pages/index/+Page.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import type { Data } from "./+data";
|
||||
import { useData } from "~/renderer/hooks";
|
||||
import Link from "~/renderer/link";
|
||||
|
||||
const HomePage = () => {
|
||||
const { posts } = useData<Data>();
|
||||
if (!posts?.length) {
|
||||
return <p>No posts.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Posts</h1>
|
||||
|
||||
{posts.map((post: any) => (
|
||||
<Link key={post.id} href={`/${post.id}`}>
|
||||
{post.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePage;
|
9
pages/index/+data.ts
Normal file
9
pages/index/+data.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const data = async () => {
|
||||
const posts = await fetch(
|
||||
"https://jsonplaceholder.typicode.com/posts?_limit=20"
|
||||
).then((response) => response.json());
|
||||
|
||||
return { posts };
|
||||
};
|
||||
|
||||
export type Data = Awaited<ReturnType<typeof data>>;
|
@ -1,39 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable";
|
||||
import WebPreview from "./_components/web-preview";
|
||||
import { usePortrait } from "@/hooks/usePortrait";
|
||||
import Editor from "./_components/editor";
|
||||
} from "~/components/ui/resizable";
|
||||
import WebPreview from "./components/web-preview";
|
||||
import { usePortrait } from "~/hooks/usePortrait";
|
||||
import Editor from "./components/editor";
|
||||
import ProjectContext from "./context/project";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { withClientOnly } from "~/renderer/client-only";
|
||||
import { useParams, useSearchParams } from "~/renderer/hooks";
|
||||
import { BASE_URL } from "~/lib/consts";
|
||||
|
||||
const ViewProjectPage = () => {
|
||||
const [isMounted, setMounted] = useState(false);
|
||||
const isPortrait = usePortrait();
|
||||
const searchParams = useSearchParams();
|
||||
const params = useParams();
|
||||
const isCompact =
|
||||
searchParams.get("compact") === "1" || searchParams.get("embed") === "1";
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!isMounted) {
|
||||
return null;
|
||||
}
|
||||
const slug = params["slug"];
|
||||
const previewUrl = BASE_URL + `/api/preview/${slug}/index.html`;
|
||||
|
||||
return (
|
||||
<ProjectContext.Provider value={{ isCompact }}>
|
||||
<ProjectContext.Provider value={{ slug, isCompact }}>
|
||||
<ResizablePanelGroup
|
||||
autoSaveId="main-panel"
|
||||
direction={isPortrait ? "vertical" : "horizontal"}
|
||||
className={cn("w-full !h-dvh", !isCompact ? "md:p-4" : "")}
|
||||
className={cn("w-full !h-dvh bg-slate-600", !isCompact ? "md:p-4" : "")}
|
||||
>
|
||||
<ResizablePanel
|
||||
defaultSize={isPortrait ? 50 : 60}
|
||||
@ -57,11 +50,11 @@ const ViewProjectPage = () => {
|
||||
collapsedSize={0}
|
||||
minSize={10}
|
||||
>
|
||||
<WebPreview />
|
||||
<WebPreview url={previewUrl} />
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</ProjectContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ViewProjectPage;
|
||||
export default withClientOnly(ViewProjectPage);
|
42
pages/project/@slug/components/console-logger.tsx
Normal file
42
pages/project/@slug/components/console-logger.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Console, Decode } from "console-feed";
|
||||
import type { Message } from "console-feed/lib/definitions/Console";
|
||||
import ErrorBoundary from "~/components/containers/error-boundary";
|
||||
|
||||
const ConsoleLogger = () => {
|
||||
const [logs, setLogs] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const onMessage = (event: MessageEvent<any>) => {
|
||||
const { data: eventData } = event;
|
||||
if (!eventData || eventData.type !== "console") {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = Decode(eventData.data);
|
||||
if (!data || !data.method || !data.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLogs((i) => [data, ...i]);
|
||||
};
|
||||
|
||||
window.addEventListener("message", onMessage);
|
||||
return () => {
|
||||
window.removeEventListener("message", onMessage);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col bg-[#242424] border-t border-t-gray-600">
|
||||
<p className="py-2 px-3 uppercase text-xs">Console</p>
|
||||
<ErrorBoundary>
|
||||
<div className="overflow-y-auto flex-1">
|
||||
<Console logs={logs} variant="dark" />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConsoleLogger;
|
@ -1,18 +1,17 @@
|
||||
import React, { useMemo } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "../../../../components/ui/dialog";
|
||||
import { UseDiscloseReturn } from "@/hooks/useDisclose";
|
||||
import { UseDiscloseReturn } from "~/hooks/useDisclose";
|
||||
import { Input } from "../../../../components/ui/input";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
import { useForm } from "@/hooks/useForm";
|
||||
import { useForm } from "~/hooks/useForm";
|
||||
import { z } from "zod";
|
||||
import FormErrorMessage from "../../../../components/ui/form-error-message";
|
||||
import trpc from "@/lib/trpc";
|
||||
import type { FileSchema } from "@/server/db/schema/file";
|
||||
import trpc from "~/lib/trpc";
|
||||
import type { FileSchema } from "~/server/db/schema/file";
|
||||
import { useWatch } from "react-hook-form";
|
||||
|
||||
type Props = {
|
@ -1,28 +1,22 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@/components/ui/resizable";
|
||||
import Tabs, { Tab } from "@/components/ui/tabs";
|
||||
} from "~/components/ui/resizable";
|
||||
import Tabs, { Tab } from "~/components/ui/tabs";
|
||||
import FileViewer from "./file-viewer";
|
||||
import trpc from "@/lib/trpc";
|
||||
import trpc from "~/lib/trpc";
|
||||
import EditorContext from "../context/editor";
|
||||
import type { FileSchema } from "@/server/db/schema/file";
|
||||
import { usePortrait } from "@/hooks/usePortrait";
|
||||
import Panel from "@/components/ui/panel";
|
||||
import { previewStore } from "./web-preview";
|
||||
import type { FileSchema } from "~/server/db/schema/file";
|
||||
import { usePortrait } from "~/hooks/usePortrait";
|
||||
import Panel from "~/components/ui/panel";
|
||||
import { previewStore } from "../stores/web-preview";
|
||||
import { useProjectContext } from "../context/project";
|
||||
import { ImperativePanelHandle } from "react-resizable-panels";
|
||||
import Sidebar from "./sidebar";
|
||||
import useCommandKey from "@/hooks/useCommandKey";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import useCommandKey from "~/hooks/useCommandKey";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { FaCompress, FaCompressArrowsAlt } from "react-icons/fa";
|
||||
import ConsoleLogger from "./console-logger";
|
||||
|
||||
@ -194,7 +188,7 @@ const Editor = () => {
|
||||
autoCapitalize="code-editor"
|
||||
direction="vertical"
|
||||
>
|
||||
<ResizablePanel defaultSize={100} minSize={20}>
|
||||
<ResizablePanel defaultSize={isPortrait ? 100 : 80} minSize={20}>
|
||||
<Tabs
|
||||
tabs={openFileList}
|
||||
current={curTabIdx}
|
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { Fragment, useMemo, useState } from "react";
|
||||
import { UseDiscloseReturn, useDisclose } from "@/hooks/useDisclose";
|
||||
import { UseDiscloseReturn, useDisclose } from "~/hooks/useDisclose";
|
||||
import {
|
||||
FiChevronRight,
|
||||
FiFilePlus,
|
||||
@ -9,8 +9,8 @@ import {
|
||||
FiMoreVertical,
|
||||
} from "react-icons/fi";
|
||||
import { FaCheck, FaThumbtack } from "react-icons/fa";
|
||||
import trpc from "@/lib/trpc";
|
||||
import type { FileSchema } from "@/server/db/schema/file";
|
||||
import trpc from "~/lib/trpc";
|
||||
import type { FileSchema } from "~/server/db/schema/file";
|
||||
import CreateFileDialog, { CreateFileSchema } from "./createfile-dialog";
|
||||
import ActionButton from "../../../../components/ui/action-button";
|
||||
import { useEditorContext } from "../context/editor";
|
||||
@ -20,11 +20,11 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { cn, getUrl } from "@/lib/utils";
|
||||
import FileIcon from "@/components/ui/file-icon";
|
||||
} from "~/components/ui/dropdown-menu";
|
||||
import { cn, getUrl } from "~/lib/utils";
|
||||
import FileIcon from "~/components/ui/file-icon";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useParams } from "~/renderer/hooks";
|
||||
|
||||
const FileListing = () => {
|
||||
const { onOpenFile, onFileChanged } = useEditorContext();
|
||||
@ -175,14 +175,14 @@ const FileItem = ({ file, createFileDlg }: FileItemProps) => {
|
||||
Copy Path
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => copy(getUrl(`project/${slug}/file`, file.path))}
|
||||
onClick={() => copy(getUrl(`api/preview/${slug}`, file.path))}
|
||||
>
|
||||
Copy URL
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
window.open(
|
||||
getUrl(`project/${slug}/file`, file.path),
|
||||
getUrl(`api/preview/${slug}`, file.path),
|
||||
"_blank"
|
||||
)
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { getFileExt } from "@/lib/utils";
|
||||
import { getFileExt } from "~/lib/utils";
|
||||
import React from "react";
|
||||
import CodeEditor from "../../../../components/ui/code-editor";
|
||||
import trpc from "@/lib/trpc";
|
||||
import trpc from "~/lib/trpc";
|
||||
|
||||
type Props = {
|
||||
id: number;
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import FileListing from "./file-listing";
|
||||
import { FaUserCircle } from "react-icons/fa";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Button } from "~/components/ui/button";
|
||||
|
||||
const Sidebar = () => {
|
||||
return (
|
67
pages/project/@slug/components/web-preview.tsx
Normal file
67
pages/project/@slug/components/web-preview.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import Panel from "~/components/ui/panel";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useProjectContext } from "../context/project";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { FaEllipsisV, FaRedo } from "react-icons/fa";
|
||||
import { Input } from "~/components/ui/input";
|
||||
import { previewStore } from "../stores/web-preview";
|
||||
|
||||
type WebPreviewProps = {
|
||||
url?: string | null;
|
||||
};
|
||||
|
||||
const WebPreview = ({ url }: WebPreviewProps) => {
|
||||
const frameRef = useRef<HTMLIFrameElement>(null);
|
||||
const project = useProjectContext();
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
if (frameRef.current) {
|
||||
frameRef.current.src = `${url}?t=${Date.now()}`;
|
||||
}
|
||||
}, [url]);
|
||||
|
||||
useEffect(() => {
|
||||
previewStore.setState({ refresh });
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const PanelComponent = !project.isCompact ? Panel : "div";
|
||||
|
||||
return (
|
||||
<PanelComponent className="h-full flex flex-col bg-slate-800">
|
||||
<div className="h-10 flex items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="dark:hover:bg-slate-700"
|
||||
onClick={refresh}
|
||||
>
|
||||
<FaRedo />
|
||||
</Button>
|
||||
<Input
|
||||
className="flex-1 dark:bg-gray-900 dark:hover:bg-gray-950 h-8 rounded-full"
|
||||
value={url || ""}
|
||||
readOnly
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="dark:hover:bg-slate-700"
|
||||
onClick={() => {}}
|
||||
>
|
||||
<FaEllipsisV />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{url != null ? (
|
||||
<iframe
|
||||
id="web-preview"
|
||||
ref={frameRef}
|
||||
className="border-none w-full flex-1 overflow-hidden bg-white"
|
||||
sandbox="allow-scripts"
|
||||
/>
|
||||
) : null}
|
||||
</PanelComponent>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebPreview;
|
@ -1,4 +1,4 @@
|
||||
import type { FileSchema } from "@/server/db/schema/file";
|
||||
import type { FileSchema } from "~/server/db/schema/file";
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
type TEditorContext = {
|
@ -1,6 +1,7 @@
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
type TProjectContext = {
|
||||
slug: string;
|
||||
isCompact?: boolean;
|
||||
};
|
||||
|
9
pages/project/@slug/stores/web-preview.ts
Normal file
9
pages/project/@slug/stores/web-preview.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { createStore } from "zustand";
|
||||
|
||||
type PreviewStore = {
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
export const previewStore = createStore<PreviewStore>(() => ({
|
||||
refresh: () => {},
|
||||
}));
|
3509
pnpm-lock.yaml
generated
3509
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
@ -1,18 +0,0 @@
|
||||
if (window.parent !== window) {
|
||||
const _log = console.log;
|
||||
const _error = console.error;
|
||||
const _warn = console.warn;
|
||||
|
||||
console.log = function (...args) {
|
||||
parent.window.postMessage({ type: "log", args: args }, "*");
|
||||
_log(...args);
|
||||
};
|
||||
console.error = function (...args) {
|
||||
parent.window.postMessage({ type: "error", args: args }, "*");
|
||||
_error(...args);
|
||||
};
|
||||
console.warn = function (...args) {
|
||||
parent.window.postMessage({ type: "warn", args: args }, "*");
|
||||
_warn(...args);
|
||||
};
|
||||
}
|
1
public/js/hook-console.js
Normal file
1
public/js/hook-console.js
Normal file
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
Before Width: | Height: | Size: 1.3 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
Before Width: | Height: | Size: 629 B |
15
renderer/+config.ts
Normal file
15
renderer/+config.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import type { Config } from "vike/types";
|
||||
|
||||
export default {
|
||||
clientRouting: true,
|
||||
passToClient: ["routeParams"],
|
||||
meta: {
|
||||
title: {
|
||||
env: { server: true, client: true },
|
||||
},
|
||||
description: {
|
||||
env: { server: true },
|
||||
},
|
||||
},
|
||||
hydrationCanBeAborted: true,
|
||||
} satisfies Config;
|
5
renderer/+onPageTransitionEnd.ts
Normal file
5
renderer/+onPageTransitionEnd.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import NProgress from "nprogress";
|
||||
|
||||
export const onPageTransitionEnd = async () => {
|
||||
NProgress.done();
|
||||
};
|
5
renderer/+onPageTransitionStart.ts
Normal file
5
renderer/+onPageTransitionStart.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import NProgress from "nprogress";
|
||||
|
||||
export const onPageTransitionStart = async () => {
|
||||
NProgress.start();
|
||||
};
|
38
renderer/+onRenderClient.tsx
Normal file
38
renderer/+onRenderClient.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { getPageMetadata } from "./utils";
|
||||
import type { OnRenderClientAsync } from "vike/types";
|
||||
import Layout from "./layout";
|
||||
|
||||
let root: ReactDOM.Root;
|
||||
|
||||
export const onRenderClient: OnRenderClientAsync = async (
|
||||
pageContext
|
||||
): ReturnType<OnRenderClientAsync> => {
|
||||
const { Page } = pageContext;
|
||||
|
||||
if (!Page)
|
||||
throw new Error(
|
||||
"My onRenderClient() hook expects pageContext.Page to be defined"
|
||||
);
|
||||
|
||||
const container = document.getElementById("react-root");
|
||||
if (!container) throw new Error("DOM element #react-root not found");
|
||||
|
||||
const page = (
|
||||
<Layout pageContext={pageContext}>
|
||||
<Page />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
if (pageContext.isHydration) {
|
||||
root = ReactDOM.hydrateRoot(container, page);
|
||||
} else {
|
||||
if (!root) {
|
||||
root = ReactDOM.createRoot(container);
|
||||
}
|
||||
root.render(page);
|
||||
}
|
||||
|
||||
const meta = getPageMetadata(pageContext);
|
||||
document.title = meta.title;
|
||||
};
|
44
renderer/+onRenderHtml.tsx
Normal file
44
renderer/+onRenderHtml.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import { escapeInject, dangerouslySkipEscape } from "vike/server";
|
||||
import type { OnRenderHtmlAsync } from "vike/types";
|
||||
import { getPageMetadata } from "./utils";
|
||||
import Layout from "./layout";
|
||||
|
||||
export const onRenderHtml: OnRenderHtmlAsync = async (
|
||||
pageContext
|
||||
): ReturnType<OnRenderHtmlAsync> => {
|
||||
const { Page } = pageContext;
|
||||
|
||||
if (!Page)
|
||||
throw new Error(
|
||||
"My onRenderHtml() hook expects pageContext.Page to be defined"
|
||||
);
|
||||
|
||||
const page = ReactDOMServer.renderToString(
|
||||
<Layout pageContext={pageContext}>
|
||||
<Page />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
// See https://vike.dev/head
|
||||
const meta = getPageMetadata(pageContext);
|
||||
|
||||
const documentHtml = escapeInject`<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="${meta.description}" />
|
||||
<title>${meta.title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="react-root">${dangerouslySkipEscape(page)}</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
return {
|
||||
documentHtml,
|
||||
pageContext: {},
|
||||
};
|
||||
};
|
34
renderer/client-only.tsx
Normal file
34
renderer/client-only.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type ClientOnlyProps = {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode | null;
|
||||
};
|
||||
|
||||
const ClientOnly = ({ children, fallback }: ClientOnlyProps) => {
|
||||
const [isMounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return isMounted ? children : fallback;
|
||||
};
|
||||
|
||||
export const withClientOnly = <T extends unknown>(
|
||||
Component: React.ComponentType<T>,
|
||||
fallback?: ReactNode | null
|
||||
): React.ComponentType<T> => {
|
||||
return (props: any) => (
|
||||
<ClientOnly fallback={fallback}>
|
||||
<Component {...props} />
|
||||
</ClientOnly>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClientOnly;
|
26
renderer/context.tsx
Normal file
26
renderer/context.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
// https://vike.dev/usePageContext
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export { usePageContext };
|
||||
export { PageContextProvider };
|
||||
|
||||
import React, { useContext } from "react";
|
||||
import type { PageContext } from "vike/types";
|
||||
|
||||
const Context = React.createContext<PageContext>(
|
||||
undefined as unknown as PageContext
|
||||
);
|
||||
|
||||
type Props = {
|
||||
pageContext: PageContext;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const PageContextProvider = ({ pageContext, children }: Props) => {
|
||||
return <Context.Provider value={pageContext}>{children}</Context.Provider>;
|
||||
};
|
||||
|
||||
/** https://vike.dev/usePageContext */
|
||||
function usePageContext() {
|
||||
const pageContext = useContext(Context);
|
||||
return pageContext;
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
@apply bg-slate-600 text-white;
|
||||
@apply bg-slate-900 text-white;
|
||||
}
|
||||
|
||||
.cm-theme {
|
16
renderer/hooks.ts
Normal file
16
renderer/hooks.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { usePageContext } from "./context";
|
||||
|
||||
export const useData = <T = any>() => {
|
||||
const { data } = usePageContext();
|
||||
return data as T;
|
||||
};
|
||||
|
||||
export const useParams = <T = any>() => {
|
||||
const { routeParams } = usePageContext();
|
||||
return (routeParams || {}) as T;
|
||||
};
|
||||
|
||||
export const useSearchParams = () => {
|
||||
const { urlParsed } = usePageContext();
|
||||
return new URLSearchParams(urlParsed.searchOriginal || "");
|
||||
};
|
23
renderer/layout.tsx
Normal file
23
renderer/layout.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { PageContextProvider } from "./context";
|
||||
import type { PageContext } from "vike/types";
|
||||
import Providers from "./providers";
|
||||
import "./globals.css";
|
||||
import "nprogress/nprogress.css";
|
||||
|
||||
type LayoutProps = {
|
||||
children: React.ReactNode;
|
||||
pageContext: PageContext;
|
||||
};
|
||||
|
||||
const Layout = ({ children, pageContext }: LayoutProps) => {
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<PageContextProvider pageContext={pageContext}>
|
||||
<Providers>{children}</Providers>
|
||||
</PageContextProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
19
renderer/link.tsx
Normal file
19
renderer/link.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { ComponentProps } from "react";
|
||||
import { usePageContext } from "./context";
|
||||
|
||||
const Link = (props: ComponentProps<"a">) => {
|
||||
const pageContext = usePageContext();
|
||||
const { urlPathname } = pageContext;
|
||||
const { href } = props;
|
||||
|
||||
const isActive =
|
||||
href === "/" ? urlPathname === href : urlPathname.startsWith(href!);
|
||||
|
||||
const className = [props.className, isActive && "is-active"]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
|
||||
return <a {...props} className={className} />;
|
||||
};
|
||||
|
||||
export default Link;
|
@ -1,8 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
|
||||
import trpc, { getBaseUrl } from "@/lib/trpc";
|
||||
import trpc, { getBaseUrl } from "~/lib/trpc";
|
||||
import { httpBatchLink } from "@trpc/react-query";
|
||||
|
||||
type Props = {
|
||||
@ -19,9 +17,6 @@ const Providers = ({ children }: Props) => {
|
||||
headers() {
|
||||
return {};
|
||||
},
|
||||
fetch(input, init = {}) {
|
||||
return fetch(input, { ...init, cache: "no-store" });
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
19
renderer/types.ts
Normal file
19
renderer/types.ts
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
declare global {
|
||||
namespace Vike {
|
||||
interface PageContext {
|
||||
Page: () => React.ReactElement;
|
||||
data?: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
};
|
||||
config: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
};
|
||||
abortReason?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
11
renderer/utils.ts
Normal file
11
renderer/utils.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import type { PageContext } from "vike/types";
|
||||
|
||||
export function getPageMetadata(pageContext: PageContext) {
|
||||
let title = pageContext.data?.title || pageContext.config.title;
|
||||
title = title ? `${title} - Vike` : "Welcome to Vike";
|
||||
|
||||
const description =
|
||||
pageContext.data?.description || pageContext.config.description || "";
|
||||
|
||||
return { title, description };
|
||||
}
|
10
server/api/index.ts
Normal file
10
server/api/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Router } from "express";
|
||||
import preview from "./preview";
|
||||
import trpc from "./trpc/handler";
|
||||
|
||||
const api = Router();
|
||||
|
||||
api.use("/trpc", trpc);
|
||||
api.use("/preview", preview);
|
||||
|
||||
export default api;
|
51
server/api/preview/index.ts
Normal file
51
server/api/preview/index.ts
Normal file
@ -0,0 +1,51 @@
|
||||
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);
|
||||
|
||||
const get = async (req: Request, res: Response) => {
|
||||
const { slug, ...pathParams } = req.params as any;
|
||||
const path = pathParams[0];
|
||||
|
||||
const fileData = await db.query.file.findFirst({
|
||||
where: and(eq(file.path, path), isNull(file.deletedAt)),
|
||||
});
|
||||
|
||||
if (!fileData) {
|
||||
return res.status(404).send("File not found!");
|
||||
}
|
||||
|
||||
const ext = getFileExt(fileData.filename);
|
||||
let content = fileData.content || "";
|
||||
|
||||
if (["html", "htm"].includes(ext)) {
|
||||
content = await serveHtml(fileData);
|
||||
}
|
||||
|
||||
if (["js", "ts", "jsx", "tsx"].includes(ext)) {
|
||||
content = await serveJs(fileData);
|
||||
}
|
||||
|
||||
res.setHeader(
|
||||
"Content-Type",
|
||||
mime.getType(fileData.filename) || "application/octet-stream"
|
||||
);
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET");
|
||||
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||
res.send(content);
|
||||
};
|
||||
|
||||
const router = Router();
|
||||
router.get("/:slug/*", get);
|
||||
|
||||
export default router;
|
@ -1,5 +1,5 @@
|
||||
import db from "@/server/db";
|
||||
import { FileSchema, file } from "@/server/db/schema/file";
|
||||
import db from "~/server/db";
|
||||
import { FileSchema, file } from "~/server/db/schema/file";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
|
||||
export const serveHtml = async (fileData: FileSchema) => {
|
||||
@ -22,7 +22,7 @@ export const serveHtml = async (fileData: FileSchema) => {
|
||||
|
||||
const bodyOpeningTagIdx = content.indexOf("<body");
|
||||
const firstScriptTagIdx = content.indexOf("<script", bodyOpeningTagIdx);
|
||||
const injectScripts = ['<script src="/js/debug-console.js"></script>'];
|
||||
const injectScripts = ['<script src="/js/hook-console.js"></script>'];
|
||||
|
||||
const importMaps = [
|
||||
{ name: "react", url: "https://esm.sh/react@18.2.0" },
|
||||
@ -35,7 +35,7 @@ export const serveHtml = async (fileData: FileSchema) => {
|
||||
return a;
|
||||
}, {});
|
||||
const json = JSON.stringify({ imports });
|
||||
injectScripts.push(`<script type="importmap">${json}</script>`);
|
||||
injectScripts.unshift(`<script type="importmap">${json}</script>`);
|
||||
}
|
||||
|
||||
if (firstScriptTagIdx >= 0 && injectScripts.length > 0) {
|
11
server/api/preview/serve-js.ts
Normal file
11
server/api/preview/serve-js.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { FileSchema } from "~/server/db/schema/file";
|
||||
import { transformJs } from "~/server/lib/transform-js";
|
||||
|
||||
export const serveJs = async (file: FileSchema) => {
|
||||
let content = file.content || "";
|
||||
|
||||
// transform js
|
||||
content = await transformJs(content);
|
||||
|
||||
return content;
|
||||
};
|
10
server/api/trpc/context.ts
Normal file
10
server/api/trpc/context.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { CreateExpressContextOptions } from "@trpc/server/adapters/express";
|
||||
|
||||
export const createContext = async ({
|
||||
req,
|
||||
res,
|
||||
}: CreateExpressContextOptions) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export type Context = Awaited<ReturnType<typeof createContext>>;
|
10
server/api/trpc/handler.ts
Normal file
10
server/api/trpc/handler.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { createExpressMiddleware } from "@trpc/server/adapters/express";
|
||||
import { appRouter } from "~/server/routers/_app";
|
||||
import { createContext } from "./context";
|
||||
|
||||
const trpc = createExpressMiddleware({
|
||||
router: appRouter,
|
||||
createContext,
|
||||
});
|
||||
|
||||
export default trpc;
|
7
server/api/trpc/trpc.ts
Normal file
7
server/api/trpc/trpc.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { createContext } from "./context";
|
||||
import { appRouter } from "../../routers/_app";
|
||||
import { createCallerFactory } from ".";
|
||||
|
||||
const trpcServer = createCallerFactory(appRouter)(createContext);
|
||||
|
||||
export default trpcServer;
|
@ -19,7 +19,7 @@ const main = async () => {
|
||||
userId: adminUser.id,
|
||||
path: "index.html",
|
||||
filename: "index.html",
|
||||
content: "<p>Hello world!</p>",
|
||||
content: '<p class="text-lg text-red-500">Hello world!</p>',
|
||||
},
|
||||
{
|
||||
userId: adminUser.id,
|
||||
@ -42,16 +42,15 @@ const main = async () => {
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Code-Share</title>
|
||||
<title>Document</title>
|
||||
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
{CONTENT}
|
||||
<script type="text/babel" src="script.js" data-type="module"></script>
|
||||
<script src="script.js" type="module" defer></script>
|
||||
</body>
|
||||
</html>`,
|
||||
},
|
54
server/index.ts
Normal file
54
server/index.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import express from "express";
|
||||
import { renderPage } from "vike/server";
|
||||
import { IS_DEV } from "./lib/consts";
|
||||
import api from "./api";
|
||||
|
||||
async function createServer() {
|
||||
const app = express();
|
||||
const root = process.cwd();
|
||||
|
||||
if (IS_DEV) {
|
||||
// start vite development server
|
||||
const { createServer: createViteServer } = await import("vite");
|
||||
const vite = await createViteServer({
|
||||
root,
|
||||
server: { middlewareMode: true },
|
||||
appType: "custom",
|
||||
});
|
||||
|
||||
app.use(vite.middlewares);
|
||||
} else {
|
||||
// serve client assets
|
||||
app.use(express.static(root + "/dist/client"));
|
||||
}
|
||||
|
||||
app.use("/api", api);
|
||||
|
||||
app.use("*", async (req, res, next) => {
|
||||
const url = req.originalUrl;
|
||||
const pageContext = {};
|
||||
const ctx = await renderPage({ urlOriginal: url, ...pageContext });
|
||||
|
||||
const { httpResponse } = ctx;
|
||||
if (!httpResponse) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const { body, statusCode, headers, earlyHints } = httpResponse;
|
||||
if (res.writeEarlyHints) {
|
||||
res.writeEarlyHints({ link: earlyHints.map((e) => e.earlyHintLink) });
|
||||
}
|
||||
|
||||
headers.forEach(([name, value]) => res.setHeader(name, value));
|
||||
res.status(statusCode).send(body);
|
||||
});
|
||||
|
||||
const host = process.env.HOST || "127.0.0.1";
|
||||
const port = Number(process.env.PORT) || 3000;
|
||||
|
||||
app.listen(port, host, () => {
|
||||
console.log(`Server listening on http://${host}:${port}`);
|
||||
});
|
||||
}
|
||||
|
||||
createServer();
|
2
server/lib/consts.ts
Normal file
2
server/lib/consts.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const IS_DEV = process.env.NODE_ENV !== "production";
|
||||
export const BASE_URL = process.env.BASE_URL || "";
|
19
server/lib/transform-js.ts
Normal file
19
server/lib/transform-js.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import * as swc from "@swc/core";
|
||||
|
||||
export const transformJs = async (code: string) => {
|
||||
try {
|
||||
const result = await swc.transform(code, {
|
||||
jsc: {
|
||||
parser: {
|
||||
jsx: true,
|
||||
syntax: "ecmascript",
|
||||
},
|
||||
target: "es5",
|
||||
},
|
||||
});
|
||||
|
||||
return result.code;
|
||||
} catch (err) {
|
||||
return code;
|
||||
}
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { router } from "../trpc";
|
||||
import { router } from "../api/trpc";
|
||||
import file from "./file";
|
||||
|
||||
export const appRouter = router({
|
@ -1,6 +1,6 @@
|
||||
import { and, asc, desc, eq, inArray, isNull, sql } from "drizzle-orm";
|
||||
import db from "../db";
|
||||
import { procedure, router } from "../trpc";
|
||||
import { procedure, router } from "../api/trpc";
|
||||
import { file, insertFileSchema, selectFileSchema } from "../db/schema/file";
|
||||
import { z } from "zod";
|
||||
import { TRPCError } from "@trpc/server";
|
@ -1,13 +0,0 @@
|
||||
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||
import { appRouter } from "@/server/routers/_app";
|
||||
import { createContext } from "@/server/context";
|
||||
|
||||
const handler = (req: Request) =>
|
||||
fetchRequestHandler({
|
||||
endpoint: "/api/trpc",
|
||||
req,
|
||||
router: appRouter,
|
||||
createContext,
|
||||
});
|
||||
|
||||
export { handler as GET, handler as POST };
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
@ -1,28 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import Providers from "./providers";
|
||||
import { IS_DEV } from "@/lib/consts";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Code Share",
|
||||
description: "Code sharing app",
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const RootLayout = ({ children }: Props) => {
|
||||
return (
|
||||
<html lang="en" className="dark" suppressHydrationWarning={IS_DEV}>
|
||||
<body className={inter.className}>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootLayout;
|
@ -1,63 +0,0 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import React, { forwardRef, useEffect, useState } from "react";
|
||||
|
||||
const ConsoleLogger = forwardRef((_, ref: any) => {
|
||||
const [iframeLogs, setIframeLogs] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref) {
|
||||
ref.current = {
|
||||
clear: () => setIframeLogs([]),
|
||||
};
|
||||
}
|
||||
|
||||
const onMessage = (event: MessageEvent<any>) => {
|
||||
const { data } = event;
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!["log", "warn", "error"].includes(data.type) || !data.args?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof data.args[0] === "string" &&
|
||||
data.args[0]?.includes("Babel transformer")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIframeLogs((i) => [data, ...i]);
|
||||
};
|
||||
|
||||
window.addEventListener("message", onMessage);
|
||||
return () => {
|
||||
window.removeEventListener("message", onMessage);
|
||||
};
|
||||
}, [ref]);
|
||||
|
||||
return (
|
||||
<div className="pt-2 h-full border-t border-slate-700">
|
||||
<div className="flex flex-col-reverse overflow-y-auto items-stretch h-full font-mono text-slate-400">
|
||||
{iframeLogs.map((item, idx) => (
|
||||
<p
|
||||
key={idx}
|
||||
className="text-xs border-b border-slate-900 first:border-b-0 px-2 py-1"
|
||||
>
|
||||
{item.args
|
||||
?.map((arg: any) => {
|
||||
if (typeof arg === "object") {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
}
|
||||
return arg;
|
||||
})
|
||||
.join(" ")}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default ConsoleLogger;
|
@ -1,48 +0,0 @@
|
||||
"use client";
|
||||
|
||||
/* eslint-disable react/display-name */
|
||||
import Panel from "@/components/ui/panel";
|
||||
import { useParams } from "next/navigation";
|
||||
import React, { Fragment, useCallback, useEffect, useRef } from "react";
|
||||
import { createStore } from "zustand";
|
||||
import { useProjectContext } from "../context/project";
|
||||
|
||||
type PreviewStore = {
|
||||
refresh: () => void;
|
||||
};
|
||||
|
||||
export const previewStore = createStore<PreviewStore>(() => ({
|
||||
refresh: () => {},
|
||||
}));
|
||||
|
||||
const WebPreview = () => {
|
||||
const { slug } = useParams();
|
||||
const frameRef = useRef<HTMLIFrameElement>(null);
|
||||
const project = useProjectContext();
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
if (frameRef.current) {
|
||||
frameRef.current.src = `/project/${slug}/file/index.html?t=${Date.now()}`;
|
||||
}
|
||||
}, [slug]);
|
||||
|
||||
useEffect(() => {
|
||||
previewStore.setState({ refresh });
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const PanelComponent = !project.isCompact ? Panel : Fragment;
|
||||
|
||||
return (
|
||||
<PanelComponent>
|
||||
<iframe
|
||||
id="web-preview"
|
||||
ref={frameRef}
|
||||
className="border-none w-full h-full bg-white"
|
||||
sandbox="allow-scripts"
|
||||
/>
|
||||
</PanelComponent>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebPreview;
|
@ -1,50 +0,0 @@
|
||||
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 { NextRequest } from "next/server";
|
||||
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";
|
||||
|
||||
const mime = new Mime(standardTypes, otherTypes);
|
||||
mime.define({ "text/javascript": ["jsx", "tsx"] }, true);
|
||||
|
||||
// Opt out of caching for all data requests in the route segment
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export const GET = async (req: NextRequest, { params }: any) => {
|
||||
const path = params.path.join("/");
|
||||
const { slug } = params;
|
||||
|
||||
const fileData = await db.query.file.findFirst({
|
||||
where: and(eq(file.path, path), isNull(file.deletedAt)),
|
||||
});
|
||||
|
||||
if (!fileData) {
|
||||
return new Response("File not found!", { status: 404 });
|
||||
}
|
||||
|
||||
const ext = getFileExt(fileData.filename);
|
||||
let content = fileData.content || "";
|
||||
|
||||
if (["html", "htm"].includes(ext)) {
|
||||
content = await serveHtml(fileData);
|
||||
}
|
||||
|
||||
if (["js", "ts", "jsx", "tsx"].includes(ext)) {
|
||||
content = await serveJs(fileData, slug);
|
||||
}
|
||||
|
||||
return new Response(content, {
|
||||
headers: {
|
||||
"Content-Type":
|
||||
mime.getType(fileData.filename) || "application/octet-stream",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET",
|
||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||
},
|
||||
});
|
||||
};
|
@ -1,41 +0,0 @@
|
||||
import { BASE_URL } from "@/lib/consts";
|
||||
import { FileSchema } from "@/server/db/schema/file";
|
||||
|
||||
export const serveJs = async (file: FileSchema, slug: string) => {
|
||||
let content = file.content || "";
|
||||
|
||||
const importRegex = /(?:import.+from.+)(?:"|')(.+)(?:"|')/g;
|
||||
content = content.replace(
|
||||
importRegex,
|
||||
(match: string, importPath: string) => {
|
||||
// local file
|
||||
if (importPath.startsWith("./")) {
|
||||
return match.replace(
|
||||
importPath,
|
||||
BASE_URL + `/project/${slug}/file` + importPath.substring(1)
|
||||
);
|
||||
}
|
||||
|
||||
// resolve to esm
|
||||
return match.replace(importPath, "https://esm.sh/" + importPath);
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
const res = await fetch("http://localhost:3001/file/" + file.path);
|
||||
if (!res.ok) {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
|
||||
const data = await res.text();
|
||||
if (typeof data !== "string") {
|
||||
throw new Error("Invalid response!");
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error((err as any).message);
|
||||
}
|
||||
|
||||
return content;
|
||||
};
|
@ -1,2 +0,0 @@
|
||||
export const IS_DEV = process.env.NODE_ENV === "development";
|
||||
export const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || "";
|
@ -1,10 +0,0 @@
|
||||
import { headers as getHeaders, cookies as getCookies } from "next/headers";
|
||||
|
||||
export const createContext = async () => {
|
||||
// const headers = getHeaders();
|
||||
// const cookies = getCookies();
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
export type Context = Awaited<ReturnType<typeof createContext>>;
|
@ -1,26 +0,0 @@
|
||||
CREATE TABLE `files` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`parent_id` integer,
|
||||
`user_id` integer NOT NULL,
|
||||
`path` text NOT NULL,
|
||||
`filename` text NOT NULL,
|
||||
`is_directory` integer DEFAULT false NOT NULL,
|
||||
`is_file` integer DEFAULT false NOT NULL,
|
||||
`content` text,
|
||||
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
`deleted_at` text,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action,
|
||||
FOREIGN KEY (`parent_id`) REFERENCES `files`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `users` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`email` text NOT NULL,
|
||||
`password` text NOT NULL,
|
||||
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
`deleted_at` text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `files_path_unique` ON `files` (`path`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `files_filename_unique` ON `files` (`filename`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);
|
@ -1,191 +0,0 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "sqlite",
|
||||
"id": "30eeec75-2990-42f2-a773-b55bfa0a4c0a",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"files": {
|
||||
"name": "files",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"parent_id": {
|
||||
"name": "parent_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"filename": {
|
||||
"name": "filename",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"is_directory": {
|
||||
"name": "is_directory",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"is_file": {
|
||||
"name": "is_file",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "CURRENT_TIMESTAMP"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"files_path_unique": {
|
||||
"name": "files_path_unique",
|
||||
"columns": [
|
||||
"path"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"files_filename_unique": {
|
||||
"name": "files_filename_unique",
|
||||
"columns": [
|
||||
"filename"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"files_user_id_users_id_fk": {
|
||||
"name": "files_user_id_users_id_fk",
|
||||
"tableFrom": "files",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"parent_id_fk": {
|
||||
"name": "parent_id_fk",
|
||||
"tableFrom": "files",
|
||||
"tableTo": "files",
|
||||
"columnsFrom": [
|
||||
"parent_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "CURRENT_TIMESTAMP"
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1708329026876,
|
||||
"tag": "0000_loud_hex",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { createContext } from "./context";
|
||||
import { appRouter } from "./routers/_app";
|
||||
import { createCallerFactory } from "./trpc";
|
||||
|
||||
export const trpcServer = createCallerFactory(appRouter)(createContext);
|
@ -1,46 +0,0 @@
|
||||
import express from "express";
|
||||
import db from "../db";
|
||||
import { and, eq, isNull } from "drizzle-orm";
|
||||
import { file } from "../db/schema/file";
|
||||
import * as swc from "@swc/core";
|
||||
|
||||
const app = express();
|
||||
|
||||
const getFileByPath = async (path: string) => {
|
||||
const fileData = await db.query.file.findFirst({
|
||||
where: and(eq(file.path, path), isNull(file.deletedAt)),
|
||||
});
|
||||
|
||||
return fileData;
|
||||
};
|
||||
|
||||
app.get("/file/*", async (req, res) => {
|
||||
const pathname = Object.values(req.params).join("/");
|
||||
const fileData = await getFileByPath(pathname);
|
||||
|
||||
if (!fileData) {
|
||||
return res.status(404).send("File not found!");
|
||||
}
|
||||
|
||||
try {
|
||||
let code = fileData.content || "";
|
||||
const result = await swc.transform(code, {
|
||||
jsc: {
|
||||
parser: {
|
||||
jsx: true,
|
||||
syntax: "ecmascript",
|
||||
},
|
||||
target: "es5",
|
||||
},
|
||||
});
|
||||
|
||||
res.contentType("text/javascript").send(result.code);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return res.status(400).send("Cannot transform file!");
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3001, () => {
|
||||
console.log("App listening on http://localhost:3001");
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user