mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-05 20:02:47 +01:00
175 lines
4.9 KiB
JavaScript
175 lines
4.9 KiB
JavaScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import { JSDOM } from 'jsdom';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const DIST_DIR = path.resolve(__dirname, '../dist');
|
|
const SITE_URL = (process.env.SITE_URL || 'https://www.bentopdf.com').replace(
|
|
/\/+$/,
|
|
''
|
|
);
|
|
|
|
const FALLBACK_SOCIAL_IMAGE = '/images/og-tools.png';
|
|
|
|
function listTopLevelHtml() {
|
|
return fs
|
|
.readdirSync(DIST_DIR)
|
|
.filter((f) => f.endsWith('.html'))
|
|
.map((f) => path.join(DIST_DIR, f));
|
|
}
|
|
|
|
function imageExists(url) {
|
|
if (!url) return true;
|
|
let pathname;
|
|
try {
|
|
pathname = url.startsWith('http') ? new URL(url).pathname : url;
|
|
} catch {
|
|
return true;
|
|
}
|
|
if (!pathname.startsWith('/images/')) return true;
|
|
return fs.existsSync(path.join(DIST_DIR, pathname.replace(/^\//, '')));
|
|
}
|
|
|
|
function fixSocialImages(document) {
|
|
const selectors = ['meta[property="og:image"]', 'meta[name="twitter:image"]'];
|
|
for (const selector of selectors) {
|
|
for (const meta of document.querySelectorAll(selector)) {
|
|
const value = meta.getAttribute('content');
|
|
if (!imageExists(value)) {
|
|
meta.setAttribute('content', `${SITE_URL}${FALLBACK_SOCIAL_IMAGE}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function fixApplicationCategory(document) {
|
|
for (const script of document.querySelectorAll(
|
|
'script[type="application/ld+json"]'
|
|
)) {
|
|
let data;
|
|
try {
|
|
data = JSON.parse(script.textContent || '');
|
|
} catch {
|
|
continue;
|
|
}
|
|
if (
|
|
data &&
|
|
(data['@type'] === 'SoftwareApplication' ||
|
|
data['@type'] === 'WebApplication') &&
|
|
data.applicationCategory !== 'UtilitiesApplication'
|
|
) {
|
|
data.applicationCategory = 'UtilitiesApplication';
|
|
script.textContent = JSON.stringify(data, null, 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
function hasLdType(document, typeName) {
|
|
return [
|
|
...document.querySelectorAll('script[type="application/ld+json"]'),
|
|
].some((s) => (s.textContent || '').includes(`"${typeName}"`));
|
|
}
|
|
|
|
function injectFaqSchema(document) {
|
|
if (hasLdType(document, 'FAQPage')) return false;
|
|
const items = [];
|
|
for (const details of document.querySelectorAll('details.faq-d')) {
|
|
const summary = details.querySelector('summary');
|
|
const answer = details.querySelector('p');
|
|
if (!summary || !answer) continue;
|
|
const question = summary.textContent.replace(/\s+/g, ' ').trim();
|
|
const answerText = answer.textContent.replace(/\s+/g, ' ').trim();
|
|
if (!question || !answerText) continue;
|
|
items.push({
|
|
'@type': 'Question',
|
|
name: question,
|
|
acceptedAnswer: { '@type': 'Answer', text: answerText },
|
|
});
|
|
}
|
|
if (items.length === 0) return false;
|
|
const script = document.createElement('script');
|
|
script.setAttribute('type', 'application/ld+json');
|
|
script.textContent = JSON.stringify(
|
|
{
|
|
'@context': 'https://schema.org',
|
|
'@type': 'FAQPage',
|
|
mainEntity: items,
|
|
},
|
|
null,
|
|
2
|
|
);
|
|
document.body.appendChild(script);
|
|
return true;
|
|
}
|
|
|
|
function injectWebsiteSchema(document) {
|
|
if (hasLdType(document, 'WebSite')) return;
|
|
const script = document.createElement('script');
|
|
script.setAttribute('type', 'application/ld+json');
|
|
script.textContent = JSON.stringify(
|
|
{
|
|
'@context': 'https://schema.org',
|
|
'@type': 'WebSite',
|
|
name: 'BentoPDF',
|
|
alternateName: ['Bento PDF', 'BentoPDF.com'],
|
|
url: `${SITE_URL}/`,
|
|
},
|
|
null,
|
|
2
|
|
);
|
|
document.body.appendChild(script);
|
|
}
|
|
|
|
function rewriteInternalLinks(document, knownSlugs) {
|
|
for (const link of document.querySelectorAll('a[href]')) {
|
|
const href = link.getAttribute('href');
|
|
if (!href || /^(https?:|\/\/|#|mailto:|tel:|data:)/.test(href)) continue;
|
|
const match = href.match(
|
|
/^(?:\.\/|\/)?([a-z0-9][a-z0-9-]{0,64})\.html(#[^#]{0,128})?$/
|
|
);
|
|
if (!match) continue;
|
|
if (!knownSlugs.has(match[1])) continue;
|
|
const target = match[1] === 'index' ? '/' : `/${match[1]}`;
|
|
link.setAttribute('href', `${target}${match[2] || ''}`);
|
|
}
|
|
}
|
|
|
|
function enhance() {
|
|
if (!fs.existsSync(DIST_DIR)) {
|
|
console.error('enhance-seo-pages: dist not found, run vite build first');
|
|
process.exit(1);
|
|
}
|
|
const files = listTopLevelHtml();
|
|
const knownSlugs = new Set(files.map((f) => path.basename(f, '.html')));
|
|
|
|
let faqSchemaCount = 0;
|
|
for (const file of files) {
|
|
const slug = path.basename(file, '.html');
|
|
const dom = new JSDOM(fs.readFileSync(file, 'utf-8'));
|
|
const document = dom.window.document;
|
|
|
|
fixSocialImages(document);
|
|
fixApplicationCategory(document);
|
|
rewriteInternalLinks(document, knownSlugs);
|
|
|
|
if (slug === 'index') {
|
|
injectWebsiteSchema(document);
|
|
}
|
|
|
|
if (slug !== '404') {
|
|
if (injectFaqSchema(document)) faqSchemaCount++;
|
|
}
|
|
|
|
fs.writeFileSync(file, dom.serialize());
|
|
dom.window.close();
|
|
}
|
|
console.log(
|
|
`SEO enhance: ${files.length} pages processed, ${faqSchemaCount} FAQPage schemas added.`
|
|
);
|
|
}
|
|
|
|
enhance();
|