Skip to content

Commit cdb33c2

Browse files
committed
Make tools collapsible in HTML generator
- Add collapsible details/summary elements for each tool - Add CSS styling with animated arrow indicators - Improves browsing experience by reducing scroll length - Each tool can be expanded/collapsed independently
1 parent 0ab57c3 commit cdb33c2

2 files changed

Lines changed: 146 additions & 6 deletions

File tree

ci/generate_index.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,53 @@
166166
margin-top: 3rem;
167167
}
168168
169+
details {
170+
border: 1px solid var(--border);
171+
border-radius: 6px;
172+
padding: 0.75rem 1rem;
173+
margin: 0.75rem 0;
174+
background: var(--code-bg);
175+
}
176+
177+
details[open] {
178+
padding-bottom: 1rem;
179+
}
180+
181+
summary {
182+
cursor: pointer;
183+
font-weight: 600;
184+
font-size: 1.1rem;
185+
user-select: none;
186+
list-style: none;
187+
display: flex;
188+
align-items: center;
189+
gap: 0.5rem;
190+
}
191+
192+
summary::-webkit-details-marker {
193+
display: none;
194+
}
195+
196+
summary::before {
197+
content: '▶';
198+
display: inline-block;
199+
transition: transform 0.2s;
200+
font-size: 0.8em;
201+
}
202+
203+
details[open] summary::before {
204+
transform: rotate(90deg);
205+
}
206+
207+
summary:hover {
208+
color: var(--link);
209+
}
210+
211+
details .content {
212+
margin-top: 1rem;
213+
padding-left: 1.3rem;
214+
}
215+
169216
footer {
170217
margin-top: 3rem;
171218
padding-top: 2rem;
@@ -198,6 +245,25 @@
198245
"""
199246

200247

248+
def wrap_tools_in_details(html: str) -> str:
249+
"""Wrap each H3 tool section in a collapsible details element."""
250+
# Pattern to match H3 headings and their content until the next H3 or H2
251+
pattern = r'(<h3>.*?</h3>)(.*?)(?=<h3>|<h2>|$)'
252+
253+
def replace_tool(match):
254+
h3_tag = match.group(1)
255+
content = match.group(2)
256+
257+
# Extract the tool name from the h3 tag
258+
tool_name_match = re.search(r'<a[^>]*>([^<]+)</a>', h3_tag)
259+
if tool_name_match:
260+
tool_name = tool_name_match.group(1)
261+
return f'<details>\n<summary>{tool_name}</summary>\n<div class="content">\n{content.strip()}\n</div>\n</details>\n'
262+
return h3_tag + content
263+
264+
return re.sub(pattern, replace_tool, html, flags=re.DOTALL)
265+
266+
201267
def generate_index() -> None:
202268
if not README_PATH.exists():
203269
print(f"Error: {README_PATH} not found.")
@@ -218,6 +284,9 @@ def generate_index() -> None:
218284
# Convert README to HTML
219285
html_content = markdown.markdown(content, extensions=["fenced_code", "tables"])
220286

287+
# Wrap tool sections in collapsible details elements
288+
html_content = wrap_tools_in_details(html_content)
289+
221290
# Inject into template
222291
final_html = HTML_TEMPLATE.replace("{content}", html_content)
223292

index.html

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,53 @@
146146
margin-top: 3rem;
147147
}
148148

149+
details {
150+
border: 1px solid var(--border);
151+
border-radius: 6px;
152+
padding: 0.75rem 1rem;
153+
margin: 0.75rem 0;
154+
background: var(--code-bg);
155+
}
156+
157+
details[open] {
158+
padding-bottom: 1rem;
159+
}
160+
161+
summary {
162+
cursor: pointer;
163+
font-weight: 600;
164+
font-size: 1.1rem;
165+
user-select: none;
166+
list-style: none;
167+
display: flex;
168+
align-items: center;
169+
gap: 0.5rem;
170+
}
171+
172+
summary::-webkit-details-marker {
173+
display: none;
174+
}
175+
176+
summary::before {
177+
content: '▶';
178+
display: inline-block;
179+
transition: transform 0.2s;
180+
font-size: 0.8em;
181+
}
182+
183+
details[open] summary::before {
184+
transform: rotate(90deg);
185+
}
186+
187+
summary:hover {
188+
color: var(--link);
189+
}
190+
191+
details .content {
192+
margin-top: 1rem;
193+
padding-left: 1.3rem;
194+
}
195+
149196
footer {
150197
margin-top: 3rem;
151198
padding-top: 2rem;
@@ -170,7 +217,9 @@ <h1>Ricardo Decal's Tools</h1>
170217
<p>This is an experiment in low-stakes vibe coding. The code lives in <a href="https://github.com/crypdick/tools"><code>crypdick/tools</code></a>.</p>
171218
<p>Inspired by <a href="https://github.com/simonw/tools">Simon Willison's tools collection</a>.</p>
172219
<h2>Available Tools</h2>
173-
<h3><a href="python/convert_arrow_to_parquet_streaming.py">convert_arrow_to_parquet_streaming.py</a></h3>
220+
<details>
221+
<summary>convert_arrow_to_parquet_streaming.py</summary>
222+
<div class="content">
174223
<p>Output of <code>uv run https://tools.ricardodecal.com/python/convert_arrow_to_parquet_streaming.py --help</code>:</p>
175224
<pre><code class="language-text">Usage: convert_arrow_to_parquet_streaming.py [OPTIONS]
176225

@@ -202,7 +251,11 @@ <h3><a href="python/convert_arrow_to_parquet_streaming.py">convert_arrow_to_parq
202251
dir
203252
--help Show this message and exit.
204253
</code></pre>
205-
<h3><a href="python/count_parquet_rows.py">count_parquet_rows.py</a></h3>
254+
</div>
255+
</details>
256+
<details>
257+
<summary>count_parquet_rows.py</summary>
258+
<div class="content">
206259
<p>Output of <code>uv run https://tools.ricardodecal.com/python/count_parquet_rows.py --help</code>:</p>
207260
<pre><code class="language-text">Usage: count_parquet_rows.py [OPTIONS] DATASET_PATH
208261

@@ -232,7 +285,11 @@ <h3><a href="python/count_parquet_rows.py">count_parquet_rows.py</a></h3>
232285
Options:
233286
--help Show this message and exit.
234287
</code></pre>
235-
<h3><a href="python/download_video.py">download_video.py</a></h3>
288+
</div>
289+
</details>
290+
<details>
291+
<summary>download_video.py</summary>
292+
<div class="content">
236293
<p>Output of <code>uv run https://tools.ricardodecal.com/python/download_video.py --help</code>:</p>
237294
<pre><code class="language-text">Usage: download_video.py [OPTIONS] URL
238295

@@ -259,7 +316,11 @@ <h3><a href="python/download_video.py">download_video.py</a></h3>
259316
directory.
260317
--help Show this message and exit.
261318
</code></pre>
262-
<h3><a href="python/ipynb_to_py_sphinx.py">ipynb_to_py_sphinx.py</a></h3>
319+
</div>
320+
</details>
321+
<details>
322+
<summary>ipynb_to_py_sphinx.py</summary>
323+
<div class="content">
263324
<p>Output of <code>uv run https://tools.ricardodecal.com/python/ipynb_to_py_sphinx.py --help</code>:</p>
264325
<pre><code class="language-text">Usage: ipynb_to_py_sphinx.py [OPTIONS] NOTEBOOK
265326

@@ -287,7 +348,11 @@ <h3><a href="python/ipynb_to_py_sphinx.py">ipynb_to_py_sphinx.py</a></h3>
287348
.py extension.
288349
--help Show this message and exit.
289350
</code></pre>
290-
<h3><a href="python/strip_pdf_metadata.py">strip_pdf_metadata.py</a></h3>
351+
</div>
352+
</details>
353+
<details>
354+
<summary>strip_pdf_metadata.py</summary>
355+
<div class="content">
291356
<p>Output of <code>uv run https://tools.ricardodecal.com/python/strip_pdf_metadata.py --help</code>:</p>
292357
<pre><code class="language-text">Usage: strip_pdf_metadata.py [OPTIONS] INPUT_FILE [OUTPUT_FILE]
293358

@@ -298,7 +363,11 @@ <h3><a href="python/strip_pdf_metadata.py">strip_pdf_metadata.py</a></h3>
298363
Options:
299364
--help Show this message and exit.
300365
</code></pre>
301-
<h3><a href="python/yt_transcript.py">yt_transcript.py</a></h3>
366+
</div>
367+
</details>
368+
<details>
369+
<summary>yt_transcript.py</summary>
370+
<div class="content">
302371
<p>Output of <code>uv run https://tools.ricardodecal.com/python/yt_transcript.py --help</code>:</p>
303372
<pre><code class="language-text">Usage: yt_transcript.py [OPTIONS] URL [OUTPUT_FILE]
304373

@@ -318,6 +387,8 @@ <h3><a href="python/yt_transcript.py">yt_transcript.py</a></h3>
318387
-l, --lang TEXT Language codes to prefer (e.g. -l en -l fr)
319388
--help Show this message and exit.
320389
</code></pre>
390+
</div>
391+
</details>
321392
<h2>License</h2>
322393
<p><a href="LICENSE">Apache 2.0</a></p>
323394

0 commit comments

Comments
 (0)