Compare commits

..

10 Commits

46 changed files with 841 additions and 8570 deletions

11
.vscode/settings.json vendored
View File

@@ -1,11 +0,0 @@
{
"cSpell.words": [
"andi",
"mapbox",
"owickstrom",
"prismjs",
"rehype",
"solidjs",
"vinxi"
]
}

23
Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
# syntax=docker/dockerfile:1
FROM oven/bun:1 AS base
WORKDIR /app
FROM base AS deps
COPY bun.lock package.json ./
RUN bun install --frozen-lockfile
FROM deps AS build
COPY . .
RUN bun run build
FROM base AS runtime
ENV NODE_ENV=production
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile --production
COPY --from=build /app/.output ./.output
EXPOSE 8085
CMD ["bun", "run", "serve"]

View File

@@ -1,32 +1,16 @@
# SolidStart # minhtran_dev website code
Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com); ## Set up dev server
## Creating a project - `bun run dev`
```bash ## Build binary
# create a new project in the current directory
npm init solid@latest
# create a new project in my-app - `bun run build`
npm init solid@latest my-app
```
## Developing ## Deploy
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - Make sure you built this first
- `rsync` to my VPS
```bash - Run `pm2 restart 0` as `0` is the current task_id for my portfolio web app
npm run dev - [ ] Setup CI/CD for this project
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Solid apps are built with _presets_, which optimise your project for deployment to different environments.
By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`.
## This project was created with the [Solid CLI](https://solid-cli.netlify.app)

View File

@@ -1,35 +0,0 @@
import { defineConfig } from "@solidjs/start/config";
//@ts-expect-error
import pkg from "@vinxi/plugin-mdx";
import { blogPostsPlugin } from "./build-helpers/blogPostsPlugin";
import remarkFrontmatter from "remark-frontmatter";
import rehypeMdxCodeProps from "rehype-mdx-code-props";
import { mdxPrism } from "./build-helpers/mdxPrism";
import remarkToc from "remark-toc";
import tailwindcss from "@tailwindcss/vite";
const { default: mdx } = pkg;
export default defineConfig({
extensions: ["mdx", "md"],
vite: {
plugins: [
mdx.withImports({})({
remarkPlugins: [remarkFrontmatter, remarkToc],
rehypePlugins: [rehypeMdxCodeProps, mdxPrism],
jsx: true,
jsxImportSource: "solid-js",
providerImportSource: "solid-mdx",
}),
blogPostsPlugin(),
tailwindcss(),
],
build: {
minify: false,
},
},
server: {
prerender: {
crawlLinks: true,
},
},
});

View File

@@ -5,6 +5,9 @@ import { resolve, join } from "node:path";
import { readdirSync, statSync, writeFileSync } from "node:fs"; import { readdirSync, statSync, writeFileSync } from "node:fs";
import { exec } from "node:child_process"; import { exec } from "node:child_process";
/*
* Code from andii.dev
*/
const processFiles = () => { const processFiles = () => {
const outputFile = resolve("src/data/posts.json"); const outputFile = resolve("src/data/posts.json");
const blogDir = resolve("src/routes/blog"); const blogDir = resolve("src/routes/blog");

View File

@@ -3,6 +3,9 @@ import { toString as nodeToString } from "hast-util-to-string";
import { refractor } from "refractor"; import { refractor } from "refractor";
import tsx from "refractor/lang/tsx.js"; import tsx from "refractor/lang/tsx.js";
/*
* Code from andii.dev
*/
refractor.register(tsx); refractor.register(tsx);
export const mdxPrism = () => { export const mdxPrism = () => {

836
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -5,20 +5,22 @@
"dev": "vinxi dev", "dev": "vinxi dev",
"build": "vinxi build", "build": "vinxi build",
"start": "vinxi start", "start": "vinxi start",
"serve": "vinxi serve --dir .output/public --port 8085",
"preview": "npx http-server ./.output/public" "preview": "npx http-server ./.output/public"
}, },
"dependencies": { "dependencies": {
"@mdx-js/mdx": "^2.3.0", "@mdx-js/mdx": "^3.1.0",
"@solidjs/router": "^0.15.3", "@solidjs/router": "^0.15.3",
"@solidjs/start": "^1.0.11", "@solidjs/start": "^1.1.1",
"@tailwindcss/postcss": "^4.0.0", "@tailwindcss/vite": "4.0.0",
"@tailwindcss/vite": "^4.0.0",
"@vinxi/plugin-mdx": "^3.7.2", "@vinxi/plugin-mdx": "^3.7.2",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"remark-toc": "^9.0.0", "remark-toc": "^9.0.0",
"solid-js": "^1.9.4", "solid-js": "^1.9.5",
"solid-mdx": "^0.0.7", "solid-mdx": "^0.0.7",
"vinxi": "^0.5.1" "tailwind-nord": "^1.3.0",
"tailwindcss": "4.0.0",
"vinxi": "^0.5.3"
}, },
"engines": { "engines": {
"node": ">=18", "node": ">=18",
@@ -37,11 +39,10 @@
"rehype-mdx-code-props": "^3.0.1", "rehype-mdx-code-props": "^3.0.1",
"remark-frontmatter": "^5.0.0", "remark-frontmatter": "^5.0.0",
"solid-jsx": "^1.1.4", "solid-jsx": "^1.1.4",
"tailwindcss": "^4.0.0",
"to-vfile": "^8.0.0", "to-vfile": "^8.0.0",
"typed-css-modules": "^0.9.1", "typed-css-modules": "^0.9.1",
"unist-util-visit": "^5.0.0", "unist-util-visit": "^5.0.0",
"vfile-matter": "^5.0.0", "vfile-matter": "^5.0.0",
"vite": "^6.0.11" "vite": "^6.1.1"
} }
} }

6987
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
import tailwind from "tailwindcss";
import autoprefixer from "autoprefixer";
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: [
autoprefixer,
tailwind
]
}
export default config

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 B

View File

@@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="16" height="16"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13.28L12.2 2H13.76L9.56 13.28H8Z" fill="black"></path>
<path d="M2 8.89995V7.95195C2 7.45595 2.16 7.05995 2.48 6.76395C2.8 6.46795 3.22 6.31995 3.74 6.31995C4.068 6.31995 4.34 6.37595 4.556 6.48795C4.772 6.59995 4.952 6.73595 5.096 6.89595C5.248 7.05595 5.38 7.21995 5.492 7.38795C5.612 7.54795 5.732 7.68395 5.852 7.79595C5.972 7.89995 6.112 7.95195 6.272 7.95195C6.424 7.95195 6.536 7.90795 6.608 7.81995C6.688 7.72395 6.728 7.59595 6.728 7.43595V6.55995H8V7.50795C8 7.99595 7.84 8.39195 7.52 8.69595C7.208 8.99195 6.788 9.13995 6.26 9.13995C5.932 9.13995 5.66 9.08395 5.444 8.97195C5.228 8.85995 5.044 8.72395 4.892 8.56395C4.748 8.40395 4.616 8.24395 4.496 8.08395C4.384 7.91595 4.268 7.77995 4.148 7.67595C4.028 7.56395 3.888 7.50795 3.728 7.50795C3.584 7.50795 3.472 7.55195 3.392 7.63995C3.312 7.72795 3.272 7.85595 3.272 8.02395V8.89995H2Z" fill="black"></path>
</svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } }
@media (prefers-color-scheme: dark) { :root { filter: invert(100%); } }
</style></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 388 KiB

BIN
public/og.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

View File

@@ -1,329 +1,46 @@
@import url('https://fonts.cdnfonts.com/css/jetbrains-mono-2'); @import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap");
@import "tailwind"; @import "tailwindcss";
@config "../tailwind.config.ts";
:root {
--line-height: 1.2rem;
--border-thickness: 2px;
--text-color: black;
font-optical-sizing: auto;
font-variant-numeric: tabular-nums lining-nums;
}
.dark {
:root {
--text-color: white;
}
}
* {
box-sizing: border-box;
text-decoration-thickness: var(--border-thickness);
}
*::selection {
@apply bg-black text-white dark:bg-white dark:text-black;
}
.button {
border: var(--border-thickness) solid;
padding:
calc(var(--line-height) / 2 - var(--border-thickness)) calc(1ch - var(--border-thickness));
margin: 0;
height: calc(var(--line-height) * 2);
width: auto;
overflow: visible;
line-height: normal;
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
-webkit-appearance: none;
@apply select-none bg-white dark:bg-black px-1h shadow-box active:shadow-none active:translate-x-[3px] active:translate-y-[3px];
}
.button:focus:not(:active) {
--border-thickness: 3px;
outline: none;
}
hr {
@apply h-2v block relative text-black dark:text-white border-none my-1v;
}
hr:after {
@apply block absolute left-0 h-0 w-full border-black dark:border-white;
content: "";
top: calc(var(--line-height) - var(--border-thickness));
border-top: calc(var(--border-thickness) * 3) double;
}
.jump-text:hover>.jump-text {
animation: jump 0.25s ease-in-out;
animation-delay: var(--animation-delay);
}
@keyframes jump {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-7px);
}
}
details {
border: var(--border-thickness) solid var(--text-color);
padding: calc(var(--line-height) - var(--border-thickness)) 1ch;
margin-bottom: var(--line-height);
margin-top: var(--line-height);
}
summary {
font-weight: var(--font-weight-medium);
cursor: pointer;
}
details[open] summary {
margin-bottom: var(--line-height);
}
details ::marker {
display: inline-block;
content: '▶';
margin: 0;
}
details[open] ::marker {
content: '▼';
}
details :last-child {
margin-bottom: 0;
}
/* DITHER ANIMATION */
.dither {
background-repeat: repeat;
position: fixed;
inset: 0;
z-index: 50;
image-rendering: optimizeSpeed;
/* STOP SMOOTHING, GIVE ME SPEED */
image-rendering: -moz-crisp-edges;
/* Firefox */
image-rendering: -o-crisp-edges;
/* Opera */
image-rendering: -webkit-optimize-contrast;
/* Chrome (and eventually Safari) */
image-rendering: pixelated;
/* Universal support since 2021 */
image-rendering: optimize-contrast;
/* CSS3 Proposed */
-ms-interpolation-mode: nearest-neighbor;
pointer-events: none;
}
.dark {
.dither {
filter: invert(1)
}
.wave-image {
filter: invert(1)
}
}
.dither-1 {
background-image: url(/images/dither_light_3.png);
}
.dither-2 {
background-image: url(/images/dither_light_3.png);
background-position: 50px 50px;
}
.dither-3 {
background-image: url(/images/dither_light_3.png);
background-position: 100px 100px;
}
.tree, .tree,
.tree ul { .tree ul {
position: relative; position: relative;
padding-left: 0; padding-left: 0;
list-style-type: none; list-style-type: none;
line-height: var(--line-height); line-height: var(--line-height);
} }
.tree ul { .tree ul {
margin: 0; margin: 0;
} }
.tree ul li { .tree ul li {
position: relative; position: relative;
padding-left: 1.5ch; padding-left: 1.5ch;
margin-left: 1.5ch; margin-left: 1.5ch;
border-left: var(--border-thickness) solid var(--text-color); border-left: var(--border-thickness) solid var(--text-color);
} }
.tree ul li:before { .tree ul li:before {
position: absolute; position: absolute;
display: block; display: block;
top: calc(var(--line-height) / 2); top: calc(var(--line-height) / 2);
left: 0; left: 0;
content: ""; content: "";
width: 1ch; width: 1ch;
border-bottom: var(--border-thickness) solid var(--text-color); border-bottom: var(--border-thickness) solid var(--text-color);
} }
.tree ul li:last-child { .tree ul li:last-child {
border-left: none; border-left: none;
} }
.tree ul li:last-child:after { .tree ul li:last-child:after {
position: absolute; position: absolute;
display: block; display: block;
top: 0; top: 0;
left: 0; left: 0;
content: ""; content: "";
height: calc(var(--line-height) / 2); height: calc(var(--line-height) / 2);
border-left: var(--border-thickness) solid var(--text-color); border-left: var(--border-thickness) solid var(--text-color);
} }
/* DEBUG UTILITIES */
.debug .debug-grid {
--line-height: 1.2rem;
--text-color: #000000;
--background-color: #FFFFFF;
--color: color-mix(in srgb, var(--text-color) 10%, var(--background-color) 90%);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
background-image:
repeating-linear-gradient(var(--color) 0 1px, transparent 1px 100%),
repeating-linear-gradient(90deg, var(--color) 0 1px, transparent 1px 100%);
background-size: 1ch var(--line-height);
margin: 0;
}
code:not(pre code) {
@apply bg-black text-white dark:bg-white dark:text-black px-1h;
@apply selection:dark:bg-black selection:dark:text-white selection:bg-white selection:text-black;
}
.debug .off-grid {
background: rgba(255, 0, 0, 0.1);
}
.debug-toggle-label {
text-align: right;
}
.debug img {
/* border: 1px solid black; */
}
.formkit-form {
font-family: "JetBrains Mono", monospace;
}
.formkit-form [data-style="clean"] {
@apply !pt-2v !px-0 !pb-1v;
}
.formkit-fields {
@apply !m-0;
}
.formkit-field {
@apply !m-0 !mr-2h;
}
.formkit-input {
border: var(--border-thickness) solid !important;
padding:
calc(var(--line-height) / 2 - var(--border-thickness)) calc(1ch - var(--border-thickness)) !important;
margin: 0 !important;
height: calc(var(--line-height) * 2) !important;
width: 100% !important;
overflow: visible !important;
line-height: normal !important;
-webkit-font-smoothing: inherit !important;
-moz-osx-font-smoothing: inherit !important;
-webkit-appearance: none !important;
@apply !font-medium;
}
.formkit-input:focus:not(:active) {
--border-thickness: 3px;
outline: none;
}
.formkit-submit {
border: var(--border-thickness) solid !important;
padding:
calc(var(--line-height) / 2 - var(--border-thickness)) calc(1ch - var(--border-thickness)) !important;
margin: 0 !important;
height: calc(var(--line-height) * 2) !important;
width: auto !important;
overflow: visible !important;
line-height: normal !important;
-webkit-font-smoothing: inherit !important;
-moz-osx-font-smoothing: inherit !important;
-webkit-appearance: none !important;
@apply !select-none !bg-white !text-black !px-1h !shadow-box !py-0;
}
.formkit-submit:active {
@apply !shadow-none !translate-x-[3px] !translate-y-[3px]
}
.formkit-alert-success {
border-width: 0 !important;
@apply !bg-transparent !text-black !m-0 !p-0 !font-bold;
}
.dark {
.formkit-input {
@apply !border-white !bg-black !text-white;
}
.formkit-submit {
@apply !bg-black !text-white;
}
.formkit-alert-success {
@apply !text-white;
}
}
.formkit-submit span {
padding: 0 !important;
}
.formkit-submit:hover span {
background-color: transparent !important;
}
.formkit-submit:focus:not(:active) {
--border-thickness: 3px !important;
outline: none !important;
}
.pixelated {
image-rendering: pixelated;
}
/* <div id="a6d9b30e24" class="formkit-alert formkit-alert-success" data-element="success" data-group="alert">Success! Check your email to confirm the subscription.</div> */

5
src/app.css.d.ts vendored
View File

@@ -1,5 +0,0 @@
declare const styles: {
readonly "visually-hidden": string;
};
export = styles;

View File

@@ -1,27 +1,13 @@
import { Router } from "@solidjs/router"; import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router"; import { FileRoutes } from "@solidjs/start/router";
import { createSignal, onCleanup, onMount, Suspense } from "solid-js"; import { Suspense } from "solid-js";
import "./app.css"; import "./app.css";
import { Layout } from "./components/Layout"; import { Layout } from "./components/Layout";
import { MetaProvider, Title } from "@solidjs/meta"; import { MetaProvider } from "@solidjs/meta";
export default function App() { export default function App() {
onMount(() => {
const listener = (e: KeyboardEvent) => {
if (e.metaKey && e.key.toLowerCase() === "k") {
e.preventDefault(); // Prevent the default action (optional)
document.body.classList.toggle("debug");
}
};
window.addEventListener("keydown", listener);
onCleanup(() => {
window.removeEventListener("keydown", listener);
});
});
return ( return (
<MetaProvider> <MetaProvider>
<Title>minhtrannhat.com</Title>
<Router <Router
root={(props) => { root={(props) => {
return ( return (

View File

@@ -1,5 +1,8 @@
import type { ParentComponent } from "solid-js"; import type { ParentComponent } from "solid-js";
/*
* Code from andii.dev
*/
export const Button: ParentComponent<{ onClick?: () => void }> = (props) => { export const Button: ParentComponent<{ onClick?: () => void }> = (props) => {
return ( return (
<button class="button" type="button" onClick={props.onClick}> <button class="button" type="button" onClick={props.onClick}>

View File

@@ -1,69 +0,0 @@
import { createSignal, onMount } from "solid-js";
export const DarkModeToggle = () => {
let ref!: HTMLButtonElement;
const [dark, setDark] = createSignal(false);
const size = 64;
const max = 5;
const time = 70;
let direction = dark() ? -1 : 1;
let current = dark() ? 0 : max;
onMount(() => {
const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
setDark(isDark);
if (isDark) {
direction = -1;
current = 0;
}
requestAnimationFrame(() => {
play();
});
});
const coord = (n: number) => -n * size;
const play = () => {
ref.style.backgroundPositionX = `${coord(current)}px`;
if (direction === -1 && current === 2) {
document.documentElement.classList.add("dark");
// ref.style.filter = "invert(0)";
}
if (direction === 1 && current === 3) {
document.documentElement.classList.remove("dark");
// ref.style.filter = "invert(1)";
}
if (direction === -1 && current === 0) return;
if (direction === 1 && current === max) return;
current += direction;
setTimeout(play, time);
};
const toggle = () => {
if (dark()) {
direction = 1;
} else {
direction = -1;
}
play();
setDark((d) => !d);
};
return (
<div class="absolute top-1v right-4h">
<button
onClick={toggle}
ref={ref}
class="pixelated"
style={{
scale: "1.5",
height: "32px",
width: "64px",
"background-image": `url("/images/toggle.png")`,
}}
aria-hidden
type="button"
/>
</div>
);
};

View File

@@ -0,0 +1,34 @@
import { For } from "solid-js";
import JobCard from "./JobCard";
import { createSignal } from "solid-js";
const Experience = () => {
const [jobs, _] = createSignal([
{
title: "Software Developer Engineer Intern",
company: "Amazon Canada",
location: "Toronto, Ontario, Canada",
range: "May 2025 - August 2025",
url: "https://flex.amazon.ca/",
},
{
title: "Software Engineer Intern",
company: "Cisco Canada",
location: "Remote",
range: "January 2023 - May 2023",
url: "https://developer.cisco.com/docs/modeling-labs/cat-9000v/",
},
]);
return (
<section class="mt-16 px-4">
<h2 class="text-xl text-nord-1 font-bold mb-6">Experience</h2>
<div class="!flex !flex-col !gap-0.5v ml-2h">
<For each={jobs()}>{(job) => <JobCard job={job} />}</For>
</div>
</section>
);
};
export default Experience;

View File

@@ -0,0 +1,17 @@
const JobCard = (props) => {
return (
<div class="p-4">
<h3 class="font-bold text-xl">
{props.job.title} @
<a href={props.job.url} class="text-blue-500 hover:text-blue-600">
{props.job.company}
</a>
</h3>
<p class="text-nord-1 mb-4">
{props.job.range} | {props.job.location}
</p>
</div>
);
};
export default JobCard;

View File

@@ -1,13 +1,9 @@
import { A } from "@solidjs/router"; import { A } from "@solidjs/router";
import type { ParentComponent } from "solid-js"; import type { ParentComponent } from "solid-js";
import { clientOnly } from "@solidjs/start";
const DarkModeToggle = clientOnly(() =>
import("./DarkModeToggle").then((r) => ({
default: r.DarkModeToggle,
})),
);
/*
* Code from andii.dev
*/
function changeFavicon(newFaviconPath: string) { function changeFavicon(newFaviconPath: string) {
const link = document.querySelector("link[rel~='icon']") as HTMLLinkElement; const link = document.querySelector("link[rel~='icon']") as HTMLLinkElement;
if (link) { if (link) {
@@ -26,22 +22,25 @@ export const Layout: ParentComponent = (props) => {
<a href="#main-content" class="sr-only"> <a href="#main-content" class="sr-only">
Skip to main content Skip to main content
</a> </a>
<div class="flex flex-col min-h-screen pt-2v py-1v px-2h max-w-thread mx-auto relative overflow-x-hidden leading-1 box-border decoration-2 underline-offset-2"> <div class="bg-nord-6 flex flex-col min-h-screen pt-2v py-1v px-2h max-w-full mx-auto relative overflow-x-hidden leading-1 box-border decoration-2 underline-offset-2">
<header class="flex flex-col items-center justify-center gap-2v px-4h py-2v"> <header class="flex flex-col items-center justify-center gap-2v px-4h py-2v">
<a href="/" class="text-2v leading-2 font-bold"> <a href="/" class="text-nord-3 text-2v leading-2 font-bold">
~/minhtrannhat ~/minh
</a> </a>
<DarkModeToggle /> <nav class="container mx-auto px-4 py-4">
<ul class="flex flex-wrap justify-center items-center gap-6">
<nav> <A
<ul class="flex items-center gap-7h"> end
<A end class="hover:underline" activeClass="font-bold" href={"/"}> class="hover:underline hover:text-nord10"
activeClass="font-bold"
href={"/"}
>
Home Home
</A> </A>
<A <A
end end
class="hover:underline" class="hover:underline hover:text-nord10"
activeClass="font-bold" activeClass="font-bold"
href={"/articles"} href={"/articles"}
> >
@@ -49,20 +48,21 @@ export const Layout: ParentComponent = (props) => {
</A> </A>
<A <A
end end
class="hover:underline" class="hover:underline hover:text-nord10"
activeClass="font-bold" activeClass="font-bold"
href={"/tags"} href={"/tags"}
> >
Tags Tags
</A> </A>
<a <A
class="hover:text-nord10"
href="/resume.pdf" href="/resume.pdf"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
onClick={() => changeFavicon("./favicon.ico")} onClick={() => changeFavicon("./favicon.ico")}
> >
Resume Resume
</a> </A>
</ul> </ul>
</nav> </nav>
</header> </header>
@@ -70,8 +70,6 @@ export const Layout: ParentComponent = (props) => {
<main id="main-content" class="mt-1v flex-auto"> <main id="main-content" class="mt-1v flex-auto">
{props.children} {props.children}
</main> </main>
<div class="debug-grid" />
</div> </div>
</> </>
); );

View File

@@ -8,6 +8,9 @@ import {
onMount, onMount,
} from "solid-js"; } from "solid-js";
/*
* Code from andii.dev
*/
const P: ParentComponent = (props) => <p class="mt-1v">{props.children}</p>; const P: ParentComponent = (props) => <p class="mt-1v">{props.children}</p>;
const Ol: ParentComponent = (props) => ( const Ol: ParentComponent = (props) => (
@@ -42,7 +45,7 @@ const Pre: ParentComponent<{ lang: string; lines?: string; file?: string }> = (
return ( return (
<div class="my-1v"> <div class="my-1v">
<div class="bg-black text-white dark:bg-white dark:text-black flex justify-between px-1h text-sm leading-1"> <div class="bg-nord2 text-nord12 text-xl flex justify-between px-1h leading-2">
<Show when={props.file} fallback={<span aria-hidden />}> <Show when={props.file} fallback={<span aria-hidden />}>
<span>{props.file}</span> <span>{props.file}</span>
</Show> </Show>
@@ -186,9 +189,9 @@ export const PostImage: Component<{
); );
}; };
export const Aside: ParentComponent = (props) => ( export const Notes: ParentComponent = (props) => (
<aside class="border-l-2 border-black dark:border-white pl-1h mt-1v"> <aside class="border-l-2 border-black dark:border-white pl-1h mt-1v">
<div class="uppercase text-sm leading-1 font-medium select-none">Aside</div> <div class="uppercase text-sm leading-1 font-medium select-none">Notes</div>
<div class="[&_*:first-child]:mt-0">{props.children}</div> <div class="[&_*:first-child]:mt-0">{props.children}</div>
</aside> </aside>
); );

View File

@@ -2,6 +2,9 @@ import dayjs from "dayjs";
import { type Component, For } from "solid-js"; import { type Component, For } from "solid-js";
import type { Post } from "~/types"; import type { Post } from "~/types";
/*
* Code from andii.dev
*/
export const Posts: Component<{ posts: Post[] }> = (props) => { export const Posts: Component<{ posts: Post[] }> = (props) => {
return ( return (
<ol class=""> <ol class="">

View File

@@ -3,6 +3,9 @@ import { type Component, For, Show } from "solid-js";
type Node = { l: string; c: TreeNode[] }; type Node = { l: string; c: TreeNode[] };
type TreeNode = string | Node; type TreeNode = string | Node;
/*
* Code from andii.dev
*/
const Subtree: Component<{ tree: TreeNode }> = (props) => { const Subtree: Component<{ tree: TreeNode }> = (props) => {
return ( return (
<Show <Show

136
src/css/prism-nord.css Normal file
View File

@@ -0,0 +1,136 @@
/* Generated with http://k88hudson.github.io/syntax-highlighting-theme-generator/www */
/*********************************************************
* General
*/
pre[class*="language-"],
code[class*="language-"] {
color: #d8dee9;
font-size: 1em;
text-shadow: none;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::selection,
code[class*="language-"]::selection,
pre[class*="language-"]::mozselection,
code[class*="language-"]::mozselection {
text-shadow: none;
background: none;
}
@media print {
pre[class*="language-"],
code[class*="language-"] {
text-shadow: none;
}
}
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
background: #2e3440;
}
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
color: #d8dee9;
background: #2e3440;
}
/*********************************************************
* Tokens
*/
.namespace {
opacity: 0.7;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #606e87;
}
.token.punctuation {
color: #81a1c1;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #b48ead;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #a2bf8c;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #80a2c1;
background: none;
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #81a1c1;
}
.token.function {
color: #8fbcbc;
}
.token.regex,
.token.important,
.token.variable {
color: #ee9900;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
/*********************************************************
* Line highlighting
*/
pre[data-line] {
position: relative;
}
pre[class*="language-"] > code[class*="language-"] {
position: relative;
z-index: 1;
}
.line-highlight {
position: absolute;
left: 0;
right: 0;
padding: inherit 0;
margin-top: 1em;
background: #3b4251;
box-shadow: inset 5px 0 0 #d8dee9;
z-index: 0;
pointer-events: none;
line-height: inherit;
white-space: pre;
}

View File

@@ -1,199 +0,0 @@
/**
* Shades of Purple Theme for Prism.js
*
* @author Ahmad Awais <https://twitter.com/MrAhmadAwais/>
* @support Follow/tweet at https://twitter.com/MrAhmadAwais/
*/
code[class*='language-'],
pre[class*='language-'] {
@apply text-black dark:text-white font-medium;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*='language-']::-moz-selection,
pre[class*='language-'] ::-moz-selection,
code[class*='language-']::-moz-selection,
code[class*='language-'] ::-moz-selection,
pre[class*='language-']::selection,
pre[class*='language-'] ::selection,
code[class*='language-']::selection,
code[class*='language-'] ::selection {
color: inherit;
}
/* Code blocks. */
pre[class*='language-'] {
@apply box-border border-black dark:border-white border-2;
padding: calc(var(--line-height) - var(--border-thickness)) calc(2ch - var(--border-thickness));
overflow: auto;
}
/*
:not(pre)>code[class*='language-'],
pre[class*='language-'] {
}
@media (prefers-color-scheme: dark) {
:not(pre)>code[class*='language-'],
pre[class*='language-'] {
@apply border-white;
}
} */
/* Inline code */
/* :not(pre)>code[class*='language-'] {
} */
.token {
font-weight: 400;
}
.token.comment,
.token.prolog,
.token.cdata {
@apply text-slate-600 dark:text-slate-400;
}
.token.delimiter,
.token.keyword,
.token.selector,
.token.important,
.token.atrule {
@apply text-black dark:text-white;
}
.token.operator,
.token.attr-name {
@apply font-medium text-black dark:text-white;
}
.token.punctuation {
@apply text-slate-500;
}
.token.boolean {
@apply font-medium text-black dark:text-white;
}
.token.tag,
.token.tag .punctuation,
.token.doctype,
.token.builtin {
@apply font-medium text-black dark:text-white;
}
.token.entity,
.token.symbol {
@apply font-medium text-black dark:text-white;
}
.token.number {
@apply font-medium text-black dark:text-white;
}
.token.property,
.token.constant,
.token.variable {
@apply font-medium text-black dark:text-white;
}
.token.string,
.token.char {
@apply text-slate-800 dark:text-slate-200;
font-style: italic;
}
.token.attr-value,
.token.attr-value .punctuation {
color: #a5c261;
}
.token.attr-value .punctuation:first-child {
color: #a9b7c6;
}
.token.url {
color: #287bde;
text-decoration: underline;
}
.token.function {
@apply font-bold text-black dark:text-white;
}
/* .token.regex {
} */
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.inserted {
background: #00ff00;
}
.token.deleted {
background: #ff000d;
}
code.language-css .token.property,
code.language-css .token.property+.token.punctuation {
color: #a9b7c6;
}
code.language-css .token.id {
color: #ffc66d;
}
code.language-css .token.selector>.token.class,
code.language-css .token.selector>.token.attribute,
code.language-css .token.selector>.token.pseudo-class,
code.language-css .token.selector>.token.pseudo-element {
color: #ffc66d;
}
.token.class-name {
@apply font-medium text-slate-700 dark:text-slate-300;
font-style: italic;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
background: none;
}
.line-highlight.line-highlight {
margin-top: 36px;
background: linear-gradient(to right, rgba(179, 98, 255, 0.17), transparent);
}
.line-highlight.line-highlight:before,
.line-highlight.line-highlight[data-end]:after {
content: '';
}

View File

@@ -1 +1,20 @@
[] [
{
"title": "A Rusty Stack Jump",
"description": "Jumping into a new stack with Rust",
"date": "2025-02-27",
"featuredImage": null,
"featuredImageDesc": null,
"tags": ["rust", "asm", "systems", "operating systems", "async"],
"slug": "rust_stack_jmp"
},
{
"title": "Testing Test Test Test",
"description": "Woah this is so cool",
"date": "2025-01-29",
"featuredImage": null,
"featuredImageDesc": null,
"tags": ["rust", "python", "mdx", "markdown"],
"slug": "test"
}
]

View File

@@ -1,4 +1,3 @@
// @ts-expect-error
import Posts from "./posts.json"; import Posts from "./posts.json";
import type { Post } from "~/types"; import type { Post } from "~/types";

View File

@@ -1,48 +0,0 @@
---
date: 20204-05-11
tags:
- solidjs
- webdev
title: How to enumerate routes in solid-start
---
This website is built with [solid-start](https://start.solidjs.com/getting-started/what-is-solidstart).
I needed a way to enumerate all of the posts under `/blog` so that I could dynamically generate lists, both for the [home page](/), and for the tags pages: [/tags/solidjs](/tags/solidjs).
However, the solid-start `<FileRouter>` doesn't expose any of that information, either at build-time or run-time. We have to figure it out ourselves.
My first thought was to have a script that watches the directory and writes to a file.
That would've worked. But while browsing through the docs trying to figure out how to get a file watcher to be part of the vite dev server, I found out about [virtual modules](https://vitejs.dev/guide/api-plugin#virtual-modules-convention).
Essentially, you can define a `js` module that has dynamic content by exporting a `js` string.
```js lang="js" lines="2,14"
export default function myPlugin() {
const virtualModuleId = 'virtual:my-module'
const resolvedVirtualModuleId = '\0' + virtualModuleId
return {
name: 'my-plugin', // required, will show up in warnings and errors
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `export const msg = "from virtual module"`
}
},
}
}
```
After adding `myPlugin` to your vite plugins list, you can import the virtual module anywhere in the code and use it as if it were a real file.
```js lang="js"
import {msg} from "virtual:my-module"
```
This seemed more "elegant" than a file watcher.

View File

@@ -1,34 +0,0 @@
---
date: 20204-05-11
tags:
- solidjs
- webdev
title: This blog uses solid-start.
description: How to set up a blog using solid-start and mdx.
---
This website uses [solid-start](https://start.solidjs.com/getting-started/what-is-solidstart).
solid-start is a Next.js-like framework for creating SSR / SSG websites. But instead of react, it builds on top of solid-js.
I've been using solid and solid-start professionally for almost 2 years. And while solid-js feels rock-solid, solid-start still feels rough around the edges.
For each of the features I wanted to implement, I ran into issues that delayed this from being a weekend project into taking about 4 weekends instead.
Here's what I wanted to accomplish:
1. Use [mdx](https://mdxjs.com/) for writing the posts
2. Define metadata for each post in the same mdx file
3. Full SSG
4. Code highlighting at compile time
5. Tags
And here's how I did it:
## Using mdx
MDX is markdown that you can intersprinkle with jsx.
The initial scaffolding is easy, follow [these steps](https://docs.solidjs.com/solid-start/getting-started), and choose the `with-mdx` option.
I opted to have my posts live under the `/blog` path.

View File

@@ -9,12 +9,9 @@ export default createHandler(() => (
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" /> <link rel="icon" type="image/x-icon" href="/favicon.ico" />
<script>
{`document.documentElement.classList.toggle('dark', window.matchMedia('(prefers-color-scheme: dark)').matches)`}
</script>
{assets} {assets}
</head> </head>
<body class="font-mono bg-white dark:bg-black dark:text-white"> <body class="font-mono">
<div id="app">{children}</div> <div id="app">{children}</div>
{scripts} {scripts}
</body> </body>

1
src/global.d.ts vendored
View File

@@ -1 +0,0 @@
/// <reference types="@solidjs/start/env" />

View File

@@ -1,34 +1,46 @@
import { For } from "solid-js"; import { For } from "solid-js";
import { posts } from "~/data/posts"; import Experience from "../components/Experience";
import { Posts } from "~/components/Posts"; import { Title, Meta } from "@solidjs/meta";
const links = [ const links = [
"https://github.com/minhtrannhat", "https://github.com/minhtrannhat",
"https://linkedin.com/in/minh-tran-nhat", "https://linkedin.com/in/minh-tran-nhat",
"https://git.minhtrannhat.com/explore/repos", "https://git.minhtrannhat.com/explore/repos",
]; ];
const Homepage = () => { const Homepage = () => {
return ( return (
<div> <div>
<section class="flex flex-col sm:flex-row gap-2v sm:gap-3h"> <Title>minhtran_dev</Title>
<Meta property="og:title" content="minhtran_dev" />
<Meta property="og:description" content="just trying my best :)" />
<Meta property="og:image" content="/og.png" />
<Meta property="og:image:alt" content="minhtran_dev site" />
<Meta property="og:image:width" content="1200" />
<Meta property="og:image:height" content="630" />
<section class="mx-4 flex flex-col sm:flex-row gap-2v sm:gap-3h">
<div class="font-medium"> <div class="font-medium">
<div class="flex items-end mb-1v gap-1h"> <div class="text-nord-1 flex items-end mb-1v gap-1h">
<p>Hi, Minh here.</p> <p>Hi, Minh here.</p>
</div> </div>
<p class="mb-1v"> <p class="mb-1v text-nord-1">
I'm Minh Tran, a Computer Engineering student at Concordia I'm a Computer Engineering student at Concordia University,
University, Montreal, Canada. Montreal, Canada.
<br /> <br />
<br /> <br />
I'm most passionate about designing distributed systems that scales Things that I'm most passionate about: distributed systems, backend
but I'm also interested in compilers and systems programming. When development, compilers and systems programming.
I'm not coding, I read books, listen to podcasts or study music <br />
<br />
When I'm not coding, I read books, listen to podcasts or study music
theory. theory.
</p> </p>
<p> <p>
Say hi:{" "} Email me at:{" "}
<a <a
class="underline" class="underline hover:text-nord-11"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
href="mailto:minh@minhtrannhat.com" href="mailto:minh@minhtrannhat.com"
@@ -37,10 +49,10 @@ const Homepage = () => {
</a> </a>
</p> </p>
</div> </div>
<ul class="sm:mt-3v text-slate-600 dark:text-slate-200 text-base sm:text-sm leading-1"> <ul class="mx-4 sm:mx-6 sm:mt-3v text-slate-600 text-base sm:text-sm leading-1">
<For each={links}> <For each={links}>
{(link) => ( {(link) => (
<li class="list-square hover:text-black dark:hover:text-white ml-2h leading-1"> <li class="list-square hover:text-nord-11 ml-2h leading-1">
<a <a
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
@@ -55,7 +67,8 @@ const Homepage = () => {
</ul> </ul>
</section> </section>
<hr /> <br />
<Experience />
</div> </div>
); );
}; };

View File

@@ -1,11 +1,20 @@
import { posts } from "~/data/posts"; import { posts } from "~/data/posts";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { Title, Meta } from "@solidjs/meta";
import { For } from "solid-js"; import { For } from "solid-js";
const Articles = () => { const Articles = () => {
return ( return (
<div> <div>
<Title>minhtran_dev - Articles</Title>
<Meta property="og:title" content="minhtran_dev Articles" />
<Meta property="og:description" content="My articles" />
<Meta property="og:image" content="/og.png" />
<Meta property="og:image:alt" content="minhtran_dev site" />
<Meta property="og:image:width" content="1200" />
<Meta property="og:image:height" content="630" />
<ol class="flex flex-col gap-1v list-square ml-2h"> <ol class="flex flex-col gap-1v list-square ml-2h">
<For each={Object.values(posts)}> <For each={Object.values(posts)}>
{(post) => ( {(post) => (

View File

@@ -2,12 +2,16 @@ import { For, Show } from "solid-js";
import type { RouteSectionProps } from "@solidjs/router"; import type { RouteSectionProps } from "@solidjs/router";
import { Meta, Title } from "@solidjs/meta"; import { Meta, Title } from "@solidjs/meta";
import { posts } from "~/data/posts"; import { posts } from "~/data/posts";
//@ts-expect-error
import { MDXProvider } from "solid-mdx"; import { MDXProvider } from "solid-mdx";
import { markdownComponents, PostImage } from "~/components/Markdown"; import { markdownComponents, PostImage } from "~/components/Markdown";
import dayjs from "dayjs"; import dayjs from "dayjs";
import "../css/prism-theme.css"; import "../css/prism-nord.css";
import type { Post } from "~/types"; import type { Post } from "~/types";
/*
* Code from andii.dev
*/
const Blog = (props: RouteSectionProps<unknown>) => { const Blog = (props: RouteSectionProps<unknown>) => {
const meta = () => const meta = () =>
posts.find((p) => props.location.pathname.endsWith(p.slug)) as Post; posts.find((p) => props.location.pathname.endsWith(p.slug)) as Post;
@@ -19,10 +23,21 @@ const Blog = (props: RouteSectionProps<unknown>) => {
return ( return (
<article class="pb-5v"> <article class="pb-5v">
<Title>minhtrannhat.com - {meta()?.title}</Title> <Title>minhtran_dev - {meta()?.title}</Title>
<Meta name="og:title" content={meta().title} /> <Meta name="og:title" content={meta().title} />
<Meta name="description" content={meta().description} /> <Meta name="description" content={meta().description} />
<Meta name="og:description" content={meta().description} /> <Meta name="og:description" content={meta().description} />
<Meta property="og:type" content="article" />
<Meta
property="og:url"
content={`https://minhtranhat.com/blog/${meta()?.slug}`}
/>
<Meta property="og:image" content="/og.png" />
<Meta property="og:image:alt" content="minhtran_dev site" />
<Meta property="og:image:width" content="1200" />
<Meta property="og:image:height" content="630" />
<Show when={meta().featuredImage}> <Show when={meta().featuredImage}>
<PostImage <PostImage
@@ -31,6 +46,7 @@ const Blog = (props: RouteSectionProps<unknown>) => {
alt={meta().featuredImageDesc || ""} alt={meta().featuredImageDesc || ""}
/> />
</Show> </Show>
<h1 class="text-2v leading-2 font-bold mb-1v">{meta().title}</h1> <h1 class="text-2v leading-2 font-bold mb-1v">{meta().title}</h1>
<div class="flex items-center gap-4h mb-2v text-sm leading-1"> <div class="flex items-center gap-4h mb-2v text-sm leading-1">

View File

@@ -0,0 +1,129 @@
---
title: A Rusty Stack Jump
description: Jumping into a new stack with Rust
date: 2025-02-27
featuredImage:
featuredImageDesc:
tags:
- rust
- asm
- systems
- operating systems
- async
---
import { Notes, PostImage } from "~/components/Markdown";
import { Tree } from "~/components/Tree";
In my quest to learn to build an async runtime in Rust, I have to learn about CPU context switching. In order to switch from one async task to another, our async runtime has to perform a context switch. This means saving the current CPU registers marked as `callee saved` by the System V ABI manual and loading the CPU registers with our new async stack.
In this article, I will show you what I have learned about jumping onto a new stack in a x86_64 CPU.
<Notes>
I'm learning about async runtimes in Rust based on the amazing book [Asynchronous Programming in Rust: Learn asynchronous programming by building working examples of futures, green threads, and runtimes](https://www.packtpub.com/en-mt/product/asynchronous-programming-in-rust-9781805128137)
It's an amazing book, don't get me wrong, but I feel like the explanation can be hand-wavy sometimes. Thus, I write this to archive my own explanation and potentially help other people who also struggle with the subject.
</Notes>
<Notes>
Most async runtimes in Rust do not use stackful coroutines (which are used by
Go's `gochannel`, Erlang's `processes`) and instead, use state machines to
manage async tasks.
</Notes>
## Contents
<hr />
## Setting the stage
Why do we need to swap the stack of async tasks in a runtime with stackful coroutines ?
Async tasks, by nature, are paused and resumed. Everytime a task is paused to move into a new task, we would have to save the current context of the task that is running and load the context of the upcoming task.
## Jumping into the new stack
Here is the code in its entirely, I'd recommend you run this on the [Rust Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024). I have left comments through out the code so you can get the general idea.
Note that you have to manually stop the process.
```rust lang="rust" file="stack_swap.rs"
use core::arch::asm;
// stack size of 48 bytes so its easy to print the stack before we switch contexts
const SSIZE: isize = 48;
// a struct that represents our CPU state
//
// This struct will stores the stack pointer
#[derive(Debug, Default)]
#[repr(C)]
struct ThreadContext {
rsp: u64,
}
// Returning ! means
// it will panic OR runs forever
fn hello() -> ! {
println!("I LOVE WAKING UP ON A NEW STACK!");
loop {}
}
// new is a pointer to a ThreadContext
unsafe fn gt_switch(new: *const ThreadContext) {
// inline assembly
asm!(
"mov rsp, [{0} + 0x00]", // move the content of where the new pointer is pointing to, into the rsp register
"ret", // ret pops the return address from our custom stack—in our example, the address of hello.
in(reg) new,
);
}
fn main() {
// initialize
let mut ctx = ThreadContext::default();
// stack initialize
// ie. 0x10
let mut stack = vec![0_u8; SSIZE as usize];
unsafe {
// we get the bottom of the stack
// remember that the stack grows downward from high memory address to low memory address
// i.e 0x40 -> because 0x30 = 0x40 - 0x10 and 0x30 = SSIZE in decimal
// NOTE: offset() is applied in units of the size of the type that the pointer points to
// in our case, stack is a pointer to u8 (a byte) so offset(SSIZE) == offset(48 bytes) == offset(0x30)
let stack_bottom = stack.as_mut_ptr().offset(SSIZE);
// we align the bottom of the stack to be 16-byte-aligned
// this is for performance reasons as some CPU instructions (SSE and SIMD)
// The technicality: 15 is b1111 so if we do (stack_bottom AND !15) we will zero out the bottom 4 bits
//
// we also want the bottom of the stack pointer to point to a byte (8bit or u8)
let sb_aligned = (stack_bottom as usize & !15) as *mut u8;
// Here, we write the address of the hello function as 64 bits(8 bytes)
// Remember that 16 bytes = 0x10 in hex
// So we go DOWN 10 memory addresses, i.e from 0x40 to 0x30
// NOTE: 16 bytes down (0x10) even though, the hello function pointer is ONLY 8 bytes
// This is because the System V ABI requires the stack pointer to be always be 16-byte aligned
std::ptr::write(sb_aligned.offset(-16) as *mut u64, hello as u64);
// we write the stack pointer into the rsp inside context
ctx.rsp = sb_aligned.offset(-16) as u64;
for i in 0..SSIZE {
println!("mem: {}, val: {}",
sb_aligned.offset(-i as isize) as usize,
*sb_aligned.offset(-i as isize))
};
// we go into the function
// we will write our stack pointer to the cpu stack pointer
// and `ret` will pop that stack pointer
gt_switch(&mut ctx);
}
}
```

118
src/routes/blog/test.mdx Normal file
View File

@@ -0,0 +1,118 @@
---
title: Testing Test Test Test
description: Woah this is so cool
date: 2025-01-29
featuredImage:
featuredImageDesc:
tags:
- rust
- python
- mdx
- markdown
---
import { Notes, PostImage } from "~/components/Markdown"
import { Tree } from "~/components/Tree"
Woah this blog is sooo cool, look at all these beautifully rendered markdown stuffs :OOOO
<Notes>
Notty Notes
</Notes>
## Contents
<hr />
## Python
### longestCommonSubsequence
```python lang="python" file="leetcode1143.py"
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
# edge cases
# both texts are just length of 1:
if len(text1) == 1 and len(text2) == 1:
return int(text1 == text2)
# only one row:
if len(text2) == 1 and len(text1) > 1:
return int(text2 in text1)
# only one col:
if len(text1) == 1 and len(text2) > 1:
return int(text1 in text2)
rows = len(text2)
cols = len(text1)
# we use bottom up 2D DP
# reaching here means it is at least 2x2
dp = [
[0 for _ in range(cols)] for _ in range(rows)
]
# seed the top left tile
dp[0][0] = 1 if text1[0] == text2[0] else 0
# we seed the first row and first col
for col in range(1, cols):
dp[0][col] = dp[0][col - 1] if text1[col] != text2[0] else 1
for row in range(1, rows):
dp[row][0] = dp[row - 1][0] if text1[0] != text2[row] else 1
# for the inner triangle, we use the following
# dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) if text1[i] != text2[j]
# else max(dp[i - 1][j], dp[i][j - 1]) + 1
for row in range(1, rows):
for col in range(1, cols):
dp[row][col] = dp[row - 1][col - 1] + 1 if text1[col] == text2[row] else max(dp[row - 1][col], dp[row][col - 1])
# return the bottom right tile
return dp[rows - 1][cols - 1]
# space and time complexity: both O(N * M)
# CAN OPTIMIZE TO GET SPACE COMPLEXITY OF O(MIN(N, M))
```
## Rust
### Async Programming stuffs
```rust lang="rust" file="ffi.rs"
// Here we have the syscalls
// Unsafe !!!
#[link(name = "c")]
extern "C" {
pub fn epoll_create(size: i32) -> i32;
pub fn close(fd: i32) -> i32;
pub fn epoll_ctl(epfd: i32, op: i32, fd: i32, event: *mut Event) -> i32;
pub fn epoll_wait(epfd: i32, events: *mut Event, maxevents: i32, timeout: i32) -> i32;
}
// Avoid padding by using repr(packed)
// Data struct is different in Rust compared to C
#[derive(Debug)]
#[repr(C)]
#[cfg_attr(target_arch = "x86_64", repr(packed))]
pub struct Event {
pub(crate) events: u32,
// Using `Token` a.k.a `epoll_data` to track which socket generated the event
pub(crate) epoll_data: usize,
}
impl Event {
pub fn token(&self) -> usize {
self.epoll_data
}
}
```

View File

@@ -1,10 +1,23 @@
import { For } from "solid-js"; import { For } from "solid-js";
import { tags } from "~/data/tags"; import { tags } from "~/data/tags";
import { Title, Meta } from "@solidjs/meta";
/*
* Code from andii.dev
*/
const Tags = () => { const Tags = () => {
return ( return (
<div> <div>
<h1 class="text-xl font-bold mt-2v mb-1v">All tags:</h1> <Title>minhtran_dev - Article Tags</Title>
<Meta property="og:title" content="minhtran_dev Article Tags" />
<Meta property="og:description" content="Tags for my articles" />
<Meta property="og:image" content="/og.png" />
<Meta property="og:image:alt" content="minhtran_dev site" />
<Meta property="og:image:width" content="1200" />
<Meta property="og:image:height" content="630" />
<h1 class="text-xl text-nord-1 font-bold mt-1v mb-1v">All tags:</h1>
<ol class="flex flex-col gap-1v list-square ml-2h"> <ol class="flex flex-col gap-1v list-square ml-2h">
<For each={Object.values(tags)}> <For each={Object.values(tags)}>
{(tag) => ( {(tag) => (

View File

@@ -3,11 +3,27 @@ import { type Component, Show } from "solid-js";
import { posts } from "~/data/posts"; import { posts } from "~/data/posts";
import { Posts } from "~/components/Posts"; import { Posts } from "~/components/Posts";
import { tags } from "~/data/tags"; import { tags } from "~/data/tags";
import { Title, Meta } from "@solidjs/meta";
/*
* Code from andii.dev
*/
const TagId: Component<RouteSectionProps<unknown>> = (props) => { const TagId: Component<RouteSectionProps<unknown>> = (props) => {
const tag = () => tags[props.params.id]; const tag = () => tags[props.params.id];
return ( return (
<Show when={tag()} fallback={<div>No posts with that tag</div>}> <Show when={tag()} fallback={<div>No posts with that tag</div>}>
<Title>minhtran_dev - Tag: {tag().id}</Title>
<Meta
property="og:title"
content={`minhtran_dev Articles Tag: ${tag().id}`}
/>
<Meta property="og:description" content={`My ${tag().id} Articles`} />
<Meta property="og:image" content="/og.png" />
<Meta property="og:image:alt" content="minhtran_dev site" />
<Meta property="og:image:width" content="1200" />
<Meta property="og:image:height" content="630" />
<div> <div>
<h1 class="text-lg font-bold mb-6">Tag: {tag().id}</h1> <h1 class="text-lg font-bold mb-6">Tag: {tag().id}</h1>

View File

@@ -1,73 +0,0 @@
import type { Accessor } from "solid-js";
import { isServer } from "solid-js/web";
import { useRouteTransitionTiming } from "./useRouteTransitionTiming";
export const useDitherAnimation = (ref: Accessor<HTMLElement | undefined>) => {
if (!isServer) {
const d1 = document.createElement("div");
d1.classList.add("dither", "dither-1");
const d2 = document.createElement("div");
d2.classList.add("dither", "dither-2");
const d3 = document.createElement("div");
d3.classList.add("dither", "dither-3");
let started = false;
useRouteTransitionTiming(
300,
() => {
ref()?.appendChild(d1);
setTimeout(() => {
ref()?.appendChild(d2);
}, 100);
setTimeout(() => {
ref()?.appendChild(d3);
}, 200);
started = true;
},
() => {
const rnd = () =>
setTimeout(() => {
try {
d1.style.backgroundPosition = `${Math.round(
Math.random() * 100,
)}px ${Math.round(Math.random() * 100)}px`;
d2.style.backgroundPosition = `${Math.round(
Math.random() * 100,
)}px ${Math.round(Math.random() * 100)}px`;
d3.style.backgroundPosition = `${Math.round(
Math.random() * 100,
)}px ${Math.round(Math.random() * 100)}px`;
} catch (error) {
console.error(error);
}
if (started) {
rnd();
}
}, 100);
rnd();
},
() => {
started = false;
try {
ref()?.removeChild(d3);
} catch (error) {
console.error(error);
}
setTimeout(() => {
try {
ref()?.removeChild(d2);
} catch (error) {
console.error(error);
}
}, 100);
setTimeout(() => {
try {
ref()?.removeChild(d1);
} catch (error) {
console.error(error);
}
}, 200);
},
);
}
};

View File

@@ -1,24 +0,0 @@
import { useIsRouting, useBeforeLeave } from "@solidjs/router";
import { createEffect } from "solid-js";
export const useRouteTransitionTiming = (
transitionTime: number,
onEnter: () => void,
onLoading: () => void,
onExit: () => void,
) => {
const isRouting = useIsRouting();
createEffect((oldR: boolean | undefined) => {
const r = isRouting();
if (oldR && !r) onExit();
return r;
});
useBeforeLeave((e) => {
e.preventDefault();
onEnter();
setTimeout(() => {
e.retry(true);
onLoading();
}, transitionTime);
});
};

View File

@@ -34,9 +34,6 @@ const config: Config = {
medium: "600", medium: "600",
bold: "800", bold: "800",
}, },
maxWidth: {
thread: "calc(min(80ch, round(down, 100%, 1ch)))",
},
lineHeight: buildLineHeights(), lineHeight: buildLineHeights(),
colors: { colors: {
black: "#000000", black: "#000000",
@@ -70,7 +67,7 @@ const config: Config = {
}, },
}, },
}, },
plugins: [], plugins: [require("tailwind-nord")],
}; };
export default config; export default config;

View File

@@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "node", "moduleResolution": "bundler",
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"esModuleInterop": true, "esModuleInterop": true,
"jsx": "preserve", "jsx": "preserve",
@@ -13,7 +13,7 @@
"types": ["vinxi/client", "vite/client", "solid-jsx/types"], "types": ["vinxi/client", "vite/client", "solid-jsx/types"],
"isolatedModules": true, "isolatedModules": true,
"paths": { "paths": {
"~/*": ["./src/*"] "~/*": ["./src/*"]
} }
} }
} }