mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-05 03:50:29 +01:00
fix: serve canonical, hreflang and sitemap URLs and restore Export in the PDF editor
This commit is contained in:
@@ -71,9 +71,9 @@ Have questions, feature requests, or want to chat with the community? Join our D
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
[](https://bentopdf.com/docs/)
|
||||
[](https://www.bentopdf.com/docs)
|
||||
|
||||
Visit our [Documentation](https://bentopdf.com/docs/) for:
|
||||
Visit our [Documentation](https://www.bentopdf.com/docs) for:
|
||||
|
||||
- **Getting Started** guide
|
||||
- **Tools Reference** (50+ tools)
|
||||
@@ -100,7 +100,7 @@ BentoPDF is **dual-licensed** to fit your needs:
|
||||
|
||||
> **One-time purchase** · **Unlimited devices & users** · **Lifetime updates** · **No AGPL obligations**
|
||||
|
||||
📖 For more details, see our [Licensing Page](https://bentopdf.com/licensing.html)
|
||||
📖 For more details, see our [Licensing Page](https://www.bentopdf.com/licensing)
|
||||
|
||||
### AGPL Components (Pre-configured via CDN)
|
||||
|
||||
@@ -810,7 +810,7 @@ systemctl --user daemon-reload
|
||||
systemctl --user enable --now bentopdf
|
||||
```
|
||||
|
||||
For detailed Quadlet configuration, see [Self-Hosting Docker Guide](https://bentopdf.com/docs/self-hosting/docker).
|
||||
For detailed Quadlet configuration, see [Self-Hosting Docker Guide](https://www.bentopdf.com/docs/self-hosting/docker).
|
||||
|
||||
### 🏢 Self-Hosted build (Simple Mode)
|
||||
|
||||
@@ -866,7 +866,7 @@ Or set the same variables when building from source — see [Custom Branding](#-
|
||||
> - **AGPL-3.0** (free): allowed if your deployment publishes its full source code under AGPL — this includes any branding modifications, custom configuration, and any code you build on top of it.
|
||||
> - **Commercial license** ($79 lifetime): required for closed-source / proprietary deployments — e.g., a private SaaS where you don't open-source your branding fork or surrounding business logic.
|
||||
>
|
||||
> See the [Licensing page](https://bentopdf.com/licensing.html) for the full comparison.
|
||||
> See the [Licensing page](https://www.bentopdf.com/licensing) for the full comparison.
|
||||
|
||||
### 🎨 Custom Branding
|
||||
|
||||
@@ -930,7 +930,7 @@ docker run -d -p 3000:8080 \
|
||||
ghcr.io/alam00000/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
Both methods can be combined — the lists are merged. For the full list of tool IDs, see the [self-hosting docs](https://bentopdf.com/docs/self-hosting/docker#disabling-specific-tools).
|
||||
Both methods can be combined — the lists are merged. For the full list of tool IDs, see the [self-hosting docs](https://www.bentopdf.com/docs/self-hosting/docker#disabling-specific-tools).
|
||||
|
||||
You can also disable specific features inside the PDF Editor (e.g., redaction, forms) without disabling the entire editor. Add `editorDisabledCategories` to your `config.json`:
|
||||
|
||||
@@ -940,7 +940,7 @@ You can also disable specific features inside the PDF Editor (e.g., redaction, f
|
||||
}
|
||||
```
|
||||
|
||||
For the full list of editor categories, see the [self-hosting docs](https://bentopdf.com/docs/self-hosting/docker#disabling-editor-features).
|
||||
For the full list of editor categories, see the [self-hosting docs](https://www.bentopdf.com/docs/self-hosting/docker#disabling-editor-features).
|
||||
|
||||
### 🔕 Disabling the GitHub Star Counter
|
||||
|
||||
|
||||
+4
-1
@@ -332,7 +332,10 @@
|
||||
<i data-lucide="arrow-right"></i>
|
||||
</a>
|
||||
</span>
|
||||
<a href="https://bentopdf.com/docs/" class="lp-btn lp-btn--ghost">
|
||||
<a
|
||||
href="https://www.bentopdf.com/docs"
|
||||
class="lp-btn lp-btn--ghost"
|
||||
>
|
||||
<i data-lucide="book-open"></i>
|
||||
<span data-i18n="nav.docs">Docs</span>
|
||||
</a>
|
||||
|
||||
@@ -11,8 +11,8 @@ export default defineConfig({
|
||||
|
||||
transformPageData(pageData) {
|
||||
const relPath = pageData.relativePath.replace(/\.md$/, '')
|
||||
const slug = relPath === 'index' ? '' : relPath.replace(/\/index$/, '/')
|
||||
const canonicalUrl = slug ? `${SITE_URL}/docs/${slug}` : `${SITE_URL}/docs/`
|
||||
const slug = relPath === 'index' ? '' : relPath.replace(/\/index$/, '')
|
||||
const canonicalUrl = slug ? `${SITE_URL}/docs/${slug}` : `${SITE_URL}/docs`
|
||||
pageData.frontmatter.head ??= []
|
||||
pageData.frontmatter.head.push(['link', { rel: 'canonical', href: canonicalUrl }])
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ BentoPDF is a free, open-source, privacy-first PDF toolkit that runs **entirely
|
||||
|
||||
### Option 1: Use the Hosted Version
|
||||
|
||||
Visit [bentopdf.com](https://bentopdf.com) to use BentoPDF instantly—no installation required.
|
||||
Visit [bentopdf.com](https://www.bentopdf.com) to use BentoPDF instantly—no installation required.
|
||||
|
||||
### Option 2: Self-Host with Docker
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
BentoPDF is dual-licensed under **AGPL-3.0** (for open-source projects) and a **Commercial License** (for proprietary/closed-source applications).
|
||||
|
||||
::: tip Full Details
|
||||
For complete licensing information, delivery details, AGPL component notices, and invoicing, visit our [Licensing Page](https://bentopdf.com/licensing.html).
|
||||
For complete licensing information, delivery details, AGPL component notices, and invoicing, visit our [Licensing Page](https://www.bentopdf.com/licensing).
|
||||
:::
|
||||
|
||||
## Get a Commercial License
|
||||
|
||||
@@ -148,7 +148,7 @@ Running the Commercial build is allowed under either of BentoPDF's two license o
|
||||
- **AGPL-3.0** (free) — allowed if your deployment publishes its full source code under AGPL, including any branding modifications and surrounding business logic.
|
||||
- **Commercial license** ($79 lifetime) — required for closed-source / proprietary deployments where you don't open-source your branding fork or business code.
|
||||
|
||||
See the [Licensing page](https://bentopdf.com/licensing.html) for the full comparison. AGPL-licensed WASM modules (PyMuPDF, Ghostscript, CoherentPDF) load from a CDN at runtime, so they don't enter your image and don't change your licensing posture.
|
||||
See the [Licensing page](https://www.bentopdf.com/licensing) for the full comparison. AGPL-licensed WASM modules (PyMuPDF, Ghostscript, CoherentPDF) load from a CDN at runtime, so they don't enter your image and don't change your licensing posture.
|
||||
:::
|
||||
|
||||
### Base URL
|
||||
|
||||
+3
-3
@@ -559,7 +559,7 @@
|
||||
class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4"
|
||||
>
|
||||
<img
|
||||
src="images/gdpr.svg"
|
||||
src="/images/gdpr.svg"
|
||||
alt="GDPR compliance"
|
||||
class="w-full h-full"
|
||||
/>
|
||||
@@ -584,7 +584,7 @@
|
||||
class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4"
|
||||
>
|
||||
<img
|
||||
src="images/ccpa.svg"
|
||||
src="/images/ccpa.svg"
|
||||
alt="CCPA compliance"
|
||||
class="w-full h-full"
|
||||
/>
|
||||
@@ -609,7 +609,7 @@
|
||||
class="w-20 h-20 md:w-24 md:h-24 rounded-full bg-blue-600 flex items-center justify-center mb-4"
|
||||
>
|
||||
<img
|
||||
src="images/hipaa.svg"
|
||||
src="/images/hipaa.svg"
|
||||
alt="HIPAA compliance"
|
||||
class="w-full h-full"
|
||||
/>
|
||||
|
||||
+11
-8
@@ -110,11 +110,8 @@ http {
|
||||
return 301 $1/$2$3;
|
||||
}
|
||||
|
||||
if ($request_uri ~ ^(/(?:en|ar|be|da|de|es|fr|id|it|ko|nl|pl|pt|ru|sk|sv|tr|vi|zh|zh-TW|uk|ja|ca))/([a-z0-9][a-z0-9-]*)/(\?.*)?$) {
|
||||
return 301 $1/$2$3;
|
||||
}
|
||||
if ($request_uri ~ ^/((?!(?:en|ar|be|da|de|es|fr|id|it|ko|nl|pl|pt|ru|sk|sv|tr|vi|zh|zh-TW|uk|ja|ca|docs|blog)/)[a-z0-9][a-z0-9-]*)/(\?.*)?$) {
|
||||
return 301 /$1$2;
|
||||
if ($request_uri ~ ^(/[^?]*[^/?])/+(\?.*)?$) {
|
||||
return 301 $1$2;
|
||||
}
|
||||
|
||||
location ~ /src/pages/ {
|
||||
@@ -122,7 +119,7 @@ http {
|
||||
}
|
||||
|
||||
location ~ ^/(en|ar|be|da|de|es|fr|id|it|ko|nl|pl|pt|ru|sk|sv|tr|vi|zh|zh-TW|uk|ja|ca)(/.*)?$ {
|
||||
try_files $uri $uri/ $uri.html =404;
|
||||
try_files $uri $uri.html $uri/index.html =404;
|
||||
expires 5m;
|
||||
}
|
||||
|
||||
@@ -174,14 +171,20 @@ http {
|
||||
|
||||
error_page 404 /404.html;
|
||||
|
||||
location = /docs {
|
||||
include /etc/nginx/security-headers-docs.conf;
|
||||
try_files /docs/index.html =404;
|
||||
expires 5m;
|
||||
}
|
||||
|
||||
location ^~ /docs/ {
|
||||
include /etc/nginx/security-headers-docs.conf;
|
||||
try_files $uri $uri/ $uri.html /docs/index.html =404;
|
||||
try_files $uri $uri.html $uri/index.html /docs/index.html =404;
|
||||
expires 5m;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ $uri.html =404;
|
||||
try_files $uri $uri.html $uri/index.html =404;
|
||||
expires 5m;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
+7
-2
@@ -184,7 +184,10 @@ export const changeLanguage = (lang: SupportedLanguage): void => {
|
||||
if (lang === 'en') {
|
||||
newRelativePath = pagePathWithoutLang;
|
||||
} else {
|
||||
newRelativePath = `/${lang}${pagePathWithoutLang}`;
|
||||
newRelativePath =
|
||||
pagePathWithoutLang === '/'
|
||||
? `/${lang}`
|
||||
: `/${lang}${pagePathWithoutLang}`;
|
||||
}
|
||||
|
||||
let newPath: string;
|
||||
@@ -293,7 +296,9 @@ export const rewriteLinks = (): void => {
|
||||
newHref = `/${currentLang}/${href}`;
|
||||
}
|
||||
|
||||
newHref = newHref.replace(/([^:])\/+/g, '$1/');
|
||||
newHref = newHref
|
||||
.replace(/([^:])\/+/g, '$1/')
|
||||
.replace(/^([^?#]*[^/?#])\/(?=$|[?#])/, '$1');
|
||||
|
||||
link.setAttribute('href', newHref);
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import type {
|
||||
AppliedRedactionTracker,
|
||||
DocManagerPlugin,
|
||||
FreeTextSystemFontAnnotation,
|
||||
ViewerCommandsPlugin,
|
||||
RedactionPluginLite,
|
||||
} from '@/types';
|
||||
|
||||
@@ -328,12 +329,8 @@ async function handleFiles(files: FileList) {
|
||||
pdfWrapper.classList.remove('hidden');
|
||||
|
||||
const { default: EmbedPDF } = await import('bentopdf-viewer');
|
||||
const disabledCategories = [
|
||||
...getEditorDisabledCategories(),
|
||||
'document-export',
|
||||
];
|
||||
viewerInstance = EmbedPDF.init({
|
||||
disabledCategories,
|
||||
disabledCategories: getEditorDisabledCategories(),
|
||||
type: 'container',
|
||||
target: pdfContainer,
|
||||
worker: true,
|
||||
@@ -425,7 +422,7 @@ async function handleFiles(files: FileList) {
|
||||
}
|
||||
downloadBtn.classList.remove('hidden');
|
||||
|
||||
downloadBtn.onclick = async () => {
|
||||
const downloadEditedPdf = async () => {
|
||||
try {
|
||||
const documentId = activeDocuments.getActiveDocumentId();
|
||||
const pendingCount = redactionPlugin?.getState().pendingCount ?? 0;
|
||||
@@ -541,6 +538,19 @@ async function handleFiles(files: FileList) {
|
||||
showAlert('Error', 'Failed to download the edited PDF.');
|
||||
}
|
||||
};
|
||||
downloadBtn.onclick = downloadEditedPdf;
|
||||
|
||||
const commands = registry.getPlugin('commands')?.provides() as unknown as
|
||||
| ViewerCommandsPlugin
|
||||
| undefined;
|
||||
commands?.unregisterCommand('document:export');
|
||||
commands?.registerCommand({
|
||||
id: 'document:export',
|
||||
labelKey: 'document.export',
|
||||
icon: 'download',
|
||||
categories: ['document', 'document-export'],
|
||||
action: () => void downloadEditedPdf(),
|
||||
});
|
||||
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
if (backBtn) {
|
||||
|
||||
@@ -14,6 +14,19 @@ export interface DocManagerPlugin {
|
||||
saveAsCopy: (id: string) => Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
export interface ViewerCommand {
|
||||
id: string;
|
||||
labelKey: string;
|
||||
icon: string;
|
||||
categories: string[];
|
||||
action: () => void;
|
||||
}
|
||||
|
||||
export interface ViewerCommandsPlugin {
|
||||
registerCommand: (command: ViewerCommand) => void;
|
||||
unregisterCommand: (id: string) => void;
|
||||
}
|
||||
|
||||
export interface EditorAnnotationRect {
|
||||
origin: { x: number; y: number };
|
||||
size: { width: number; height: number };
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href="https://www.bentopdf.com/docs/"
|
||||
href="https://www.bentopdf.com/docs"
|
||||
class="nav-link"
|
||||
data-i18n="nav.docs"
|
||||
>Docs</a
|
||||
@@ -186,7 +186,7 @@
|
||||
<a href="/hyper-compress" class="mobile-nav-link">Hyper Compress</a>
|
||||
</div>
|
||||
<a
|
||||
href="https://www.bentopdf.com/docs/"
|
||||
href="https://www.bentopdf.com/docs"
|
||||
class="mobile-nav-link"
|
||||
data-i18n="nav.docs"
|
||||
>Docs</a
|
||||
|
||||
+91
@@ -7,6 +7,97 @@
|
||||
"cleanUrls": true,
|
||||
"trailingSlash": false,
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/ua/change-text-color",
|
||||
"destination": "/uk/text-color",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/ua/src/pages/change-text-color",
|
||||
"destination": "/uk/text-color",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/:locale(ar|be|da|de|es|fr|id|it|ja|ko|nl|pl|pt|ru|sk|sv|tr|uk|vi|zh|zh-TW)/src/pages/change-text-color",
|
||||
"destination": "/:locale/text-color",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/(en|tools|src/pages|en/src/pages)/change-text-color",
|
||||
"destination": "/text-color",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/ua/pdf-to-docx",
|
||||
"destination": "/uk/pdf-to-word",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/ua/src/pages/pdf-to-docx",
|
||||
"destination": "/uk/pdf-to-word",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/:locale(ar|be|da|de|es|fr|id|it|ja|ko|nl|pl|pt|ru|sk|sv|tr|uk|vi|zh|zh-TW)/src/pages/pdf-to-docx",
|
||||
"destination": "/:locale/pdf-to-word",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/(en|tools|src/pages|en/src/pages)/pdf-to-docx",
|
||||
"destination": "/pdf-to-word",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/ua/decrypt-pdf",
|
||||
"destination": "/uk/unlock-pdf",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/ua/src/pages/decrypt-pdf",
|
||||
"destination": "/uk/unlock-pdf",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/:locale(ar|be|da|de|es|fr|id|it|ja|ko|nl|pl|pt|ru|sk|sv|tr|uk|vi|zh|zh-TW)/src/pages/decrypt-pdf",
|
||||
"destination": "/:locale/unlock-pdf",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/(en|tools|src/pages|en/src/pages)/decrypt-pdf",
|
||||
"destination": "/unlock-pdf",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/ua/encrypt-pdf",
|
||||
"destination": "/uk/protect-pdf",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/ua/src/pages/encrypt-pdf",
|
||||
"destination": "/uk/protect-pdf",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/:locale(ar|be|da|de|es|fr|id|it|ja|ko|nl|pl|pt|ru|sk|sv|tr|uk|vi|zh|zh-TW)/src/pages/encrypt-pdf",
|
||||
"destination": "/:locale/protect-pdf",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/(en|tools|src/pages|en/src/pages)/encrypt-pdf",
|
||||
"destination": "/protect-pdf",
|
||||
"permanent": true
|
||||
},
|
||||
{ "source": "/ua", "destination": "/uk", "permanent": true },
|
||||
{
|
||||
"source": "/ua/src/pages/:page",
|
||||
"destination": "/uk/:page",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/en/src/pages/:page",
|
||||
"destination": "/:page",
|
||||
"permanent": true
|
||||
},
|
||||
{ "source": "/ua/:path*", "destination": "/uk/:path*", "permanent": true },
|
||||
{ "source": "/en/:path*", "destination": "/:path*", "permanent": true },
|
||||
{
|
||||
|
||||
+9
-2
@@ -515,11 +515,18 @@ function rewriteHtmlPathsPlugin(): Plugin {
|
||||
|
||||
for (const fileName of Object.keys(bundle)) {
|
||||
if (!fileName.endsWith('.html')) continue;
|
||||
const diskPath = resolve(outDir, fileName);
|
||||
if (!fs.existsSync(diskPath)) continue;
|
||||
const diskPath = [
|
||||
resolve(outDir, fileName),
|
||||
resolve(outDir, fileName.replace(/^src\/pages\//, '')),
|
||||
].find((candidate) => fs.existsSync(candidate));
|
||||
if (!diskPath) continue;
|
||||
const source = fs.readFileSync(diskPath, 'utf8');
|
||||
const updated = source
|
||||
.replace(hrefRegex, `href="${normalizedBase}`)
|
||||
.replaceAll(
|
||||
`href="${normalizedBase}"`,
|
||||
`href="${normalizedBase.slice(0, -1)}"`
|
||||
)
|
||||
.replace(srcRegex, `src="${normalizedBase}`)
|
||||
.replace(contentRegex, `content="${normalizedBase}`);
|
||||
if (updated !== source) {
|
||||
|
||||
Reference in New Issue
Block a user