mirror of
https://github.com/10h30/blog-balodeplao.git
synced 2026-05-12 15:21:15 +09:00
feat: add R2 image handling and utility functions for image URLs
This commit is contained in:
@@ -4,6 +4,7 @@ import mdx from "@astrojs/mdx";
|
|||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import icon from "astro-icon";
|
import icon from "astro-icon";
|
||||||
import remarkReadingTime from "remark-reading-time";
|
import remarkReadingTime from "remark-reading-time";
|
||||||
|
import { remarkR2Images } from './src/plugins/remark-r2-images.mjs';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: "https://balodeplao.com/",
|
site: "https://balodeplao.com/",
|
||||||
@@ -17,6 +18,7 @@ export default defineConfig({
|
|||||||
file.data.readingTime.minutes;
|
file.data.readingTime.minutes;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
remarkR2Images
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
|
|||||||
Generated
+2
-49
@@ -21,7 +21,8 @@
|
|||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"remark-reading-time": "^2.0.2",
|
"remark-reading-time": "^2.0.2",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3",
|
||||||
|
"unist-util-visit": "^5.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
@@ -1249,9 +1250,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1268,9 +1266,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1287,9 +1282,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1306,9 +1298,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1325,9 +1314,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1344,9 +1330,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1363,9 +1346,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1382,9 +1362,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1401,9 +1378,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1426,9 +1400,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1451,9 +1422,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1476,9 +1444,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1501,9 +1466,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1526,9 +1488,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1551,9 +1510,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1576,9 +1532,6 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|||||||
+2
-1
@@ -31,7 +31,8 @@
|
|||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"remark-reading-time": "^2.0.2",
|
"remark-reading-time": "^2.0.2",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3",
|
||||||
|
"unist-util-visit": "^5.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import Tags from "@/components/ui/Tags.astro";
|
import Tags from "@/components/ui/Tags.astro";
|
||||||
import Categories from "@/components/ui/Categories.astro";
|
import Categories from "@/components/ui/Categories.astro";
|
||||||
import { type CollectionEntry, render } from "astro:content";
|
import { type CollectionEntry, render } from "astro:content";
|
||||||
|
import { toR2Url } from "@/utils/r2";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
post: CollectionEntry<"blog">;
|
post: CollectionEntry<"blog">;
|
||||||
@@ -9,6 +10,7 @@ export interface Props {
|
|||||||
|
|
||||||
const { post } = Astro.props;
|
const { post } = Astro.props;
|
||||||
const { remarkPluginFrontmatter } = await render(post);
|
const { remarkPluginFrontmatter } = await render(post);
|
||||||
|
const coverImage = toR2Url(post.data.image);
|
||||||
|
|
||||||
const readingTime = remarkPluginFrontmatter?.minutesRead
|
const readingTime = remarkPluginFrontmatter?.minutesRead
|
||||||
? `${Math.ceil(remarkPluginFrontmatter.minutesRead)} phút đọc`
|
? `${Math.ceil(remarkPluginFrontmatter.minutesRead)} phút đọc`
|
||||||
@@ -20,9 +22,9 @@ const readingTime = remarkPluginFrontmatter?.minutesRead
|
|||||||
>
|
>
|
||||||
<div class="relative h-48 w-full overflow-hidden">
|
<div class="relative h-48 w-full overflow-hidden">
|
||||||
{
|
{
|
||||||
post.data.image && (
|
coverImage && (
|
||||||
<img
|
<img
|
||||||
src={post.data.image}
|
src={coverImage}
|
||||||
alt={post.data.title}
|
alt={post.data.title}
|
||||||
class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const R2_BASE = 'https://media.balodeplao.com';
|
||||||
@@ -3,6 +3,7 @@ import BaseLayout from "@/layouts/BaseLayout.astro";
|
|||||||
import Schema from "@/components/seo/Schema.astro";
|
import Schema from "@/components/seo/Schema.astro";
|
||||||
import Tags from "@/components/ui/Tags.astro";
|
import Tags from "@/components/ui/Tags.astro";
|
||||||
import Categories from "@/components/ui/Categories.astro";
|
import Categories from "@/components/ui/Categories.astro";
|
||||||
|
import { toR2Url } from "@/utils/r2";
|
||||||
|
|
||||||
import { getCollection, render } from "astro:content";
|
import { getCollection, render } from "astro:content";
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ const { post } = Astro.props;
|
|||||||
const { Content, remarkPluginFrontmatter } = await render(post);
|
const { Content, remarkPluginFrontmatter } = await render(post);
|
||||||
const { title, description, pubDate, author, image, categories, tags } =
|
const { title, description, pubDate, author, image, categories, tags } =
|
||||||
post.data;
|
post.data;
|
||||||
|
const coverImage = toR2Url(image);
|
||||||
|
|
||||||
const formattedDate = pubDate.toLocaleDateString("vi-VN", {
|
const formattedDate = pubDate.toLocaleDateString("vi-VN", {
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
@@ -32,7 +34,7 @@ const readingTime = remarkPluginFrontmatter?.minutesRead
|
|||||||
const metadata = {
|
const metadata = {
|
||||||
title: title,
|
title: title,
|
||||||
description: description,
|
description: description,
|
||||||
ogImage: image,
|
ogImage: coverImage,
|
||||||
};
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -70,9 +72,13 @@ const metadata = {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
image && (
|
coverImage && (
|
||||||
<div class="reveal relative mb-8 h-96 w-full overflow-hidden rounded-xl shadow-lg">
|
<div class="reveal relative mb-8 h-96 w-full overflow-hidden rounded-xl shadow-lg">
|
||||||
<img src={image} alt={title} class="h-full w-full object-cover" />
|
<img
|
||||||
|
src={coverImage}
|
||||||
|
alt={title}
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { visit } from 'unist-util-visit';
|
||||||
|
import { R2_BASE } from '../config/r2.mjs';
|
||||||
|
|
||||||
|
export function remarkR2Images() {
|
||||||
|
return (tree) => {
|
||||||
|
visit(tree, 'image', (node) => {
|
||||||
|
if (node.url.startsWith('/images/')) {
|
||||||
|
node.url = `${R2_BASE}${node.url}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
visit(tree, 'html', (node) => {
|
||||||
|
node.value = node.value.replace(
|
||||||
|
/src="(\/images\/[^"]+)"/g,
|
||||||
|
`src="${R2_BASE}$1"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { R2_BASE } from "@/config/r2.mjs";
|
||||||
|
|
||||||
|
export function toR2Url(path?: string): string | undefined {
|
||||||
|
if (!path) return undefined;
|
||||||
|
if (path.startsWith("http")) return path;
|
||||||
|
return `${R2_BASE}${path}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user