feat: update dependencies and add Nix code highlighting feature
This commit is contained in:
parent
e396d5a947
commit
a126b84165
5 changed files with 163 additions and 32 deletions
128
src/app/flake.nix/page.tsx
Normal file
128
src/app/flake.nix/page.tsx
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef, useCallback } from "react";
|
||||
import hljs from "highlight.js/lib/core";
|
||||
import nix from "highlight.js/lib/languages/nix";
|
||||
import "highlight.js/styles/github-dark.css";
|
||||
|
||||
hljs.registerLanguage("nix", nix);
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
showSaveFilePicker?: (options?: {
|
||||
suggestedName?: string;
|
||||
types?: Array<{
|
||||
description?: string;
|
||||
accept: Record<string, string[]>;
|
||||
}>;
|
||||
}) => Promise<FileSystemFileHandle>;
|
||||
}
|
||||
}
|
||||
|
||||
const code = `{
|
||||
description = "My personal website";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
}
|
||||
|
||||
outputs = { self, nixpkgs }: {
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in {
|
||||
// install pakages
|
||||
nix.settings = {
|
||||
sandbox = false;
|
||||
experimental-features = [ "flakes" ];
|
||||
};
|
||||
time.timeZone = "Asia/Seoul";
|
||||
}
|
||||
}`;
|
||||
|
||||
const customTheme = `
|
||||
.hljs {
|
||||
background: #191017 !important;
|
||||
color: #fcf8f9 !important;
|
||||
}
|
||||
.hljs-attr { color: #f38ba8; }
|
||||
.hljs-string { color: #a6e3a1; }
|
||||
.hljs-number { color: #f9e2af; }
|
||||
.hljs-literal { color: #89b4fa; }
|
||||
.hljs-type { color: #f5c2e7; }
|
||||
.hljs-title { color: #94e2d5; }
|
||||
`;
|
||||
|
||||
export default function FlakeNix() {
|
||||
const codeRef = useRef<HTMLElement>(null);
|
||||
|
||||
const downloadFile = useCallback(async () => {
|
||||
if (window.showSaveFilePicker) {
|
||||
try {
|
||||
const handle = await window.showSaveFilePicker({
|
||||
suggestedName: "flake.nix",
|
||||
types: [
|
||||
{
|
||||
description: "Nix Files",
|
||||
accept: { "text/plain": [".nix"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
const writable = await handle.createWritable();
|
||||
await writable.write(code);
|
||||
await writable.close();
|
||||
} catch (e) {
|
||||
// 사용자가 취소한 경우
|
||||
if ((e as Error).name !== "AbortError") {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// fallback: showSaveFilePicker를 지원하지 않는 브라우저
|
||||
const blob = new Blob([code], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "flake.nix";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const style = document.createElement("style");
|
||||
style.textContent = customTheme;
|
||||
document.head.appendChild(style);
|
||||
|
||||
if (codeRef.current) {
|
||||
hljs.highlightElement(codeRef.current);
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
|
||||
e.preventDefault();
|
||||
downloadFile();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [downloadFile]);
|
||||
|
||||
return (
|
||||
<main className="flex h-screen items-center justify-center">
|
||||
<div className="max-w-3xl w-full mx-auto px-6 rounded-2xl overflow-auto">
|
||||
<pre>
|
||||
<code ref={codeRef} className="language-nix">
|
||||
{code}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import Image from "next/image";
|
|||
export default async function BlogIndex() {
|
||||
return (
|
||||
<main className="min-h-screen w-screen">
|
||||
<section id="top" className="max-w-3xl w-full mx-auto px-4">
|
||||
{/* <section id="top" className="max-w-3xl w-full mx-auto px-4">
|
||||
<div className="flex flex-row items-center w-full h-full gap-6 mt-8">
|
||||
<Image
|
||||
src="/Frame.svg"
|
||||
|
|
@ -25,8 +25,9 @@ export default async function BlogIndex() {
|
|||
<Image src="/1500x500.jpg" alt="test image" className="object-cover object-center transition-transform duration-300 hover:scale-105" width={3168} height={1344} />
|
||||
</picture>
|
||||
</figure>
|
||||
</section>
|
||||
<section id="about" className="h-screen max-w-3xl mx-auto px-4">
|
||||
</section> */}
|
||||
<Top />
|
||||
<section id="about" className="h-screen max-w-4xl mx-auto px-4">
|
||||
<div className=" mt-8">
|
||||
<h1 className="text-2xl font-bold mb-4 w-full">💕 About</h1>
|
||||
<p>오직 <strong>호기심과 실행력</strong>으로만 성장해 온 <strong>개발자, 정보보안전문가</strong> 남현석입니다.</p>
|
||||
|
|
|
|||
|
|
@ -60,30 +60,30 @@ export default function Top() {
|
|||
|
||||
const handleBackgroundTouchClick = () => {
|
||||
if (!isMobile) return;
|
||||
|
||||
|
||||
setClickCount((prev) => {
|
||||
const newCount = prev + 1;
|
||||
|
||||
|
||||
// Clear existing timeout
|
||||
if (clickTimeoutRef.current) {
|
||||
clearTimeout(clickTimeoutRef.current);
|
||||
}
|
||||
|
||||
|
||||
// Check if required clicks reached
|
||||
if (newCount >= REQUIRED_CLICKS) {
|
||||
const newValue = !isMagicalGirlEnabled;
|
||||
setIsMagicalGirlEnabled(newValue);
|
||||
|
||||
|
||||
document.cookie = `MagicalGirl=${newValue}; path=/; max-age=${60 * 60 * 24 * 365}`;
|
||||
window.location.reload();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Reset click count after 1 second if not enough clicks
|
||||
clickTimeoutRef.current = setTimeout(() => {
|
||||
setClickCount(0);
|
||||
}, 1000);
|
||||
|
||||
|
||||
return newCount;
|
||||
});
|
||||
};
|
||||
|
|
@ -131,17 +131,15 @@ export default function Top() {
|
|||
}
|
||||
}}
|
||||
>
|
||||
{isMagicalGirlEnabled && (
|
||||
<Image
|
||||
src={"/char.avif"}
|
||||
alt="character"
|
||||
width={4740}
|
||||
height={7584}
|
||||
className="w-[50vh] lg:w-[30vw] translate-y-[10%] transition-transform duration-100 ease-out"
|
||||
unoptimized
|
||||
onClick={requestPermission}
|
||||
/>
|
||||
)}
|
||||
<Image
|
||||
src={"/char.avif"}
|
||||
alt="character"
|
||||
width={4740}
|
||||
height={7584}
|
||||
className="w-[50vh] lg:w-[30vw] translate-y-[10%] transition-transform duration-100 ease-out"
|
||||
unoptimized
|
||||
onClick={requestPermission}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue