Commit
·
7c72565
1
Parent(s):
292d92c
Fix command loading and copy-to-clipboard functionality
Browse files- Use rglob() to recursively find all .md files in nested folders (now shows all 360 commands)
- Implement proper copy-to-clipboard with JSON encoding for special characters
- Add unique function per command to avoid conflicts
- Use html.escape() to prevent XSS and display issues
- Add error handling for clipboard API failures
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
app.py
CHANGED
|
@@ -38,15 +38,17 @@ def load_commands():
|
|
| 38 |
if not commands_dir.exists():
|
| 39 |
return []
|
| 40 |
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
| 50 |
|
| 51 |
return commands
|
| 52 |
|
|
@@ -78,6 +80,8 @@ def search_commands(search_term, commands_data):
|
|
| 78 |
|
| 79 |
def create_command_card(command):
|
| 80 |
"""Create an HTML card for a command"""
|
|
|
|
|
|
|
| 81 |
name = command['name']
|
| 82 |
path = command['path']
|
| 83 |
category = command.get('category', 'misc')
|
|
@@ -97,11 +101,11 @@ def create_command_card(command):
|
|
| 97 |
category_color = CATEGORY_COLORS.get(category, '#9ca3af')
|
| 98 |
category_display = category.replace('-', ' ').title()
|
| 99 |
|
| 100 |
-
#
|
| 101 |
-
|
| 102 |
|
| 103 |
-
# Generate unique ID
|
| 104 |
-
|
| 105 |
|
| 106 |
card_html = f"""
|
| 107 |
<details style="border: 1px solid #e5e7eb; border-radius: 8px; margin: 10px 0; background: white; overflow: hidden;">
|
|
@@ -109,10 +113,10 @@ def create_command_card(command):
|
|
| 109 |
<div style="display: flex; justify-content: space-between; align-items: center; gap: 12px;">
|
| 110 |
<div style="flex: 1;">
|
| 111 |
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
|
| 112 |
-
<span style="font-size: 18px; font-weight: 600; color: #1f2937;">/{name}</span>
|
| 113 |
-
<span style="display: inline-block; padding: 4px 10px; background: {category_color}; color: white; border-radius: 12px; font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">{category_display}</span>
|
| 114 |
</div>
|
| 115 |
-
<p style="margin: 0; color: #6b7280; font-size: 14px; line-height: 1.4;">{description[:150]}{'...' if len(description) > 150 else ''}</p>
|
| 116 |
</div>
|
| 117 |
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" style="flex-shrink: 0; transition: transform 0.2s;">
|
| 118 |
<path d="M6 8L10 12L14 8" stroke="#9ca3af" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
@@ -121,13 +125,29 @@ def create_command_card(command):
|
|
| 121 |
</summary>
|
| 122 |
<div style="padding: 0 16px 16px 16px; border-top: 1px solid #f3f4f6;">
|
| 123 |
<div style="position: relative; background: #f9fafb; padding: 16px; border-radius: 6px; margin-top: 12px;">
|
| 124 |
-
<button
|
|
|
|
| 125 |
style="position: absolute; top: 12px; right: 12px; background: #2563eb; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background 0.2s;"
|
| 126 |
onmouseover="this.style.background='#1d4ed8'"
|
| 127 |
onmouseout="this.style.background='#2563eb'">
|
| 128 |
📋
|
| 129 |
</button>
|
| 130 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
</div>
|
| 132 |
</div>
|
| 133 |
</details>
|
|
|
|
| 38 |
if not commands_dir.exists():
|
| 39 |
return []
|
| 40 |
|
| 41 |
+
# Recursively find all .md files in commands directory
|
| 42 |
+
for cmd_file in sorted(commands_dir.rglob("*.md")):
|
| 43 |
+
# Get the top-level category (first subdirectory under commands/)
|
| 44 |
+
parts = cmd_file.relative_to(commands_dir).parts
|
| 45 |
+
category = parts[0] if parts else 'misc'
|
| 46 |
+
|
| 47 |
+
commands.append({
|
| 48 |
+
'name': cmd_file.stem,
|
| 49 |
+
'path': str(cmd_file),
|
| 50 |
+
'category': category
|
| 51 |
+
})
|
| 52 |
|
| 53 |
return commands
|
| 54 |
|
|
|
|
| 80 |
|
| 81 |
def create_command_card(command):
|
| 82 |
"""Create an HTML card for a command"""
|
| 83 |
+
import html
|
| 84 |
+
|
| 85 |
name = command['name']
|
| 86 |
path = command['path']
|
| 87 |
category = command.get('category', 'misc')
|
|
|
|
| 101 |
category_color = CATEGORY_COLORS.get(category, '#9ca3af')
|
| 102 |
category_display = category.replace('-', ' ').title()
|
| 103 |
|
| 104 |
+
# JSON encode the content for safe JavaScript usage
|
| 105 |
+
content_json = json.dumps(content)
|
| 106 |
|
| 107 |
+
# Generate unique ID
|
| 108 |
+
unique_id = f"cmd-{name.replace(' ', '-').replace('/', '-')}"
|
| 109 |
|
| 110 |
card_html = f"""
|
| 111 |
<details style="border: 1px solid #e5e7eb; border-radius: 8px; margin: 10px 0; background: white; overflow: hidden;">
|
|
|
|
| 113 |
<div style="display: flex; justify-content: space-between; align-items: center; gap: 12px;">
|
| 114 |
<div style="flex: 1;">
|
| 115 |
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
|
| 116 |
+
<span style="font-size: 18px; font-weight: 600; color: #1f2937;">/{html.escape(name)}</span>
|
| 117 |
+
<span style="display: inline-block; padding: 4px 10px; background: {category_color}; color: white; border-radius: 12px; font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">{html.escape(category_display)}</span>
|
| 118 |
</div>
|
| 119 |
+
<p style="margin: 0; color: #6b7280; font-size: 14px; line-height: 1.4;">{html.escape(description[:150])}{'...' if len(description) > 150 else ''}</p>
|
| 120 |
</div>
|
| 121 |
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" style="flex-shrink: 0; transition: transform 0.2s;">
|
| 122 |
<path d="M6 8L10 12L14 8" stroke="#9ca3af" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
|
|
| 125 |
</summary>
|
| 126 |
<div style="padding: 0 16px 16px 16px; border-top: 1px solid #f3f4f6;">
|
| 127 |
<div style="position: relative; background: #f9fafb; padding: 16px; border-radius: 6px; margin-top: 12px;">
|
| 128 |
+
<button id="copy-btn-{unique_id}"
|
| 129 |
+
onclick="copyToClipboard{unique_id}()"
|
| 130 |
style="position: absolute; top: 12px; right: 12px; background: #2563eb; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 16px; transition: background 0.2s;"
|
| 131 |
onmouseover="this.style.background='#1d4ed8'"
|
| 132 |
onmouseout="this.style.background='#2563eb'">
|
| 133 |
📋
|
| 134 |
</button>
|
| 135 |
+
<script>
|
| 136 |
+
function copyToClipboard{unique_id}() {{
|
| 137 |
+
const text = {content_json};
|
| 138 |
+
navigator.clipboard.writeText(text).then(() => {{
|
| 139 |
+
const btn = document.getElementById('copy-btn-{unique_id}');
|
| 140 |
+
btn.innerHTML = '✓ Copied';
|
| 141 |
+
setTimeout(() => {{
|
| 142 |
+
btn.innerHTML = '📋';
|
| 143 |
+
}}, 1500);
|
| 144 |
+
}}).catch(err => {{
|
| 145 |
+
console.error('Failed to copy:', err);
|
| 146 |
+
alert('Failed to copy to clipboard');
|
| 147 |
+
}});
|
| 148 |
+
}}
|
| 149 |
+
</script>
|
| 150 |
+
<pre style="margin: 0; white-space: pre-wrap; font-size: 13px; line-height: 1.6; color: #374151; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; padding-right: 60px;">{html.escape(content)}</pre>
|
| 151 |
</div>
|
| 152 |
</div>
|
| 153 |
</details>
|