Skip to content

Commit

Permalink
Feat/blog (#82)
Browse files Browse the repository at this point in the history
* feat(wip): add book route and nand2tetris page

* feat: add Boolean Logic chapter

* feat: add typescript-type-challenges-in-real-projects blog

* feat: add real world type gymnastic blog

* chore: ai to chatbot
  • Loading branch information
hidaviddong authored Jul 9, 2024
1 parent aecf76b commit 57d293d
Show file tree
Hide file tree
Showing 22 changed files with 597 additions and 26 deletions.
3 changes: 3 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
},
"style": {
"useSelfClosingElements": "off"
},
"security":{
"noDangerouslySetInnerHtml":"off"
}
}
},
Expand Down
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"devDependencies": {
"@biomejs/biome": "1.8.1",
"@shikijs/twoslash": "^1.10.3",
"@tailwindcss/typography": "^0.5.13",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand Down
File renamed without changes.
12 changes: 6 additions & 6 deletions src/components/ui/code.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export default function Code({ html }) {
return (
// biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
<div className="prose border rounded-md md:w-full w-[320px] overflow-hidden" dangerouslySetInnerHTML={{ __html: html }} />
)
}
import React from "react";
import { cn } from "@/lib/utils";
const Code = React.forwardRef(({ className, ...props }, ref) => (
<div ref={ref} className={cn("prose border rounded-md md:w-full w-[320px] ", className)} {...props} />
))
export default Code
28 changes: 22 additions & 6 deletions src/config/project.js → src/config/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const Works = [
{
name: "LLM Chatbot",
description: "Medical field chat application.",
name: "Chatbot",
description: "Chat application in medical field.",
time: "2023-2024",
href: "/project/ai",
background: "/images/ai.webp",
href: "/project/chatbot",
background: "/images/chatbot.webp",
keywords: ["llm", "large language model", "gpt", "medical", "chatbot"]
},
{
Expand Down Expand Up @@ -59,6 +59,21 @@ const OpenSourceProjects = [
keywords: ["comments", "open source", "React", "TypeScript", "Node.js"]
},
];

const Blog = [
{
name: 'Reading: The Elements of Computing Systems',
description: "Understand how hardware and software worked at a low level.",
time: '2024',
href: '/blog/nand2tetris',
},
{
name: 'TypeScript Type Challenges in Real Projects',
description: "Managing complex key combinations in TypeScript.",
time: '2024',
href: '/blog/typescript-type-challenges-in-real-projects',
}
]
const SONG_BASEURL = "https://img1.doubanio.com/lpic/";
const Songs = [
{ title: "Blue Moon", artist: "David Tao", url: "s4208287" },
Expand Down Expand Up @@ -202,14 +217,15 @@ const Music = [
imageUrl: "love-under-the-moon",
url: "BV1CW411n7Jm",
},
];
]
export {
Works,
OpenSourceProjects,
Blog,
SONG_BASEURL,
Songs,
Movies,
MOVIE_BASEURL,
Music,
MUSICVIDEO_BASEURL,
MUSICVIDEO_BASEURL
};
5 changes: 5 additions & 0 deletions src/pages/blog/+Layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function Layout({ children }) {
return (
<article className="prose p-12 prose-p:my-0">{children}</article>
)
}
30 changes: 30 additions & 0 deletions src/pages/blog/+Page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Blog } from "@/config";


const Lists = ({ title, items }) => (
<section>
<h3>{title}</h3>
<ul>
{items.map(({ name, href }) => (
<li key={name}>
<a
aria-label={name}
variant="link"
className="no-underline text-md text-zinc-500 hover:text-zinc-800 text-primary underline-offset-4 hover:underline"
href={href}
>
{name}
</a>
</li>
))}
</ul>
</section>
);

export default function Page() {
return (
<>
<Lists title="Blog" items={Blog} />
</>
);
}
8 changes: 8 additions & 0 deletions src/pages/blog/+data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Blog } from "@/config"

export async function data(pageContext) {
const blog = Blog.find((project) => project.href === pageContext.urlParsed.pathname)
return {
blog
}
}
86 changes: 86 additions & 0 deletions src/pages/blog/nand2tetris/+Page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useData } from "vike-react/useData"
export default function Page() {
const { blog } = useData()
return (
<div className="flex flex-col">
<div className="font-medium text-base flex flex-col gap-1">
<p className="text-zinc-700">{blog.name}</p>
<p className=" text-zinc-500 text-sm">{blog.time} · {blog.description}</p>
</div>

<h4>Boolean Logic</h4>
<ul>
<li>A Boolean function is a function that operates on binary inputs and returns binary outputs. </li>
<li>Any Boolean function can be realized using <b>Nand</b>.</li>
<li>De Morgan's laws are important rules in Boolean algebra:
<ul>
<li>Not (A And B) = Not A Or Not B</li>
<li>Not (A Or B) = Not A And Not B</li>
</ul>
</li>
</ul>

<p>This is truth table for Nand.</p>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>A Nand B</th>
</tr>
</thead>
<tbody className="text-center">
<tr>
<td>0</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>If we set both A and B to the same variable X, the truth table is equal to the <b>Not</b> truth table.</p>
<table>
<thead>
<tr>
<th>X</th>
<th>X</th>
<th>X Nand X</th>
</tr>
</thead>
<tbody className="text-center">
<tr>
<td>0</td>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>0</td>
</tr>
</tbody>
</table>
<p> So <b>Not X = X Nand X.</b></p>
<br />
<p>Since <b>And</b> is equal to <b>Not Nand</b>,and <b>Not</b> can be expressed using <b>Nand</b>: </p>
<p>A And B = Not (A Nand B) = (A Nand B) Nand (A Nand B)</p>
<br />
<p>Accroding to De Morgan's laws, it can be proven that <b>Or</b> can also be expressed using <b>Nand</b>:</p>
<p>A Or B = Not (Not A and Not B)</p>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import './style.css'
import Code from "@/components/ui/code"
import { useData } from "vike-react/useData"
export default function Page() {
const { html, html2, html3, html4, html5, blog } = useData()
return (
<div className="flex flex-col">
<div className="font-medium text-base flex flex-col gap-1">
<p className="text-zinc-700">{blog.name}</p>
<p className=" text-zinc-500 text-sm">{blog.time} · {blog.description}</p>
</div>
<h4>Background</h4>
<div className='flex flex-col justify-center space-y-4'>
<p>
In my previous
<a
aria-label="Spectral Analysis"
variant="link"
className="mx-1 no-underline text-md text-zinc-500 hover:text-zinc-800 text-primary underline-offset-4 hover:underline"
href="/project/data-visualization"
>
data visualization project
</a>
, I have a type issue.
</p>
<p>
This project is about spectral analysis. It has many electrodes with different labels, such as electrode A, B, C.
</p>
<p>It can easily write a TypeScript type to constrain these keys:</p>
<Code dangerouslySetInnerHTML={{ __html: html }} />
<p>But these electrodes can be combined in pairs. such as electeode A-B, A-C, B-C.</p>
<p>You can add more electrode like this below:</p>
<Code dangerouslySetInnerHTML={{ __html: html2 }} />
<p>However, as the number of keys increase, this type becomes hard to maintain.</p>

</div>

<h4>Solution</h4>
<div className='flex justify-center flex-col space-y-4'>
<p>What if this question is in JavaScript world?</p>
<p>The first solution is to use a double loop: </p>
<Code dangerouslySetInnerHTML={{ __html: html3 }} />
<p>The second solution is to use recursive:</p>
<Code className="p-0" dangerouslySetInnerHTML={{ __html: html4 }} />

<p>It's easy to understanding the idea, in this type issue, we can use TypeScript generics to achieve.</p>
<Code className="p-0" dangerouslySetInnerHTML={{ __html: html5 }} />
<ul>
<li>T extends string[] means that the generic to accpet a string array.</li>
<li>Further check if T is an array with at least on element. If so, it destructures the first element as F and the rest as R.</li>
<li>CombKeys&lt;R&gt;: This is a recursive call to CombKeys to handle the remaining elements R.</li>
<li>: never:if T is not an array , the result is never.</li>
</ul>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { codeToHtml } from 'shiki'
import { rendererRich, transformerTwoslash } from '@shikijs/twoslash'
import { Blog } from "@/config"
export async function data(pageContext) {
const html = await codeToHtml(`type ElectrodeKeys = 'A'|'B'|'C'`, {
lang: 'ts',
theme: 'vitesse-light'
});

const html2 = await codeToHtml(`type ElectrodeKeys = 'A'|'B'|'C'|'A-B'|'A-C'|'B-C'`, {
lang: 'ts',
theme: 'vitesse-light'
});

const html3 = await codeToHtml(`function generatePairs(arr:string[]) {
const pairs = [];
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
pairs.push(\`\${ arr[i]}-\${ arr[j]}\`);
}
}
return pairs;
}`, {
lang: 'ts',
theme: 'vitesse-light'
});

const html4 = await codeToHtml(`function generatePairs(arr:string[]) {
const pairs:string[] = []
function recursiveGenerate(currentIndex:number, currentPair:string[]) {
if (currentPair.length === 2) {
pairs.push(currentPair.join('-'));
return;
}
for (let i = currentIndex; i < arr.length; i++) {
recursiveGenerate(i + 1, [...currentPair, arr[i]]);
}
}
recursiveGenerate(0, []);
return pairs;
}`, {
lang: 'ts',
theme: 'vitesse-light'
});




const html5 = await codeToHtml(
`type ElectrodeKeys = ['A','B','C']\ntype CombKeys<T extends string[]> =\n T extends [infer F extends string, ...infer R extends string[]]
? \`\${F}-\${R[number]}\` | CombKeys<R>
: never\ntype CombElectrodeKeys = CombKeys<ElectrodeKeys>`, {
lang: 'ts',
theme: 'vitesse-light',
transformers: [
transformerTwoslash({
renderer: rendererRich()
})
]
}



)
const blog = Blog.find((blog) => blog.href === pageContext.urlParsed.pathname)
return {
html,
html2,
html3,
html4,
html5,
blog
}
}
Loading

0 comments on commit 57d293d

Please sign in to comment.