acca846856
Memind CI / Test, build, and release guards (pull_request) Failing after 4m17s
引入批量回填与审计工具链,强制 mindspace-geo 元数据,扩展 sitemap/llms 收录 /learn,并为 Plaza 壳页注入 canonical/OG/JSON-LD;本地 593 页已通过 check:mindspace-cover。 Co-authored-by: Cursor <cursoragent@cursor.com>
158 lines
4.1 KiB
JavaScript
158 lines
4.1 KiB
JavaScript
import { extractSharePreviewMeta, PLATFORM_SITE_NAME } from './mindspace-og-tags.mjs';
|
|
import {
|
|
parseMindspaceGeoMeta,
|
|
resolveGeoFaq,
|
|
} from './mindspace-geo-meta.mjs';
|
|
import { extractSeoDescription } from './mindspace-seo-tags.mjs';
|
|
|
|
function escapeAttr(value) {
|
|
return String(value ?? '')
|
|
.replaceAll('&', '&')
|
|
.replaceAll('"', '"')
|
|
.replaceAll('<', '<')
|
|
.replaceAll('>', '>');
|
|
}
|
|
|
|
function rawTitleFromHtml(html) {
|
|
return String(html ?? '').match(/<title[^>]*>([^<]*)<\/title>/i)?.[1]?.trim() ?? '';
|
|
}
|
|
|
|
function rawH1FromHtml(html) {
|
|
const match = String(html ?? '').match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
|
|
if (!match) return '';
|
|
return match[1].replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
|
|
}
|
|
|
|
function hasJsonLd(html) {
|
|
return /<script[^>]+type=["']application\/ld\+json["']/i.test(String(html ?? ''));
|
|
}
|
|
|
|
function appendBeforeHeadClose(html, block) {
|
|
const source = String(html ?? '');
|
|
if (!block) return source;
|
|
if (/<\/head>/i.test(source)) {
|
|
return source.replace(/<\/head>/i, `${block}\n</head>`);
|
|
}
|
|
if (/<head[^>]*>/i.test(source)) {
|
|
return source.replace(/(<head[^>]*>)/i, `$1${block}`);
|
|
}
|
|
return `${block}\n${source}`;
|
|
}
|
|
|
|
function buildArticleJsonLd({
|
|
title,
|
|
description,
|
|
imageUrl,
|
|
pageUrl,
|
|
siteName = PLATFORM_SITE_NAME,
|
|
faq = [],
|
|
}) {
|
|
const graph = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'Article',
|
|
headline: title,
|
|
description,
|
|
inLanguage: 'zh-CN',
|
|
isAccessibleForFree: true,
|
|
publisher: {
|
|
'@type': 'Organization',
|
|
name: siteName,
|
|
},
|
|
};
|
|
if (pageUrl) graph.mainEntityOfPage = pageUrl;
|
|
if (imageUrl) graph.image = [imageUrl];
|
|
if (Array.isArray(faq) && faq.length > 0) {
|
|
graph.hasPart = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'FAQPage',
|
|
mainEntity: faq
|
|
.map((item) => ({
|
|
'@type': 'Question',
|
|
name: String(item?.q ?? item?.question ?? '').trim(),
|
|
acceptedAnswer: {
|
|
'@type': 'Answer',
|
|
text: String(item?.a ?? item?.answer ?? '').trim(),
|
|
},
|
|
}))
|
|
.filter((item) => item.name && item.acceptedAnswer.text),
|
|
};
|
|
}
|
|
return graph;
|
|
}
|
|
|
|
export function buildGeoStructuredData(
|
|
html,
|
|
{
|
|
origin = '',
|
|
pageUrl = '',
|
|
pageDirUrl = '',
|
|
publication = null,
|
|
siteName = PLATFORM_SITE_NAME,
|
|
htmlFilePath = '',
|
|
fileExists = null,
|
|
meta = {},
|
|
} = {},
|
|
) {
|
|
const source = String(html ?? '');
|
|
const authorMeta = parseMindspaceGeoMeta(source);
|
|
const preview = extractSharePreviewMeta(source, {
|
|
origin,
|
|
pageUrl,
|
|
pageDirUrl,
|
|
siteName,
|
|
meta,
|
|
htmlFilePath,
|
|
fileExists,
|
|
});
|
|
const title =
|
|
rawMetaOgTitle(source) ||
|
|
rawTitleFromHtml(source) ||
|
|
rawH1FromHtml(source) ||
|
|
'MindSpace 页面';
|
|
const description =
|
|
String(authorMeta.summary ?? '').trim() ||
|
|
extractSeoDescription(source) ||
|
|
preview.description ||
|
|
'';
|
|
const canonical =
|
|
String(publication?.publicUrl ?? pageUrl ?? '').trim() || pageUrl;
|
|
const absoluteUrl = canonical.startsWith('http')
|
|
? canonical
|
|
: origin && canonical.startsWith('/')
|
|
? `${origin}${canonical}`
|
|
: pageUrl;
|
|
return buildArticleJsonLd({
|
|
title,
|
|
description,
|
|
imageUrl: preview.imageUrl,
|
|
pageUrl: absoluteUrl,
|
|
siteName: preview.siteName || siteName,
|
|
faq: resolveGeoFaq(source, authorMeta),
|
|
});
|
|
}
|
|
|
|
function rawMetaOgTitle(html) {
|
|
return (
|
|
String(html ?? '').match(
|
|
/<meta[^>]+property=["']og:title["'][^>]+content=["']([^"']+)["']/i,
|
|
)?.[1] ||
|
|
String(html ?? '').match(
|
|
/<meta[^>]+content=["']([^"']+)["'][^>]+property=["']og:title["']/i,
|
|
)?.[1] ||
|
|
''
|
|
);
|
|
}
|
|
|
|
export function injectGeoTags(
|
|
html,
|
|
ctx = {},
|
|
) {
|
|
const sourceHtml = String(ctx.sourceHtml ?? html ?? '');
|
|
const targetHtml = String(html ?? '');
|
|
if (hasJsonLd(targetHtml)) return targetHtml;
|
|
const payload = buildGeoStructuredData(sourceHtml, ctx);
|
|
const serialized = JSON.stringify(payload).replace(/</g, '\\u003c');
|
|
const block = `\n <script type="application/ld+json">${serialized}</script>\n`;
|
|
return appendBeforeHeadClose(targetHtml, block);
|
|
}
|