diff --git a/astro.config.mjs b/astro.config.mjs
index 74e6638..93460fe 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -6,6 +6,7 @@ import partytown from '@astrojs/partytown';
import rehypeToc from 'rehype-toc';
import rehypeSlug from 'rehype-slug';
import remarkLinkCard from 'remark-link-card-plus';
+import { remarkMermaidInjector } from './src/plugins/remark/remark-mermaid-injector.mjs';
// https://astro.build/config
export default defineConfig({
@@ -26,8 +27,10 @@ export default defineConfig({
}),
],
markdown: {
+ excludeLangs: ['mermaid'],
rehypePlugins: [rehypeSlug, [rehypeToc, { headings: ['h2', 'h3', 'h4'] }]],
remarkPlugins: [
+ remarkMermaidInjector,
[
remarkLinkCard,
{ cache: false, shortenUrl: true, thumbnailPosition: 'left' },
diff --git a/package.json b/package.json
index 9b320c2..da2bb6d 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,8 @@
"prettier": "^3.2.5",
"prettier-plugin-astro": "^0.13.0",
"prettier-plugin-tailwindcss": "^0.6.11",
- "sass": "^1.77.4"
+ "sass": "^1.77.4",
+ "unist-util-visit": "^5.0.0"
},
"pnpm": {
"overrides": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b329f86..00200ef 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -129,6 +129,9 @@ importers:
sass:
specifier: ^1.77.4
version: 1.89.0
+ unist-util-visit:
+ specifier: ^5.0.0
+ version: 5.0.0
packages:
@@ -3082,9 +3085,6 @@ packages:
unist-util-visit-children@3.0.0:
resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==}
- unist-util-visit-parents@6.0.1:
- resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
-
unist-util-visit-parents@6.0.2:
resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}
@@ -6844,11 +6844,6 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
- unist-util-visit-parents@6.0.1:
- dependencies:
- '@types/unist': 3.0.3
- unist-util-is: 6.0.0
-
unist-util-visit-parents@6.0.2:
dependencies:
'@types/unist': 3.0.3
@@ -6858,7 +6853,7 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
unist-util-is: 6.0.0
- unist-util-visit-parents: 6.0.1
+ unist-util-visit-parents: 6.0.2
unstorage@1.17.3:
dependencies:
diff --git a/src/layouts/BlogLayout.astro b/src/layouts/BlogLayout.astro
index 504c5db..ed7baab 100644
--- a/src/layouts/BlogLayout.astro
+++ b/src/layouts/BlogLayout.astro
@@ -37,6 +37,18 @@ const authorX = author?.links?.find(l => l.name === 'X')?.id;
+
+
+
]+src=["']([^"']+)["'][^>]*>/g;
+ const mdImageRegex = /!\[[^\]]*\]\(([^)]+)\)/g;
+
+ const imgMatches = [...body.matchAll(imgTagRegex)];
+ const mdMatches = [...body.matchAll(mdImageRegex)];
+ const allUrls = [
+ ...imgMatches.map(match => match[1]),
+ ...mdMatches.map(match => match[1]),
+ ];
+
+ if (allUrls.length === 0) return null;
+
+ for (const url of allUrls) {
+ const isRelative = url.startsWith('/');
+ let fullUrl = url;
+ const ext = fullUrl.split('?')[0].split('.').pop()?.toLowerCase();
+ let type;
+
+ switch (ext) {
+ case 'jpg':
+ case 'jpeg':
+ type = 'image/jpeg';
+ break;
+ case 'png':
+ type = 'image/png';
+ break;
+ case 'gif':
+ type = 'image/gif';
+ break;
+ case 'webp':
+ type = 'image/webp';
+ break;
+ case 'svg':
+ type = 'image/svg+xml';
+ break;
+ default:
+ continue;
+ }
+
+ const imageObj = {
+ url: fullUrl,
+ type: type,
+ length: 0,
+ };
+
+ if (isRelative) {
+ relativeImages.push(imageObj);
+ } else {
+ absoluteImages.push(imageObj);
+ }
+ }
+
+ const images = [...relativeImages, ...absoluteImages];
+ return images.length > 0 ? images : null;
+}
+
+async function getFeed(
+ siteUrl: string,
+ maxItems?: number,
+ filter?: { tag?: string; author?: string }
+) {
+ let blogs = await getCollection('blog');
+ if (filter) {
+ if (filter.author) {
+ blogs = blogs.filter(blog => blog.data.author === filter.author);
+ }
+ if (filter.tag) {
+ blogs = blogs.filter(blog => blog.data.tags?.includes(filter.tag!));
+ }
+ }
+ blogs = blogs
+ .sort(
+ (a, b) =>
+ new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime()
+ )
+ .slice(0, maxItems || 50);
+ let title = siteInfo.appName;
+ if (filter?.author) {
+ title += ` - Author: ${filter.author}`;
+ }
+ if (filter?.tag) {
+ title += ` - Tag: ${filter.tag}`;
+ }
+ const rssOptions: RSSOptions = {
+ title: title,
+ description: siteInfo.description,
+ site: siteUrl,
+ items: blogs.map(blog => ({
+ title: blog.data.title,
+ pubDate: new Date(blog.data.pubDate),
+ description: blog.data.description,
+ link: blog.data.externalUrl
+ ? blog.data.externalUrl
+ : `/blog/${blog.slug}/`,
+ categories: blog.data.tags,
+ enclosure: extractImageUrl(blog.body)?.[0] || {
+ url: `/og/${blog.slug}.png`,
+ type: 'image/png',
+ length: 0,
+ },
+ })),
+ customData: `ja-jp`,
+ };
+ return rssOptions;
+}
+
+export { getFeed };
diff --git a/src/pages/[author]/rss.xml.js b/src/pages/[author]/rss.xml.js
index 8300acf..f16dc3a 100644
--- a/src/pages/[author]/rss.xml.js
+++ b/src/pages/[author]/rss.xml.js
@@ -1,6 +1,5 @@
import rss from '@astrojs/rss';
-import { getCollection } from 'astro:content';
-import siteInfo from '@/data/siteInfo';
+import { getFeed } from '@/lib/getFeed';
import member from '@/data/member';
export async function getStaticPaths() {
@@ -13,21 +12,7 @@ export async function getStaticPaths() {
export async function GET(context) {
const { author } = context.params;
- const blogs = await getCollection('blog');
+ const feedOption = await getFeed(context.site, 50, { author });
- const filteredBlogs = blogs.filter(blog => blog.data.author === author);
-
- return rss({
- title: `${siteInfo.appName} - ${author}`,
- description: `${author}の記事一覧`,
- site: context.site,
- items: filteredBlogs.map(blog => ({
- title: blog.data.title,
- pubDate: blog.data.pubDate,
- description: blog.data.description,
- customData: blog.data.customData,
- link: `/blog/${blog.slug}/`,
- })),
- customData: 'ja-jp',
- });
+ return rss(feedOption);
}
diff --git a/src/pages/rss.xml.js b/src/pages/rss.xml.js
index d691c62..018bc0f 100644
--- a/src/pages/rss.xml.js
+++ b/src/pages/rss.xml.js
@@ -1,20 +1,7 @@
import rss from '@astrojs/rss';
-import { getCollection } from 'astro:content';
-import siteInfo from '@/data/siteInfo';
+import { getFeed } from '@/lib/getFeed';
export async function GET(context) {
- const blogs = await getCollection('blog');
- return rss({
- title: siteInfo.appName,
- description: siteInfo.description,
- site: context.site,
- items: blogs.map(blog => ({
- title: blog.data.title,
- pubDate: blog.data.pubDate,
- description: blog.data.description,
- customData: blog.data.customData,
- link: `/blog/${blog.slug}/`,
- })),
- customData: `ja-jp`,
- });
+ const feedOption = await getFeed(context.site);
+ return rss(feedOption);
}
diff --git a/src/plugins/remark/remark-mermaid-injector.mjs b/src/plugins/remark/remark-mermaid-injector.mjs
new file mode 100644
index 0000000..fe15b99
--- /dev/null
+++ b/src/plugins/remark/remark-mermaid-injector.mjs
@@ -0,0 +1,88 @@
+import { visit } from 'unist-util-visit';
+
+/**
+ * remark プラグイン
+ *
+ * markdown ASTにてmermaidコードブロックがある場合、クライアントでの描画スクリプト(mermaid.js処理)を末尾に挿入
+ *
+ */
+export function remarkMermaidInjector() {
+ return function (tree) {
+ let mermaidFound = false;
+
+ // mermaidコードブロックの存在を確認
+ visit(tree, 'code', node => {
+ if (node.lang === 'mermaid') {
+ mermaidFound = true;
+ return false;
+ }
+ });
+
+ // mermaidブロックがある場合、末尾にスクリプトを追加
+ if (mermaidFound) {
+ const scriptNode = {
+ type: 'html',
+ value: ``,
+ };
+
+ tree.children.push(scriptNode);
+ }
+ };
+}