Skip to content

Commit 77512e5

Browse files
authored
Add Jinja2 templates for HTML generation (#2)
* Add Jinja2 templates for HTML generation - Add jinja2 as a dependency - Create templates directory with base.html, page.html, and index.html - Refactor generate_html to use Jinja2 template rendering - All 47 tests pass with identical HTML output * Refactor HTML generation to use Jinja2 macros with autoescape - Add comprehensive macros.html with reusable components for all HTML generation (pagination, tool displays, messages, index items, etc.) - Enable Jinja2 autoescape for security - Update base.html, page.html, index.html to use |safe filter for pre-rendered HTML content - Simplify Python render functions to call template macros - Update test snapshots with minor formatting differences Transcript: https://gistpreview.github.io/?ffc01d1c04e47ed7934a58ae04a066d1/index.html
1 parent e943de1 commit 77512e5

14 files changed

+456
-257
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies = [
1212
"click",
1313
"click-default-group",
1414
"httpx",
15+
"jinja2",
1516
"markdown",
1617
"questionary",
1718
]

src/claude_code_publish/__init__.py

Lines changed: 100 additions & 208 deletions
Large diffs are not rendered by default.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>{% block title %}Claude Code transcript{% endblock %}</title>
7+
<style>{{ css|safe }}</style>
8+
</head>
9+
<body>
10+
<div class="container">
11+
{%- block content %}{% endblock %}
12+
</div>
13+
<script>{{ js|safe }}</script>
14+
</body>
15+
</html>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% extends "base.html" %}
2+
3+
{% block title %}Claude Code transcript - Index{% endblock %}
4+
5+
{% block content %}
6+
<h1>Claude Code transcript</h1>
7+
{{ pagination_html|safe }}
8+
<p style="color: var(--text-muted); margin-bottom: 24px;">{{ prompt_num }} prompts · {{ total_messages }} messages · {{ total_tool_calls }} tool calls · {{ total_commits }} commits · {{ total_pages }} pages</p>
9+
{{ index_items_html|safe }}
10+
{{ pagination_html|safe }}
11+
{%- endblock %}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
{# Pagination for regular pages #}
2+
{% macro pagination(current_page, total_pages) %}
3+
{% if total_pages <= 1 %}
4+
<div class="pagination"><a href="index.html" class="index-link">Index</a></div>
5+
{%- else %}
6+
<div class="pagination">
7+
<a href="index.html" class="index-link">Index</a>
8+
{% if current_page > 1 -%}
9+
<a href="page-{{ '%03d'|format(current_page - 1) }}.html">&larr; Prev</a>
10+
{%- else -%}
11+
<span class="disabled">&larr; Prev</span>
12+
{%- endif %}
13+
{% for page in range(1, total_pages + 1) -%}
14+
{% if page == current_page -%}
15+
<span class="current">{{ page }}</span>
16+
{%- else -%}
17+
<a href="page-{{ '%03d'|format(page) }}.html">{{ page }}</a>
18+
{%- endif %}
19+
{% endfor -%}
20+
{% if current_page < total_pages -%}
21+
<a href="page-{{ '%03d'|format(current_page + 1) }}.html">Next &rarr;</a>
22+
{%- else -%}
23+
<span class="disabled">Next &rarr;</span>
24+
{%- endif %}
25+
</div>
26+
{%- endif %}
27+
{% endmacro %}
28+
29+
{# Pagination for index page #}
30+
{% macro index_pagination(total_pages) %}
31+
{% if total_pages < 1 %}
32+
<div class="pagination"><span class="current">Index</span></div>
33+
{%- else %}
34+
<div class="pagination">
35+
<span class="current">Index</span>
36+
<span class="disabled">&larr; Prev</span>
37+
{% for page in range(1, total_pages + 1) -%}
38+
<a href="page-{{ '%03d'|format(page) }}.html">{{ page }}</a>
39+
{% endfor -%}
40+
{% if total_pages >= 1 -%}
41+
<a href="page-001.html">Next &rarr;</a>
42+
{%- else -%}
43+
<span class="disabled">Next &rarr;</span>
44+
{%- endif %}
45+
</div>
46+
{%- endif %}
47+
{% endmacro %}
48+
49+
{# Todo list #}
50+
{% macro todo_list(todos, tool_id) %}
51+
<div class="todo-list" data-tool-id="{{ tool_id }}"><div class="todo-header"><span class="todo-header-icon"></span> Task List</div><ul class="todo-items">
52+
{%- for todo in todos -%}
53+
{%- set status = todo.status|default('pending') -%}
54+
{%- set content = todo.content|default('') -%}
55+
{%- if status == 'completed' -%}
56+
{%- set icon = '✓' -%}
57+
{%- set status_class = 'todo-completed' -%}
58+
{%- elif status == 'in_progress' -%}
59+
{%- set icon = '→' -%}
60+
{%- set status_class = 'todo-in-progress' -%}
61+
{%- else -%}
62+
{%- set icon = '○' -%}
63+
{%- set status_class = 'todo-pending' -%}
64+
{%- endif -%}
65+
<li class="todo-item {{ status_class }}"><span class="todo-icon">{{ icon }}</span><span class="todo-content">{{ content }}</span></li>
66+
{%- endfor -%}
67+
</ul></div>
68+
{%- endmacro %}
69+
70+
{# Write tool #}
71+
{% macro write_tool(file_path, content, tool_id) %}
72+
{%- set filename = file_path.split('/')[-1] if '/' in file_path else file_path -%}
73+
<div class="file-tool write-tool" data-tool-id="{{ tool_id }}">
74+
<div class="file-tool-header write-header"><span class="file-tool-icon">📝</span> Write <span class="file-tool-path">{{ filename }}</span></div>
75+
<div class="file-tool-fullpath">{{ file_path }}</div>
76+
<div class="truncatable"><div class="truncatable-content"><pre class="file-content">{{ content }}</pre></div><button class="expand-btn">Show more</button></div>
77+
</div>
78+
{%- endmacro %}
79+
80+
{# Edit tool #}
81+
{% macro edit_tool(file_path, old_string, new_string, replace_all, tool_id) %}
82+
{%- set filename = file_path.split('/')[-1] if '/' in file_path else file_path -%}
83+
<div class="file-tool edit-tool" data-tool-id="{{ tool_id }}">
84+
<div class="file-tool-header edit-header"><span class="file-tool-icon">✏️</span> Edit <span class="file-tool-path">{{ filename }}</span>{% if replace_all %} <span class="edit-replace-all">(replace all)</span>{% endif %}</div>
85+
<div class="file-tool-fullpath">{{ file_path }}</div>
86+
<div class="truncatable"><div class="truncatable-content">
87+
<div class="edit-section edit-old"><div class="edit-label"></div><pre class="edit-content">{{ old_string }}</pre></div>
88+
<div class="edit-section edit-new"><div class="edit-label">+</div><pre class="edit-content">{{ new_string }}</pre></div>
89+
</div><button class="expand-btn">Show more</button></div>
90+
</div>
91+
{%- endmacro %}
92+
93+
{# Bash tool #}
94+
{% macro bash_tool(command, description, tool_id) %}
95+
<div class="tool-use bash-tool" data-tool-id="{{ tool_id }}">
96+
<div class="tool-header"><span class="tool-icon">$</span> Bash</div>
97+
{%- if description %}
98+
<div class="tool-description">{{ description }}</div>
99+
{%- endif -%}
100+
<div class="truncatable"><div class="truncatable-content"><pre class="bash-command">{{ command }}</pre></div><button class="expand-btn">Show more</button></div>
101+
</div>
102+
{%- endmacro %}
103+
104+
{# Generic tool use - input_json is pre-formatted so needs |safe #}
105+
{% macro tool_use(tool_name, description, input_json, tool_id) %}
106+
<div class="tool-use" data-tool-id="{{ tool_id }}"><div class="tool-header"><span class="tool-icon"></span> {{ tool_name }}</div>
107+
{%- if description -%}
108+
<div class="tool-description">{{ description }}</div>
109+
{%- endif -%}
110+
<div class="truncatable"><div class="truncatable-content"><pre class="json">{{ input_json }}</pre></div><button class="expand-btn">Show more</button></div></div>
111+
{%- endmacro %}
112+
113+
{# Tool result - content_html is pre-rendered so needs |safe #}
114+
{% macro tool_result(content_html, is_error) %}
115+
{%- set error_class = ' tool-error' if is_error else '' -%}
116+
<div class="tool-result{{ error_class }}"><div class="truncatable"><div class="truncatable-content">{{ content_html|safe }}</div><button class="expand-btn">Show more</button></div></div>
117+
{%- endmacro %}
118+
119+
{# Thinking block - content_html is pre-rendered markdown so needs |safe #}
120+
{% macro thinking(content_html) %}
121+
<div class="thinking"><div class="thinking-label">Thinking</div>{{ content_html|safe }}</div>
122+
{%- endmacro %}
123+
124+
{# Assistant text - content_html is pre-rendered markdown so needs |safe #}
125+
{% macro assistant_text(content_html) %}
126+
<div class="assistant-text">{{ content_html|safe }}</div>
127+
{%- endmacro %}
128+
129+
{# User content - content_html is pre-rendered so needs |safe #}
130+
{% macro user_content(content_html) %}
131+
<div class="user-content">{{ content_html|safe }}</div>
132+
{%- endmacro %}
133+
134+
{# Commit card (in tool results) #}
135+
{% macro commit_card(commit_hash, commit_msg, github_repo) %}
136+
{%- if github_repo -%}
137+
{%- set github_link = 'https://github.com/' ~ github_repo ~ '/commit/' ~ commit_hash -%}
138+
<div class="commit-card"><a href="{{ github_link }}"><span class="commit-card-hash">{{ commit_hash[:7] }}</span> {{ commit_msg }}</a></div>
139+
{%- else -%}
140+
<div class="commit-card"><span class="commit-card-hash">{{ commit_hash[:7] }}</span> {{ commit_msg }}</div>
141+
{%- endif %}
142+
{%- endmacro %}
143+
144+
{# Message wrapper - content_html is pre-rendered so needs |safe #}
145+
{% macro message(role_class, role_label, msg_id, timestamp, content_html) %}
146+
<div class="message {{ role_class }}" id="{{ msg_id }}"><div class="message-header"><span class="role-label">{{ role_label }}</span><a href="#{{ msg_id }}" class="timestamp-link"><time datetime="{{ timestamp }}" data-timestamp="{{ timestamp }}">{{ timestamp }}</time></a></div><div class="message-content">{{ content_html|safe }}</div></div>
147+
{%- endmacro %}
148+
149+
{# Continuation wrapper - content_html is pre-rendered so needs |safe #}
150+
{% macro continuation(content_html) %}
151+
<details class="continuation"><summary>Session continuation summary</summary>{{ content_html|safe }}</details>
152+
{%- endmacro %}
153+
154+
{# Index item (prompt) - rendered_content and stats_html are pre-rendered so need |safe #}
155+
{% macro index_item(prompt_num, link, timestamp, rendered_content, stats_html) %}
156+
<div class="index-item"><a href="{{ link }}"><div class="index-item-header"><span class="index-item-number">#{{ prompt_num }}</span><time datetime="{{ timestamp }}" data-timestamp="{{ timestamp }}">{{ timestamp }}</time></div><div class="index-item-content">{{ rendered_content|safe }}</div></a>{{ stats_html|safe }}</div>
157+
{%- endmacro %}
158+
159+
{# Index commit #}
160+
{% macro index_commit(commit_hash, commit_msg, timestamp, github_repo) %}
161+
{%- if github_repo -%}
162+
{%- set github_link = 'https://github.com/' ~ github_repo ~ '/commit/' ~ commit_hash -%}
163+
<div class="index-commit"><a href="{{ github_link }}"><div class="index-commit-header"><span class="index-commit-hash">{{ commit_hash[:7] }}</span><time datetime="{{ timestamp }}" data-timestamp="{{ timestamp }}">{{ timestamp }}</time></div><div class="index-commit-msg">{{ commit_msg }}</div></a></div>
164+
{%- else -%}
165+
<div class="index-commit"><div class="index-commit-header"><span class="index-commit-hash">{{ commit_hash[:7] }}</span><time datetime="{{ timestamp }}" data-timestamp="{{ timestamp }}">{{ timestamp }}</time></div><div class="index-commit-msg">{{ commit_msg }}</div></div>
166+
{%- endif %}
167+
{%- endmacro %}
168+
169+
{# Index stats - tool_stats_str and long_texts_html are pre-rendered so need |safe #}
170+
{% macro index_stats(tool_stats_str, long_texts_html) %}
171+
{%- if tool_stats_str or long_texts_html -%}
172+
<div class="index-item-stats">
173+
{%- if tool_stats_str -%}<span>{{ tool_stats_str }}</span>{%- endif -%}
174+
{{ long_texts_html|safe }}
175+
</div>
176+
{%- endif %}
177+
{%- endmacro %}
178+
179+
{# Long text in index - rendered_content is pre-rendered markdown so needs |safe #}
180+
{% macro index_long_text(rendered_content) %}
181+
<div class="index-item-long-text"><div class="truncatable"><div class="truncatable-content"><div class="index-item-long-text-content">{{ rendered_content|safe }}</div></div><button class="expand-btn">Show more</button></div></div>
182+
{%- endmacro %}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{% extends "base.html" %}
2+
3+
{% block title %}Claude Code transcript - page {{ page_num }}{% endblock %}
4+
5+
{% block content %}
6+
<h1><a href="index.html" style="color: inherit; text-decoration: none;">Claude Code transcript</a> - page {{ page_num }}/{{ total_pages }}</h1>
7+
{{ pagination_html|safe }}
8+
{{ messages_html|safe }}
9+
{{ pagination_html|safe }}
10+
{%- endblock %}

tests/__snapshots__/test_generate_html/TestGenerateHtml.test_generates_index_html.html

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,22 +126,38 @@
126126
<body>
127127
<div class="container">
128128
<h1>Claude Code transcript</h1>
129-
<div class="pagination">
129+
130+
131+
<div class="pagination">
130132
<span class="current">Index</span>
131-
<span class="disabled"> Prev</span>
133+
<span class="disabled">&larr; Prev</span>
132134
<a href="page-001.html">1</a>
133135
<a href="page-002.html">2</a>
134-
<a href="page-001.html">Next </a>
136+
<a href="page-001.html">Next &rarr;</a>
135137
</div>
138+
136139
<p style="color: var(--text-muted); margin-bottom: 24px;">5 prompts · 33 messages · 12 tool calls · 2 commits · 2 pages</p>
137-
<div class="index-item"><a href="page-001.html#msg-2025-12-24T10-00-00-000Z"><div class="index-item-header"><span class="index-item-number">#1</span><time datetime="2025-12-24T10:00:00.000Z" data-timestamp="2025-12-24T10:00:00.000Z">2025-12-24T10:00:00.000Z</time></div><div class="index-item-content"><p>Create a simple Python function to add two numbers</p></div></a><div class="index-item-stats"><span>3 bash · 1 write · 1 todo</span></div></div><div class="index-commit"><a href="https://github.com/example/project/commit/abc1234"><div class="index-commit-header"><span class="index-commit-hash">abc1234</span><time datetime="2025-12-24T10:00:40.000Z" data-timestamp="2025-12-24T10:00:40.000Z">2025-12-24T10:00:40.000Z</time></div><div class="index-commit-msg">Add math_utils with add function</div></a></div><div class="index-item"><a href="page-001.html#msg-2025-12-24T10-01-00-000Z"><div class="index-item-header"><span class="index-item-number">#2</span><time datetime="2025-12-24T10:01:00.000Z" data-timestamp="2025-12-24T10:01:00.000Z">2025-12-24T10:01:00.000Z</time></div><div class="index-item-content"><p>Now edit the file to add a subtract function</p></div></a><div class="index-item-stats"><span>1 glob · 1 edit · 1 grep</span></div></div><div class="index-item"><a href="page-001.html#msg-2025-12-24T10-02-00-000Z"><div class="index-item-header"><span class="index-item-number">#3</span><time datetime="2025-12-24T10:02:00.000Z" data-timestamp="2025-12-24T10:02:00.000Z">2025-12-24T10:02:00.000Z</time></div><div class="index-item-content"><p>Run the tests again</p></div></a><div class="index-item-stats"><span>1 bash</span></div></div><div class="index-item"><a href="page-001.html#msg-2025-12-24T10-03-00-000Z"><div class="index-item-header"><span class="index-item-number">#4</span><time datetime="2025-12-24T10:03:00.000Z" data-timestamp="2025-12-24T10:03:00.000Z">2025-12-24T10:03:00.000Z</time></div><div class="index-item-content"><p>Fix the issue and commit</p></div></a><div class="index-item-stats"><span>1 edit · 1 bash</span></div></div><div class="index-commit"><a href="https://github.com/example/project/commit/def5678"><div class="index-commit-header"><span class="index-commit-hash">def5678</span><time datetime="2025-12-24T10:03:20.000Z" data-timestamp="2025-12-24T10:03:20.000Z">2025-12-24T10:03:20.000Z</time></div><div class="index-commit-msg">Add subtract function and fix tests</div></a></div><div class="index-item"><a href="page-002.html#msg-2025-12-24T10-05-00-000Z"><div class="index-item-header"><span class="index-item-number">#5</span><time datetime="2025-12-24T10:05:00.000Z" data-timestamp="2025-12-24T10:05:00.000Z">2025-12-24T10:05:00.000Z</time></div><div class="index-item-content"><p>Add a multiply function too</p></div></a><div class="index-item-stats"><span>1 edit</span></div></div>
138-
<div class="pagination">
140+
141+
<div class="index-item"><a href="page-001.html#msg-2025-12-24T10-00-00-000Z"><div class="index-item-header"><span class="index-item-number">#1</span><time datetime="2025-12-24T10:00:00.000Z" data-timestamp="2025-12-24T10:00:00.000Z">2025-12-24T10:00:00.000Z</time></div><div class="index-item-content"><p>Create a simple Python function to add two numbers</p></div></a><div class="index-item-stats"><span>3 bash · 1 write · 1 todo</span>
142+
</div></div><div class="index-commit"><a href="https://github.com/example/project/commit/abc1234"><div class="index-commit-header"><span class="index-commit-hash">abc1234</span><time datetime="2025-12-24T10:00:40.000Z" data-timestamp="2025-12-24T10:00:40.000Z">2025-12-24T10:00:40.000Z</time></div><div class="index-commit-msg">Add math_utils with add function</div></a></div>
143+
<div class="index-item"><a href="page-001.html#msg-2025-12-24T10-01-00-000Z"><div class="index-item-header"><span class="index-item-number">#2</span><time datetime="2025-12-24T10:01:00.000Z" data-timestamp="2025-12-24T10:01:00.000Z">2025-12-24T10:01:00.000Z</time></div><div class="index-item-content"><p>Now edit the file to add a subtract function</p></div></a><div class="index-item-stats"><span>1 glob · 1 edit · 1 grep</span>
144+
</div></div>
145+
<div class="index-item"><a href="page-001.html#msg-2025-12-24T10-02-00-000Z"><div class="index-item-header"><span class="index-item-number">#3</span><time datetime="2025-12-24T10:02:00.000Z" data-timestamp="2025-12-24T10:02:00.000Z">2025-12-24T10:02:00.000Z</time></div><div class="index-item-content"><p>Run the tests again</p></div></a><div class="index-item-stats"><span>1 bash</span>
146+
</div></div>
147+
<div class="index-item"><a href="page-001.html#msg-2025-12-24T10-03-00-000Z"><div class="index-item-header"><span class="index-item-number">#4</span><time datetime="2025-12-24T10:03:00.000Z" data-timestamp="2025-12-24T10:03:00.000Z">2025-12-24T10:03:00.000Z</time></div><div class="index-item-content"><p>Fix the issue and commit</p></div></a><div class="index-item-stats"><span>1 edit · 1 bash</span>
148+
</div></div><div class="index-commit"><a href="https://github.com/example/project/commit/def5678"><div class="index-commit-header"><span class="index-commit-hash">def5678</span><time datetime="2025-12-24T10:03:20.000Z" data-timestamp="2025-12-24T10:03:20.000Z">2025-12-24T10:03:20.000Z</time></div><div class="index-commit-msg">Add subtract function and fix tests</div></a></div>
149+
<div class="index-item"><a href="page-002.html#msg-2025-12-24T10-05-00-000Z"><div class="index-item-header"><span class="index-item-number">#5</span><time datetime="2025-12-24T10:05:00.000Z" data-timestamp="2025-12-24T10:05:00.000Z">2025-12-24T10:05:00.000Z</time></div><div class="index-item-content"><p>Add a multiply function too</p></div></a><div class="index-item-stats"><span>1 edit</span>
150+
</div></div>
151+
152+
153+
<div class="pagination">
139154
<span class="current">Index</span>
140-
<span class="disabled"> Prev</span>
155+
<span class="disabled">&larr; Prev</span>
141156
<a href="page-001.html">1</a>
142157
<a href="page-002.html">2</a>
143-
<a href="page-001.html">Next </a>
158+
<a href="page-001.html">Next &rarr;</a>
144159
</div>
160+
145161
</div>
146162
<script>
147163
document.querySelectorAll('time[data-timestamp]').forEach(function(el) {

0 commit comments

Comments
 (0)