Files
memind/mindspace-geo-tags.mjs
T
john fde6503bdf
Memind CI / Test, build, and release guards (push) Has been cancelled
feat(mindspace): add SEO/GEO delivery, page template catalog, and admin hooks
Enable optional SEO/GEO injection and discovery routes for confirmed public pages while keeping private pages noindex. Add premium page template skills, portal catalog API, template shop UI, and Baidu push gated by memind_adm config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 08:06:12 +08:00

169 lines
4.5 KiB
JavaScript

import { extractCoverSignals } from './mindspace-thumbnails.mjs';
import { extractSharePreviewMeta, PLATFORM_SITE_NAME } from './mindspace-og-tags.mjs';
import { extractSeoDescription } from './mindspace-seo-tags.mjs';
function escapeAttr(value) {
return String(value ?? '')
.replaceAll('&', '&amp;')
.replaceAll('"', '&quot;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;');
}
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 parseMindspaceGeoMeta(html) {
const tag = String(html ?? '').match(/<meta[^>]*name=["']mindspace-geo["'][^>]*>/i)?.[0];
if (!tag) return {};
const contentMatch =
tag.match(/content=(["'])([\s\S]*?)\1/i) ?? tag.match(/content=["']([^"']+)["']/i);
const raw = contentMatch?.[2] ?? contentMatch?.[1];
if (!raw) return {};
try {
return JSON.parse(raw.replaceAll('&quot;', '"'));
} catch {
return {};
}
}
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: authorMeta.faq,
});
}
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);
}