Files
wehub-resource-sync 6ede33ccdb
Build and Push Docker Images / create_manifest (web, surfsense-web, , cpu) (push) Has been cancelled
Build and Push Docker Images / finalize_release (push) Has been cancelled
Obsidian Plugin Lint / lint (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / compute_version (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / verify_digests (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, , cpu) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda, cuda) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda126, cuda126) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:33:44 +08:00

55 lines
1.9 KiB
TypeScript

import { promises as fs } from "node:fs";
import path from "node:path";
export interface FaqEntry {
question: string;
answer: string;
}
/**
* Extracts FAQ items from a blog post MDX file by parsing the `## FAQ` section.
*
* The FAQ section is bounded by `## FAQ` and the next H2 heading. Each H3 inside
* is treated as a question, with the body until the next H3 (or the end of the
* FAQ section) as its answer. Common Markdown decorations (links, bold, inline
* code) are stripped so the JSON-LD output contains plain text suitable for
* Google's FAQ rich-result eligibility checks.
*
* Returns an empty array when the post has no FAQ section, or when the file
* cannot be read (e.g. for posts that do not yet exist on disk).
*/
export async function extractFaqFromBlogPost(slug: string): Promise<FaqEntry[]> {
try {
const filepath = path.join(process.cwd(), "blog", "content", `${slug}.mdx`);
const content = await fs.readFile(filepath, "utf-8");
return extractFaqFromContent(content);
} catch {
return [];
}
}
export function extractFaqFromContent(content: string): FaqEntry[] {
const faqHeading = content.match(/^##\s+FAQ\s*$/m);
if (!faqHeading || faqHeading.index === undefined) return [];
const afterFaq = content.slice(faqHeading.index + faqHeading[0].length);
const nextH2 = afterFaq.match(/^##\s+/m);
const faqBody = nextH2 ? afterFaq.slice(0, nextH2.index) : afterFaq;
const blocks = faqBody.split(/^###\s+/m).slice(1);
return blocks
.map((block) => {
const newlineIdx = block.indexOf("\n");
const question = (newlineIdx === -1 ? block : block.slice(0, newlineIdx)).trim();
const answer = (newlineIdx === -1 ? "" : block.slice(newlineIdx + 1))
.trim()
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.replace(/\*\*([^*]+)\*\*/g, "$1")
.replace(/`([^`]+)`/g, "$1")
.replace(/\s+/g, " ");
return { question, answer };
})
.filter((item) => item.question.length > 0 && item.answer.length > 0);
}