feat: themeing - Bug fixes
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
@import url("https://fonts.cdnfonts.com/css/jetbrains-mono-2");
|
||||
@import "tailwindcss";
|
||||
|
||||
@config "../tailwind.config.ts";
|
||||
|
||||
.tree,
|
||||
@@ -45,3 +43,5 @@
|
||||
height: calc(var(--line-height) / 2);
|
||||
border-left: var(--border-thickness) solid var(--text-color);
|
||||
}
|
||||
|
||||
@import "tailwindcss";
|
||||
|
25
src/app.tsx
25
src/app.tsx
@@ -1,27 +1,20 @@
|
||||
import { Router } from "@solidjs/router";
|
||||
import { FileRoutes } from "@solidjs/start/router";
|
||||
import { createSignal, onCleanup, onMount, Suspense } from "solid-js";
|
||||
import { Suspense } from "solid-js";
|
||||
import "./app.css";
|
||||
import { Layout } from "./components/Layout";
|
||||
import { MetaProvider, Title } from "@solidjs/meta";
|
||||
import { MetaProvider, Title, Meta } from "@solidjs/meta";
|
||||
|
||||
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 (
|
||||
<MetaProvider>
|
||||
<Title>minhtrannhat.com</Title>
|
||||
<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" />
|
||||
<Router
|
||||
root={(props) => {
|
||||
return (
|
||||
|
@@ -3,13 +3,13 @@ import JobCard from "./JobCard";
|
||||
import { createSignal } from "solid-js";
|
||||
|
||||
const Experience = () => {
|
||||
const [jobs, setJobs] = createSignal([
|
||||
const [jobs, _] = createSignal([
|
||||
{
|
||||
title: "Software Developer Engineer Intern",
|
||||
company: "Amazon (Delivery Extensions Team)",
|
||||
location: "Toronto, Ontario, Canada",
|
||||
range: "May 2025 - July 2025",
|
||||
url: "",
|
||||
url: "https://amazon.jobs/content/en/teams/ftr",
|
||||
},
|
||||
{
|
||||
title: "Software Engineer Intern",
|
||||
@@ -21,9 +21,10 @@ const Experience = () => {
|
||||
]);
|
||||
|
||||
return (
|
||||
<section class="mt-6 mx-auto px-4">
|
||||
<h2 class="mt-6 text-xl font-bold mb-8">Experience</h2>
|
||||
<div>
|
||||
<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>
|
||||
|
@@ -1,13 +1,13 @@
|
||||
const JobCard = (props) => {
|
||||
return (
|
||||
<div class="mb-8">
|
||||
<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-gray-600 mb-4">
|
||||
<p class="text-nord-1 mb-4">
|
||||
{props.job.range} | {props.job.location}
|
||||
</p>
|
||||
</div>
|
||||
|
@@ -22,17 +22,22 @@ export const Layout: ParentComponent = (props) => {
|
||||
<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">
|
||||
<a href="/" class="text-nord-3 text-2v leading-2 font-bold">
|
||||
~/minhtrannhat
|
||||
~/minh
|
||||
</a>
|
||||
|
||||
<nav class="container mx-auto px-4 py-4">
|
||||
<ul class="flex flex-wrap justify-center items-center gap-6">
|
||||
<A end class="hover:underline" activeClass="font-bold" href={"/"}>
|
||||
<A
|
||||
end
|
||||
class="hover:underline hover:text-nord10"
|
||||
activeClass="font-bold"
|
||||
href={"/"}
|
||||
>
|
||||
Home
|
||||
</A>
|
||||
<A
|
||||
end
|
||||
class="hover:underline"
|
||||
class="hover:underline hover:text-nord10"
|
||||
activeClass="font-bold"
|
||||
href={"/articles"}
|
||||
>
|
||||
@@ -40,20 +45,21 @@ export const Layout: ParentComponent = (props) => {
|
||||
</A>
|
||||
<A
|
||||
end
|
||||
class="hover:underline"
|
||||
class="hover:underline hover:text-nord10"
|
||||
activeClass="font-bold"
|
||||
href={"/tags"}
|
||||
>
|
||||
Tags
|
||||
</A>
|
||||
<a
|
||||
<A
|
||||
class="hover:text-nord10"
|
||||
href="/resume.pdf"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={() => changeFavicon("./favicon.ico")}
|
||||
>
|
||||
Resume
|
||||
</a>
|
||||
</A>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
@@ -42,7 +42,7 @@ const Pre: ParentComponent<{ lang: string; lines?: string; file?: string }> = (
|
||||
|
||||
return (
|
||||
<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 flex justify-between px-1h text-sm leading-1">
|
||||
<Show when={props.file} fallback={<span aria-hidden />}>
|
||||
<span>{props.file}</span>
|
||||
</Show>
|
||||
@@ -186,9 +186,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">
|
||||
<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>
|
||||
</aside>
|
||||
);
|
||||
|
136
src/css/prism-nord.css
Normal file
136
src/css/prism-nord.css
Normal 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;
|
||||
}
|
@@ -1 +1,11 @@
|
||||
[]
|
||||
[
|
||||
{
|
||||
"title": "Testing Test Test Test",
|
||||
"description": "Woah this is so cool",
|
||||
"date": "2025-1-29",
|
||||
"featuredImage": null,
|
||||
"featuredImageDesc": null,
|
||||
"tags": ["rust", "python", "mdx", "markdown"],
|
||||
"slug": "test"
|
||||
}
|
||||
]
|
||||
|
@@ -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.
|
@@ -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.
|
1
src/global.d.ts
vendored
1
src/global.d.ts
vendored
@@ -1 +0,0 @@
|
||||
/// <reference types="@solidjs/start/env" />
|
@@ -1,14 +0,0 @@
|
||||
---
|
||||
date: 2022-12-27
|
||||
title: Software Engineer Intern
|
||||
company: Cisco Systems
|
||||
location: Remote
|
||||
range: January - May 2023
|
||||
url: https://www.cisco.com/c/en/us/products/cloud-systems-management/modeling-labs/index.html
|
||||
---
|
||||
|
||||
- Developed a **multi-threaded Python microservice** for a Cisco internal SaaS ingesting usage telemetry data of Cisco Cat9kv switches from thousands of GNS3 networking projects to be stored in **Elastic Search**.
|
||||
- Containerized and integrated **Elastic Search** and **Kibana** into existing microservices system.
|
||||
- Created insightful **Kibana** charts highlighting Cat9kv switch flavors daily usage and number of daily users to present to Cisco Enterprise Networking Group executives.
|
||||
- Wrote custom **Ansible** modules to synchronize folders across Cisco managed servers while ensuring 100% folder structure and files integrity with hashing algorithm utilizing Python SHA1 crytographic library.
|
||||
- Automated custom **Ansible** modules testing with **Bash** scripts. Testing environment made with **Vagrant** and **Docker** to simulate production servers
|
@@ -19,8 +19,8 @@ const Homepage = () => {
|
||||
Montreal, Canada.
|
||||
<br />
|
||||
<br />
|
||||
I'm most passionate about designing distributed systems that scales
|
||||
but I'm also interested in compilers and systems programming.
|
||||
Things that I'm most passionate about: distributed systems, backend
|
||||
development, compilers and systems programming.
|
||||
<br />
|
||||
<br />
|
||||
When I'm not coding, I read books, listen to podcasts or study music
|
||||
@@ -29,7 +29,7 @@ const Homepage = () => {
|
||||
<p>
|
||||
Say hi:{" "}
|
||||
<a
|
||||
class="underline"
|
||||
class="underline hover:text-nord-11"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="mailto:minh@minhtrannhat.com"
|
||||
@@ -41,7 +41,7 @@ const Homepage = () => {
|
||||
<ul class="mx-4 sm:mx-6 sm:mt-3v text-slate-600 text-base sm:text-sm leading-1">
|
||||
<For each={links}>
|
||||
{(link) => (
|
||||
<li class="list-square hover:text-black ml-2h leading-1">
|
||||
<li class="list-square hover:text-nord-11 ml-2h leading-1">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
@@ -5,6 +5,7 @@ import { posts } from "~/data/posts";
|
||||
import { MDXProvider } from "solid-mdx";
|
||||
import { markdownComponents, PostImage } from "~/components/Markdown";
|
||||
import dayjs from "dayjs";
|
||||
import "../css/prism-nord.css";
|
||||
import type { Post } from "~/types";
|
||||
|
||||
const Blog = (props: RouteSectionProps<unknown>) => {
|
||||
@@ -18,7 +19,7 @@ const Blog = (props: RouteSectionProps<unknown>) => {
|
||||
|
||||
return (
|
||||
<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="description" content={meta().description} />
|
||||
<Meta name="og:description" content={meta().description} />
|
||||
|
118
src/routes/blog/test.mdx
Normal file
118
src/routes/blog/test.mdx
Normal file
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: Testing Test Test Test
|
||||
description: Woah this is so cool
|
||||
date: 2025-1-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
|
||||
}
|
||||
}
|
||||
```
|
@@ -4,7 +4,7 @@ import { tags } from "~/data/tags";
|
||||
const Tags = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1 class="text-xl font-bold mt-2v mb-1v">All tags:</h1>
|
||||
<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">
|
||||
<For each={Object.values(tags)}>
|
||||
{(tag) => (
|
||||
|
Reference in New Issue
Block a user