mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-10 17:14:34 +01:00
fix: serve canonical, hreflang and sitemap URLs and restore Export in the PDF editor
This commit is contained in:
1 parent
7eec9a0d64
commit
6e8e012d3d
20 files changed
+209
-53
No files matched your search
@@ -8,6 +8,7 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const DIST_DIR = path.resolve(__dirname, '../dist');
|
||||
const BASE_PATH = (process.env.BASE_URL || '/').replace(/\/$/, '');
|
||||
const SITE_URL = (process.env.SITE_URL || 'https://www.bentopdf.com').replace(
|
||||
/\/+$/,
|
||||
''
|
||||
@@ -125,7 +126,8 @@ function rewriteInternalLinks(document, knownSlugs) {
|
||||
);
|
||||
if (!match) continue;
|
||||
if (!knownSlugs.has(match[1])) continue;
|
||||
const target = match[1] === 'index' ? '/' : `/${match[1]}`;
|
||||
const target =
|
||||
match[1] === 'index' ? BASE_PATH || '/' : `${BASE_PATH}/${match[1]}`;
|
||||
link.setAttribute('href', `${target}${match[2] || ''}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ ${renderFaqs(meta.faqs)}
|
||||
'@type': 'ListItem',
|
||||
position: 1,
|
||||
name: 'Blog',
|
||||
item: `${SITE_URL}/blog/`,
|
||||
item: `${SITE_URL}/blog`,
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
@@ -319,9 +319,9 @@ function renderIndex(posts) {
|
||||
content="Guides and honest comparisons from the maintainer of BentoPDF: how PDF tools handle your files, and how to get things done without uploading them."
|
||||
/>
|
||||
<meta name="robots" content="index, follow, max-image-preview:large" />
|
||||
<link rel="canonical" href="${SITE_URL}/blog/" />
|
||||
<link rel="canonical" href="${SITE_URL}/blog" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="${SITE_URL}/blog/" />
|
||||
<meta property="og:url" content="${SITE_URL}/blog" />
|
||||
<meta property="og:title" content="The BentoPDF Blog" />
|
||||
<meta
|
||||
property="og:description"
|
||||
@@ -363,7 +363,7 @@ ${cards}
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Blog',
|
||||
name: 'The BentoPDF Blog',
|
||||
url: `${SITE_URL}/blog/`,
|
||||
url: `${SITE_URL}/blog`,
|
||||
description:
|
||||
'Guides and honest comparisons from the maintainer of BentoPDF.',
|
||||
publisher: {
|
||||
|
||||
@@ -60,8 +60,7 @@ function buildUrl(langPrefix, pagePath) {
|
||||
if (BASE_PATH && BASE_PATH !== '') parts.push(BASE_PATH.replace(/^\//, ''));
|
||||
if (langPrefix) parts.push(langPrefix);
|
||||
if (pagePath) parts.push(pagePath.replace(/^\//, ''));
|
||||
const url = parts.filter(Boolean).join('/').replace(/\/+$/, '') || SITE_URL;
|
||||
return langPrefix && !pagePath ? `${url}/` : url;
|
||||
return parts.filter(Boolean).join('/').replace(/\/+$/, '') || SITE_URL;
|
||||
}
|
||||
|
||||
function resolveTranslation(langResources, key) {
|
||||
@@ -142,7 +141,7 @@ const BREADCRUMB_MARKER = 'data-bentopdf-breadcrumb';
|
||||
const BRAND_NAME = process.env.VITE_BRAND_NAME || 'BentoPDF';
|
||||
|
||||
function buildLocalHomeHref(lang) {
|
||||
const langSegment = lang === 'en' ? '/' : `/${lang}/`;
|
||||
const langSegment = lang === 'en' ? '/' : `/${lang}`;
|
||||
return `${BASE_PATH}${langSegment}`.replace(/\/+/g, '/');
|
||||
}
|
||||
|
||||
@@ -378,7 +377,10 @@ function processFileForLanguage(
|
||||
newHref = `${BASE_PATH}/${lang}/${href}`;
|
||||
}
|
||||
|
||||
link.setAttribute('href', newHref);
|
||||
link.setAttribute(
|
||||
'href',
|
||||
newHref.replace(/^([^?#]*[^/?#])\/(?=$|[?#])/, '$1')
|
||||
);
|
||||
});
|
||||
|
||||
const result = dom.serialize();
|
||||
|
||||
@@ -81,7 +81,7 @@ function buildUrl(lang, pageName) {
|
||||
if (lang === 'en') {
|
||||
return pagePath ? `${SITE_URL}/${pagePath}` : SITE_URL;
|
||||
}
|
||||
return pagePath ? `${SITE_URL}/${lang}/${pagePath}` : `${SITE_URL}/${lang}/`;
|
||||
return pagePath ? `${SITE_URL}/${lang}/${pagePath}` : `${SITE_URL}/${lang}`;
|
||||
}
|
||||
|
||||
function generateSitemap() {
|
||||
@@ -177,7 +177,7 @@ function generateSitemap() {
|
||||
.map((file) => file.replace('.html', ''));
|
||||
for (const name of blogFiles) {
|
||||
const url =
|
||||
name === 'index' ? `${SITE_URL}/blog/` : `${SITE_URL}/blog/${name}`;
|
||||
name === 'index' ? `${SITE_URL}/blog` : `${SITE_URL}/blog/${name}`;
|
||||
const sourcePath = path.join(projectRoot, 'blog', `${name}.html`);
|
||||
let lastmod;
|
||||
try {
|
||||
|
||||
@@ -111,7 +111,7 @@ function listDocsHtml(dir, prefix) {
|
||||
);
|
||||
} else if (entry.name.endsWith('.html') && entry.name !== '404.html') {
|
||||
const slug = entry.name.replace(/\.html$/, '');
|
||||
urls.push(slug === 'index' ? `${prefix}/` : `${prefix}/${slug}`);
|
||||
urls.push(slug === 'index' ? prefix : `${prefix}/${slug}`);
|
||||
}
|
||||
}
|
||||
return urls;
|
||||
@@ -130,7 +130,7 @@ function addDocsToSitemap() {
|
||||
return;
|
||||
}
|
||||
let xml = fs.readFileSync(sitemapPath, 'utf-8');
|
||||
if (xml.includes(`<loc>${siteUrl}/docs/</loc>`)) return;
|
||||
if (xml.includes(`<loc>${siteUrl}/docs</loc>`)) return;
|
||||
|
||||
const urls = listDocsHtml(targetDocsPath, '/docs');
|
||||
const entries = urls
|
||||
|
||||
+24
-4
@@ -85,6 +85,11 @@ for (const lang of LOCALE_DIRS) {
|
||||
: {};
|
||||
}
|
||||
|
||||
function hasTrailingSlash(href) {
|
||||
const { pathname } = new URL(href);
|
||||
return pathname !== '/' && pathname.endsWith('/');
|
||||
}
|
||||
|
||||
function expectedCanonicalForFile(rel) {
|
||||
const parts = rel.split('/');
|
||||
const fileName = parts.pop();
|
||||
@@ -121,7 +126,11 @@ function auditHtml(file, knownSlugs) {
|
||||
if (!isLocalized) {
|
||||
const internal = findAll(html, /<a[^>]+href=["'](\/[^"'#?]*)["']/g);
|
||||
for (const href of internal) {
|
||||
const clean = href.replace(/\/$/, '');
|
||||
const clean = (
|
||||
BASE_PATH && (href === BASE_PATH || href.startsWith(`${BASE_PATH}/`))
|
||||
? href.slice(BASE_PATH.length)
|
||||
: href
|
||||
).replace(/\/$/, '');
|
||||
if (clean === '') continue;
|
||||
const seg = clean.slice(1);
|
||||
if (seg.includes('/')) continue;
|
||||
@@ -162,6 +171,12 @@ function auditHtml(file, knownSlugs) {
|
||||
}
|
||||
try {
|
||||
const url = new URL(actual);
|
||||
if (hasTrailingSlash(actual)) {
|
||||
fail(
|
||||
'canonical',
|
||||
`${file.rel}: canonical "${actual}" ends with "/" (the server redirects it to the slashless form)`
|
||||
);
|
||||
}
|
||||
if (url.hostname !== HOST) {
|
||||
warn(
|
||||
'canonical',
|
||||
@@ -230,11 +245,10 @@ function auditHtml(file, knownSlugs) {
|
||||
`${file.rel}: hreflang href host "${url.hostname}" != expected "${HOST}"`
|
||||
);
|
||||
}
|
||||
const trimmed = url.pathname.replace(/^\/|\/$/g, '');
|
||||
if (LOCALE_DIRS.has(trimmed) && !url.pathname.endsWith('/')) {
|
||||
if (hasTrailingSlash(m[1])) {
|
||||
fail(
|
||||
'hreflang',
|
||||
`${file.rel}: language-home hreflang "${m[1]}" must end with "/" (the server 301s the slashless form)`
|
||||
`${file.rel}: hreflang "${m[1]}" ends with "/" (the server redirects it to the slashless form)`
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
@@ -334,6 +348,12 @@ function auditSitemap() {
|
||||
|
||||
try {
|
||||
const url = new URL(loc);
|
||||
if (hasTrailingSlash(loc)) {
|
||||
fail(
|
||||
'sitemap',
|
||||
`sitemap URL ends with "/" (the server redirects it to the slashless form): ${loc}`
|
||||
);
|
||||
}
|
||||
if (url.hostname !== HOST) {
|
||||
fail(
|
||||
'sitemap',
|
||||
|
||||
Reference in new issue
Block a user