feat: add custom placeholder mapping for non-standard templates

Added optional placeholderMapping to project.json for templates that
don't follow the standard ITEM_{PROPERTY} convention.

Features:
- Override default placeholder names per field
- Useful for legacy templates or migration from other tools
- Fallback to ITEM_{PROPERTY} convention when not specified

Example usage:
{
  "fieldMapping": {
    "id": "ID",
    "title": "Title"
  },
  "placeholderMapping": {
    "id": "TASK_ID",     // Use {{TASK_ID}} instead of {{ITEM_ID}}
    "title": "TASK_NAME" // Use {{TASK_NAME}} instead of {{ITEM_TITLE}}
  }
}

Changes:
- generator.js: Check for cfg.placeholderMapping before using default
- Added 2 new tests for custom mapping behavior
- Updated TEMPLATE_V2_GUIDE.md with documentation and examples

All 58 tests passing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-28 03:24:33 +01:00
parent 2bab447fa8
commit b5e4550efd
3 changed files with 76 additions and 1 deletions

View File

@@ -278,5 +278,52 @@ describe('Timeline Generator', () => {
expect(viewBoxMatch[1]).toBe(widthMatch[1])
expect(viewBoxMatch[2]).toBe(heightMatch[1])
})
it('should support custom placeholder mapping', () => {
const customConfig = {
...config,
placeholderMapping: {
id: 'TASK_ID', // Map item.id to {{TASK_ID}} instead of {{ITEM_ID}}
title: 'TASK_NAME' // Map item.title to {{TASK_NAME}} instead of {{ITEM_TITLE}}
}
}
// Create template with custom placeholders
const customTemplate = `<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<g id="month-template" style="display:none">
<text x="{{MONTH_TEXT_X}}" y="{{MONTH_LABEL_Y}}">{{MONTH_LABEL}}</text>
</g>
<g id="lane-template" style="display:none">
<text>{{LANE_NAME}}</text>
</g>
<g id="item-template" style="display:none">
<text x="{{TEXT_X}}" y="{{TEXT_Y}}">{{TASK_ID}}: {{TASK_NAME}}</text>
</g>
</defs>
{{MONTHS}}
{{LANES}}
</svg>`
const result = timelineGenerator.generate(items, customConfig, customTemplate)
// Should use custom placeholder names
expect(result).toContain('T-1: First Task') // TASK_ID: TASK_NAME
expect(result).toContain('T-2: Second Task')
// Should not contain default placeholder syntax
expect(result).not.toContain('{{ITEM_ID}}')
expect(result).not.toContain('{{ITEM_TITLE}}')
})
it('should use default convention when placeholderMapping is not provided', () => {
const template = createSampleTemplate()
const result = timelineGenerator.generate(items, config, template)
// Should use default ITEM_* convention
expect(result).toContain('T-1 First Task') // Default template has space, not colon
expect(result).not.toContain('{{ITEM_ID}}')
expect(result).not.toContain('{{ITEM_TITLE}}')
})
})
})