generated from coulomb/repo-seed
refactor: use abstract ITEM placeholders with dynamic property mapping
Changed from fixed TASK placeholders to flexible ITEM placeholders that
automatically map all CSV fields to template placeholders.
Key changes:
- Renamed task-template → item-template in all templates
- Changed TASK_* → ITEM_* placeholder naming
- Implemented dynamic placeholder generation from item properties
- Any field in fieldMapping now creates ITEM_{FIELD} placeholder
- Updated all tests and documentation
Naming convention: CSV field → item.property → ITEM_PROPERTY
Example: "assignee" → item.assignee → {{ITEM_ASSIGNEE}}
This enables users to add custom fields without modifying generator code:
- Add "assignee": "Assignee" to fieldMapping
- Use {{ITEM_ASSIGNEE}} in template
- No code changes required
Benefits:
- More flexible and extensible
- Clearer abstraction (items vs tasks)
- Consistent naming convention
- Better documented
All 56 tests passing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
39
generator.js
39
generator.js
@@ -54,13 +54,13 @@ window.timelineGenerator = {
|
||||
validateTemplate(template) {
|
||||
const hasMonthTemplate = template.includes('id="month-template"');
|
||||
const hasLaneTemplate = template.includes('id="lane-template"');
|
||||
const hasTaskTemplate = template.includes('id="task-template"');
|
||||
const hasItemTemplate = template.includes('id="item-template"');
|
||||
|
||||
if (!hasMonthTemplate || !hasLaneTemplate || !hasTaskTemplate) {
|
||||
if (!hasMonthTemplate || !hasLaneTemplate || !hasItemTemplate) {
|
||||
const missing = [];
|
||||
if (!hasMonthTemplate) missing.push('month-template');
|
||||
if (!hasLaneTemplate) missing.push('lane-template');
|
||||
if (!hasTaskTemplate) missing.push('task-template');
|
||||
if (!hasItemTemplate) missing.push('item-template');
|
||||
|
||||
throw new Error(
|
||||
`Template is missing required elements: ${missing.join(', ')}. ` +
|
||||
@@ -97,7 +97,7 @@ window.timelineGenerator = {
|
||||
// Extract template elements (will throw if not found)
|
||||
const monthTemplate = this.extractTemplate(template, 'month-template');
|
||||
const laneTemplate = this.extractTemplate(template, 'lane-template');
|
||||
const taskTemplate = this.extractTemplate(template, 'task-template');
|
||||
const itemTemplate = this.extractTemplate(template, 'item-template');
|
||||
|
||||
const monthLabelY = 90;
|
||||
const gridTop = top - 20;
|
||||
@@ -156,7 +156,7 @@ window.timelineGenerator = {
|
||||
laneElement = laneElement.replace(/id="lane-template"/, '');
|
||||
laneBlocks += laneElement;
|
||||
|
||||
// Generate tasks for this lane
|
||||
// Generate items for this lane
|
||||
const laneItems = laneMap.get(laneName).sort((a, b) => a.due - b.due);
|
||||
laneItems.forEach((it, idx) => {
|
||||
const mi = monthIndexForDate(it.due);
|
||||
@@ -164,19 +164,26 @@ window.timelineGenerator = {
|
||||
const cx = left + clampedMi * monthWidth + monthWidth * 0.5;
|
||||
const cy = laneY + 10 + idx * 18;
|
||||
|
||||
const taskValues = {
|
||||
TASK_X: cx,
|
||||
TASK_Y: cy,
|
||||
// Start with layout placeholders
|
||||
const itemValues = {
|
||||
ITEM_X: cx,
|
||||
ITEM_Y: cy,
|
||||
TEXT_X: cx + 12,
|
||||
TEXT_Y: cy + 4,
|
||||
TASK_ID: this.escapeXml(it.id || ""),
|
||||
TASK_TITLE: this.escapeXml(it.title || "")
|
||||
TEXT_Y: cy + 4
|
||||
};
|
||||
|
||||
let taskElement = this.replacePlaceholders(taskTemplate, taskValues);
|
||||
taskElement = taskElement.replace(/style="display:\s*none;?"/, '');
|
||||
taskElement = taskElement.replace(/id="task-template"/, '');
|
||||
laneBlocks += taskElement;
|
||||
// Dynamically add data placeholders from all item properties
|
||||
for (const [key, value] of Object.entries(it)) {
|
||||
if (key !== 'due') { // Skip due date as it's used for positioning
|
||||
const placeholderName = `ITEM_${key.toUpperCase()}`;
|
||||
itemValues[placeholderName] = this.escapeXml(value || "");
|
||||
}
|
||||
}
|
||||
|
||||
let itemElement = this.replacePlaceholders(itemTemplate, itemValues);
|
||||
itemElement = itemElement.replace(/style="display:\s*none;?"/, '');
|
||||
itemElement = itemElement.replace(/id="item-template"/, '');
|
||||
laneBlocks += itemElement;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -193,7 +200,7 @@ window.timelineGenerator = {
|
||||
processedTemplate = processedTemplate
|
||||
.replace(/<g id="month-template"[^>]*>[\s\S]*?<\/g>/, '')
|
||||
.replace(/<g id="lane-template"[^>]*>[\s\S]*?<\/g>/, '')
|
||||
.replace(/<g id="task-template"[^>]*>[\s\S]*?<\/g>/, '');
|
||||
.replace(/<g id="item-template"[^>]*>[\s\S]*?<\/g>/, '');
|
||||
|
||||
// Add width and height attributes to the SVG element
|
||||
processedTemplate = processedTemplate.replace(
|
||||
|
||||
Reference in New Issue
Block a user