8 Commits

Author SHA1 Message Date
dependabot[bot] c521c7c7f9 Bump astro from 6.1.9 to 6.1.10
Bumps [astro](https://github.com/withastro/astro/tree/HEAD/packages/astro) from 6.1.9 to 6.1.10.
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/astro@6.1.10/packages/astro)

---
updated-dependencies:
- dependency-name: astro
  dependency-version: 6.1.10
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-13 23:53:25 +00:00
Hadi 25fb5a4bf0 Fix TOC links
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 21:03:40 +02:00
Hadi d257a0f26e update repos
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:52:09 +02:00
Hadi 3dfbdcf970 New separators
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:51:46 +02:00
Hadi 930c3bf3bb Clean homepage
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:40:22 +02:00
Hadi a055640fa8 Edit hero & add Infosec Notes section
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:21:17 +02:00
Hadi 9c0bbc4b77 edit sidebars
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:16:01 +02:00
Hadi 7968c662f6 Edit sidebar
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 17:03:05 +02:00
19 changed files with 452 additions and 285 deletions
+1 -1
View File
@@ -19,7 +19,7 @@
"@lucide/astro": "^0.552.0", "@lucide/astro": "^0.552.0",
"@tailwindcss/vite": "^4.2.4", "@tailwindcss/vite": "^4.2.4",
"@types/bun": "^1.3.13", "@types/bun": "^1.3.13",
"astro": "6.1.9", "astro": "6.1.10",
"daisyui": "^5.5.19", "daisyui": "^5.5.19",
"node-html-parser": "^7.1.0", "node-html-parser": "^7.1.0",
"svelte": "^5.55.5", "svelte": "^5.55.5",
+10 -5
View File
@@ -18,14 +18,19 @@ const latestPosts = sortedPosts.slice(0, 3);
<div class="max-w-6xl mx-auto"> <div class="max-w-6xl mx-auto">
<div class="text-center mb-12"> <div class="text-center mb-12">
<h2 class="text-4xl font-bold mb-4">Latest Blog Posts</h2> <h2 class="text-4xl font-bold mb-4">Latest Blog Posts</h2>
<p class="text-lg text-base-content/70">
Thoughts, insights, and tutorials on cybersecurity, OSINT, and
technology.
</p>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{latestPosts.map((post) => <BlogCard post={post} />)} {
latestPosts.map((post) => (
<BlogCard
displayBanner={false}
displayTags={false}
displayDate={false}
post={post}
/>
))
}
</div> </div>
<div class="text-center mt-12"> <div class="text-center mt-12">
+12 -5
View File
@@ -5,6 +5,9 @@ import { Image } from "astro:assets";
import { formatDate } from "../utils/notes"; import { formatDate } from "../utils/notes";
interface Props { interface Props {
displayBanner: boolean;
displayDate: boolean;
displayTags: boolean;
post: CollectionEntry<"blog">; post: CollectionEntry<"blog">;
} }
@@ -14,6 +17,8 @@ const { post } = Astro.props;
<article <article
class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow" class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow"
> >
{ Astro.props.displayBanner && (
<figure class="aspect-video"> <figure class="aspect-video">
<Image <Image
src={post.data.image} src={post.data.image}
@@ -23,25 +28,27 @@ const { post } = Astro.props;
height={400} height={400}
/> />
</figure> </figure>
)}
<div class="card-body"> <div class="card-body">
{ Astro.props.displayDate && (
<time class="text-sm text-base-content/60"> <time class="text-sm text-base-content/60">
{formatDate(post.data.publishDate)} {formatDate(post.data.publishDate)}
</time> </time>
)}
<h2 class="card-title hover:text-primary transition-colors"> <h2 class="card-title hover:text-primary transition-colors">
<a href={`/blog/${post.id}`}>{post.data.title}</a> <a href={`/blog/${post.id}`}>{post.data.title}</a>
</h2> </h2>
<p class="text-base-content/80">{post.data.description}</p> <p class="text-base-content/80">{post.data.description}</p>
{
post.data.tags && post.data.tags.length > 0 && ( { Astro.props.displayTags && post.data.tags && post.data.tags.length > 0 && (
<div class="flex flex-wrap gap-2 mt-2"> <div class="flex flex-wrap gap-2 mt-2">
{post.data.tags.map((tag) => ( {post.data.tags.map((tag) => (
<TagBadge tag={tag} /> <TagBadge tag={tag} />
))} ))}
</div> </div>
) )}
}
<div class="card-actions justify-end mt-4"> <div class="card-actions justify-end mt-4">
<a href={`/blog/${post.id}`} class="btn btn-primary btn-sm"> <a href={`/blog/${post.id}`} class="btn btn-soft btn-primary btn-sm">
Read More Read More
</a> </a>
</div> </div>
+5 -2
View File
@@ -2,6 +2,7 @@
import { ExternalLink, ChevronDown } from "@lucide/astro"; import { ExternalLink, ChevronDown } from "@lucide/astro";
interface Props { interface Props {
displayBanner: boolean;
repo: { repo: {
name: string; name: string;
description: string; description: string;
@@ -34,6 +35,7 @@ const hasMultiplePlatforms = platforms.length > 1;
<article <article
class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow" class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow"
> >
{ Astro.props.displayBanner && repo.banner_url && (
<figure class="aspect-video bg-base-200 overflow-hidden"> <figure class="aspect-video bg-base-200 overflow-hidden">
<img <img
src={repo.banner_url} src={repo.banner_url}
@@ -42,6 +44,7 @@ const hasMultiplePlatforms = platforms.length > 1;
onerror="this.parentElement.style.display='none'" onerror="this.parentElement.style.display='none'"
/> />
</figure> </figure>
)}
<div class="card-body"> <div class="card-body">
<h2 class="card-title hover:text-primary transition-colors"> <h2 class="card-title hover:text-primary transition-colors">
@@ -83,7 +86,7 @@ const hasMultiplePlatforms = platforms.length > 1;
<div <div
tabindex="0" tabindex="0"
role="button" role="button"
class="btn btn-primary btn-sm gap-1" class="btn btn-soft btn-primary btn-sm gap-1"
> >
<ExternalLink class="size-4" /> <ExternalLink class="size-4" />
View Source View Source
@@ -107,7 +110,7 @@ const hasMultiplePlatforms = platforms.length > 1;
href={repo.html_url} href={repo.html_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="btn btn-primary btn-sm gap-1" class="btn btn-soft btn-primary btn-sm gap-1"
> >
<ExternalLink class="size-4" /> <ExternalLink class="size-4" />
View on Gitea View on Gitea
+5 -27
View File
@@ -1,5 +1,5 @@
--- ---
import { ArrowRight, FolderCode, Key, Rss } from "@lucide/astro"; import { FolderCode, Key, Rss } from "@lucide/astro";
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import type { SocialLinks } from "../config"; import type { SocialLinks } from "../config";
@@ -8,7 +8,6 @@ interface Props {
title: string; title: string;
description: string; description: string;
avatar: any; avatar: any;
location?: string;
socialLinks?: SocialLinks; socialLinks?: SocialLinks;
gpgKey?: string; gpgKey?: string;
rssFeed?: string; rssFeed?: string;
@@ -19,18 +18,17 @@ const {
title, title,
description, description,
avatar, avatar,
location,
socialLinks, socialLinks,
gpgKey, gpgKey,
rssFeed, rssFeed,
} = Astro.props; } = Astro.props;
--- ---
<section class="hero min-h-[65vh]"> <section class="hero py-20">
<div class="hero-content flex-col lg:flex-row-reverse max-w-7xl gap-8"> <div class="hero-content flex-col lg:flex-row-reverse max-w-7xl gap-10">
<div class="avatar"> <div class="avatar">
<div <div
class="w-48 ring-primary ring-offset-base-100 rounded-full ring-2 ring-offset-2" class="w-32 md:w-48 ring-primary ring-offset-base-100 rounded-full ring-2 ring-offset-2"
> >
<Image src={avatar} alt={name} /> <Image src={avatar} alt={name} />
</div> </div>
@@ -40,8 +38,7 @@ const {
Hi, I'm {name} Hi, I'm {name}
</h1> </h1>
<p class="text-xl text-base-content/80 mb-2">{title}</p> <p class="text-xl text-base-content/80 mb-2">{title}</p>
{location && <p class="text-base-content/60 mb-4">{location}</p>} <p class="text-lg max-w-lg leading-relaxed mb-6">
<p class="text-lg leading-relaxed mb-6">
{description} {description}
</p> </p>
{ {
@@ -335,25 +332,6 @@ const {
</div> </div>
) )
} }
<div class="mt-12 flex flex-wrap gap-5">
<a href="/blog" class="btn btn-ghost gap-2">
Blog Posts
<ArrowRight class="size-4" />
</a>
<a href="/projects" class="btn btn-ghost gap-2">
Projects
<ArrowRight class="size-4" />
</a>
<a href="/notes" class="btn btn-ghost gap-2">
Infosec Notes
<ArrowRight class="size-4" />
</a>
<a href="/#contact" class="btn btn-ghost gap-2">
Contact Me
<ArrowRight class="size-4" />
</a>
</div>
</div> </div>
</div> </div>
</section> </section>
+28 -2
View File
@@ -10,9 +10,10 @@ interface Props {
graphEdges: { from: string; to: string }[]; graphEdges: { from: string; to: string }[];
forwardLinks: CollectionEntry<"notes">[]; forwardLinks: CollectionEntry<"notes">[];
backlinks: CollectionEntry<"notes">[]; backlinks: CollectionEntry<"notes">[];
externalLinks: { url: string; label: string }[];
} }
const { entry, graphNodes, graphEdges, forwardLinks, backlinks } = Astro.props; const { entry, graphNodes, graphEdges, forwardLinks, backlinks, externalLinks } = Astro.props;
--- ---
<aside <aside
@@ -26,7 +27,7 @@ const { entry, graphNodes, graphEdges, forwardLinks, backlinks } = Astro.props;
> >
graph graph
</p> </p>
<NoteGraph client:visible nodes={graphNodes} edges={graphEdges} /> <NoteGraph client:load nodes={graphNodes} edges={graphEdges} />
{ {
graphNodes.length < 2 && ( graphNodes.length < 2 && (
<p class="font-mono text-[9px] text-base-content/20 text-center py-2"> <p class="font-mono text-[9px] text-base-content/20 text-center py-2">
@@ -90,6 +91,31 @@ const { entry, graphNodes, graphEdges, forwardLinks, backlinks } = Astro.props;
) )
} }
{
externalLinks.length > 0 && (
<div class="p-3 border-t border-base-300/40">
<p class="font-mono text-[10px] text-base-content/25 uppercase tracking-widest mb-2">
external
</p>
<ul class="space-y-1">
{externalLinks.map(({ url, label }) => (
<li>
<a
href={url}
target="_blank"
rel="noopener noreferrer"
class="font-mono text-xs text-base-content/45 hover:text-primary/80 transition-colors block truncate"
title={url}
>
↗ {label}
</a>
</li>
))}
</ul>
</div>
)
}
<div class="px-4 pt-4 pb-1 border-t border-base-300/40 mt-auto"> <div class="px-4 pt-4 pb-1 border-t border-base-300/40 mt-auto">
<time <time
datetime={entry.data.publishDate.toISOString()} datetime={entry.data.publishDate.toISOString()}
@@ -65,25 +65,7 @@
} }
</script> </script>
<aside <div class="flex flex-col flex-1 min-h-0">
class="w-56 shrink-0 flex flex-col border-r border-base-300/60 h-[calc(100vh-3rem)]"
style="background: oklch(4% 0 0);"
>
<!-- Mobile close bar -->
<div class="lg:hidden flex items-center justify-between px-3 py-2 border-b border-base-300/40 shrink-0">
<span class="font-mono text-[10px] text-base-content/30 uppercase tracking-widest">nav</span>
<label
for="nav-drawer"
class="cursor-pointer text-base-content/30 hover:text-base-content/70 transition-colors p-1"
aria-label="close sidebar"
>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 6 6 18M6 6l12 12"/>
</svg>
</label>
</div>
<!-- Search -->
<div class="px-3 py-3 border-b border-base-300/40 shrink-0"> <div class="px-3 py-3 border-b border-base-300/40 shrink-0">
<div <div
class="flex items-center gap-1.5 px-2 py-1.5 rounded-md bg-base-200/50 border border-base-300/40 focus-within:border-base-300/70 transition-colors" class="flex items-center gap-1.5 px-2 py-1.5 rounded-md bg-base-200/50 border border-base-300/40 focus-within:border-base-300/70 transition-colors"
@@ -98,7 +80,6 @@
</div> </div>
</div> </div>
<!-- Nav -->
<nav class="flex-1 min-h-0 overflow-y-auto overflow-x-hidden px-2 py-2 space-y-px"> <nav class="flex-1 min-h-0 overflow-y-auto overflow-x-hidden px-2 py-2 space-y-px">
{#each categories as cat} {#each categories as cat}
{@const catNotes = notes.filter( {@const catNotes = notes.filter(
@@ -107,7 +88,6 @@
{#if catNotes.length > 0 || !search} {#if catNotes.length > 0 || !search}
{@const isFolder = notes.some((n) => n.id.includes("/") && getCategory(n) === cat)} {@const isFolder = notes.some((n) => n.id.includes("/") && getCategory(n) === cat)}
<div> <div>
<!-- Category header -->
<div class="flex items-center w-full"> <div class="flex items-center w-full">
<button <button
onclick={() => toggle(cat)} onclick={() => toggle(cat)}
@@ -142,7 +122,6 @@
{/if} {/if}
</div> </div>
<!-- Notes list -->
{#if openCategories.includes(cat)} {#if openCategories.includes(cat)}
<ul <ul
class="ml-4 mt-0.5 pb-1 space-y-px" class="ml-4 mt-0.5 pb-1 space-y-px"
@@ -170,4 +149,4 @@
{/if} {/if}
{/each} {/each}
</nav> </nav>
</aside> </div>
+26
View File
@@ -0,0 +1,26 @@
---
import NoteNavContent from "./NoteNavContent.svelte";
import type { CollectionEntry } from "astro:content";
interface Props {
notes: CollectionEntry<"notes">[];
currentEntry?: CollectionEntry<"notes">;
currentCategory?: string;
categories: string[];
}
const { notes, currentEntry, currentCategory, categories } = Astro.props;
---
<aside
class="w-56 shrink-0 flex flex-col border-r border-base-300/60 h-full"
style="background: oklch(4% 0 0);"
>
<NoteNavContent
client:load
notes={notes}
currentEntry={currentEntry}
currentCategory={currentCategory}
categories={categories}
/>
</aside>
+25
View File
@@ -0,0 +1,25 @@
---
import { ArrowRight } from "@lucide/astro";
---
<section id="notes" class="py-10 px-4">
<div class="max-w-6xl mx-auto text-center">
<div class="text-center mb-12">
<h2 class="text-4xl font-bold mb-4">Infosec Notes</h2>
</div>
<div class="mt-8">
<p class="text-base text-base-content/70 mb-6">
Cheatsheets and references on tools and techniques I use for CTFs and
pentesting.
</p>
<div class="text-center mt-12">
<a href="/notes" class="btn btn-ghost gap-2">
Browse Notes
<ArrowRight class="size-4" />
</a>
</div>
</div>
</div>
</section>
+6 -6
View File
@@ -10,14 +10,14 @@ const latestRepos = repos.slice(0, 3);
<div class="max-w-6xl mx-auto"> <div class="max-w-6xl mx-auto">
<div class="text-center mb-12"> <div class="text-center mb-12">
<h2 class="text-4xl font-bold mb-4">Check out my latest work</h2> <h2 class="text-4xl font-bold mb-4">Check out my latest work</h2>
<p class="text-lg text-base-content/70">
I enjoy the challenge of reimagining existing programs & scripts in my
own unique way.
</p>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{latestRepos.map((repo) => <GiteaProjectCard repo={repo} />)} {
latestRepos.map((repo) => (
<GiteaProjectCard displayBanner={false} repo={repo} />
))
}
</div> </div>
<div class="text-center mt-12"> <div class="text-center mt-12">
-2
View File
@@ -23,7 +23,6 @@ export interface SiteConfig {
title: string; title: string;
description: string; description: string;
avatar: string; avatar: string;
location: string;
socialLinks: SocialLinks; socialLinks: SocialLinks;
gpgKey?: string; gpgKey?: string;
rssFeed?: string; rssFeed?: string;
@@ -39,7 +38,6 @@ export const siteConfig: SiteConfig = {
description: description:
"Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧", "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧",
avatar: "/avatar.png", avatar: "/avatar.png",
location: "🇫🇷 France",
socialLinks: { socialLinks: {
github: "https://github.com/anotherhadi", github: "https://github.com/anotherhadi",
gitlab: "https://gitlab.com/anotherhadi_mirror", gitlab: "https://gitlab.com/anotherhadi_mirror",
+229 -117
View File
@@ -7,7 +7,7 @@
"login_name": "", "login_name": "",
"source_id": 0, "source_id": 0,
"full_name": "Hadi", "full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu", "email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae", "avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi", "html_url": "https://git.hadi.icu/anotherhadi",
"language": "", "language": "",
@@ -34,7 +34,7 @@
"fork": false, "fork": false,
"template": false, "template": false,
"mirror": true, "mirror": true,
"size": 429945, "size": 430163,
"language": "Nix", "language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/nixy/languages", "languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/nixy/languages",
"html_url": "https://git.hadi.icu/anotherhadi/nixy", "html_url": "https://git.hadi.icu/anotherhadi/nixy",
@@ -47,13 +47,14 @@
"stars_count": 0, "stars_count": 0,
"forks_count": 0, "forks_count": 0,
"watchers_count": 1, "watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0, "open_issues_count": 0,
"open_pr_counter": 0, "open_pr_counter": 0,
"release_counter": 0, "release_counter": 0,
"default_branch": "main", "default_branch": "main",
"archived": false, "archived": false,
"created_at": "2026-03-30T17:31:04+02:00", "created_at": "2026-03-30T17:31:04+02:00",
"updated_at": "2026-04-23T09:52:11+02:00", "updated_at": "2026-05-07T09:47:53+02:00",
"archived_at": "1970-01-01T01:00:00+01:00", "archived_at": "1970-01-01T01:00:00+01:00",
"permissions": { "permissions": {
"admin": false, "admin": false,
@@ -90,7 +91,7 @@
"internal": false, "internal": false,
"mirror_interval": "8h0m0s", "mirror_interval": "8h0m0s",
"object_format_name": "sha1", "object_format_name": "sha1",
"mirror_updated": "2026-04-23T11:17:25+02:00", "mirror_updated": "2026-05-07T16:22:32+02:00",
"topics": [ "topics": [
"dotfiles", "dotfiles",
"hyprland", "hyprland",
@@ -106,6 +107,220 @@
}, },
"banner_url": "https://git.hadi.icu/anotherhadi/nixy/raw/branch/main/.github/assets/banner.png" "banner_url": "https://git.hadi.icu/anotherhadi/nixy/raw/branch/main/.github/assets/banner.png"
}, },
{
"id": 9,
"owner": {
"id": 1,
"login": "anotherhadi",
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2026-03-30T17:21:50+02:00",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "127.0.0.1",
"website": "https://hadi.icu",
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "anotherhadi"
},
"name": "usbguard-tui",
"full_name": "anotherhadi/usbguard-tui",
"description": "A terminal UI for managing USB devices via usbguard. TUI built with golang & bubbletea.",
"empty": false,
"private": false,
"fork": false,
"template": false,
"mirror": true,
"size": 425,
"language": "Go",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/usbguard-tui/languages",
"html_url": "https://git.hadi.icu/anotherhadi/usbguard-tui",
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/usbguard-tui",
"link": "",
"ssh_url": "gitea@git.hadi.icu:anotherhadi/usbguard-tui.git",
"clone_url": "https://git.hadi.icu/anotherhadi/usbguard-tui.git",
"original_url": "https://github.com/anotherhadi/usbguard-tui",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2026-04-30T17:40:42+02:00",
"updated_at": "2026-05-06T14:42:02+02:00",
"archived_at": "1970-01-01T01:00:00+01:00",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_code": true,
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": false,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": false,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": false,
"allow_rebase": false,
"allow_rebase_explicit": false,
"allow_squash_merge": false,
"allow_fast_forward_only_merge": false,
"allow_rebase_update": false,
"allow_manual_merge": true,
"autodetect_manual_merge": false,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "",
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-05-07T12:42:33+02:00",
"topics": [
"bubbletea",
"tui",
"usbguard"
],
"licenses": [
"MIT"
],
"mirrors": {
"github": "https://github.com/anotherhadi/usbguard-tui",
"gitlab": "https://gitlab.com/anotherhadi_mirror/usbguard-tui"
},
"banner_url": "https://git.hadi.icu/anotherhadi/usbguard-tui/raw/branch/main/.github/assets/banner.png"
},
{
"id": 3,
"owner": {
"id": 1,
"login": "anotherhadi",
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2026-03-30T17:21:50+02:00",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "127.0.0.1",
"website": "https://hadi.icu",
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "anotherhadi"
},
"name": "blog",
"full_name": "anotherhadi/blog",
"description": "Thoughts, insights, and tutorials on cybersecurity, OSINT, and technology.",
"empty": false,
"private": false,
"fork": false,
"template": false,
"mirror": true,
"size": 3863,
"language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog/languages",
"html_url": "https://git.hadi.icu/anotherhadi/blog",
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog",
"link": "",
"ssh_url": "gitea@git.hadi.icu:anotherhadi/blog.git",
"clone_url": "https://git.hadi.icu/anotherhadi/blog.git",
"original_url": "https://github.com/anotherhadi/blog",
"website": "https://hadi.icu",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2026-03-30T17:39:47+02:00",
"updated_at": "2026-05-04T17:03:05+02:00",
"archived_at": "1970-01-01T01:00:00+01:00",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_code": true,
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": false,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": false,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": false,
"allow_rebase": false,
"allow_rebase_explicit": false,
"allow_squash_merge": false,
"allow_fast_forward_only_merge": false,
"allow_rebase_update": false,
"allow_manual_merge": true,
"autodetect_manual_merge": false,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://git.hadi.icu/repo-avatars/527f936266a33d3ccfe22013ef2ca0f61e20a4941912b16e4b4e9dcb14bf4e30",
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-05-07T17:22:32+02:00",
"topics": [
"blog",
"cybersecurity",
"portfolio"
],
"licenses": [
"MIT"
],
"mirrors": {
"github": "https://github.com/anotherhadi/blog",
"gitlab": "https://gitlab.com/anotherhadi_mirror/blog"
},
"banner_url": "https://git.hadi.icu/anotherhadi/blog/raw/branch/main/.github/assets/banner.png"
},
{ {
"id": 6, "id": 6,
"owner": { "owner": {
@@ -114,7 +329,7 @@
"login_name": "", "login_name": "",
"source_id": 0, "source_id": 0,
"full_name": "Hadi", "full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu", "email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae", "avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi", "html_url": "https://git.hadi.icu/anotherhadi",
"language": "", "language": "",
@@ -141,7 +356,7 @@
"fork": false, "fork": false,
"template": false, "template": false,
"mirror": true, "mirror": true,
"size": 676, "size": 681,
"language": "Svelte", "language": "Svelte",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/iknowyou/languages", "languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/iknowyou/languages",
"html_url": "https://git.hadi.icu/anotherhadi/iknowyou", "html_url": "https://git.hadi.icu/anotherhadi/iknowyou",
@@ -154,6 +369,7 @@
"stars_count": 0, "stars_count": 0,
"forks_count": 0, "forks_count": 0,
"watchers_count": 1, "watchers_count": 1,
"branch_count": 2,
"open_issues_count": 0, "open_issues_count": 0,
"open_pr_counter": 0, "open_pr_counter": 0,
"release_counter": 0, "release_counter": 0,
@@ -197,7 +413,7 @@
"internal": false, "internal": false,
"mirror_interval": "8h0m0s", "mirror_interval": "8h0m0s",
"object_format_name": "sha1", "object_format_name": "sha1",
"mirror_updated": "2026-04-23T14:17:25+02:00", "mirror_updated": "2026-05-07T20:02:34+02:00",
"topics": [ "topics": [
"osint", "osint",
"osint-tool" "osint-tool"
@@ -211,112 +427,6 @@
}, },
"banner_url": "https://git.hadi.icu/anotherhadi/iknowyou/raw/branch/main/.github/assets/banner.png" "banner_url": "https://git.hadi.icu/anotherhadi/iknowyou/raw/branch/main/.github/assets/banner.png"
}, },
{
"id": 3,
"owner": {
"id": 1,
"login": "anotherhadi",
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2026-03-30T17:21:50+02:00",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "127.0.0.1",
"website": "https://hadi.icu",
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "anotherhadi"
},
"name": "blog",
"full_name": "anotherhadi/blog",
"description": "Thoughts, insights, and tutorials on cybersecurity, OSINT, and technology.",
"empty": false,
"private": false,
"fork": false,
"template": false,
"mirror": true,
"size": 3009,
"language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog/languages",
"html_url": "https://git.hadi.icu/anotherhadi/blog",
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog",
"link": "",
"ssh_url": "gitea@git.hadi.icu:anotherhadi/blog.git",
"clone_url": "https://git.hadi.icu/anotherhadi/blog.git",
"original_url": "https://github.com/anotherhadi/blog",
"website": "https://hadi.icu",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2026-03-30T17:39:47+02:00",
"updated_at": "2026-04-11T17:35:02+02:00",
"archived_at": "1970-01-01T01:00:00+01:00",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_code": true,
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": false,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": false,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": false,
"allow_rebase": false,
"allow_rebase_explicit": false,
"allow_squash_merge": false,
"allow_fast_forward_only_merge": false,
"allow_rebase_update": false,
"allow_manual_merge": true,
"autodetect_manual_merge": false,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://git.hadi.icu/repo-avatars/527f936266a33d3ccfe22013ef2ca0f61e20a4941912b16e4b4e9dcb14bf4e30",
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-04-23T11:17:26+02:00",
"topics": [
"blog",
"cybersecurity",
"portfolio"
],
"licenses": [
"MIT"
],
"mirrors": {
"github": "https://github.com/anotherhadi/blog",
"gitlab": "https://gitlab.com/anotherhadi_mirror/blog"
},
"banner_url": "https://git.hadi.icu/anotherhadi/blog/raw/branch/main/.github/assets/banner.png"
},
{ {
"id": 2, "id": 2,
"owner": { "owner": {
@@ -325,7 +435,7 @@
"login_name": "", "login_name": "",
"source_id": 0, "source_id": 0,
"full_name": "Hadi", "full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu", "email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae", "avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi", "html_url": "https://git.hadi.icu/anotherhadi",
"language": "", "language": "",
@@ -352,7 +462,7 @@
"fork": false, "fork": false,
"template": false, "template": false,
"mirror": true, "mirror": true,
"size": 530, "size": 535,
"language": "Nix", "language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/default-creds/languages", "languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/default-creds/languages",
"html_url": "https://git.hadi.icu/anotherhadi/default-creds", "html_url": "https://git.hadi.icu/anotherhadi/default-creds",
@@ -365,6 +475,7 @@
"stars_count": 0, "stars_count": 0,
"forks_count": 0, "forks_count": 0,
"watchers_count": 1, "watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0, "open_issues_count": 0,
"open_pr_counter": 0, "open_pr_counter": 0,
"release_counter": 0, "release_counter": 0,
@@ -408,7 +519,7 @@
"internal": false, "internal": false,
"mirror_interval": "8h0m0s", "mirror_interval": "8h0m0s",
"object_format_name": "sha1", "object_format_name": "sha1",
"mirror_updated": "2026-04-23T11:17:25+02:00", "mirror_updated": "2026-05-07T17:12:33+02:00",
"topics": [ "topics": [
"cybersecurity", "cybersecurity",
"cybersecurity-tools", "cybersecurity-tools",
@@ -432,7 +543,7 @@
"login_name": "", "login_name": "",
"source_id": 0, "source_id": 0,
"full_name": "Hadi", "full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu", "email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae", "avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi", "html_url": "https://git.hadi.icu/anotherhadi",
"language": "", "language": "",
@@ -472,6 +583,7 @@
"stars_count": 0, "stars_count": 0,
"forks_count": 0, "forks_count": 0,
"watchers_count": 1, "watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0, "open_issues_count": 0,
"open_pr_counter": 0, "open_pr_counter": 0,
"release_counter": 0, "release_counter": 0,
+6 -1
View File
@@ -45,7 +45,12 @@ const sortedPosts = blogPosts.sort(
) : ( ) : (
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{sortedPosts.map((post) => ( {sortedPosts.map((post) => (
<BlogCard post={post} /> <BlogCard
displayBanner={true}
displayTags={true}
displayDate={true}
post={post}
/>
))} ))}
</div> </div>
) )
+7 -2
View File
@@ -3,26 +3,31 @@ import Layout from "../layouts/Layout.astro";
import Hero from "../components/Hero.astro"; import Hero from "../components/Hero.astro";
import Projects from "../components/Projects.astro"; import Projects from "../components/Projects.astro";
import Blog from "../components/Blog.astro"; import Blog from "../components/Blog.astro";
import Notes from "../components/Notes.astro";
import Contact from "../components/Contact.astro"; import Contact from "../components/Contact.astro";
import { siteConfig } from "../config"; import { siteConfig } from "../config";
import avatar from "../../public/avatar.jpg"; import avatar from "../../public/avatar.jpg";
--- ---
<Layout title={`Another Hadi`} description={siteConfig.description}> <Layout title={`Another Hadi`} description={siteConfig.description}>
<main> <main class="px-10">
<Hero <Hero
name={siteConfig.name} name={siteConfig.name}
title={siteConfig.title} title={siteConfig.title}
description={siteConfig.description} description={siteConfig.description}
avatar={avatar} avatar={avatar}
location={siteConfig.location}
socialLinks={siteConfig.socialLinks} socialLinks={siteConfig.socialLinks}
gpgKey={siteConfig.gpgKey} gpgKey={siteConfig.gpgKey}
rssFeed={siteConfig.rssFeed} rssFeed={siteConfig.rssFeed}
/> />
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Blog /> <Blog />
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Notes />
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Projects /> <Projects />
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Contact /> <Contact />
</main> </main>
</Layout> </Layout>
+38 -77
View File
@@ -3,10 +3,10 @@ import { getCollection, render } from "astro:content";
import Layout from "../../layouts/Layout.astro"; import Layout from "../../layouts/Layout.astro";
import { List, PanelRight } from "@lucide/astro"; import { List, PanelRight } from "@lucide/astro";
import NoteTOC from "../../components/NoteTOC.astro"; import NoteTOC from "../../components/NoteTOC.astro";
import NoteNavSidebar from "../../components/NoteNavSidebar.svelte"; import NoteNavSidebar from "../../components/NoteNavSidebar.astro";
import NoteGraphSidebar from "../../components/NoteGraphSidebar.astro"; import NoteGraphSidebar from "../../components/NoteGraphSidebar.astro";
import NoteVars from "../../components/NoteVars.svelte"; import NoteVars from "../../components/NoteVars.svelte";
import { getCategory, extractLinks, extractHeadings } from "../../utils/notes"; import { getCategory, extractLinks, extractExternalLinks } from "../../utils/notes";
export async function getStaticPaths() { export async function getStaticPaths() {
const notes = await getCollection("notes"); const notes = await getCollection("notes");
@@ -17,7 +17,7 @@ export async function getStaticPaths() {
} }
const { entry } = Astro.props; const { entry } = Astro.props;
const { Content } = await render(entry); const { Content, headings: astroHeadings } = await render(entry);
const allNotes = await getCollection("notes"); const allNotes = await getCollection("notes");
const sortedNotes = allNotes.sort((a, b) => const sortedNotes = allNotes.sort((a, b) =>
@@ -60,14 +60,22 @@ const noteVars = [
), ),
]; ];
const headings = extractHeadings(entry.body ?? ""); const headings = astroHeadings.map((h) => ({ depth: h.depth, text: h.text, id: h.slug }));
const externalLinks = extractExternalLinks(entry.body ?? "");
--- ---
<style> <style>
.drawer.lg\:drawer-open > .drawer-side, @media (min-width: 768px) {
.drawer.xl\:drawer-open > .drawer-side { .drawer.md\:drawer-open > .drawer-side {
top: 3rem; top: 3rem;
height: calc(100vh - 3rem); height: calc(100vh - 3rem);
}
}
@media (min-width: 1280px) {
.drawer.xl\:drawer-open > .drawer-side {
top: 3rem;
height: calc(100vh - 3rem);
}
} }
</style> </style>
@@ -76,12 +84,12 @@ const headings = extractHeadings(entry.body ?? "");
description={entry.data.description} description={entry.data.description}
> >
<main class="max-w-screen-2xl mx-auto"> <main class="max-w-screen-2xl mx-auto">
<div class="drawer drawer-end xl:drawer-open min-h-[calc(100vh-3rem)]"> <div class="drawer md:drawer-open min-h-[calc(100vh-3rem)]">
<input id="graph-drawer" type="checkbox" class="drawer-toggle" /> <input id="nav-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex min-h-[calc(100vh-3rem)]"> <div class="drawer-content flex min-h-[calc(100vh-3rem)] min-w-0">
<div class="drawer lg:drawer-open w-full"> <div class="drawer drawer-end xl:drawer-open w-full" id="right-drawer">
<input id="nav-drawer" type="checkbox" class="drawer-toggle" /> <input id="graph-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col min-w-0"> <div class="drawer-content flex flex-col min-w-0">
<main class="flex-1 px-4 sm:px-6 lg:px-10 py-6 lg:py-10 min-w-0"> <main class="flex-1 px-4 sm:px-6 lg:px-10 py-6 lg:py-10 min-w-0">
@@ -115,7 +123,7 @@ const headings = extractHeadings(entry.body ?? "");
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<label <label
for="nav-drawer" for="nav-drawer"
class="btn btn-ghost btn-xs lg:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50" class="btn btn-ghost btn-xs md:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
> >
<List size={11} /> <List size={11} />
nav nav
@@ -124,7 +132,7 @@ const headings = extractHeadings(entry.body ?? "");
<label <label
for="graph-drawer" for="graph-drawer"
id="graph-toggle" id="graph-toggle"
class="btn btn-ghost btn-xs font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50" class="btn btn-ghost btn-xs xl:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
title="Toggle graph" title="Toggle graph"
> >
<PanelRight size={11} /> <PanelRight size={11} />
@@ -204,32 +212,29 @@ const headings = extractHeadings(entry.body ?? "");
</main> </main>
</div> </div>
<div class="drawer-side z-50"> <div class="drawer-side z-[60]">
<label <label
for="nav-drawer" for="graph-drawer"
aria-label="close sidebar" aria-label="close sidebar"
class="drawer-overlay"></label> class="drawer-overlay"></label>
<NoteNavSidebar <NoteGraphSidebar
client:load entry={entry}
notes={sortedNotes} graphNodes={graphNodes}
currentEntry={entry} graphEdges={graphEdges}
categories={categories} forwardLinks={forwardLinks}
backlinks={backlinks}
externalLinks={externalLinks}
/> />
</div> </div>
</div> </div>
</div> </div>
<div class="drawer-side z-40"> <div class="drawer-side z-[70]">
<label <label for="nav-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
for="graph-drawer" <NoteNavSidebar
aria-label="close sidebar" notes={sortedNotes}
class="drawer-overlay xl:hidden"></label> currentEntry={entry}
<NoteGraphSidebar categories={categories}
entry={entry}
graphNodes={graphNodes}
graphEdges={graphEdges}
forwardLinks={forwardLinks}
backlinks={backlinks}
/> />
</div> </div>
</div> </div>
@@ -290,51 +295,7 @@ const headings = extractHeadings(entry.body ?? "");
}); });
} }
function initXlGraphToggle() {
const graphDrawer = document.getElementById(
"graph-drawer",
) as HTMLInputElement | null;
if (!graphDrawer) return;
const outerDrawer = graphDrawer.closest<HTMLElement>(".drawer.drawer-end");
const xlQuery = window.matchMedia("(min-width: 1280px)");
const STORAGE_KEY = "notes-graph-sidebar";
function setXlSidebar(open: boolean) {
if (!outerDrawer) return;
if (open) {
outerDrawer.classList.add("xl:drawer-open");
} else {
outerDrawer.classList.remove("xl:drawer-open");
}
localStorage.setItem(STORAGE_KEY, open ? "1" : "0");
}
const graphToggle = document.getElementById("graph-toggle");
graphToggle?.addEventListener("click", (e) => {
if (!xlQuery.matches) return;
e.preventDefault();
setXlSidebar(!outerDrawer?.classList.contains("xl:drawer-open"));
});
if (xlQuery.matches) {
const saved = localStorage.getItem(STORAGE_KEY);
// Open by default unless user explicitly closed it
setXlSidebar(saved !== "0");
}
xlQuery.addEventListener("change", (e) => {
if (!e.matches) {
outerDrawer?.classList.remove("xl:drawer-open");
} else {
const saved = localStorage.getItem(STORAGE_KEY);
setXlSidebar(saved !== "0");
}
});
}
document.addEventListener("astro:page-load", () => { document.addEventListener("astro:page-load", () => {
injectHeadingAnchors(); injectHeadingAnchors();
initXlGraphToggle();
}); });
</script> </script>
+10 -12
View File
@@ -1,7 +1,7 @@
--- ---
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import Layout from "../../layouts/Layout.astro"; import Layout from "../../layouts/Layout.astro";
import NoteNavSidebar from "../../components/NoteNavSidebar.svelte"; import NoteNavSidebar from "../../components/NoteNavSidebar.astro";
import { getCategory } from "../../utils/notes"; import { getCategory } from "../../utils/notes";
import { List } from "@lucide/astro"; import { List } from "@lucide/astro";
@@ -41,9 +41,11 @@ if (!categoryNotes) {
--- ---
<style> <style>
.drawer.lg\:drawer-open > .drawer-side { @media (min-width: 768px) {
top: 3rem; .drawer.md\:drawer-open > .drawer-side {
height: calc(100vh - 3rem); top: 3rem;
height: calc(100vh - 3rem);
}
} }
</style> </style>
@@ -52,7 +54,7 @@ if (!categoryNotes) {
description={`Notes on ${category}.`} description={`Notes on ${category}.`}
> >
<main class="max-w-screen-2xl mx-auto"> <main class="max-w-screen-2xl mx-auto">
<div class="drawer lg:drawer-open min-h-[calc(100vh-3rem)]"> <div class="drawer md:drawer-open min-h-[calc(100vh-3rem)]">
<input id="nav-drawer" type="checkbox" class="drawer-toggle" /> <input id="nav-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col min-w-0"> <div class="drawer-content flex flex-col min-w-0">
@@ -72,7 +74,7 @@ if (!categoryNotes) {
</div> </div>
<label <label
for="nav-drawer" for="nav-drawer"
class="btn btn-ghost btn-xs lg:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50" class="btn btn-ghost btn-xs md:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
> >
<List size={11} /> <List size={11} />
nav nav
@@ -161,13 +163,9 @@ if (!categoryNotes) {
</main> </main>
</div> </div>
<div class="drawer-side z-50"> <div class="drawer-side z-[70]">
<label <label for="nav-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
for="nav-drawer"
aria-label="close sidebar"
class="drawer-overlay"></label>
<NoteNavSidebar <NoteNavSidebar
client:load
notes={allNotes} notes={allNotes}
currentCategory={category} currentCategory={category}
categories={categories} categories={categories}
+1 -1
View File
@@ -36,7 +36,7 @@ import repos from "../../data/repos.json";
) : ( ) : (
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{repos.map((repo) => ( {repos.map((repo) => (
<GiteaProjectCard repo={repo} /> <GiteaProjectCard displayBanner={true} repo={repo} />
))} ))}
</div> </div>
) )
+13 -2
View File
@@ -36,6 +36,13 @@
--noise: 0; --noise: 0;
} }
.drawer-side > aside > astro-island {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
.btn:not(.btn-circle):not(.btn-square) { .btn:not(.btn-circle):not(.btn-square) {
@apply rounded-lg; @apply rounded-lg;
} }
@@ -50,7 +57,9 @@
background: transparent; background: transparent;
color: var(--color-base-content); color: var(--color-base-content);
text-decoration: none !important; text-decoration: none !important;
transition: background 0.15s ease, border-color 0.15s ease; transition:
background 0.15s ease,
border-color 0.15s ease;
margin-block: 0.25rem; margin-block: 0.25rem;
} }
.link-card::after { .link-card::after {
@@ -60,7 +69,9 @@
opacity: 0; opacity: 0;
color: var(--color-primary); color: var(--color-primary);
font-size: 0.75rem; font-size: 0.75rem;
transition: opacity 0.15s ease, transform 0.15s ease; transition:
opacity 0.15s ease,
transform 0.15s ease;
transform: translate(-4px, 4px); transform: translate(-4px, 4px);
flex-shrink: 0; flex-shrink: 0;
} }
+28
View File
@@ -15,6 +15,34 @@ function slugify(text: string): string {
.replace(/ +/g, "-"); .replace(/ +/g, "-");
} }
export function extractExternalLinks(body: string): { url: string; label: string }[] {
const seen = new Set<string>();
const links: { url: string; label: string }[] = [];
// Markdown: [label](https://...)
const mdRe = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g;
let m;
while ((m = mdRe.exec(body)) !== null) {
if (!seen.has(m[2])) {
seen.add(m[2]);
links.push({ url: m[2], label: m[1] });
}
}
// HTML: <a href="https://...">...</a> — use h4 content as label if present, else href host
const htmlRe = /<a\s[^>]*href="(https?:\/\/[^"]+)"[^>]*>([\s\S]*?)<\/a>/g;
while ((m = htmlRe.exec(body)) !== null) {
const url = m[1];
if (seen.has(url)) continue;
seen.add(url);
const h4 = m[2].match(/<h4[^>]*>([\s\S]*?)<\/h4>/);
const label = h4 ? h4[1].trim() : new URL(url).hostname;
links.push({ url, label });
}
return links;
}
export function extractLinks(body: string): string[] { export function extractLinks(body: string): string[] {
const re = /\(\/notes\/([^)#\s]+)(?:#[^)\s]*)?\)/g; const re = /\(\/notes\/([^)#\s]+)(?:#[^)\s]*)?\)/g;
const ids: string[] = []; const ids: string[] = [];