@@ -7,12 +7,10 @@ const { createMarkdownItInstance } = require('./file-processor');
77const { generateSeoMetaTags } = require ( '../plugins/seo' ) ;
88const { generateAnalyticsScripts } = require ( '../plugins/analytics' ) ;
99const { renderIcon } = require ( './icon-renderer' ) ;
10- const { formatHtml } = require ( './html-formatter' ) ;
1110
1211let mdInstance = null ;
1312let themeInitScript = '' ;
1413
15- // Load the theme initialization script into memory once to avoid repeated disk I/O
1614( async ( ) => {
1715 try {
1816 const themeInitPath = path . join ( __dirname , '..' , 'templates' , 'partials' , 'theme-init.js' ) ;
@@ -23,51 +21,58 @@ let themeInitScript = '';
2321 } catch ( e ) { /* ignore */ }
2422} ) ( ) ;
2523
26- // Removes excessive whitespace and blank lines from the generated HTML
24+ // Basic whitespace cleanup (keep this simple version)
2725function cleanupHtml ( html ) {
2826 if ( ! html ) return '' ;
29- return html
30- . replace ( / ^ [ \t ] + $ / gm, '' )
31- . replace ( / \n { 3 , } / g, '\n\n' )
32- . trim ( ) ;
27+ return html . replace ( / ^ \s * [ \r \n ] / gm, '' ) . trim ( ) ;
3328}
3429
35- // Rewrites links based on build mode (Offline/File protocol vs Web Server)
36- function fixHtmlLinks ( htmlContent , relativePathToRoot , isOfflineMode ) {
30+ function fixHtmlLinks ( htmlContent , relativePathToRoot , isOfflineMode , configBase = '/' ) {
3731 if ( ! htmlContent ) return '' ;
3832 const root = relativePathToRoot || './' ;
33+ const baseUrl = configBase . endsWith ( '/' ) ? configBase : configBase + '/' ;
3934
40- return htmlContent . replace ( / h r e f = " ( (?: \/ | \. \/ | \. \. \/ ) [ ^ " ] * ) " / g, ( match , href ) => {
41- let finalPath = href ;
42-
43- // Convert absolute project paths to relative
44- if ( href . startsWith ( '/' ) ) {
45- finalPath = root + href . substring ( 1 ) ;
35+ return htmlContent . replace ( / ( h r e f | s r c ) = [ " ' ] ( [ ^ " ' ] + ) [ " ' ] / g, ( match , attr , url ) => {
36+ if ( url . startsWith ( '#' ) || url . startsWith ( 'http' ) || url . startsWith ( 'mailto:' ) || url === '' ) {
37+ return match ;
4638 }
47-
48- // Handle offline mode (force index.html for directories)
39+
40+ let finalPath = url ;
41+
42+ // 1. Handle Base URL removal
43+ if ( baseUrl !== '/' && url . startsWith ( baseUrl ) ) {
44+ finalPath = '/' + url . substring ( baseUrl . length ) ;
45+ }
46+
47+ // 2. Handle Absolute Paths
48+ if ( finalPath . startsWith ( '/' ) ) {
49+ // Simple logic: if root relative, prepend relative path
50+ finalPath = root + finalPath . substring ( 1 ) ;
51+ }
52+
53+ // 3. Offline Mode Logic
4954 if ( isOfflineMode ) {
50- const cleanPath = finalPath . split ( '#' ) [ 0 ] . split ( '?' ) [ 0 ] ;
51- if ( ! path . extname ( cleanPath ) ) {
52- if ( finalPath . includes ( '#' ) ) {
53- const parts = finalPath . split ( '#' ) ;
54- const prefix = parts [ 0 ] . endsWith ( '/' ) ? parts [ 0 ] : parts [ 0 ] + '/' ;
55- finalPath = prefix + 'index.html#' + parts [ 1 ] ;
56- } else {
57- finalPath += ( finalPath . endsWith ( '/' ) ? '' : '/' ) + 'index.html' ;
55+ const [ pathOnly ] = finalPath . split ( / [ ? # ] / ) ;
56+ const ext = path . extname ( pathOnly ) ;
57+ const isAsset = [ '.css' , '.js' , '.png' , '.jpg' , '.jpeg' , '.gif' , '.svg' , '.ico' ] . includes ( ext . toLowerCase ( ) ) ;
58+
59+ if ( ! isAsset && ! ext ) {
60+ if ( finalPath . endsWith ( '/' ) ) {
61+ finalPath += 'index.html' ;
62+ } else if ( ! finalPath . includes ( '#' ) ) {
63+ finalPath += '/index.html' ;
5864 }
5965 }
6066 } else {
61- // Web mode (strip index.html for clean URLs)
6267 if ( finalPath . endsWith ( '/index.html' ) ) {
6368 finalPath = finalPath . substring ( 0 , finalPath . length - 10 ) ;
6469 }
6570 }
66- return `href="${ finalPath } "` ;
71+
72+ return `${ attr } ="${ finalPath } "` ;
6773 } ) ;
6874}
6975
70- // aggregates HTML snippets from various plugins (SEO, Analytics, etc.)
7176async function processPluginHooks ( config , pageData , relativePathToRoot ) {
7277 let metaTagsHtml = '' ;
7378 let faviconLinkHtml = '' ;
@@ -77,17 +82,16 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
7782 let pluginBodyScriptsHtml = '' ;
7883
7984 const safeRoot = relativePathToRoot || './' ;
80- const indent = ' ' ; // 4 spaces for cleaner output
8185
8286 if ( config . favicon ) {
8387 const cleanFaviconPath = config . favicon . startsWith ( '/' ) ? config . favicon . substring ( 1 ) : config . favicon ;
8488 const finalFaviconHref = `${ safeRoot } ${ cleanFaviconPath } ` ;
85- faviconLinkHtml = `${ indent } <link rel="icon" href="${ finalFaviconHref } " type="image/x-icon" sizes="any">\n${ indent } <link rel="shortcut icon" href="${ finalFaviconHref } " type="image/x-icon">` ;
89+ faviconLinkHtml = `<link rel="icon" href="${ finalFaviconHref } " type="image/x-icon" sizes="any">\n<link rel="shortcut icon" href="${ finalFaviconHref } " type="image/x-icon">` ;
8690 }
8791
8892 if ( config . theme && config . theme . name && config . theme . name !== 'default' ) {
8993 const themeCssPath = `assets/css/docmd-theme-${ config . theme . name } .css` ;
90- themeCssLinkHtml = `${ indent } <link rel="stylesheet" href="${ safeRoot } ${ themeCssPath } ">` ;
94+ themeCssLinkHtml = `<link rel="stylesheet" href="${ safeRoot } ${ themeCssPath } ">` ;
9195 }
9296
9397 if ( config . plugins ?. seo ) {
@@ -103,35 +107,30 @@ async function processPluginHooks(config, pageData, relativePathToRoot) {
103107 return { metaTagsHtml, faviconLinkHtml, themeCssLinkHtml, pluginStylesHtml, pluginHeadScriptsHtml, pluginBodyScriptsHtml } ;
104108}
105109
106- // Main function to assemble the page data and render the EJS template
107110async function generateHtmlPage ( templateData , isOfflineMode = false ) {
108111 let { content, frontmatter, outputPath, headings, config } = templateData ;
109112 const { currentPagePath, prevPage, nextPage, relativePathToRoot, navigationHtml, siteTitle } = templateData ;
110113 const pageTitle = frontmatter . title ;
111114
112115 if ( ! relativePathToRoot ) templateData . relativePathToRoot = './' ;
113116
114- // Process content links and generate plugin assets
115- content = fixHtmlLinks ( content , templateData . relativePathToRoot , isOfflineMode ) ;
117+ content = fixHtmlLinks ( content , templateData . relativePathToRoot , isOfflineMode , config . base ) ;
116118 const pluginOutputs = await processPluginHooks ( config , { frontmatter, outputPath } , templateData . relativePathToRoot ) ;
117119
118- // Process footer markdown if present
119120 let footerHtml = '' ;
120121 if ( config . footer ) {
121122 if ( ! mdInstance ) mdInstance = createMarkdownItInstance ( config ) ;
122123 footerHtml = mdInstance . renderInline ( config . footer ) ;
123- footerHtml = fixHtmlLinks ( footerHtml , templateData . relativePathToRoot , isOfflineMode ) ;
124+ footerHtml = fixHtmlLinks ( footerHtml , templateData . relativePathToRoot , isOfflineMode , config . base ) ;
124125 }
125126
126- // Determine which template to use
127127 let templateName = frontmatter . noStyle === true ? 'no-style.ejs' : 'layout.ejs' ;
128128 const layoutTemplatePath = path . join ( __dirname , '..' , 'templates' , templateName ) ;
129129 if ( ! await fs . exists ( layoutTemplatePath ) ) throw new Error ( `Template not found: ${ layoutTemplatePath } ` ) ;
130130 const layoutTemplate = await fs . readFile ( layoutTemplatePath , 'utf8' ) ;
131131
132132 const isActivePage = currentPagePath && content && content . trim ( ) . length > 0 ;
133133
134- // Build the "Edit this page" link
135134 let editUrl = null ;
136135 let editLinkText = 'Edit this page' ;
137136 if ( config . editLink && config . editLink . enabled && config . editLink . baseUrl ) {
@@ -141,10 +140,9 @@ async function generateHtmlPage(templateData, isOfflineMode = false) {
141140 editLinkText = config . editLink . text || editLinkText ;
142141 }
143142
144- // Prepare complete data object for EJS
145143 const ejsData = {
146144 ...templateData ,
147- description : frontmatter . description || '' , // Fix for reference error
145+ description : frontmatter . description || '' ,
148146 footerHtml, editUrl, editLinkText, isActivePage,
149147 defaultMode : config . theme ?. defaultMode || 'light' ,
150148 logo : config . logo , sidebarConfig : config . sidebar || { } , theme : config . theme ,
@@ -155,13 +153,12 @@ async function generateHtmlPage(templateData, isOfflineMode = false) {
155153 isOfflineMode
156154 } ;
157155
158- // Render and format
159156 const rawHtml = renderHtmlPage ( layoutTemplate , ejsData , layoutTemplatePath ) ;
160157 const pkgVersion = require ( '../../package.json' ) . version ;
161158 const brandingComment = `<!-- Generated by docmd (v${ pkgVersion } ) - https://docmd.io -->\n` ;
162159
163- // Apply smart formatting to the final HTML string
164- return brandingComment + formatHtml ( rawHtml ) ;
160+ // REMOVED: formatHtml(rawHtml)
161+ return brandingComment + cleanupHtml ( rawHtml ) ;
165162}
166163
167164function renderHtmlPage ( templateContent , ejsData , filename = 'template.ejs' , options = { } ) {
@@ -173,14 +170,12 @@ function renderHtmlPage(templateContent, ejsData, filename = 'template.ejs', opt
173170 }
174171}
175172
176- // Generate the sidebar navigation HTML separately
177173async function generateNavigationHtml ( navItems , currentPagePath , relativePathToRoot , config , isOfflineMode = false ) {
178174 const navTemplatePath = path . join ( __dirname , '..' , 'templates' , 'navigation.ejs' ) ;
179175 if ( ! await fs . exists ( navTemplatePath ) ) throw new Error ( `Navigation template not found: ${ navTemplatePath } ` ) ;
180176 const navTemplate = await fs . readFile ( navTemplatePath , 'utf8' ) ;
181177 const safeRoot = relativePathToRoot || './' ;
182178
183- // We render raw here; the main page formatter will clean this up later
184179 return ejs . render ( navTemplate , {
185180 navItems, currentPagePath, relativePathToRoot : safeRoot , config, isOfflineMode, renderIcon
186181 } , { filename : navTemplatePath } ) ;
0 commit comments