diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 2ef4a1f..0000000 --- a/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# Fresh build directory -_fresh/ -# npm dependencies -node_modules/ -/local \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 09cf720..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "recommendations": [ - "denoland.vscode-deno" - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json index bf6ade0..c89c3aa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,8 @@ { + "deno.enablePaths": [ + "./" + ], "deno.enable": true, - "deno.lint": true, "deno.unstable": true, - "editor.defaultFormatter": "denoland.vscode-deno", - "[typescriptreact]": { - "editor.defaultFormatter": "denoland.vscode-deno" - }, - "[typescript]": { - "editor.defaultFormatter": "denoland.vscode-deno" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "denoland.vscode-deno" - }, - "[javascript]": { - "editor.defaultFormatter": "denoland.vscode-deno" - } + "editor.inlayHints.enabled": "off" } diff --git a/README.md b/README.md index ec0e33e..e3988d7 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,7 @@ -# Fresh project +# Twifeur -Your new Fresh project is ready to go. You can follow the Fresh "Getting -Started" guide here: https://fresh.deno.dev/docs/getting-started +Feur -### Usage +## Feur -Make sure to install Deno: https://deno.land/manual/getting_started/installation - -Then start the project: - -``` -deno task start -``` - -This will watch the project directory and restart as necessary. +Feur feur. diff --git a/api/login.ts b/api/login.ts new file mode 100644 index 0000000..8cfbae6 --- /dev/null +++ b/api/login.ts @@ -0,0 +1,16 @@ +import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts"; +import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts"; +import { FeurEnv } from "../main.ts"; +import { login, set_user } from "../lib/auth.ts"; + +export async function login_route(context: Context) { + const data = await context.req.formData(); + let username = data.get("login"), pass = data.get("password"); + if (username === null || pass === null) return context.redirect("/login"); + username = username.toString(), pass = pass.toString(); + const logged = await login(username, pass); + if (logged === null) return context.redirect("/login"); + console.log("Logged in", { username }); + set_user(context, logged); + return context.redirect("/user"); +} diff --git a/api/logout.ts b/api/logout.ts new file mode 100644 index 0000000..eeabd70 --- /dev/null +++ b/api/logout.ts @@ -0,0 +1,9 @@ +import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts"; +import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts"; +import { FeurEnv } from "../main.ts"; +import { set_user } from "../lib/auth.ts"; + +export function logout_route(context: Context) { + set_user(context, null); + return context.redirect("/"); +} diff --git a/auth/auth.ts b/auth/auth.ts deleted file mode 100644 index b3608c8..0000000 --- a/auth/auth.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { assert } from "$std/assert/assert.ts"; -import { User } from "../lib/store.ts"; -import { generate } from "https://deno.land/std@0.224.0/uuid/v1.ts"; - -class Authentificator { - tokens; - user_storage; - constructor(user_storage: UserStorage) { - this.tokens = new TokenSet(); - this.user_storage = user_storage; - } -} - -interface UserStorage { - get_user(login: string): User | null; -} - -export class Token { - raw; - user_id; - constructor(raw: string, user_id: string) { - this.raw = raw; - this.user_id = user_id; - } -} - -class TokenSet { - tokens; - - constructor() { - this.tokens = new Map(); - } - - get(token: string) { - return this.tokens.get(token) ?? null; - } - - create(user_id: string) { - const raw = generate(); - assert(typeof raw == "string"); - const token = new Token(raw, user_id); - this.tokens.set(raw, token); - } -} - -class StaticUserStorage implements UserStorage { - users; - - constructor() { - this.users = new Map(); - } - - get_user(user_id: string) { - return this.users.get(user_id) ?? null; - } - - with(user: User) { - this.users.set(user.id, user); - return this; - } -} - -export const auth = new Authentificator( - new StaticUserStorage().with({ - id: "pleinplein", - email: "feur@feur.feur", - like_set: new Set(), - name: "Feur", - password: "feurfeur", - pfp_url: "https://feur.feur.feur/feur.feur", - }), -); diff --git a/components/Button.tsx b/components/Button.tsx deleted file mode 100644 index f1b80a0..0000000 --- a/components/Button.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { JSX } from "preact"; -import { IS_BROWSER } from "$fresh/runtime.ts"; - -export function Button(props: JSX.HTMLAttributes) { - return ( - -

{props.count}

- - - ); -} diff --git a/lib/auth.ts b/lib/auth.ts new file mode 100644 index 0000000..c834ca8 --- /dev/null +++ b/lib/auth.ts @@ -0,0 +1,24 @@ +import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts"; +import { db, User } from "./storage.ts"; +import { FeurEnv } from "../main.ts"; +import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts"; + +export async function login(login: string, password: string) { + for await (const user of db.all("user")) { + if (await user.get("username") !== login) continue; + if (await user.get("password") === password) return user; + return null; + } + return null; +} + +export async function get_user(context: Context) { + const id = context.get("session").get("user"); + if (typeof id !== "string") return null; + return await db.get("user", id); +} + +export function set_user(context: Context, user: User | null) { + if (user === null) context.get("session").set("user", ""); + else context.get("session").set("user", user.id); +} diff --git a/lib/models/User.ts b/lib/models/User.ts deleted file mode 100644 index 8859b48..0000000 --- a/lib/models/User.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts"; - -export const UserModel = z.object({ - id: z.string().uuid().describe("primary"), - name: z.string(), - email: z.string(), - password: z.string(), - pfp_url: z.string(), -}); diff --git a/lib/storage.ts b/lib/storage.ts new file mode 100644 index 0000000..804be37 --- /dev/null +++ b/lib/storage.ts @@ -0,0 +1,36 @@ +import { + EntryFor, + Schema, + Store, +} from "https://git.barnulf.net/mb/debilus/raw/commit/fc701bec680dd73be29c72164f47ee87fac540c7/mod.ts"; +import { project_root } from "./utils.ts"; + +export const db = await Store.open( + new Schema({ + user: { + username: "string", + password: "string", + upvoted_posts: ["many", "post"], + upvoted_comments: ["many", "comment"], + posts: ["many", "post"], + }, + post: { + title: "string", + content: "string", + upvotes: "number", + author: ["one", "user"], + comments: ["many", "comment"], + }, + comment: { + content: "string", + upvotes: "number", + author: ["one", "user"], + post: ["one", "post"], + }, + }), + `${project_root()}/local/db.kv`, +); + +export type User = EntryFor; +export type Post = EntryFor; +export type Comment = EntryFor; diff --git a/lib/store.ts b/lib/store.ts deleted file mode 100644 index 2511b59..0000000 --- a/lib/store.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { createPentagon } from "https://deno.land/x/pentagon@v0.1.5/mod.ts"; -import { project_root_dir } from "../utils.ts"; -import { z } from "https://deno.land/x/zod@v3.21.4/mod.ts"; - -const kv = await Deno.openKv(project_root_dir() + "local/kv"); - -export type User = z.infer; -export const user_model = z.object({ - id: z.string().uuid().describe("primary"), - name: z.string(), - email: z.string(), - password: z.string(), - pfp_url: z.string(), - like_set: z.set(z.string().uuid()), -}); - -export type Post = z.infer; -export const post_model = z.object({ - id: z.string().uuid().describe("primary"), - title: z.string(), - content: z.string(), - date: z.number(), - like_count: z.number(), - author_id: z.string().uuid(), -}); - -export type Comment = z.infer; -export const comment_model = z.object({ - id: z.string().uuid().describe("primary"), - date: z.number(), - post_id: z.string().uuid(), - author_id: z.string().uuid(), -}); - -export const db = createPentagon(kv, { - users: { - schema: user_model, - relations: { - posts: ["posts", [post_model], "id", "author_id"], - comments: ["comments", [comment_model], "id", "author_id"], - }, - }, - posts: { - schema: post_model, - relations: { - author: ["users", user_model, "author_id", "id"], - comments: ["comments", [comment_model], "id", "post_id"], - }, - }, - comments: { - schema: comment_model, - relations: { - author: ["users", user_model, "author_id", "id"], - post: ["post", post_model, "post_id", "id"], - }, - }, -}); diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..565b5bf --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,14 @@ +import { dirname } from "https://deno.land/std@0.224.0/path/dirname.ts"; + +import { CSSProperties } from "https://deno.land/std@0.40.0/types/react.d.ts"; + +export function _css(prop: CSSProperties) { + return prop; +} + +export function project_root() { + const this_url = new URL(import.meta.url); + const this_abs_path = Deno.realPathSync(this_url.pathname); + const lib_path = dirname(this_abs_path); + return dirname(lib_path); +} diff --git a/local/.gitignore b/local/.gitignore new file mode 100644 index 0000000..aa0e8eb --- /dev/null +++ b/local/.gitignore @@ -0,0 +1,2 @@ +* +!/.gitignore \ No newline at end of file diff --git a/main.ts b/main.ts old mode 100644 new mode 100755 index 02ea409..5ad6af6 --- a/main.ts +++ b/main.ts @@ -1,14 +1,33 @@ -/// -/// -/// -/// -/// -/// +#!/bin/env -S deno run --allow-net --unstable-kv --watch --allow-read=./pages,./local,./lib,. --allow-write=./local -import "$std/dotenv/load.ts"; +import { Hono } from "https://deno.land/x/hono@v4.3.10/mod.ts"; +import { serveStatic } from "https://deno.land/x/hono@v4.3.10/middleware.ts"; +import { CookieStore, Session, sessionMiddleware } from "https://deno.land/x/hono_sessions@v0.5.8/mod.ts"; -import { start } from "$fresh/server.ts"; -import manifest from "./fresh.gen.ts"; -import config from "./fresh.config.ts"; +export type FeurEnv = { Variables: { session: Session } }; +const app = new Hono(); -await start(manifest, config); +app.use("/static/*", serveStatic({ root: "./pages/" })); + +const store = new CookieStore(); +const encryptionKey = "FeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeurFeur"; +app.use("*", sessionMiddleware({ store, encryptionKey })); + +app.get("/", (c) => c.redirect("/home")); + +import HomePage from "./pages/home.tsx"; +app.get("/home", (c) => c.html(HomePage(c))); + +import LoginPage from "./pages/login.tsx"; +app.get("/login", (c) => c.html(LoginPage())); + +import { login_route } from "./api/login.ts"; +app.post("/api/login", (c) => login_route(c)); + +import { logout_route } from "./api/logout.ts"; +app.get("/api/logout", (c) => logout_route(c)); + +import UserPage from "./pages/user.tsx"; +app.get("/user", async (c) => await UserPage(c)); + +Deno.serve(app.fetch); diff --git a/pages/components/base.tsx b/pages/components/base.tsx new file mode 100644 index 0000000..eacc13a --- /dev/null +++ b/pages/components/base.tsx @@ -0,0 +1,22 @@ +/** @jsx jsx */ + +import { PropsWithChildren, jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts" +import { _css } from "../../lib/utils.ts"; + +export function BasePage({ name, children }: PropsWithChildren<{ name: string }>) { + return ( + + + + {name} - Twifeur + + + + {children} + + + ) +} diff --git a/routes/feur.tsx b/pages/components/footer.tsx similarity index 100% rename from routes/feur.tsx rename to pages/components/footer.tsx diff --git a/pages/components/heading.tsx b/pages/components/heading.tsx new file mode 100644 index 0000000..8fb551e --- /dev/null +++ b/pages/components/heading.tsx @@ -0,0 +1,53 @@ +/** @jsx jsx */ + +import { PropsWithChildren, jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts" +import { _css } from "../../lib/utils.ts"; +import { User } from "../../lib/storage.ts"; + +// +export default function Heading({ username }: PropsWithChildren<{username: string | null}>) { + return ( +
+
+
+

TwiFeur

+
+ + + + {(username !== null) && } +
+
+ ) +} + +function MenuItem({ name, location }: { name: string, location: string }) { + return ( + +

{name}

+
+ ) +} diff --git a/pages/components/login.tsx b/pages/components/login.tsx new file mode 100644 index 0000000..b926bc3 --- /dev/null +++ b/pages/components/login.tsx @@ -0,0 +1,13 @@ +/** @jsx jsx */ + +import { jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts" + +export default function Login() { + return ( +
+ + + +
+ ) +} diff --git a/pages/components/main.tsx b/pages/components/main.tsx new file mode 100644 index 0000000..e6eb672 --- /dev/null +++ b/pages/components/main.tsx @@ -0,0 +1,51 @@ +/** @jsx jsx */ + +import { PropsWithChildren, jsx, Fragment } from "https://deno.land/x/hono@v4.3.10/middleware.ts" +import { _css } from "../../lib/utils.ts"; + +export default function Main({ children }: PropsWithChildren) { + return ( + +
+
+
+ {children} +
+
+
+
+
+ Footer +
+
+
+ ) +} diff --git a/pages/components/post.tsx b/pages/components/post.tsx new file mode 100644 index 0000000..91aea6e --- /dev/null +++ b/pages/components/post.tsx @@ -0,0 +1,12 @@ +/** @jsx jsx */ + +import { PropsWithChildren, jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts" +import { _css } from "../../lib/utils.ts"; + +export function Post({ children, name }: PropsWithChildren<{ name: string }>) { + return ( +
+ +
+ ) +} diff --git a/pages/home.tsx b/pages/home.tsx new file mode 100644 index 0000000..795de82 --- /dev/null +++ b/pages/home.tsx @@ -0,0 +1,20 @@ +/** @jsx jsx */ + +import { jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts" +import { BasePage } from "./components/base.tsx" +import Main from "./components/main.tsx"; +import Heading from "./components/heading.tsx"; +import { get_user } from "../lib/auth.ts"; +import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts"; +import { FeurEnv } from "../main.ts"; +import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts"; + +export default async function HomePage(context: Context) { + const user = await get_user(context); + return ( + + +
Main
+
+ ) +} diff --git a/pages/login.tsx b/pages/login.tsx new file mode 100644 index 0000000..1eeab01 --- /dev/null +++ b/pages/login.tsx @@ -0,0 +1,18 @@ +/** @jsx jsx */ + +import { jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts" +import { BasePage } from "./components/base.tsx"; +import Heading from "./components/heading.tsx"; +import Main from "./components/main.tsx"; +import Login from "./components/login.tsx"; + +export default function LoginPage() { + return ( + +
+

Login

+ +
+
+ ) +} diff --git a/pages/static/style.css b/pages/static/style.css new file mode 100644 index 0000000..9c3f605 --- /dev/null +++ b/pages/static/style.css @@ -0,0 +1,23 @@ +html, +body, +header, +main, +footer { + margin: 0; + padding: 0; +} + +h1 { + font-size: 1.5rem; +} + +h3 { + font-size: 1rem; +} + +body { + background-color: #181818; + color: #eee; + font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; + overflow-x: hidden +} \ No newline at end of file diff --git a/pages/user.tsx b/pages/user.tsx new file mode 100644 index 0000000..779c2d9 --- /dev/null +++ b/pages/user.tsx @@ -0,0 +1,24 @@ +/** @jsx jsx */ + +import { jsx } from "https://deno.land/x/hono@v4.3.10/middleware.ts" +import { BasePage } from "./components/base.tsx"; +import Heading from "./components/heading.tsx"; +import Main from "./components/main.tsx"; +import Login from "./components/login.tsx"; +import { User } from "../lib/storage.ts"; +import { Context } from "https://deno.land/x/hono@v4.3.10/mod.ts"; +import { FeurEnv } from "../main.ts"; +import { BlankInput } from "https://deno.land/x/hono@v4.3.10/types.ts"; +import { get_user } from "../lib/auth.ts"; + +export default async function UserPage(context: Context) { + const user = await get_user(context); + if (user === null) return context.text("Must be logged.", 401); + return context.html( + +
+

Logged as {await user.get("username")}

+
+
+ ) +} diff --git a/routes/_404.tsx b/routes/_404.tsx deleted file mode 100644 index c63ae2e..0000000 --- a/routes/_404.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Head } from "$fresh/runtime.ts"; - -export default function Error404() { - return ( - <> - - 404 - Page not found - -
-
- the Fresh logo: a sliced lemon dripping with juice -

404 - Page not found

-

- The page you were looking for doesn't exist. -

- Go back home -
-
- - ); -} diff --git a/routes/_app.tsx b/routes/_app.tsx deleted file mode 100644 index c280e2b..0000000 --- a/routes/_app.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { type PageProps } from "$fresh/server.ts"; -export default function App({ Component }: PageProps) { - return ( - - - - - twifeur - - - - - - - ); -} diff --git a/routes/_middleware.ts b/routes/_middleware.ts deleted file mode 100644 index 6057b2e..0000000 --- a/routes/_middleware.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { FreshContext } from "$fresh/server.ts"; -import { deleteCookie, getCookies, setCookie } from "$std/http/cookie.ts"; -import { auth, Token } from "../auth/auth.ts"; - -type State = { - user_id: string | null; -}; - -export async function handler( - req: Request, - ctx: FreshContext, -) { - const cookies = getCookies(req.headers); - const token = get_session_token(cookies); - - ctx.state.user_id = token?.user_id ?? null; - - const resp = await ctx.next(); - set_session_token(resp.headers, token); - return resp; -} - -function get_session_token(cookies: Record) { - const stored = cookies["auth_token"]; - if (stored === undefined) return null; - const token = auth.tokens.get(stored); - if (token === null) return null; - return token; -} - -function set_session_token(headers: Headers, token: Token | null) { - if (token === null) { - deleteCookie(headers, "auth_token"); - return; - } - setCookie(headers, { - name: "auth_token", - value: token.raw, - }); -} diff --git a/routes/api/joke.ts b/routes/api/joke.ts deleted file mode 100644 index db17edd..0000000 --- a/routes/api/joke.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { FreshContext } from "$fresh/server.ts"; - -// Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/ -const JOKES = [ - "Why do Java developers often wear glasses? They can't C#.", - "A SQL query walks into a bar, goes up to two tables and says “can I join you?”", - "Wasn't hard to crack Forrest Gump's password. 1forrest1.", - "I love pressing the F5 key. It's refreshing.", - "Called IT support and a chap from Australia came to fix my network connection. I asked “Do you come from a LAN down under?”", - "There are 10 types of people in the world. Those who understand binary and those who don't.", - "Why are assembly programmers often wet? They work below C level.", - "My favourite computer based band is the Black IPs.", - "What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.", - "An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.", -]; - -export const handler = (_req: Request, _ctx: FreshContext): Response => { - const randomIndex = Math.floor(Math.random() * JOKES.length); - const body = JOKES[randomIndex]; - return new Response(body); -}; diff --git a/routes/greet/[name].tsx b/routes/greet/[name].tsx deleted file mode 100644 index 9c06827..0000000 --- a/routes/greet/[name].tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { PageProps } from "$fresh/server.ts"; - -export default function Greet(props: PageProps) { - return
Hello {props.params.name}
; -} diff --git a/routes/index.tsx b/routes/index.tsx deleted file mode 100644 index 67a22a7..0000000 --- a/routes/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useSignal } from "@preact/signals"; -import Counter from "../islands/Counter.tsx"; - -export default function Home() { - const count = useSignal(3); - return ( -
-
- the Fresh logo: a sliced lemon dripping with juice -

Welcome to Fresh

-

- Try updating this message in the - ./routes/index.tsx file, and refresh. -

- -
-
- ); -} diff --git a/static/favicon.ico b/static/favicon.ico deleted file mode 100644 index 1cfaaa2..0000000 Binary files a/static/favicon.ico and /dev/null differ diff --git a/static/logo.svg b/static/logo.svg deleted file mode 100644 index ef2fbe4..0000000 --- a/static/logo.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/static/styles.css b/static/styles.css deleted file mode 100644 index e94132d..0000000 --- a/static/styles.css +++ /dev/null @@ -1,129 +0,0 @@ - -*, -*::before, -*::after { - box-sizing: border-box; -} -* { - margin: 0; -} -button { - color: inherit; -} -button, [role="button"] { - cursor: pointer; -} -code { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, - "Liberation Mono", "Courier New", monospace; - font-size: 1em; -} -img, -svg { - display: block; -} -img, -video { - max-width: 100%; - height: auto; -} - -html { - line-height: 1.5; - -webkit-text-size-adjust: 100%; - font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, - "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; -} -.transition-colors { - transition-property: background-color, border-color, color, fill, stroke; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} -.my-6 { - margin-bottom: 1.5rem; - margin-top: 1.5rem; -} -.text-4xl { - font-size: 2.25rem; - line-height: 2.5rem; -} -.mx-2 { - margin-left: 0.5rem; - margin-right: 0.5rem; -} -.my-4 { - margin-bottom: 1rem; - margin-top: 1rem; -} -.mx-auto { - margin-left: auto; - margin-right: auto; -} -.px-4 { - padding-left: 1rem; - padding-right: 1rem; -} -.py-8 { - padding-bottom: 2rem; - padding-top: 2rem; -} -.bg-\[\#86efac\] { - background-color: #86efac; -} -.text-3xl { - font-size: 1.875rem; - line-height: 2.25rem; -} -.py-6 { - padding-bottom: 1.5rem; - padding-top: 1.5rem; -} -.px-2 { - padding-left: 0.5rem; - padding-right: 0.5rem; -} -.py-1 { - padding-bottom: 0.25rem; - padding-top: 0.25rem; -} -.border-gray-500 { - border-color: #6b7280; -} -.bg-white { - background-color: #fff; -} -.flex { - display: flex; -} -.gap-8 { - grid-gap: 2rem; - gap: 2rem; -} -.font-bold { - font-weight: 700; -} -.max-w-screen-md { - max-width: 768px; -} -.flex-col { - flex-direction: column; -} -.items-center { - align-items: center; -} -.justify-center { - justify-content: center; -} -.border-2 { - border-width: 2px; -} -.rounded { - border-radius: 0.25rem; -} -.hover\:bg-gray-200:hover { - background-color: #e5e7eb; -} -.tabular-nums { - font-variant-numeric: tabular-nums; -} diff --git a/utils.ts b/utils.ts deleted file mode 100644 index 749ad4d..0000000 --- a/utils.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { dirname } from "$std/path/dirname.ts"; - -export function project_root_dir() { - const this_url = new URL(import.meta.url); - const this_dir = dirname(this_url.pathname); - return this_dir + "/"; -}