Skip to content

Commit 722787b

Browse files
committed
Move CSS and JS from Python strings to Jinja templates
Extract the CSS and JavaScript code from __init__.py into proper template files: - templates/styles.css: All CSS styles - templates/scripts.js: Copy button WebComponent and other JS Add get_css() and get_js() functions to load and render these templates, replacing the CSS and JS Python string constants. This improves code organization by keeping frontend assets in separate files that can be edited independently.
1 parent 3a9ba64 commit 722787b

File tree

7 files changed

+246
-258
lines changed

7 files changed

+246
-258
lines changed

src/claude_code_transcripts/__init__.py

Lines changed: 22 additions & 226 deletions
Large diffs are not rendered by default.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
class CopyButton extends HTMLElement {
2+
constructor() {
3+
super();
4+
this.attachShadow({ mode: 'open' });
5+
}
6+
connectedCallback() {
7+
const label = this.getAttribute('label') || 'Copy';
8+
const style = document.createElement('style');
9+
style.textContent = 'button { background: transparent; border: 1px solid #757575; border-radius: 4px; padding: 2px 6px; font-size: 0.7rem; color: #757575; cursor: pointer; white-space: nowrap; font-family: inherit; } button:hover { background: rgba(0,0,0,0.05); border-color: #1976d2; color: #1976d2; } button.copied { background: #e8f5e9; border-color: #4caf50; color: #2e7d32; }';
10+
const btn = document.createElement('button');
11+
btn.textContent = label;
12+
btn.addEventListener('click', (e) => this.handleClick(e, btn, label));
13+
this.shadowRoot.appendChild(style);
14+
this.shadowRoot.appendChild(btn);
15+
}
16+
handleClick(e, btn, label) {
17+
e.preventDefault();
18+
e.stopPropagation();
19+
let content = this.getAttribute('data-content');
20+
if (!content) {
21+
const selector = this.getAttribute('data-content-from');
22+
if (selector) {
23+
const el = this.closest('.message, .index-item, .index-commit, .search-result')?.querySelector(selector) || document.querySelector(selector);
24+
if (el) content = el.innerText;
25+
}
26+
}
27+
if (content) {
28+
navigator.clipboard.writeText(content).then(() => {
29+
btn.textContent = 'Copied!';
30+
btn.classList.add('copied');
31+
setTimeout(() => { btn.textContent = label; btn.classList.remove('copied'); }, 2000);
32+
});
33+
}
34+
}
35+
}
36+
customElements.define('copy-button', CopyButton);
37+
document.querySelectorAll('time[data-timestamp]').forEach(function(el) {
38+
const timestamp = el.getAttribute('data-timestamp');
39+
const date = new Date(timestamp);
40+
const now = new Date();
41+
const isToday = date.toDateString() === now.toDateString();
42+
const timeStr = date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
43+
if (isToday) { el.textContent = timeStr; }
44+
else { el.textContent = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' ' + timeStr; }
45+
});
46+
document.querySelectorAll('pre.json').forEach(function(el) {
47+
let text = el.textContent;
48+
text = text.replace(/"([^"]+)":/g, '<span style="color: #ce93d8">"$1"</span>:');
49+
text = text.replace(/: "([^"]*)"/g, ': <span style="color: #81d4fa">"$1"</span>');
50+
text = text.replace(/: (\d+)/g, ': <span style="color: #ffcc80">$1</span>');
51+
text = text.replace(/: (true|false|null)/g, ': <span style="color: #f48fb1">$1</span>');
52+
el.innerHTML = text;
53+
});
54+
document.querySelectorAll('.truncatable').forEach(function(wrapper) {
55+
const content = wrapper.querySelector('.truncatable-content');
56+
const btn = wrapper.querySelector('.expand-btn');
57+
if (content.scrollHeight > 250) {
58+
wrapper.classList.add('truncated');
59+
btn.addEventListener('click', function() {
60+
if (wrapper.classList.contains('truncated')) { wrapper.classList.remove('truncated'); wrapper.classList.add('expanded'); btn.textContent = 'Show less'; }
61+
else { wrapper.classList.remove('expanded'); wrapper.classList.add('truncated'); btn.textContent = 'Show more'; }
62+
});
63+
}
64+
});
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
:root { --bg-color: #f5f5f5; --card-bg: #ffffff; --user-bg: #e3f2fd; --user-border: #1976d2; --assistant-bg: #f5f5f5; --assistant-border: #9e9e9e; --thinking-bg: #fff8e1; --thinking-border: #ffc107; --thinking-text: #666; --tool-bg: #f3e5f5; --tool-border: #9c27b0; --tool-result-bg: #e8f5e9; --tool-error-bg: #ffebee; --text-color: #212121; --text-muted: #757575; --code-bg: #263238; --code-text: #aed581; }
2+
* { box-sizing: border-box; }
3+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg-color); color: var(--text-color); margin: 0; padding: 16px; line-height: 1.6; }
4+
.container { max-width: 800px; margin: 0 auto; }
5+
h1 { font-size: 1.5rem; margin-bottom: 24px; padding-bottom: 8px; border-bottom: 2px solid var(--user-border); }
6+
.header-row { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; border-bottom: 2px solid var(--user-border); padding-bottom: 8px; margin-bottom: 24px; }
7+
.header-row h1 { border-bottom: none; padding-bottom: 0; margin-bottom: 0; flex: 1; min-width: 200px; }
8+
.message { margin-bottom: 16px; border-radius: 12px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
9+
.message.user { background: var(--user-bg); border-left: 4px solid var(--user-border); }
10+
.message.assistant { background: var(--card-bg); border-left: 4px solid var(--assistant-border); }
11+
.message.tool-reply { background: #fff8e1; border-left: 4px solid #ff9800; }
12+
.tool-reply .role-label { color: #e65100; }
13+
.tool-reply .tool-result { background: transparent; padding: 0; margin: 0; }
14+
.tool-reply .tool-result .truncatable.truncated::after { background: linear-gradient(to bottom, transparent, #fff8e1); }
15+
.message-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 16px; background: rgba(0,0,0,0.03); font-size: 0.85rem; }
16+
.header-actions { display: flex; align-items: center; gap: 8px; }
17+
.role-label { font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
18+
.user .role-label { color: var(--user-border); }
19+
time { color: var(--text-muted); font-size: 0.8rem; }
20+
.timestamp-link { color: inherit; text-decoration: none; }
21+
.timestamp-link:hover { text-decoration: underline; }
22+
.message:target { animation: highlight 2s ease-out; }
23+
@keyframes highlight { 0% { background-color: rgba(25, 118, 210, 0.2); } 100% { background-color: transparent; } }
24+
.message-content { padding: 16px; }
25+
.message-content p { margin: 0 0 12px 0; }
26+
.message-content p:last-child { margin-bottom: 0; }
27+
.thinking { background: var(--thinking-bg); border: 1px solid var(--thinking-border); border-radius: 8px; padding: 12px; margin: 12px 0; font-size: 0.9rem; color: var(--thinking-text); }
28+
.thinking-label { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; color: #f57c00; margin-bottom: 8px; }
29+
.thinking p { margin: 8px 0; }
30+
.assistant-text { margin: 8px 0; }
31+
.tool-use { background: var(--tool-bg); border: 1px solid var(--tool-border); border-radius: 8px; padding: 12px; margin: 12px 0; }
32+
.tool-header { font-weight: 600; color: var(--tool-border); margin-bottom: 8px; display: flex; align-items: center; gap: 8px; }
33+
.tool-icon { font-size: 1.1rem; }
34+
.tool-description { font-size: 0.9rem; color: var(--text-muted); margin-bottom: 8px; font-style: italic; }
35+
.tool-result { background: var(--tool-result-bg); border-radius: 8px; padding: 12px; margin: 12px 0; }
36+
.tool-result.tool-error { background: var(--tool-error-bg); }
37+
.file-tool { border-radius: 8px; padding: 12px; margin: 12px 0; }
38+
.write-tool { background: linear-gradient(135deg, #e3f2fd 0%, #e8f5e9 100%); border: 1px solid #4caf50; }
39+
.edit-tool { background: linear-gradient(135deg, #fff3e0 0%, #fce4ec 100%); border: 1px solid #ff9800; }
40+
.file-tool-header { font-weight: 600; margin-bottom: 4px; display: flex; align-items: center; gap: 8px; font-size: 0.95rem; }
41+
.write-header { color: #2e7d32; }
42+
.edit-header { color: #e65100; }
43+
.file-tool-icon { font-size: 1rem; }
44+
.file-tool-path { font-family: monospace; background: rgba(0,0,0,0.08); padding: 2px 8px; border-radius: 4px; }
45+
.file-tool-fullpath { font-family: monospace; font-size: 0.8rem; color: var(--text-muted); margin-bottom: 8px; word-break: break-all; }
46+
.file-content { margin: 0; }
47+
.edit-section { display: flex; margin: 4px 0; border-radius: 4px; overflow: hidden; }
48+
.edit-label { padding: 8px 12px; font-weight: bold; font-family: monospace; display: flex; align-items: flex-start; }
49+
.edit-old { background: #fce4ec; }
50+
.edit-old .edit-label { color: #b71c1c; background: #f8bbd9; }
51+
.edit-old .edit-content { color: #880e4f; }
52+
.edit-new { background: #e8f5e9; }
53+
.edit-new .edit-label { color: #1b5e20; background: #a5d6a7; }
54+
.edit-new .edit-content { color: #1b5e20; }
55+
.edit-content { margin: 0; flex: 1; background: transparent; font-size: 0.85rem; }
56+
.edit-replace-all { font-size: 0.75rem; font-weight: normal; color: var(--text-muted); }
57+
.write-tool .truncatable.truncated::after { background: linear-gradient(to bottom, transparent, #e6f4ea); }
58+
.edit-tool .truncatable.truncated::after { background: linear-gradient(to bottom, transparent, #fff0e5); }
59+
.todo-list { background: linear-gradient(135deg, #e8f5e9 0%, #f1f8e9 100%); border: 1px solid #81c784; border-radius: 8px; padding: 12px; margin: 12px 0; }
60+
.todo-header { font-weight: 600; color: #2e7d32; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; font-size: 0.95rem; }
61+
.todo-items { list-style: none; margin: 0; padding: 0; }
62+
.todo-item { display: flex; align-items: flex-start; gap: 10px; padding: 6px 0; border-bottom: 1px solid rgba(0,0,0,0.06); font-size: 0.9rem; }
63+
.todo-item:last-child { border-bottom: none; }
64+
.todo-icon { flex-shrink: 0; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; font-weight: bold; border-radius: 50%; }
65+
.todo-completed .todo-icon { color: #2e7d32; background: rgba(46, 125, 50, 0.15); }
66+
.todo-completed .todo-content { color: #558b2f; text-decoration: line-through; }
67+
.todo-in-progress .todo-icon { color: #f57c00; background: rgba(245, 124, 0, 0.15); }
68+
.todo-in-progress .todo-content { color: #e65100; font-weight: 500; }
69+
.todo-pending .todo-icon { color: #757575; background: rgba(0,0,0,0.05); }
70+
.todo-pending .todo-content { color: #616161; }
71+
pre { background: var(--code-bg); color: var(--code-text); padding: 12px; border-radius: 6px; overflow-x: auto; font-size: 0.85rem; line-height: 1.5; margin: 8px 0; white-space: pre-wrap; word-wrap: break-word; }
72+
pre.json { color: #e0e0e0; }
73+
code { background: rgba(0,0,0,0.08); padding: 2px 6px; border-radius: 4px; font-size: 0.9em; }
74+
pre code { background: none; padding: 0; }
75+
.user-content { margin: 0; }
76+
.truncatable { position: relative; }
77+
.truncatable.truncated .truncatable-content { max-height: 200px; overflow: hidden; }
78+
.truncatable.truncated::after { content: ''; position: absolute; bottom: 32px; left: 0; right: 0; height: 60px; background: linear-gradient(to bottom, transparent, var(--card-bg)); pointer-events: none; }
79+
.message.user .truncatable.truncated::after { background: linear-gradient(to bottom, transparent, var(--user-bg)); }
80+
.message.tool-reply .truncatable.truncated::after { background: linear-gradient(to bottom, transparent, #fff8e1); }
81+
.tool-use .truncatable.truncated::after { background: linear-gradient(to bottom, transparent, var(--tool-bg)); }
82+
.tool-result .truncatable.truncated::after { background: linear-gradient(to bottom, transparent, var(--tool-result-bg)); }
83+
.expand-btn { display: none; width: 100%; padding: 8px 16px; margin-top: 4px; background: rgba(0,0,0,0.05); border: 1px solid rgba(0,0,0,0.1); border-radius: 6px; cursor: pointer; font-size: 0.85rem; color: var(--text-muted); }
84+
.expand-btn:hover { background: rgba(0,0,0,0.1); }
85+
.truncatable.truncated .expand-btn, .truncatable.expanded .expand-btn { display: block; }
86+
.pagination { display: flex; justify-content: center; gap: 8px; margin: 24px 0; flex-wrap: wrap; }
87+
.pagination a, .pagination span { padding: 5px 10px; border-radius: 6px; text-decoration: none; font-size: 0.85rem; }
88+
.pagination a { background: var(--card-bg); color: var(--user-border); border: 1px solid var(--user-border); }
89+
.pagination a:hover { background: var(--user-bg); }
90+
.pagination .current { background: var(--user-border); color: white; }
91+
.pagination .disabled { color: var(--text-muted); border: 1px solid #ddd; }
92+
.pagination .index-link { background: var(--user-border); color: white; }
93+
details.continuation { margin-bottom: 16px; }
94+
details.continuation summary { cursor: pointer; padding: 12px 16px; background: var(--user-bg); border-left: 4px solid var(--user-border); border-radius: 12px; font-weight: 500; color: var(--text-muted); }
95+
details.continuation summary:hover { background: rgba(25, 118, 210, 0.15); }
96+
details.continuation[open] summary { border-radius: 12px 12px 0 0; margin-bottom: 0; }
97+
.index-item { margin-bottom: 16px; border-radius: 12px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); background: var(--user-bg); border-left: 4px solid var(--user-border); }
98+
.index-item a { display: block; text-decoration: none; color: inherit; }
99+
.index-item a:hover { background: rgba(25, 118, 210, 0.1); }
100+
.index-item-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 16px; background: rgba(0,0,0,0.03); font-size: 0.85rem; }
101+
.index-item-header .header-actions { display: flex; align-items: center; gap: 8px; }
102+
.index-item-number { font-weight: 600; color: var(--user-border); }
103+
.index-item-content { padding: 16px; }
104+
.index-item-stats { padding: 8px 16px 12px 32px; font-size: 0.85rem; color: var(--text-muted); border-top: 1px solid rgba(0,0,0,0.06); }
105+
.index-item-commit { margin-top: 6px; padding: 4px 8px; background: #fff3e0; border-radius: 4px; font-size: 0.85rem; color: #e65100; }
106+
.index-item-commit code { background: rgba(0,0,0,0.08); padding: 1px 4px; border-radius: 3px; font-size: 0.8rem; margin-right: 6px; }
107+
.commit-card { margin: 8px 0; padding: 10px 14px; background: #fff3e0; border-left: 4px solid #ff9800; border-radius: 6px; }
108+
.commit-card a { text-decoration: none; color: #5d4037; display: block; }
109+
.commit-card a:hover { color: #e65100; }
110+
.commit-card-hash { font-family: monospace; color: #e65100; font-weight: 600; margin-right: 8px; }
111+
.index-commit { margin-bottom: 12px; padding: 10px 16px; background: #fff3e0; border-left: 4px solid #ff9800; border-radius: 8px; box-shadow: 0 1px 2px rgba(0,0,0,0.05); }
112+
.index-commit a { display: block; text-decoration: none; color: inherit; }
113+
.index-commit a:hover { background: rgba(255, 152, 0, 0.1); margin: -10px -16px; padding: 10px 16px; border-radius: 8px; }
114+
.index-commit-header { display: flex; justify-content: space-between; align-items: center; font-size: 0.85rem; margin-bottom: 4px; }
115+
.index-commit-hash { font-family: monospace; color: #e65100; font-weight: 600; }
116+
.index-commit-msg { color: #5d4037; }
117+
.index-item-long-text { margin-top: 8px; padding: 12px; background: var(--card-bg); border-radius: 8px; border-left: 3px solid var(--assistant-border); }
118+
.index-item-long-text .truncatable.truncated::after { background: linear-gradient(to bottom, transparent, var(--card-bg)); }
119+
.index-item-long-text-content { color: var(--text-color); }
120+
#search-box { display: none; align-items: center; gap: 8px; }
121+
#search-box input { padding: 6px 12px; border: 1px solid var(--assistant-border); border-radius: 6px; font-size: 16px; width: 180px; }
122+
#search-box button, #modal-search-btn, #modal-close-btn { background: var(--user-border); color: white; border: none; border-radius: 6px; padding: 6px 10px; cursor: pointer; display: flex; align-items: center; justify-content: center; }
123+
#search-box button:hover, #modal-search-btn:hover { background: #1565c0; }
124+
#modal-close-btn { background: var(--text-muted); margin-left: 8px; }
125+
#modal-close-btn:hover { background: #616161; }
126+
#search-modal[open] { border: none; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,0.2); padding: 0; width: 90vw; max-width: 900px; height: 80vh; max-height: 80vh; display: flex; flex-direction: column; }
127+
#search-modal::backdrop { background: rgba(0,0,0,0.5); }
128+
.search-modal-header { display: flex; align-items: center; gap: 8px; padding: 16px; border-bottom: 1px solid var(--assistant-border); background: var(--bg-color); border-radius: 12px 12px 0 0; }
129+
.search-modal-header input { flex: 1; padding: 8px 12px; border: 1px solid var(--assistant-border); border-radius: 6px; font-size: 16px; }
130+
#search-status { padding: 8px 16px; font-size: 0.85rem; color: var(--text-muted); border-bottom: 1px solid rgba(0,0,0,0.06); }
131+
#search-results { flex: 1; overflow-y: auto; padding: 16px; }
132+
.search-result { margin-bottom: 16px; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
133+
.search-result a { display: block; text-decoration: none; color: inherit; }
134+
.search-result a:hover { background: rgba(25, 118, 210, 0.05); }
135+
.search-result-header { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: rgba(0,0,0,0.03); border-bottom: 1px solid rgba(0,0,0,0.06); }
136+
.search-result-page { font-size: 0.8rem; color: var(--text-muted); text-decoration: none; }
137+
.search-result-page:hover { text-decoration: underline; }
138+
.search-result-content { padding: 12px; }
139+
.search-result mark { background: #fff59d; padding: 1px 2px; border-radius: 2px; }
140+
copy-button { display: inline-block; margin-right: 8px; }
141+
copy-button button { background: transparent; border: 1px solid var(--text-muted); border-radius: 4px; padding: 2px 6px; font-size: 0.7rem; color: var(--text-muted); cursor: pointer; white-space: nowrap; }
142+
copy-button button:hover { background: rgba(0,0,0,0.05); border-color: var(--user-border); color: var(--user-border); }
143+
copy-button button.copied { background: #e8f5e9; border-color: #4caf50; color: #2e7d32; }
144+
@media (max-width: 600px) { body { padding: 8px; } .message, .index-item { border-radius: 8px; } .message-content, .index-item-content { padding: 12px; } pre { font-size: 0.8rem; padding: 8px; } #search-box input { width: 120px; } #search-modal[open] { width: 95vw; height: 90vh; } }

0 commit comments

Comments
 (0)