Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions scripts/shared-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import fs from "node:fs";
import path from "node:path";
import https from "node:https";
import sharp from "sharp";

// Re-export all parsing functions from the shared module
export {
Expand Down Expand Up @@ -44,6 +45,14 @@ function contentTypeToExt(contentType: string): string {
return ".png";
}

/** Convert an SVG file to PNG in-place, returns the new filepath */
async function convertSvgToPng(svgPath: string): Promise<string> {
const pngPath = svgPath.replace(/\.svg$/i, ".png");
await sharp(svgPath, { density: 144 }).png().toFile(pngPath);
fs.unlinkSync(svgPath);
return pngPath;
}

/** Download an image from a URL to a local file. Returns the detected content-type. */
export async function downloadImage(
url: string,
Expand Down Expand Up @@ -80,7 +89,7 @@ export async function downloadImage(
filepath,
redirectCount + 1,
)
.then(resolve)
.then((ct) => resolve(ct))
.catch(reject);
}

Expand Down Expand Up @@ -198,30 +207,38 @@ export async function processImages(
return { filename, publicPath };
}

// Process banner image — saved as {slug}/banner.ext
// Process banner image — saved as {slug}/banner.png (converted from SVG if needed)
if (bannerImages.length > 0) {
const img = bannerImages[0];
const urlExt = img.url.match(/\.(png|jpg|jpeg|gif|svg|webp)/i)?.[0];
const tempPath = path.join(imagesDir, `banner_tmp`);
const detectedType = await downloadImage(img.url, tempPath);
const ext = urlExt || contentTypeToExt(detectedType);
const filename = `banner${ext}`;
fs.renameSync(tempPath, path.join(imagesDir, filename));
banner = `/content-images/${contentType}/${slug}/${filename}`;
console.log(` ✓ ${filename} (banner)`);
let ext = urlExt || contentTypeToExt(detectedType);
let finalPath = path.join(imagesDir, `banner${ext}`);
fs.renameSync(tempPath, finalPath);
if (ext === ".svg") {
finalPath = await convertSvgToPng(finalPath);
ext = ".png";
}
banner = `/content-images/${contentType}/${slug}/banner${ext}`;
console.log(` ✓ banner${ext} (banner)`);
}

// Process logo image — saved as {slug}/logo.ext
// Process logo image — saved as {slug}/logo.png (converted from SVG if needed)
if (logoImages.length > 0) {
const img = logoImages[0];
const urlExt = img.url.match(/\.(png|jpg|jpeg|gif|svg|webp)/i)?.[0];
const tempPath = path.join(imagesDir, `logo_tmp`);
const detectedType = await downloadImage(img.url, tempPath);
const ext = urlExt || contentTypeToExt(detectedType);
const filename = `logo${ext}`;
fs.renameSync(tempPath, path.join(imagesDir, filename));
logo = `/content-images/${contentType}/${slug}/${filename}`;
console.log(` ✓ ${filename} (logo)`);
let ext = urlExt || contentTypeToExt(detectedType);
let finalPath = path.join(imagesDir, `logo${ext}`);
fs.renameSync(tempPath, finalPath);
if (ext === ".svg") {
finalPath = await convertSvgToPng(finalPath);
ext = ".png";
}
logo = `/content-images/${contentType}/${slug}/logo${ext}`;
console.log(` ✓ logo${ext} (logo)`);
}

// Process description images
Expand Down
5 changes: 5 additions & 0 deletions src/app/preview/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { Metadata } from "next";
import { Suspense } from "react";
import PreviewForm from "./PreviewForm";

export const metadata: Metadata = {
robots: { index: false, follow: false },
};

export default function PreviewPage() {
return (
<div className="min-h-screen bg-gray-900">
Expand Down
12 changes: 6 additions & 6 deletions src/lib/parse-issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export function parseSection(markdown: string, sectionName: string): string {
sectionName === "Description" ? DESC_STOP : "\\n## [^#]|---|$";
const section = markdown.match(
new RegExp(
`## ${sectionName}\\s+(?:<!--[\\s\\S]*?-->\\s*)?([\\s\\S]*?)(?=${stop})`,
`## ${sectionName}\\s+(?:<!--[\\s\\S]*?-->[ \\t]*)?([\\s\\S]*?)(?=${stop})`,
),
);
return section ? section[1].trim() : "";
Expand All @@ -105,7 +105,7 @@ export function parseSection(markdown: string, sectionName: string): string {
export function parseList(markdown: string, sectionName: string): string[] {
const section = markdown.match(
new RegExp(
`## ${sectionName}\\s+(?:<!--[\\s\\S]*?-->\\s*)?([\\s\\S]*?)(?=\\n## [^#]|---|$)`,
`## ${sectionName}\\s+(?:<!--[\\s\\S]*?-->[ \\t]*)?([\\s\\S]*?)(?=\\n## [^#]|---|$)`,
),
);
if (!section) return [];
Expand Down Expand Up @@ -150,7 +150,7 @@ export function extractFirstImage(
): string {
const section = markdown.match(
new RegExp(
`## ${sectionName}\\s+(?:<!--[\\s\\S]*?-->\\s*)?([\\s\\S]*?)(?=\\n## [^#]|$)`,
`## ${sectionName}\\s+(?:<!--[\\s\\S]*?-->[ \\t]*)?([\\s\\S]*?)(?=\\n## [^#]|$)`,
),
);
if (!section) return "";
Expand All @@ -169,18 +169,18 @@ export function extractImagesBySections(issueBody: string) {
const descriptionImages: ParsedImage[] = [];

const bannerSection = issueBody.match(
/## Banner Image\s+(?:<!--.*?-->\s*)?([\s\S]*?)(?=\n## [^#]|$)/,
/## Banner Image\s+(?:<!--.*?-->[ \t]*)?([\s\S]*?)(?=\n## [^#]|$)/,
);
if (bannerSection) bannerImages.push(...extractImages(bannerSection[1]));

const logoSection = issueBody.match(
/## Logo\s+(?:<!--.*?-->\s*)?([\s\S]*?)(?=\n## [^#]|$)/,
/## Logo\s+(?:<!--.*?-->[ \t]*)?([\s\S]*?)(?=\n## [^#]|$)/,
);
if (logoSection) logoImages.push(...extractImages(logoSection[1]));

const descSection = issueBody.match(
new RegExp(
`## Description\\s+(?:<!--[\\s\\S]*?-->\\s*)?([\\s\\S]*?)(?=${DESC_STOP})`,
`## Description\\s+(?:<!--[\\s\\S]*?-->[ \\t]*)?([\\s\\S]*?)(?=${DESC_STOP})`,
),
);
if (descSection) descriptionImages.push(...extractImages(descSection[1]));
Expand Down