danielrosehill Claude commited on
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>

Files changed (1) hide show
  1. app.py +38 -18
app.py CHANGED
@@ -38,15 +38,17 @@ def load_commands():
38
  if not commands_dir.exists():
39
  return []
40
 
41
- for category_dir in sorted(commands_dir.iterdir()):
42
- if category_dir.is_dir():
43
- category = category_dir.name
44
- for cmd_file in sorted(category_dir.glob("*.md")):
45
- commands.append({
46
- 'name': cmd_file.stem,
47
- 'path': str(cmd_file),
48
- 'category': category
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
- # Escape content for JavaScript
101
- escaped_content = content.replace('\\', '\\\\').replace('`', '\\`').replace('$', '\\$').replace('"', '\\"')
102
 
103
- # Generate unique ID for details element
104
- detail_id = f"cmd-{name.replace(' ', '-')}"
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 onclick="navigator.clipboard.writeText('{escaped_content}'); this.innerHTML='✓ Copied'; setTimeout(() => this.innerHTML='📋', 1500)"
 
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
- <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;">{content}</pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>