feat: add YouTube embedding support with lite-youtube-embed and create remarkYouTube plugin

This commit is contained in:
2026-03-25 09:15:12 +09:00
parent 9421ecd257
commit 9e42996677
5 changed files with 62 additions and 0 deletions
+2
View File
@@ -7,6 +7,7 @@ import remarkReadingTime from "remark-reading-time";
import remarkUnwrapImages from "remark-unwrap-images"; import remarkUnwrapImages from "remark-unwrap-images";
import { remarkR2Images } from "./src/plugins/remark-r2-images.mjs"; import { remarkR2Images } from "./src/plugins/remark-r2-images.mjs";
import { rehypePictureWebp } from "./src/plugins/rehype-picture-webp.mjs"; import { rehypePictureWebp } from "./src/plugins/rehype-picture-webp.mjs";
import { remarkYouTube } from "./src/plugins/remark-youtube.mjs";
export default defineConfig({ export default defineConfig({
site: "https://balodeplao.com/", site: "https://balodeplao.com/",
@@ -22,6 +23,7 @@ export default defineConfig({
}, },
remarkR2Images, remarkR2Images,
remarkUnwrapImages, remarkUnwrapImages,
remarkYouTube,
], ],
rehypePlugins: [rehypePictureWebp], rehypePlugins: [rehypePictureWebp],
}, },
+31
View File
@@ -17,6 +17,7 @@
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.1.18",
"astro": "^6.0.4", "astro": "^6.0.4",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"lite-youtube-embed": "^0.3.4",
"remark-reading-time": "^2.0.2", "remark-reading-time": "^2.0.2",
"remark-unwrap-images": "^4.0.1", "remark-unwrap-images": "^4.0.1",
"tailwindcss": "^4.1.18", "tailwindcss": "^4.1.18",
@@ -1300,6 +1301,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1322,6 +1324,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1344,6 +1347,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1360,6 +1364,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1376,6 +1381,7 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1392,6 +1398,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1408,6 +1415,7 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1424,6 +1432,7 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1440,6 +1449,7 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1456,6 +1466,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1472,6 +1483,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1488,6 +1500,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1504,6 +1517,7 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1526,6 +1540,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1548,6 +1563,7 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1570,6 +1586,7 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1592,6 +1609,7 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1614,6 +1632,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1636,6 +1655,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1658,6 +1678,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1680,6 +1701,7 @@
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -1699,6 +1721,7 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later", "license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1718,6 +1741,7 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later", "license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1737,6 +1761,7 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later", "license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true, "optional": true,
"os": [ "os": [
@@ -7389,6 +7414,12 @@
"node": ">=20.0.0" "node": ">=20.0.0"
} }
}, },
"node_modules/lite-youtube-embed": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/lite-youtube-embed/-/lite-youtube-embed-0.3.4.tgz",
"integrity": "sha512-aXgxpwK7AIW58GEbRzA8EYaY4LWvF3FKak6B9OtSJmuNyLhX2ouD4cMTxz/yR5HFInhknaYd2jLWOTRTvT8oAw==",
"license": "Apache-2.0"
},
"node_modules/local-pkg": { "node_modules/local-pkg": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
+1
View File
@@ -27,6 +27,7 @@
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.1.18",
"astro": "^6.0.4", "astro": "^6.0.4",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"lite-youtube-embed": "^0.3.4",
"remark-reading-time": "^2.0.2", "remark-reading-time": "^2.0.2",
"remark-unwrap-images": "^4.0.1", "remark-unwrap-images": "^4.0.1",
"tailwindcss": "^4.1.18", "tailwindcss": "^4.1.18",
+4
View File
@@ -6,6 +6,7 @@ import Categories from "@/components/ui/Categories.astro";
import Destinations from "@/components/ui/Destinations.astro"; import Destinations from "@/components/ui/Destinations.astro";
import { toR2Url } from "@/utils/r2"; import { toR2Url } from "@/utils/r2";
import Picture from "@/components/ui/Picture.astro"; import Picture from "@/components/ui/Picture.astro";
import "lite-youtube-embed/src/lite-yt-embed.css";
import { getCollection, render, type CollectionEntry } from "astro:content"; import { getCollection, render, type CollectionEntry } from "astro:content";
@@ -119,4 +120,7 @@ const metadata = {
} }
</div> </div>
</section> </section>
<script>
import "lite-youtube-embed/src/lite-yt-embed.js";
</script>
</BaseLayout> </BaseLayout>
+24
View File
@@ -0,0 +1,24 @@
import { visit } from "unist-util-visit";
const YT_REGEX =
/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
export function remarkYouTube() {
return (tree) => {
visit(tree, "paragraph", (node, index, parent) => {
if (node.children.length !== 1) return;
const child = node.children[0];
if (child.type !== "link" && child.type !== "text") return;
const url = child.type === "link" ? child.url : child.value;
const match = url?.match(YT_REGEX);
if (!match) return;
const videoId = match[1];
parent.children.splice(index, 1, {
type: "html",
value: `<lite-youtube videoid="${videoId}" style="background-image:url('https://i.ytimg.com/vi/${videoId}/hqdefault.jpg')"><a href="https://youtube.com/watch?v=${videoId}" class="lyt-playbtn" title="Play Video"><span class="lyt-visually-hidden">Play Video</span></a></lite-youtube>`,
});
});
};
}