Skip to content

Commit 9567915

Browse files
committed
templated timelines now
1 parent 15b174e commit 9567915

22 files changed

+908
-1222
lines changed

scripts/build.js

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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 = /<div[^>]*class="[^"]*timeline[^"]*"[^>]*>(.*?\{\{TIMELINE:[^}]+\}\}.*?)<\/div>/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(/\{\{TIMELINE:([^}]+)\}\}/);
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+
158256
function renderTemplate(template, data) {
159257
let result = template;
160258

0 commit comments

Comments
 (0)