change notes sidebars behavior

Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
Hadi
2026-04-24 23:53:02 +02:00
parent 97bdfd9a6e
commit 294c4e3acd
5 changed files with 87 additions and 25 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ const username = "anotherhadi"
const bio = "Infosec engineer."
---
<div class="flex gap-3 justify-start">
<div class="flex flex-wrap gap-3 justify-start">
<div
class="ring-base-300 ring-offset-base-100 rounded-full ring-2 ring-offset-2 flex justify-center items-center"
>
+2 -2
View File
@@ -15,7 +15,7 @@ function isActive(href: string) {
---
<header
class="fixed top-0 left-0 right-0 z-50 h-12 flex items-center px-5"
class="fixed top-0 left-0 right-0 z-[60] h-12 flex items-center px-5"
style="background: oklch(0% 0 0 / 0.85); backdrop-filter: blur(12px); border-bottom: 1px solid oklch(22% 0 0);"
>
<div class="flex items-center justify-between w-full max-w-screen-xl mx-auto">
@@ -67,7 +67,7 @@ function isActive(href: string) {
<div
id="mobile-menu"
class="hidden fixed inset-x-0 top-12 z-40 md:hidden border-b border-base-300/60 py-2"
class="hidden fixed inset-x-0 top-12 z-[59] md:hidden border-b border-base-300/60 py-2"
style="background: oklch(2% 0 0 / 0.97); backdrop-filter: blur(12px);"
>
{
+1 -1
View File
@@ -19,7 +19,7 @@ const notes = defineCollection({
schema: z.object({
title: z.string(),
description: z.string(),
category: z.string(),
category: z.string().optional(),
tags: z.array(z.string()).default([]),
publishDate: z.coerce.date(),
}),
+74 -18
View File
@@ -2,6 +2,7 @@
import { getCollection, render } from "astro:content";
import Layout from "../../layouts/Layout.astro";
import { Shield, ChevronLeft, List, PanelRight } from "@lucide/astro";
import Author from "../../components/Author.astro";
export async function getStaticPaths() {
const notes = await getCollection("notes");
@@ -16,7 +17,13 @@ const { Content } = await render(entry);
const allNotes = await getCollection("notes");
const sortedNotes = allNotes.sort((a, b) => a.data.title.localeCompare(b.data.title));
const categories = [...new Set(allNotes.map((n) => n.data.category))].sort();
function getCategory(n: { id: string; data: { category?: string } }): string {
if (n.data.category) return n.data.category;
const parts = n.id.split("/");
return parts.length > 1 ? parts[0] : "General";
}
const categories = [...new Set(allNotes.map(getCategory))].sort();
function formatDate(date: Date) {
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
@@ -50,6 +57,14 @@ const graphEdges = [
...backlinks.map((n) => ({ from: n.id, to: entry.id })),
];
function extractInlineHashtags(body: string): string[] {
const re = /#(\w+)/g;
const tags: string[] = [];
let m;
while ((m = re.exec(body)) !== null) tags.push(m[1].toLowerCase());
return [...new Set(tags)];
}
function slugify(text: string) {
return text.toLowerCase().replace(/`[^`]*`/g, "").replace(/[^\w\s-]/g, "").trim().replace(/[\s_]+/g, "-");
}
@@ -62,12 +77,21 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
}
---
<style>
/* Both sidebars sit below the navbar when in drawer-open mode */
.drawer.lg\:drawer-open > .drawer-side,
.drawer.xl\:drawer-open > .drawer-side {
top: 3rem;
height: calc(100vh - 3rem);
}
</style>
<Layout
title={`${entry.data.title} — Security Notes`}
description={entry.data.description}
>
<div class="drawer drawer-end min-h-[calc(100vh-3rem)]">
<div class="drawer drawer-end xl:drawer-open min-h-[calc(100vh-3rem)]">
<input id="graph-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex min-h-[calc(100vh-3rem)]">
@@ -103,10 +127,10 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
</div>
</div>
<header class="mb-10">
<header class="mb-8">
<div class="flex items-center gap-3 mb-5">
<span class="text-xl font-bold tracking-tight">
<span class="text-primary/50 font-mono mr-0.5">/</span>{entry.data.category}
<span class="text-primary/50 font-mono mr-0.5">/</span>{getCategory(entry)}
</span>
<span class="text-base-content/20 text-xs">·</span>
<time datetime={entry.data.publishDate.toISOString()} class="text-xs text-base-content/35">
@@ -116,7 +140,7 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
<h1 class="text-4xl sm:text-5xl font-bold tracking-tight mb-3">{entry.data.title}</h1>
<p class="text-base-content/50 mb-4">{entry.data.description}</p>
{entry.data.tags.length > 0 && (
<div class="flex flex-wrap gap-1">
<div class="flex flex-wrap gap-1 mb-4">
{entry.data.tags.map((tag) => (
<a href={`/notes?tag=${tag}`}
class="font-mono text-[10px] px-1.5 py-0.5 border border-base-300/40 text-base-content/25 hover:text-primary/70 hover:border-primary/40 transition-colors">
@@ -181,7 +205,7 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
<div class="drawer-side z-50">
<label for="nav-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<aside
class="w-56 min-h-full flex flex-col border-r border-base-300/60"
class="w-56 flex flex-col border-r border-base-300/60 h-full"
style="background: oklch(4% 0 0);"
>
<div class="px-4 py-4 border-b border-base-300/40">
@@ -208,7 +232,7 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
</span>
</div>
<ul class="ml-3 space-y-0.5 border-l border-base-300/30 pl-2">
{sortedNotes.filter((n) => n.data.category === cat).map((n) => (
{sortedNotes.filter((n) => getCategory(n) === cat).map((n) => (
<li>
<a
href={`/notes/${n.id}`}
@@ -219,7 +243,7 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
: "text-base-content/45 hover:text-base-content/80 hover:bg-base-200/30",
]}
data-title={n.data.title.toLowerCase()}
data-tags={n.data.tags.join(",")}
data-tags={[...n.data.tags, ...extractInlineHashtags(n.body ?? "")].join(",")}
>
{n.id === entry.id ? "▶ " : ""}{n.data.title}
</a>
@@ -239,7 +263,7 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
<label for="graph-drawer" aria-label="close sidebar" class="drawer-overlay xl:hidden"></label>
<aside
id="right-sidebar"
class="w-52 min-h-full flex flex-col border-l border-base-300/60"
class="w-52 flex flex-col border-l border-base-300/60 h-full overflow-y-auto"
style="background: oklch(4% 0 0);"
>
<div class="border-b border-base-300/40">
@@ -291,6 +315,10 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
<p class="font-mono text-[9px] text-base-content/20">no linked notes</p>
</div>
)}
<div class="px-4 py-6">
<Author />
</div>
</aside>
</div>
</div>
@@ -462,6 +490,7 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
const graphDrawer = document.getElementById("graph-drawer") as HTMLInputElement | null;
if (!graphDrawer) return;
// On non-xl: let DaisyUI overlay work via checkbox
function onGraphDrawerChange() {
if (graphDrawer!.checked) {
requestAnimationFrame(() => { stopGraph = startGraph() ?? null; });
@@ -471,16 +500,35 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
}
graphDrawer.addEventListener("change", onGraphDrawerChange);
const outerDrawer = graphDrawer.closest<HTMLElement>(".drawer.drawer-end");
const xlQuery = window.matchMedia("(min-width: 1280px)");
if (xlQuery.matches && !graphDrawer.checked) {
graphDrawer.checked = true;
onGraphDrawerChange();
}
xlQuery.addEventListener("change", (e) => {
if (!e.matches && graphDrawer.checked) {
graphDrawer.checked = false;
function setXlSidebar(open: boolean) {
if (!outerDrawer) return;
if (open) {
outerDrawer.classList.add("xl:drawer-open");
requestAnimationFrame(() => { stopGraph = startGraph() ?? null; });
} else {
outerDrawer.classList.remove("xl:drawer-open");
if (stopGraph) { stopGraph(); stopGraph = null; }
}
}
// On xl: toggle the class instead of the checkbox (avoids DaisyUI overlay + scroll lock)
const graphToggle = document.getElementById("graph-toggle");
graphToggle?.addEventListener("click", (e) => {
if (!xlQuery.matches) return;
e.preventDefault();
setXlSidebar(!outerDrawer?.classList.contains("xl:drawer-open"));
});
// Auto-open on xl, close when leaving xl
if (xlQuery.matches) {
outerDrawer?.classList.add("xl:drawer-open");
requestAnimationFrame(() => { stopGraph = startGraph() ?? null; });
}
xlQuery.addEventListener("change", (e) => {
if (!e.matches) setXlSidebar(false);
});
if (!document.getElementById("heading-anchor-styles")) {
@@ -533,12 +581,20 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
document.querySelectorAll<HTMLInputElement>("[data-search]").forEach((input) => {
input.addEventListener("input", (e) => {
const target = e.target as HTMLInputElement;
const search = target.value.toLowerCase().trim();
const raw = target.value.toLowerCase().trim();
document.querySelectorAll<HTMLInputElement>("[data-search]").forEach((o) => {
if (o !== target) o.value = target.value;
});
const isTag = raw.startsWith("#");
const search = isTag ? raw.slice(1) : raw;
navItems.forEach((item) => {
const match = !search || (item.dataset.title ?? "").includes(search) || (item.dataset.tags ?? "").includes(search);
const title = item.dataset.title ?? "";
const tags = item.dataset.tags ? item.dataset.tags.split(",") : [];
const match = !search || (
isTag
? tags.some((t) => t.includes(search))
: title.includes(search) || tags.join(",").includes(search)
);
item.style.display = match ? "" : "none";
});
});
+9 -3
View File
@@ -8,7 +8,13 @@ const sortedNotes = notes.sort(
(a, b) => b.data.publishDate.getTime() - a.data.publishDate.getTime()
);
const categories = [...new Set(notes.map((n) => n.data.category))].sort();
function getCategory(n: { id: string; data: { category?: string } }): string {
if (n.data.category) return n.data.category;
const parts = n.id.split("/");
return parts.length > 1 ? parts[0] : "General";
}
const categories = [...new Set(notes.map(getCategory))].sort();
const searchIndex = Object.fromEntries(
sortedNotes.map((n) => [
@@ -53,7 +59,7 @@ const searchIndex = Object.fromEntries(
<div id="notes-container" class="space-y-12">
{
categories.map((cat) => {
const catNotes = sortedNotes.filter((n) => n.data.category === cat);
const catNotes = sortedNotes.filter((n) => getCategory(n) === cat);
return (
<section data-category={cat.toLowerCase()}>
<div class="flex items-baseline gap-3 mb-4">
@@ -136,7 +142,7 @@ const searchIndex = Object.fromEntries(
const tags = card.dataset.tags ? card.dataset.tags.split(",") : [];
const show = !query || (
isTag
? tags.some((t) => t.includes(query))
? tags.some((t) => t.includes(query)) || (searchIndex[id] ?? "").includes(`#${query}`)
: (searchIndex[id] ?? "").includes(query)
);
card.style.display = show ? "" : "none";