@@ -41,7 +41,7 @@ async function generateHTML(isDev = false) {
4141 if ( slides . length === 1 ) {
4242 // Generate single index.html for the presentation
4343 const slide = slides [ 0 ] ;
44- const slidesContent = buildSlidesContent ( [ slide ] ) ;
44+ const slidesContent = await buildSlidesContent ( [ slide ] ) ;
4545 const title = extractTitle ( slide . content , slide . isHtml ) ;
4646
4747 let html = renderTemplate ( slidesTemplate , {
@@ -55,7 +55,7 @@ async function generateHTML(isDev = false) {
5555 } else {
5656 // Generate a separate HTML file for each slide
5757 for ( const slide of slides ) {
58- const slidesContent = buildSlidesContent ( [ slide ] ) ;
58+ const slidesContent = await buildSlidesContent ( [ slide ] ) ;
5959 const title = extractTitle ( slide . content , slide . isHtml ) ;
6060
6161 let html = renderTemplate ( slidesTemplate , {
@@ -113,7 +113,7 @@ async function generateIndexPage(presentationFiles, distDir, isDev) {
113113 console . log ( '✅ Generated index.html (landing page)' ) ;
114114}
115115
116- function buildSlidesContent ( slides ) {
116+ async function buildSlidesContent ( slides ) {
117117 let slidesContent = '' ;
118118 let markdownContent = '' ;
119119 let isInMarkdownSection = false ;
@@ -124,7 +124,7 @@ function buildSlidesContent(slides) {
124124 if ( slide . isHtml ) {
125125 // Close markdown section if we're in one
126126 if ( isInMarkdownSection ) {
127- slidesContent += createMarkdownSection ( markdownContent ) ;
127+ slidesContent += await createMarkdownSection ( markdownContent ) ;
128128 markdownContent = '' ;
129129 isInMarkdownSection = false ;
130130 }
@@ -139,14 +139,16 @@ function buildSlidesContent(slides) {
139139
140140 // Close final markdown section if needed
141141 if ( isInMarkdownSection ) {
142- slidesContent += createMarkdownSection ( markdownContent ) ;
142+ slidesContent += await createMarkdownSection ( markdownContent ) ;
143143 }
144144
145145 return slidesContent ;
146146}
147147
148- function createMarkdownSection ( markdownContent ) {
149- const cleanContent = markdownContent . replace ( / \n \n - - - \n \n $ / , '' ) ;
148+ async function createMarkdownSection ( markdownContent ) {
149+ // Process timeline imports before cleaning content
150+ const processedContent = await processTimelineImports ( markdownContent ) ;
151+ const cleanContent = processedContent . replace ( / \n \n - - - \n \n $ / , '' ) ;
150152 return `<section data-markdown data-separator="^---" data-separator-vertical="^--">
151153 <textarea data-template>
152154${ cleanContent }
@@ -155,6 +157,102 @@ ${cleanContent}
155157 ` ;
156158}
157159
160+ async function processTimelineImports ( content ) {
161+ let processedContent = content ;
162+
163+ // Find all timeline divs with their placeholders
164+ // Match: <div class="timeline" ... data-timeline-fragments-select="...">{{TIMELINE:filename}}</div>
165+ const timelineDivPattern = / < d i v [ ^ > ] * c l a s s = " [ ^ " ] * t i m e l i n e [ ^ " ] * " [ ^ > ] * > ( .* ?\{ \{ T I M E L I N E : [ ^ } ] + \} \} .* ?) < \/ d i v > / gs;
166+ const allTimelineDivs = [ ...content . matchAll ( timelineDivPattern ) ] ;
167+
168+ console . log ( `Found ${ allTimelineDivs . length } timeline div(s) in content` ) ;
169+
170+ for ( const divMatch of allTimelineDivs ) {
171+ const fullDiv = divMatch [ 0 ] ;
172+ const divInner = divMatch [ 1 ] ;
173+
174+ // Extract the placeholder from within this div
175+ const placeholderMatch = divInner . match ( / \{ \{ T I M E L I N E : ( [ ^ } ] + ) \} \} / ) ;
176+ if ( ! placeholderMatch ) continue ;
177+
178+ const placeholder = placeholderMatch [ 0 ] ;
179+ const filename = placeholderMatch [ 1 ] . trim ( ) ;
180+
181+ try {
182+ // Read the timeline HTML file
183+ const timelinePath = path . join ( __dirname , '../slides/templates/timelines' , `${ filename } .html` ) ;
184+ let timelineContent = await fs . readFile ( timelinePath , 'utf-8' ) ;
185+
186+ // Check for fragment attributes (select and color variants)
187+ const fragmentTypes = [
188+ { attr : 'data-timeline-fragments-select' , className : 'select' } ,
189+ { attr : 'data-timeline-fragments-color-0' , className : 'color-0' } ,
190+ { attr : 'data-timeline-fragments-color-1' , className : 'color-1' } ,
191+ { attr : 'data-timeline-fragments-color-2' , className : 'color-2' }
192+ ] ;
193+
194+ let hasFragments = false ;
195+
196+ for ( const { attr, className } of fragmentTypes ) {
197+ const fragmentsMatch = fullDiv . match ( new RegExp ( `${ attr } ="([^"]*)"` , 'i' ) ) ;
198+
199+ if ( fragmentsMatch ) {
200+ hasFragments = true ;
201+ const fragmentsAttr = fragmentsMatch [ 1 ] ;
202+ console . log ( `📝 Processing timeline ${ filename } with ${ attr } : ${ fragmentsAttr } ` ) ;
203+
204+ // Parse the fragments attribute: "year:index,year:index,..."
205+ const fragmentPairs = fragmentsAttr . split ( ',' ) . map ( s => s . trim ( ) ) ;
206+
207+ for ( const pair of fragmentPairs ) {
208+ const [ year , index ] = pair . split ( ':' ) . map ( s => s . trim ( ) ) ;
209+ if ( year && index ) {
210+ console . log ( ` Year ${ year } -> Index ${ index } (${ className } )` ) ;
211+
212+ // Replace each element type that matches this year
213+ timelineContent = timelineContent . replace (
214+ new RegExp ( `<div class="timeline-dot" style="--year: ${ year } ;">` , 'g' ) ,
215+ `<div class="timeline-dot fragment custom ${ className } " data-fragment-index="${ index } " style="--year: ${ year } ;">`
216+ ) ;
217+
218+ timelineContent = timelineContent . replace (
219+ new RegExp ( `<div class="timeline-item" style="--year: ${ year } ;">` , 'g' ) ,
220+ `<div class="timeline-item fragment custom ${ className } " data-fragment-index="${ index } " style="--year: ${ year } ;">`
221+ ) ;
222+
223+ if ( attr !== 'data-timeline-fragments-select' ) {
224+ // Also apply to timeline-year within timeline-item
225+ timelineContent = timelineContent . replace (
226+ new RegExp ( `(<div class="timeline-item fragment custom ${ className } " data-fragment-index="${ index } " style="--year: ${ year } ;">\\s*<div class="timeline-content">\\s*)<div class="timeline-year"` , 'g' ) ,
227+ `$1<div class="timeline-year fragment custom ${ className } " data-fragment-index="${ index } "`
228+ ) ;
229+ }
230+ }
231+ }
232+
233+ console . log ( `✅ Applied ${ className } fragment classes for ${ filename } ` ) ;
234+ }
235+ }
236+
237+ if ( ! hasFragments ) {
238+ console . log ( `📝 Processing timeline ${ filename } (no fragments)` ) ;
239+ }
240+
241+ // Replace THIS SPECIFIC placeholder with the processed content
242+ // Use a more specific replacement that only targets this exact div
243+ const newDiv = fullDiv . replace ( placeholder , timelineContent . trim ( ) ) ;
244+ processedContent = processedContent . replace ( fullDiv , newDiv ) ;
245+
246+ console . log ( `✅ Imported timeline: ${ filename } .html` ) ;
247+ } catch ( error ) {
248+ console . error ( `❌ Failed to import timeline ${ filename } .html:` , error . message ) ;
249+ // Keep the placeholder if import fails
250+ }
251+ }
252+
253+ return processedContent ;
254+ }
255+
158256function renderTemplate ( template , data ) {
159257 let result = template ;
160258
0 commit comments