🖼️ added support for links to blogposts and support image sizing from .md files

This commit is contained in:
RJ
2025-11-24 14:31:57 +02:00
committed by Rares J
parent a039528fb3
commit 1baef49f73
6 changed files with 85 additions and 42 deletions

View File

@@ -43,9 +43,8 @@ export function OptimizedImage({
width={width} width={width}
height={height} height={height}
priority={priority} priority={priority}
className={`w-full h-auto transition-opacity duration-300 ${ style={{ maxWidth: '100%', height: 'auto' }}
isLoading ? 'opacity-0' : 'opacity-100' className={`transition-opacity duration-300 ${isLoading ? 'opacity-0' : 'opacity-100'}`}
}`}
onLoad={() => setIsLoading(false)} onLoad={() => setIsLoading(false)}
onError={() => setHasError(true)} onError={() => setHasError(true)}
placeholder="blur" placeholder="blur"

View File

@@ -25,6 +25,10 @@ export default function Page() {
} }
``` ```
### Check out this article:
[Check this out](tech/articol-tehnic.md)
## Conclusion ## Conclusion
Next.js 15 brings many improvements for building modern web applications. Next.js 15 brings many improvements for building modern web applications.

View File

@@ -1,45 +1,16 @@
--- ---
title: 'Articol Tehnic din Subdirector' title: 'Technical Article'
description: 'Test pentru subdirectoare și organizare ierarhică' description: 'A technical article to test internal links'
date: '2025-01-10' date: '2025-01-10'
author: 'Tech Writer' author: 'John Doe'
category: 'Tehnologie' category: 'Tech'
tags: ['nextjs', 'react', 'typescript'] tags: ['tech', 'test']
draft: false
--- ---
# Articol Tehnic # Technical Article
Acesta este un articol stocat într-un subdirector pentru a testa funcționalitatea de organizare ierarhică. This is a test article for internal blog post linking.
## Next.js și React ## Content
Next.js este un framework React puternic care oferă: You are reading the technical article that was linked from the example post.
- Server-side rendering (SSR)
- Static site generation (SSG)
- API routes
- File-based routing
## Exemplu de cod TypeScript
```typescript
interface User {
id: number
name: string
email: string
}
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
```
### Use of coolers
- ![Use of coolers](./cooler.jpg?w=400&h=300)
## Concluzie
Subdirectoarele funcționează perfect pentru organizarea conținutului!

View File

@@ -6,6 +6,7 @@ import remarkGfm from 'remark-gfm'
import { FrontMatter, Post } from './types/frontmatter' import { FrontMatter, Post } from './types/frontmatter'
import { generateExcerpt } from './utils' import { generateExcerpt } from './utils'
import { remarkCopyImages } from './remark-copy-images' import { remarkCopyImages } from './remark-copy-images'
import { remarkInternalLinks } from './remark-internal-links'
const POSTS_PATH = path.join(process.cwd(), 'content', 'blog') const POSTS_PATH = path.join(process.cwd(), 'content', 'blog')
@@ -75,6 +76,7 @@ export async function getPostBySlug(slug: string | string[]): Promise<Post | nul
publicDir: 'public/blog', publicDir: 'public/blog',
currentSlug: sanitized.join('/'), currentSlug: sanitized.join('/'),
}) })
.use(remarkInternalLinks)
.process(content) .process(content)
const processedContent = processed.toString() const processedContent = processed.toString()

View File

@@ -0,0 +1,67 @@
import { visit } from 'unist-util-visit'
import { Node } from 'unist'
interface LinkNode extends Node {
type: 'link'
url: string
children: Node[]
}
/**
* Detects internal blog post links:
* - Relative paths (no http/https)
* - Not absolute paths (doesn't start with /)
* - Ends with .md
*/
function isInternalBlogLink(url: string): boolean {
return (
!url.startsWith('http://') &&
!url.startsWith('https://') &&
!url.startsWith('/') &&
url.includes('.md')
)
}
/**
* Transforms internal .md links to blog routes:
* - tech/article.md → /blog/tech/article
* - article.md#section → /blog/article#section
* - nested/path/post.md?ref=foo → /blog/nested/path/post?ref=foo
*/
function transformToBlogPath(url: string): string {
// Split into path, hash, and query
const hashIndex = url.indexOf('#')
const queryIndex = url.indexOf('?')
let path = url
let hash = ''
let query = ''
if (hashIndex !== -1) {
path = url.substring(0, hashIndex)
hash = url.substring(hashIndex)
}
if (queryIndex !== -1 && queryIndex < (hashIndex === -1 ? url.length : hashIndex)) {
path = url.substring(0, queryIndex)
query = url.substring(queryIndex, hashIndex === -1 ? url.length : hashIndex)
}
// Remove .md extension
const cleanPath = path.replace(/\.md$/, '')
// Build final URL
return `/blog/${cleanPath}${query}${hash}`
}
export function remarkInternalLinks() {
return (tree: Node) => {
visit(tree, 'link', (node: Node) => {
const linkNode = node as LinkNode
if (isInternalBlogLink(linkNode.url)) {
linkNode.url = transformToBlogPath(linkNode.url)
}
})
}
}

2
next-env.d.ts vendored
View File

@@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts"; import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.