# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
This commit is contained in:
Anthony Stirling 2025-09-24 20:37:51 +01:00 committed by GitHub
parent 6441dc1d6f
commit 166f6d2d98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 51 additions and 18 deletions

View File

@ -2,6 +2,7 @@
<html lang="en-GB">
<head>
<meta charset="UTF-8" />
<base href="%BASE_URL%" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />

View File

@ -5,6 +5,7 @@ import LocalIcon from './LocalIcon';
import { useTranslation } from 'react-i18next';
import { useFileHandler } from '../../hooks/useFileHandler';
import { useFilesModalContext } from '../../contexts/FilesModalContext';
import { BASE_PATH } from '../../constants/app';
const LandingPage = () => {
const { addFiles } = useFileHandler();
@ -72,7 +73,7 @@ const LandingPage = () => {
}}
>
<img
src={colorScheme === 'dark' ? '/branding/StirlingPDFLogoNoTextDark.svg' : '/branding/StirlingPDFLogoNoTextLight.svg'}
src={colorScheme === 'dark' ? `${BASE_PATH}/branding/StirlingPDFLogoNoTextDark.svg` : `${BASE_PATH}/branding/StirlingPDFLogoNoTextLight.svg`}
alt="Stirling PDF Logo"
style={{
height: 'auto',
@ -98,7 +99,7 @@ const LandingPage = () => {
{/* Stirling PDF Branding */}
<Group gap="xs" align="center">
<img
src={colorScheme === 'dark' ? '/branding/StirlingPDFLogoWhiteText.svg' : '/branding/StirlingPDFLogoGreyText.svg'}
src={colorScheme === 'dark' ? `${BASE_PATH}/branding/StirlingPDFLogoWhiteText.svg` : `${BASE_PATH}/branding/StirlingPDFLogoGreyText.svg`}
alt="Stirling PDF"
style={{ height: '2.2rem', width: 'auto' }}
/>

View File

@ -6,6 +6,7 @@ import { useTooltipPosition } from '../../hooks/useTooltipPosition';
import { TooltipTip } from '../../types/tips';
import { TooltipContent } from './tooltip/TooltipContent';
import { useSidebarContext } from '../../contexts/SidebarContext';
import { BASE_PATH } from '../../constants/app';
import styles from './tooltip/Tooltip.module.css';
export interface TooltipProps {
@ -328,7 +329,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
<div className={styles['tooltip-logo']}>
{header.logo || (
<img
src="/logo-tooltip.svg"
src={`${BASE_PATH}/logo-tooltip.svg`}
alt="Stirling PDF"
style={{ width: '1.4rem', height: '1.4rem', display: 'block' }}
/>

View File

@ -5,3 +5,19 @@ export const getBaseUrl = (): string => {
const { config } = useAppConfig();
return config?.baseUrl || 'https://stirling.com';
};
// Base path from Vite config - build-time constant, normalized (no trailing slash)
// When no subpath, use empty string instead of '.' to avoid relative path issues
export const BASE_PATH = (import.meta.env.BASE_URL || '/').replace(/\/$/, '').replace(/^\.$/, '');
/** For in-app navigations when you must touch window.location (rare). */
export const withBasePath = (path: string): string => {
const clean = path.startsWith('/') ? path : `/${path}`;
return `${BASE_PATH}${clean}`;
};
/** For OAuth (needs absolute URL with scheme+host) */
export const absoluteWithBasePath = (path: string): string => {
const clean = path.startsWith('/') ? path : `/${path}`;
return `${window.location.origin}${BASE_PATH}${clean}`;
};

View File

@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { BASE_PATH } from '../constants/app';
declare global {
interface Window {
@ -37,17 +38,17 @@ export const useCookieConsent = ({ analyticsEnabled = false }: CookieConsentConf
// Load the cookie consent CSS files first
const mainCSS = document.createElement('link');
mainCSS.rel = 'stylesheet';
mainCSS.href = '/css/cookieconsent.css';
mainCSS.href = `${BASE_PATH}/css/cookieconsent.css`;
document.head.appendChild(mainCSS);
const customCSS = document.createElement('link');
customCSS.rel = 'stylesheet';
customCSS.href = '/css/cookieconsentCustomisation.css';
customCSS.href = `${BASE_PATH}/css/cookieconsentCustomisation.css`;
document.head.appendChild(customCSS);
// Load the cookie consent library
const script = document.createElement('script');
script.src = '/js/thirdParty/cookieconsent.umd.js';
script.src = `${BASE_PATH}/js/thirdParty/cookieconsent.umd.js`;
script.onload = () => {
// Small delay to ensure DOM is ready
setTimeout(() => {

View File

@ -7,6 +7,7 @@ import { ToolId } from '../types/toolId';
import { parseToolRoute, updateToolRoute, clearToolRoute } from '../utils/urlRouting';
import { ToolRegistry } from '../data/toolsTaxonomy';
import { firePixel } from '../utils/scarfTracking';
import { withBasePath } from '../constants/app';
/**
* Hook to sync workbench and tool with URL using registry
@ -51,7 +52,8 @@ export function useNavigationUrlSync(
} else if (prevSelectedTool.current !== null) {
// Only clear URL if we had a tool before (user navigated away)
// Don't clear on initial load when both current and previous are null
if (window.location.pathname !== '/') {
const homePath = withBasePath('/');
if (window.location.pathname !== homePath) {
clearToolRoute(false); // Use pushState for user navigation
}
}

View File

@ -74,7 +74,9 @@ i18n
loadPath: (lngs: string[], namespaces: string[]) => {
// Map 'en' to 'en-GB' for loading translations
const lng = lngs[0] === 'en' ? 'en-GB' : lngs[0];
return `/locales/${lng}/${namespaces[0]}.json`;
const basePath = import.meta.env.BASE_URL || '/';
const cleanBasePath = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;
return `${cleanBasePath}/locales/${lng}/${namespaces[0]}.json`;
},
},

View File

@ -10,6 +10,7 @@ import App from './App';
import './i18n'; // Initialize i18next
import posthog from 'posthog-js';
import { PostHogProvider } from 'posthog-js/react';
import { BASE_PATH } from './constants/app';
// Compute initial color scheme
function getInitialScheme(): 'light' | 'dark' {
@ -60,7 +61,7 @@ root.render(
<PostHogProvider
client={posthog}
>
<BrowserRouter>
<BrowserRouter basename={BASE_PATH}>
<App />
</BrowserRouter>
</PostHogProvider>

View File

@ -1,10 +1,11 @@
import React, { useEffect } from "react";
import { BaseToolProps } from "../types/tool";
import { withBasePath } from "../constants/app";
const SwaggerUI: React.FC<BaseToolProps> = () => {
useEffect(() => {
// Redirect to Swagger UI
window.open("/swagger-ui/5.21.0/index.html", "_blank");
window.open(withBasePath("/swagger-ui/5.21.0/index.html"), "_blank");
}, []);
return (
@ -12,7 +13,7 @@ const SwaggerUI: React.FC<BaseToolProps> = () => {
<p>Opening Swagger UI in a new tab...</p>
<p>
If it didn't open automatically,{" "}
<a href="/swagger-ui/5.21.0/index.html" target="_blank" rel="noopener noreferrer">
<a href={withBasePath("/swagger-ui/5.21.0/index.html")} target="_blank" rel="noopener noreferrer">
click here
</a>
</p>

View File

@ -8,12 +8,17 @@ import { getDefaultWorkbench } from '../types/workbench';
import { ToolRegistry, getToolWorkbench, getToolUrlPath } from '../data/toolsTaxonomy';
import { firePixel } from './scarfTracking';
import { URL_TO_TOOL_MAP } from './urlMapping';
import { BASE_PATH, withBasePath } from '../constants/app';
/**
* Parse the current URL to extract tool routing information
*/
export function parseToolRoute(registry: ToolRegistry): ToolRoute {
const path = window.location.pathname;
const fullPath = window.location.pathname;
// Remove base path to get app-relative path
const path = BASE_PATH && fullPath.startsWith(BASE_PATH)
? fullPath.slice(BASE_PATH.length) || '/'
: fullPath;
const searchParams = new URLSearchParams(window.location.search);
// First, check URL mapping for multiple URL aliases
@ -83,7 +88,8 @@ export function updateToolRoute(toolId: ToolId, registry: ToolRegistry, replace:
return;
}
const newPath = getToolUrlPath(toolId, tool);
const toolPath = getToolUrlPath(toolId, tool);
const newPath = withBasePath(toolPath);
const searchParams = new URLSearchParams(window.location.search);
// Remove tool query parameter since we're using path-based routing
@ -99,7 +105,7 @@ export function clearToolRoute(replace: boolean = false): void {
const searchParams = new URLSearchParams(window.location.search);
searchParams.delete('tool');
updateUrl('/', searchParams, replace);
updateUrl(withBasePath('/'), searchParams, replace);
}
/**
@ -117,11 +123,12 @@ export function generateShareableUrl(toolId: ToolId | null, registry: ToolRegist
const baseUrl = window.location.origin;
if (!toolId || !registry[toolId]) {
return baseUrl;
return `${baseUrl}${BASE_PATH || ''}`;
}
const tool = registry[toolId];
const path = getToolUrlPath(toolId, tool);
return `${baseUrl}${path}`;
const toolPath = getToolUrlPath(toolId, tool);
const fullPath = withBasePath(toolPath);
return `${baseUrl}${fullPath}`;
}

View File

@ -12,5 +12,5 @@ export default defineConfig({
},
},
},
base: "./",
base: process.env.RUN_SUBPATH ? `/${process.env.RUN_SUBPATH}` : './',
});