-
Notifications
You must be signed in to change notification settings - Fork 0
v1.1.4 #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v1.1.4 #162
Changes from all commits
c7513a0
321f771
95353ca
e26b7ed
f80efad
4f4fe4a
dde0919
5137a89
9a1a5fe
2daa07b
3b03bb7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,115 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { type RSSOptions } from '@astrojs/rss'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getCollection } from 'astro:content'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import siteInfo from '@/data/siteInfo'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function extractImageUrl(body: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!body) return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const relativeImages = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const absoluteImages = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const imgTagRegex = /<img[^>]+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]), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+7
to
+16
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const relativeImages = []; | |
| const absoluteImages = []; | |
| const imgTagRegex = /<img[^>]+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]), | |
| const relativeImages: { url: string; type: string; length: number }[] = []; | |
| const absoluteImages: { url: string; type: string; length: number }[] = []; | |
| // Match <img> tags with a src attribute, allowing escaped quotes inside the URL and multiline attributes. | |
| const imgTagRegex = | |
| /<img\b[^>]*\bsrc=(["'])(?<url>(?:\\.|(?!\1).)*?)\1[^>]*>/gis; | |
| // Match Markdown images, allowing escaped characters in alt text and URL. | |
| const mdImageRegex = | |
| /!\[(?:[^\]\\]|\\.)*]\((?<url>(?:[^)\\]|\\.)+)\)/g; | |
| const imgMatches = [...body.matchAll(imgTagRegex)]; | |
| const mdMatches = [...body.matchAll(mdImageRegex)]; | |
| const allUrls = [ | |
| ...imgMatches.map(match => (match.groups?.url ?? match[1])), | |
| ...mdMatches.map(match => (match.groups?.url ?? match[1])), |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If an image URL has no file extension (e.g., https://example.com/image or dynamic image endpoints), the ext variable will be undefined or the last path segment, causing the image to be skipped via the continue statement. This could exclude valid images from CDNs or image services that don't use file extensions. Consider defaulting to image/jpeg or determining the type from response headers for such URLs.
| 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 pathWithoutQuery = fullUrl.split('?')[0]; | |
| const lastSegment = pathWithoutQuery.split('/').pop() || ''; | |
| const hasExtension = lastSegment.includes('.'); | |
| const ext = hasExtension ? lastSegment.split('.').pop()!.toLowerCase() : undefined; | |
| let type: string | undefined; | |
| if (!hasExtension) { | |
| // No file extension in URL; default to a generic image type. | |
| type = 'image/jpeg'; | |
| } else { | |
| 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; | |
| } |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The extractImageUrl function extracts relative URLs (starting with /) but doesn't convert them to absolute URLs for RSS enclosures. While @astrojs/rss may auto-resolve relative link URLs using the site parameter, RSS enclosures traditionally require absolute URLs and it's unclear if the library handles this for enclosures. Consider explicitly converting relative URLs to absolute by prepending the site URL: url: isRelative ? new URL(fullUrl, siteUrl).href : fullUrl.
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The image extraction logic prioritizes relative URLs over absolute URLs without considering which image is actually first in the content. This means if a blog post has an external image first (like a header image from GitHub), it will be deprioritized in favor of a local image that appears later. Consider preserving the original order or explicitly documenting this prioritization behavior, as RSS readers typically display the first enclosure as the featured image.
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The siteUrl parameter is typed as string, but context.site is a URL object (or potentially undefined). This type mismatch could cause issues. Consider changing the parameter type to string | URL or URL | undefined and handling the conversion within the function, or explicitly convert at the call site using context.site?.toString() or context.site.href.
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The non-null assertion operator ! is used on filter.tag even though you've already checked if (filter.tag) on line 75. While this works, it's redundant. TypeScript's control flow analysis should already narrow the type after the if check, making the ! unnecessary. Consider removing it for cleaner code.
| blogs = blogs.filter(blog => blog.data.tags?.includes(filter.tag!)); | |
| blogs = blogs.filter(blog => blog.data.tags?.includes(filter.tag)); |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The title for author-specific feeds changed from ${siteInfo.appName} - ${author} to ${siteInfo.appName} - Author: ${filter.author}. While adding "Author:" is more descriptive, this represents a breaking change for users who have subscribed to the feed under the old title. Feed readers may treat this as a different feed. If maintaining compatibility is important, consider keeping the original format.
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Building the title through string concatenation with += makes it harder to read and maintain. Consider using a template literal or array join for clarity, e.g., [siteInfo.appName, filter?.author && 'Author: ' + filter.author, filter?.tag && 'Tag: ' + filter.tag].filter(Boolean).join(' - ').
| let title = siteInfo.appName; | |
| if (filter?.author) { | |
| title += ` - Author: ${filter.author}`; | |
| } | |
| if (filter?.tag) { | |
| title += ` - Tag: ${filter.tag}`; | |
| } | |
| const titleSegments = [ | |
| siteInfo.appName, | |
| filter?.author && `Author: ${filter.author}`, | |
| filter?.tag && `Tag: ${filter.tag}`, | |
| ]; | |
| const title = titleSegments.filter(Boolean).join(' - '); |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pubDate is converted to a Date object with new Date(blog.data.pubDate), but according to the content schema, pubDate is defined as a string. If this string is not in a valid date format, this could produce an "Invalid Date". Consider validating the date format in the schema using z.string().datetime() or z.coerce.date() for more robust handling, or add error handling here to gracefully handle invalid dates.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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: `<language>ja-jp</language>`, | ||
| }); | ||
| const feedOption = await getFeed(context.site); | ||
| return rss(feedOption); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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; | ||||||||||||||||
| } | ||||||||||||||||
| }); | ||||||||||||||||
|
Comment on lines
+14
to
+19
|
||||||||||||||||
|
|
||||||||||||||||
| // mermaidブロックがある場合、末尾にスクリプトを追加 | ||||||||||||||||
| if (mermaidFound) { | ||||||||||||||||
| const scriptNode = { | ||||||||||||||||
| type: 'html', | ||||||||||||||||
| value: `<script> | ||||||||||||||||
| async function initMermaidDiagrams() { | ||||||||||||||||
| const blocks = document.querySelectorAll( | ||||||||||||||||
| 'pre[data-language="mermaid"] code' | ||||||||||||||||
|
||||||||||||||||
| 'pre[data-language="mermaid"] code' | |
| 'pre[data-language="mermaid"] code, pre code.language-mermaid' |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using @latest for the Mermaid CDN URL means users will automatically receive potentially breaking changes without warning. This could cause diagrams to break unexpectedly in production. Consider pinning to a specific major or minor version (e.g., mermaid@11 or mermaid@11.5.0) to ensure stability while still allowing compatible updates.
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The script dynamically loads from an external CDN without Subresource Integrity (SRI) verification. This creates a security risk: if the CDN is compromised or serves malicious code, it could execute arbitrary JavaScript on your site. Consider either bundling Mermaid as a dependency or adding SRI hashes to verify the integrity of the loaded script.
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The script loads and initializes Mermaid on every page with mermaid blocks, but if multiple markdown files with mermaid diagrams are rendered on the same page (e.g., a blog listing), this could result in multiple identical scripts being added. While the window.mermaid check prevents duplicate library loads, having multiple event listeners on DOMContentLoaded is redundant. Consider moving this to a global script in the layout or using a module-based approach to ensure single initialization.
| document.addEventListener("DOMContentLoaded", initMermaidDiagrams); | |
| if (!window.__mermaidDomContentLoadedListenerAdded) { | |
| window.__mermaidDomContentLoadedListenerAdded = true; | |
| document.addEventListener("DOMContentLoaded", initMermaidDiagrams); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function
extractImageUrl(singular) returns an array or null, but the name suggests it returns a single URL. Consider renaming toextractImageUrls(plural) orextractImagesto better reflect that it returns multiple images.