Added commit tracker/link to sidebar
This commit is contained in:
parent
9a2d25ed11
commit
b8909cf9ce
12 changed files with 156 additions and 44 deletions
|
|
@ -40,7 +40,7 @@ export default async function CategoryPage({ params }: CategoryPageProps) {
|
|||
|
||||
if (!category) {
|
||||
return (
|
||||
<main>
|
||||
<main className="max-w-3xl 2xl:max-w-4xl mx-auto px-4 py-8 lg:py-16 w-full">
|
||||
<h1>Category not found</h1>
|
||||
<Link href="/categories">Back to categories</Link>
|
||||
</main>
|
||||
|
|
@ -48,7 +48,7 @@ export default async function CategoryPage({ params }: CategoryPageProps) {
|
|||
}
|
||||
|
||||
return (
|
||||
<main>
|
||||
<main className="max-w-3xl 2xl:max-w-4xl mx-auto px-4 py-8 lg:py-16 w-full">
|
||||
<h1>Category: {category}</h1>
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default function CategoriesPage() {
|
|||
const categoryNames = Object.keys(categories)
|
||||
|
||||
return (
|
||||
<main className="max-w-3xl mx-auto px-4 py-8 lg:py-16 w-full">
|
||||
<main className="max-w-3xl 2xl:max-w-4xl mx-auto px-4 py-8 lg:py-16 w-full">
|
||||
<div className="text-sm text-fg-dim mb-2">~/categories</div>
|
||||
|
||||
<ul className="border-l border-neutral-800 ml-1">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default function BlogIndex() {
|
|||
}
|
||||
|
||||
return (
|
||||
<main className="max-w-3xl mx-auto px-4 py-8 mb-16 lg:py-16">
|
||||
<main className="max-w-3xl 2xl:max-w-4xl mx-auto px-4 py-8 mb-16 lg:py-16">
|
||||
<section>
|
||||
{posts.length === 0 ? (
|
||||
<div className="rounded-xl border border-border p-6 text-fg-dim">
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export default async function Post({ params }: { params: Promise<{ slug: string
|
|||
|
||||
return (
|
||||
<main className="flex w-full">
|
||||
<article className="max-w-3xl mx-auto px-4 py-8 lg:py-16">
|
||||
<article className="max-w-3xl 2xl:max-w-4xl mx-auto px-4 py-8 lg:py-16">
|
||||
{meta.category && (
|
||||
<Link
|
||||
href={`/categories/${slugify(meta.category)}`}
|
||||
|
|
@ -139,9 +139,7 @@ export default async function Post({ params }: { params: Promise<{ slug: string
|
|||
</div>
|
||||
</article>
|
||||
|
||||
<aside className="hidden lg:block w-80">
|
||||
<TableOfContents items={tableOfContents} />
|
||||
</aside>
|
||||
<TableOfContents items={tableOfContents} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ type ImageWrapperProps = {
|
|||
|
||||
export function ImageWrapper({ src, alt, isListing }: ImageWrapperProps) {
|
||||
return (
|
||||
<figure className={cn('flex flex-col items-center gap-1 mb-4', isListing ? 'h-60' : 'w-full')}>
|
||||
<figure className={cn('flex flex-col items-center gap-1 mb-4 w-full', isListing && 'h-60')}>
|
||||
<div className="border border-border rounded-xl w-full overflow-hidden flex-1">
|
||||
<div
|
||||
className="flex items-center gap-2 px-2 h-6 border-b border-border w-full bg-fg/10"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,11 @@ import { usePathname } from 'next/navigation'
|
|||
import { ExternalLink } from '@/components/ui/ExternalLink'
|
||||
import { navItems } from '@/components/layout/navItems'
|
||||
|
||||
export function NavLinks() {
|
||||
type NavLinksProps = {
|
||||
gitOriginUrl: string | null
|
||||
}
|
||||
|
||||
export function NavLinks({ gitOriginUrl }: NavLinksProps) {
|
||||
const pathname = usePathname()
|
||||
|
||||
const isActive = (href: string) => {
|
||||
|
|
@ -26,7 +30,11 @@ export function NavLinks() {
|
|||
>
|
||||
{item.isExternal ? (
|
||||
<ExternalLink
|
||||
href={item.href}
|
||||
href={
|
||||
item.href.includes('{gitOriginUrl}')
|
||||
? item.href.replace('{gitOriginUrl}', gitOriginUrl ?? '')
|
||||
: item.href
|
||||
}
|
||||
className="flex items-center gap-2 px-2 py-1 hover:no-underline focus:no-underline"
|
||||
>
|
||||
{item.icon}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,69 @@
|
|||
import { metadata } from '@/app/metadata'
|
||||
import { NotepadText } from 'lucide-react'
|
||||
import { NavLinks } from '@/components/layout/NavLinks'
|
||||
import { siBluesky, siGithub, siMatrix, siTelegram, type SimpleIcon } from 'simple-icons'
|
||||
import { ExternalLink } from '@/components/ui/ExternalLink'
|
||||
import { getGitCommitHash, getGitOriginUrl } from '@/utils/git'
|
||||
|
||||
type SocialLink = {
|
||||
icon: SimpleIcon
|
||||
url: string
|
||||
}
|
||||
|
||||
const socialLinks: SocialLink[] = [
|
||||
{
|
||||
icon: siBluesky,
|
||||
url: 'https://bsky.app/profile/hangerthem.com',
|
||||
},
|
||||
{
|
||||
icon: siGithub,
|
||||
url: 'https://github.com/hangerthem',
|
||||
},
|
||||
{
|
||||
icon: siMatrix,
|
||||
url: 'https://matrix.to/#/@hangerthem:hangerthem.com',
|
||||
},
|
||||
{
|
||||
icon: siTelegram,
|
||||
url: 'https://t.me/hangerthem',
|
||||
},
|
||||
]
|
||||
|
||||
export async function Sidebar() {
|
||||
const gitCommitHash = (await getGitCommitHash())?.substring(0, 7)
|
||||
const gitOriginUrl = await getGitOriginUrl()
|
||||
|
||||
export function Sidebar() {
|
||||
return (
|
||||
<aside className="hidden lg:block w-64 shrink-0 px-4 bg-fg/5">
|
||||
<div className="sticky top-6">
|
||||
<h2 className="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||
<NotepadText className="w-6 h-6" />
|
||||
<span>{metadata.title!.toString()}</span>
|
||||
</h2>
|
||||
<ul className="space-y-2">
|
||||
<NavLinks />
|
||||
</ul>
|
||||
<div className="sticky top-0 flex flex-col h-screen gap-4 py-4">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||
<NotepadText className="w-6 h-6" />
|
||||
<span>{metadata.title!.toString()}</span>
|
||||
</h2>
|
||||
<ul className="space-y-2">
|
||||
<NavLinks gitOriginUrl={gitOriginUrl} />
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-auto flex gap-4 mx-auto">
|
||||
{socialLinks.map(({ icon, url }) => (
|
||||
<ExternalLink key={url} href={url}>
|
||||
<svg
|
||||
dangerouslySetInnerHTML={{ __html: icon.svg }}
|
||||
className="inline-block w-6 h-6 mr-1 fill-fg-dim hover:fill-accent transition-colors"
|
||||
/>
|
||||
</ExternalLink>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{gitCommitHash && gitOriginUrl && (
|
||||
<div className="text-xs text-fg-dim mt-2 mx-auto">
|
||||
<span>Commit: </span>
|
||||
<ExternalLink href={gitOriginUrl + '/commit/' + gitCommitHash}>
|
||||
{gitCommitHash}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export const navItems: NavItem[] = [
|
|||
isExternal: true,
|
||||
},
|
||||
{
|
||||
href: 'https://git.hangerthem.com/hangerthem/blog.hangerthem.com',
|
||||
href: '{gitOriginUrl}',
|
||||
label: 'Source Code',
|
||||
icon: <GitBranch className={navIconsClassName} />,
|
||||
isExternal: true,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { ImageWrapper } from '@/components/content/ImageWrapper'
|
||||
import { cn } from '@/utils/cn'
|
||||
import { PostMeta } from '@/types/Post.type'
|
||||
import Link from 'next/link'
|
||||
import { dateFormatter } from '@/utils/time'
|
||||
import { cn } from '@/utils/cn'
|
||||
|
||||
type PostProps = {
|
||||
post: PostMeta
|
||||
|
|
@ -32,11 +32,11 @@ export function Post({ post, isLatest, showImageSlot }: PostProps) {
|
|||
return (
|
||||
<article className={cn('flex flex-col gap-4', isLatest && 'items-center md:flex-row')}>
|
||||
{(isLatest || showImageSlot) && (
|
||||
<div className={isLatest && coverImage ? 'flex-1' : 'h-60'}>
|
||||
<div className={isLatest && coverImage ? 'flex-1 w-full' : 'h-60'}>
|
||||
{coverImage && <ImageWrapper src={coverImage} alt={coverImageAlt ?? title} isListing />}
|
||||
</div>
|
||||
)}
|
||||
<div className={cn('flex flex-col gap-1', isLatest && 'flex-1')}>
|
||||
<div className={cn('flex flex-col gap-1 w-full', isLatest && 'flex-1')}>
|
||||
{isLatest && <span className="text-xs font-mono text-accent">Latest post</span>}
|
||||
{category && (
|
||||
<div className="font-mono text-xs flex gap-1 flex-wrap text-fg-dim mt-1 categories">
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import Link from 'next/link'
|
|||
export function RecentlyUpdatedPosts() {
|
||||
const posts = getAllPostsMeta()
|
||||
return (
|
||||
<aside className="w-80 hidden lg:block py-4 lg:py-12">
|
||||
<div className="sticky top-8 border-l border-border h-fit pl-4 py-4">
|
||||
<aside className="w-80 xl:w-100 hidden lg:block">
|
||||
<div className="sticky top-12 border-l border-border h-fit pl-4 py-4">
|
||||
<h2 className="text-xl font-bold mb-4">Recently updated</h2>
|
||||
<ul className="flex flex-col gap-2 ml-1">
|
||||
{posts
|
||||
|
|
|
|||
|
|
@ -38,23 +38,25 @@ export function TableOfContents({ items }: { items: TocItem[] }) {
|
|||
if (items.length === 0) return null
|
||||
|
||||
return (
|
||||
<nav aria-label="Table of contents" className="sticky top-20">
|
||||
<h2 className="text-2xl font-bold mb-4">Table of Contents</h2>
|
||||
<ul className="space-y-1 text-sm">
|
||||
{items.map((item) => (
|
||||
<li key={item.id} className={indentByLevel[item.level] ?? 'pl-0'}>
|
||||
<a
|
||||
href={`#${item.id}`}
|
||||
className={`block truncate transition-colors ${
|
||||
activeId === item.id ? 'text-accent font-medium' : 'text-fg-dim hover:text-fg'
|
||||
}`}
|
||||
aria-current={activeId === item.id ? 'location' : undefined}
|
||||
>
|
||||
{item.text}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
<aside className="hidden lg:block w-80 xl:w-100">
|
||||
<nav aria-label="Table of contents" className="sticky top-20">
|
||||
<h2 className="text-2xl font-bold mb-4">Table of Contents</h2>
|
||||
<ul className="space-y-1 text-sm">
|
||||
{items.map((item) => (
|
||||
<li key={item.id} className={indentByLevel[item.level] ?? 'pl-0'}>
|
||||
<a
|
||||
href={`#${item.id}`}
|
||||
className={`block truncate transition-colors ${
|
||||
activeId === item.id ? 'text-accent font-medium' : 'text-fg-dim hover:text-fg'
|
||||
}`}
|
||||
aria-current={activeId === item.id ? 'location' : undefined}
|
||||
>
|
||||
{item.text}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
53
utils/git.ts
Normal file
53
utils/git.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { exec } from 'child_process'
|
||||
|
||||
/**
|
||||
* Get the current Git commit hash.
|
||||
* @returns The current Git commit hash as a string, or null if an error occurs.
|
||||
*/
|
||||
export async function getGitCommitHash(): Promise<string | null> {
|
||||
try {
|
||||
const commitHash = (await new Promise((resolve, reject) => {
|
||||
exec('git rev-parse HEAD', (error, stdout) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve(stdout.toString().trim())
|
||||
}
|
||||
})
|
||||
})) as string
|
||||
|
||||
return commitHash
|
||||
} catch (error) {
|
||||
console.error('Error getting Git commit hash:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Git origin URL.
|
||||
* @returns The Git origin URL as a string, or null if an error occurs.
|
||||
*/
|
||||
export async function getGitOriginUrl(): Promise<string | null> {
|
||||
try {
|
||||
const originUrl = (await new Promise((resolve, reject) => {
|
||||
exec('git config --get remote.origin.url', (error, stdout) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve(stdout.toString().trim())
|
||||
}
|
||||
})
|
||||
})) as string
|
||||
|
||||
if (originUrl.startsWith('ssh://')) {
|
||||
const httpsUrl = originUrl.replace(/^ssh:\/\/git@/, 'https://').replace(/\.git$/, '')
|
||||
const urlWithoutPort = httpsUrl.replace(/:\d+/, '')
|
||||
return urlWithoutPort
|
||||
}
|
||||
|
||||
return originUrl
|
||||
} catch (error) {
|
||||
console.error('Error getting Git origin URL:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue