gh-here 3.0.3 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +0 -0
- package/lib/constants.js +21 -16
- package/lib/content-search.js +212 -0
- package/lib/error-handler.js +39 -28
- package/lib/file-utils.js +438 -287
- package/lib/git.js +11 -55
- package/lib/gitignore.js +70 -41
- package/lib/renderers.js +17 -33
- package/lib/server.js +73 -196
- package/lib/symbol-parser.js +600 -0
- package/package.json +1 -1
- package/public/app.js +135 -68
- package/public/css/components/buttons.css +423 -0
- package/public/css/components/forms.css +171 -0
- package/public/css/components/modals.css +286 -0
- package/public/css/components/notifications.css +36 -0
- package/public/css/file-table.css +318 -0
- package/public/css/file-tree.css +269 -0
- package/public/css/file-viewer.css +1259 -0
- package/public/css/layout.css +372 -0
- package/public/css/main.css +35 -0
- package/public/css/reset.css +64 -0
- package/public/css/search.css +694 -0
- package/public/css/symbol-outline.css +279 -0
- package/public/css/variables.css +135 -0
- package/public/js/constants.js +50 -34
- package/public/js/content-search-handler.js +551 -0
- package/public/js/file-viewer.js +437 -0
- package/public/js/focus-mode.js +280 -0
- package/public/js/inline-search.js +659 -0
- package/public/js/modal-manager.js +14 -28
- package/public/js/symbol-outline.js +454 -0
- package/public/js/utils.js +152 -94
- package/.claude/settings.local.json +0 -30
- package/SAMPLE.md +0 -287
- package/lib/validation.js +0 -77
- package/public/app.js.backup +0 -1902
- package/public/highlight.css +0 -121
- package/public/js/draft-manager.js +0 -36
- package/public/js/editor-manager.js +0 -159
- package/public/styles.css +0 -2727
- package/test.js +0 -138
- package/tests/draftManager.test.js +0 -241
- package/tests/fileTypeDetection.test.js +0 -111
- package/tests/httpService.test.js +0 -268
- package/tests/languageDetection.test.js +0 -145
- package/tests/pathUtils.test.js +0 -136
package/lib/file-utils.js
CHANGED
|
@@ -1,332 +1,483 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File utilities module
|
|
3
|
+
* Handles file icons, language detection, formatting, and type classification
|
|
4
|
+
*
|
|
5
|
+
* @module file-utils
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
const path = require('path');
|
|
2
9
|
const octicons = require('@primer/octicons');
|
|
3
10
|
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Constants
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
const BYTE_UNITS = ['B', 'KB', 'MB', 'GB'];
|
|
16
|
+
const BYTE_BASE = 1024;
|
|
17
|
+
|
|
18
|
+
// File type categories
|
|
19
|
+
const IMAGE_EXTENSIONS = new Set([
|
|
20
|
+
'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp', 'tiff', 'ico'
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const BINARY_EXTENSIONS = new Set([
|
|
24
|
+
// Archives
|
|
25
|
+
'zip', 'tar', 'gz', 'rar', '7z',
|
|
26
|
+
// Executables
|
|
27
|
+
'exe', 'bin', 'app', 'deb', 'rpm',
|
|
28
|
+
// Documents
|
|
29
|
+
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
30
|
+
// Media
|
|
31
|
+
'mp4', 'mov', 'avi', 'mkv', 'mp3', 'wav', 'flac',
|
|
32
|
+
// Compiled
|
|
33
|
+
'class', 'so', 'dll', 'dylib',
|
|
34
|
+
// Images (also binary)
|
|
35
|
+
...IMAGE_EXTENSIONS
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
// Language mapping for Monaco/editor support
|
|
39
|
+
const LANGUAGE_MAP = {
|
|
40
|
+
// JavaScript family
|
|
41
|
+
'js': 'javascript',
|
|
42
|
+
'mjs': 'javascript',
|
|
43
|
+
'jsx': 'javascript',
|
|
44
|
+
'ts': 'typescript',
|
|
45
|
+
'tsx': 'typescript',
|
|
46
|
+
|
|
47
|
+
// Web
|
|
48
|
+
'html': 'html',
|
|
49
|
+
'htm': 'html',
|
|
50
|
+
'css': 'css',
|
|
51
|
+
'scss': 'scss',
|
|
52
|
+
'sass': 'sass',
|
|
53
|
+
'less': 'less',
|
|
54
|
+
|
|
55
|
+
// Data / config
|
|
56
|
+
'json': 'json',
|
|
57
|
+
'xml': 'xml',
|
|
58
|
+
'yaml': 'yaml',
|
|
59
|
+
'yml': 'yaml',
|
|
60
|
+
|
|
61
|
+
// Shell & scripts
|
|
62
|
+
'sh': 'bash',
|
|
63
|
+
'bash': 'bash',
|
|
64
|
+
'zsh': 'bash',
|
|
65
|
+
'fish': 'bash',
|
|
66
|
+
'ps1': 'powershell',
|
|
67
|
+
|
|
68
|
+
// Compiled / systems
|
|
69
|
+
'c': 'c',
|
|
70
|
+
'h': 'c',
|
|
71
|
+
'cpp': 'cpp',
|
|
72
|
+
'cc': 'cpp',
|
|
73
|
+
'cxx': 'cpp',
|
|
74
|
+
'hpp': 'cpp',
|
|
75
|
+
'rs': 'rust',
|
|
76
|
+
'go': 'go',
|
|
77
|
+
'java': 'java',
|
|
78
|
+
'kt': 'kotlin',
|
|
79
|
+
'swift': 'swift',
|
|
80
|
+
|
|
81
|
+
// Scripting
|
|
82
|
+
'py': 'python',
|
|
83
|
+
'php': 'php',
|
|
84
|
+
'rb': 'ruby',
|
|
85
|
+
'dart': 'dart',
|
|
86
|
+
'r': 'r',
|
|
87
|
+
'sql': 'sql',
|
|
88
|
+
'scala': 'scala',
|
|
89
|
+
'clj': 'clojure',
|
|
90
|
+
'lua': 'lua',
|
|
91
|
+
'pl': 'perl',
|
|
92
|
+
'groovy': 'groovy',
|
|
93
|
+
|
|
94
|
+
// Markup / frameworks
|
|
95
|
+
'md': 'markdown',
|
|
96
|
+
'markdown': 'markdown',
|
|
97
|
+
'vue': 'vue',
|
|
98
|
+
'svelte': 'svelte',
|
|
99
|
+
|
|
100
|
+
// Misc text
|
|
101
|
+
'txt': 'plaintext',
|
|
102
|
+
'log': 'plaintext',
|
|
103
|
+
|
|
104
|
+
// Special filename-style extensions
|
|
105
|
+
'dockerfile': 'dockerfile'
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Language colors for stats
|
|
109
|
+
const LANGUAGE_COLORS = {
|
|
110
|
+
javascript: '#f1e05a',
|
|
111
|
+
typescript: '#2b7489',
|
|
112
|
+
python: '#3572A5',
|
|
113
|
+
java: '#b07219',
|
|
114
|
+
html: '#e34c26',
|
|
115
|
+
css: '#563d7c',
|
|
116
|
+
json: '#292929',
|
|
117
|
+
markdown: '#083fa1',
|
|
118
|
+
go: '#00ADD8',
|
|
119
|
+
rust: '#dea584',
|
|
120
|
+
php: '#4F5D95',
|
|
121
|
+
ruby: '#701516',
|
|
122
|
+
other: '#cccccc'
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// File Icon Configuration
|
|
127
|
+
// ============================================================================
|
|
128
|
+
|
|
4
129
|
/**
|
|
5
|
-
*
|
|
6
|
-
|
|
130
|
+
* Icon configuration for special files by exact name match
|
|
131
|
+
*/
|
|
132
|
+
const SPECIAL_FILE_ICONS = {
|
|
133
|
+
// Package managers
|
|
134
|
+
'package.json': { icon: 'file-code', color: 'text-green' },
|
|
135
|
+
'composer.json': { icon: 'file-code', color: 'text-green' },
|
|
136
|
+
|
|
137
|
+
// TypeScript config
|
|
138
|
+
'tsconfig.json': { icon: 'file-code', color: 'text-blue' },
|
|
139
|
+
'jsconfig.json': { icon: 'file-code', color: 'text-blue' },
|
|
140
|
+
|
|
141
|
+
// Linters & formatters
|
|
142
|
+
'.eslintrc': { icon: 'gear', color: 'text-purple' },
|
|
143
|
+
'.eslintrc.json': { icon: 'gear', color: 'text-purple' },
|
|
144
|
+
'.eslintrc.js': { icon: 'gear', color: 'text-purple' },
|
|
145
|
+
'.eslintrc.yml': { icon: 'gear', color: 'text-purple' },
|
|
146
|
+
'.prettierrc': { icon: 'gear', color: 'text-blue' },
|
|
147
|
+
'prettier.config.js': { icon: 'gear', color: 'text-blue' },
|
|
148
|
+
'.prettierrc.json': { icon: 'gear', color: 'text-blue' },
|
|
149
|
+
|
|
150
|
+
// Build tools
|
|
151
|
+
'webpack.config.js': { icon: 'gear', color: 'text-orange' },
|
|
152
|
+
'vite.config.js': { icon: 'gear', color: 'text-orange' },
|
|
153
|
+
'rollup.config.js': { icon: 'gear', color: 'text-orange' },
|
|
154
|
+
'next.config.js': { icon: 'gear', color: 'text-orange' },
|
|
155
|
+
'nuxt.config.js': { icon: 'gear', color: 'text-orange' },
|
|
156
|
+
'svelte.config.js': { icon: 'gear', color: 'text-orange' },
|
|
157
|
+
'tailwind.config.js': { icon: 'gear', color: 'text-purple' },
|
|
158
|
+
'postcss.config.js': { icon: 'gear', color: 'text-purple' },
|
|
159
|
+
'babel.config.js': { icon: 'gear', color: 'text-purple' },
|
|
160
|
+
'.babelrc': { icon: 'gear', color: 'text-purple' },
|
|
161
|
+
|
|
162
|
+
// Docker
|
|
163
|
+
'dockerfile': { icon: 'container', color: 'text-blue' },
|
|
164
|
+
'dockerfile.dev': { icon: 'container', color: 'text-blue' },
|
|
165
|
+
'.dockerignore': { icon: 'container', color: 'text-blue' },
|
|
166
|
+
'docker-compose.yml': { icon: 'container', color: 'text-blue' },
|
|
167
|
+
'docker-compose.yaml': { icon: 'container', color: 'text-blue' },
|
|
168
|
+
|
|
169
|
+
// Git
|
|
170
|
+
'.gitignore': { icon: 'git-branch', color: 'text-orange' },
|
|
171
|
+
'.gitattributes': { icon: 'git-branch', color: 'text-orange' },
|
|
172
|
+
'.gitmodules': { icon: 'git-branch', color: 'text-orange' },
|
|
173
|
+
|
|
174
|
+
// Documentation
|
|
175
|
+
'readme.md': { icon: 'book', color: 'text-blue' },
|
|
176
|
+
'readme.txt': { icon: 'book', color: 'text-blue' },
|
|
177
|
+
'changelog.md': { icon: 'book', color: 'text-blue' },
|
|
178
|
+
'history.md': { icon: 'book', color: 'text-blue' },
|
|
179
|
+
'license': { icon: 'law', color: 'text-yellow' },
|
|
180
|
+
'license.txt': { icon: 'law', color: 'text-yellow' },
|
|
181
|
+
'license.md': { icon: 'law', color: 'text-yellow' },
|
|
182
|
+
|
|
183
|
+
// Build
|
|
184
|
+
'makefile': { icon: 'tools', color: 'text-gray' },
|
|
185
|
+
'makefile.am': { icon: 'tools', color: 'text-gray' },
|
|
186
|
+
'cmakelists.txt': { icon: 'tools', color: 'text-gray' },
|
|
187
|
+
|
|
188
|
+
// Locks
|
|
189
|
+
'yarn.lock': { icon: 'lock', color: 'text-yellow' },
|
|
190
|
+
'package-lock.json': { icon: 'lock', color: 'text-yellow' },
|
|
191
|
+
'pipfile.lock': { icon: 'lock', color: 'text-yellow' },
|
|
192
|
+
|
|
193
|
+
// CI/CD
|
|
194
|
+
'.travis.yml': { icon: 'gear', color: 'text-green' },
|
|
195
|
+
'.circleci': { icon: 'gear', color: 'text-green' },
|
|
196
|
+
|
|
197
|
+
// Environment
|
|
198
|
+
'.env': { icon: 'key', color: 'text-yellow' },
|
|
199
|
+
'.env.local': { icon: 'key', color: 'text-yellow' }
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Icon configuration for files by extension
|
|
204
|
+
*/
|
|
205
|
+
const EXTENSION_ICONS = {
|
|
206
|
+
// JavaScript family
|
|
207
|
+
'.js': { icon: 'file-code', color: 'text-yellow' },
|
|
208
|
+
'.mjs': { icon: 'file-code', color: 'text-yellow' },
|
|
209
|
+
'.jsx': { icon: 'file-code', color: 'text-blue' },
|
|
210
|
+
'.ts': { icon: 'file-code', color: 'text-blue' },
|
|
211
|
+
'.tsx': { icon: 'file-code', color: 'text-blue' },
|
|
212
|
+
|
|
213
|
+
// Frameworks
|
|
214
|
+
'.vue': { icon: 'file-code', color: 'text-green' },
|
|
215
|
+
'.svelte': { icon: 'file-code', color: 'text-orange' },
|
|
216
|
+
|
|
217
|
+
// Languages
|
|
218
|
+
'.py': { icon: 'file-code', color: 'text-blue' },
|
|
219
|
+
'.pyx': { icon: 'file-code', color: 'text-blue' },
|
|
220
|
+
'.pyi': { icon: 'file-code', color: 'text-blue' },
|
|
221
|
+
'.java': { icon: 'file-code', color: 'text-red' },
|
|
222
|
+
'.class': { icon: 'file-code', color: 'text-red' },
|
|
223
|
+
'.c': { icon: 'file-code', color: 'text-blue' },
|
|
224
|
+
'.h': { icon: 'file-code', color: 'text-blue' },
|
|
225
|
+
'.cpp': { icon: 'file-code', color: 'text-blue' },
|
|
226
|
+
'.cxx': { icon: 'file-code', color: 'text-blue' },
|
|
227
|
+
'.cc': { icon: 'file-code', color: 'text-blue' },
|
|
228
|
+
'.hpp': { icon: 'file-code', color: 'text-blue' },
|
|
229
|
+
'.cs': { icon: 'file-code', color: 'text-purple' },
|
|
230
|
+
'.go': { icon: 'file-code', color: 'text-blue' },
|
|
231
|
+
'.rs': { icon: 'file-code', color: 'text-orange' },
|
|
232
|
+
'.php': { icon: 'file-code', color: 'text-purple' },
|
|
233
|
+
'.rb': { icon: 'file-code', color: 'text-red' },
|
|
234
|
+
'.swift': { icon: 'file-code', color: 'text-orange' },
|
|
235
|
+
'.kt': { icon: 'file-code', color: 'text-purple' },
|
|
236
|
+
'.kts': { icon: 'file-code', color: 'text-purple' },
|
|
237
|
+
'.dart': { icon: 'file-code', color: 'text-blue' },
|
|
238
|
+
'.scala': { icon: 'file-code', color: 'text-red' },
|
|
239
|
+
'.clj': { icon: 'file-code', color: 'text-green' },
|
|
240
|
+
'.cljs': { icon: 'file-code', color: 'text-green' },
|
|
241
|
+
'.hs': { icon: 'file-code', color: 'text-purple' },
|
|
242
|
+
'.elm': { icon: 'file-code', color: 'text-blue' },
|
|
243
|
+
'.r': { icon: 'file-code', color: 'text-blue' },
|
|
244
|
+
|
|
245
|
+
// Web
|
|
246
|
+
'.html': { icon: 'file-code', color: 'text-orange' },
|
|
247
|
+
'.css': { icon: 'paintbrush', color: 'text-purple' },
|
|
248
|
+
'.scss': { icon: 'paintbrush', color: 'text-purple' },
|
|
249
|
+
'.sass': { icon: 'paintbrush', color: 'text-purple' },
|
|
250
|
+
'.less': { icon: 'paintbrush', color: 'text-purple' },
|
|
251
|
+
|
|
252
|
+
// Data
|
|
253
|
+
'.json': { icon: 'file-code', color: 'text-yellow' },
|
|
254
|
+
'.xml': { icon: 'file-code', color: 'text-orange' },
|
|
255
|
+
'.yml': { icon: 'file-code', color: 'text-purple' },
|
|
256
|
+
'.yaml': { icon: 'file-code', color: 'text-purple' },
|
|
257
|
+
|
|
258
|
+
// Documentation
|
|
259
|
+
'.md': { icon: 'book', color: 'text-blue' },
|
|
260
|
+
'.markdown': { icon: 'book', color: 'text-blue' },
|
|
261
|
+
'.txt': { icon: 'file', color: 'text-gray' },
|
|
262
|
+
|
|
263
|
+
// Media
|
|
264
|
+
'.png': { icon: 'file-media', color: 'text-purple' },
|
|
265
|
+
'.jpg': { icon: 'file-media', color: 'text-purple' },
|
|
266
|
+
'.jpeg': { icon: 'file-media', color: 'text-purple' },
|
|
267
|
+
'.gif': { icon: 'file-media', color: 'text-purple' },
|
|
268
|
+
'.svg': { icon: 'file-media', color: 'text-purple' },
|
|
269
|
+
'.webp': { icon: 'file-media', color: 'text-purple' },
|
|
270
|
+
'.mp4': { icon: 'device-camera-video', color: 'text-red' },
|
|
271
|
+
'.mov': { icon: 'device-camera-video', color: 'text-red' },
|
|
272
|
+
'.avi': { icon: 'device-camera-video', color: 'text-red' },
|
|
273
|
+
'.mkv': { icon: 'device-camera-video', color: 'text-red' },
|
|
274
|
+
'.mp3': { icon: 'unmute', color: 'text-purple' },
|
|
275
|
+
'.wav': { icon: 'unmute', color: 'text-purple' },
|
|
276
|
+
'.flac': { icon: 'unmute', color: 'text-purple' },
|
|
277
|
+
|
|
278
|
+
// Archives
|
|
279
|
+
'.zip': { icon: 'file-zip', color: 'text-yellow' },
|
|
280
|
+
'.tar': { icon: 'file-zip', color: 'text-yellow' },
|
|
281
|
+
'.gz': { icon: 'file-zip', color: 'text-yellow' },
|
|
282
|
+
'.rar': { icon: 'file-zip', color: 'text-yellow' },
|
|
283
|
+
'.7z': { icon: 'file-zip', color: 'text-yellow' },
|
|
284
|
+
|
|
285
|
+
// Shell
|
|
286
|
+
'.sh': { icon: 'terminal', color: 'text-green' },
|
|
287
|
+
'.bash': { icon: 'terminal', color: 'text-green' },
|
|
288
|
+
'.zsh': { icon: 'terminal', color: 'text-green' },
|
|
289
|
+
'.fish': { icon: 'terminal', color: 'text-green' },
|
|
290
|
+
|
|
291
|
+
// Other
|
|
292
|
+
'.sql': { icon: 'file-code', color: 'text-orange' },
|
|
293
|
+
'.pdf': { icon: 'file-binary', color: 'text-red' }
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// ============================================================================
|
|
297
|
+
// Utilities
|
|
298
|
+
// ============================================================================
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Extract extension from file path or extension string
|
|
302
|
+
* @param {string} filePathOrExt - File path or extension
|
|
303
|
+
* @returns {string} Normalized extension (lowercase, no dot)
|
|
304
|
+
*/
|
|
305
|
+
function getExtension(filePathOrExt) {
|
|
306
|
+
// If it's already just an extension (no dots, slashes), return as-is
|
|
307
|
+
if (!filePathOrExt.includes('.') &&
|
|
308
|
+
!filePathOrExt.includes('/') &&
|
|
309
|
+
!filePathOrExt.includes('\\')) {
|
|
310
|
+
return filePathOrExt.toLowerCase();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Extract extension from file path
|
|
314
|
+
return path.extname(filePathOrExt).toLowerCase().slice(1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get icon configuration for a special file by name
|
|
319
|
+
* @param {string} filename - Lowercase filename
|
|
320
|
+
* @returns {Object|null} Icon config or null
|
|
321
|
+
*/
|
|
322
|
+
function getSpecialFileIcon(filename) {
|
|
323
|
+
// Check exact matches
|
|
324
|
+
if (SPECIAL_FILE_ICONS[filename]) {
|
|
325
|
+
return SPECIAL_FILE_ICONS[filename];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Check prefix matches (e.g., .env.*, .github/*, README*)
|
|
329
|
+
if (filename.startsWith('.env.')) {
|
|
330
|
+
return { icon: 'key', color: 'text-yellow' };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (filename.startsWith('.github')) {
|
|
334
|
+
return { icon: 'gear', color: 'text-green' };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (filename.startsWith('readme')) {
|
|
338
|
+
return { icon: 'book', color: 'text-blue' };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Check suffix matches (e.g., *.lock)
|
|
342
|
+
if (filename.endsWith('.lock')) {
|
|
343
|
+
return { icon: 'lock', color: 'text-yellow' };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Get icon configuration for a file by extension
|
|
351
|
+
* @param {string} ext - File extension (with or without dot)
|
|
352
|
+
* @returns {Object|null} Icon config or null
|
|
353
|
+
*/
|
|
354
|
+
function getExtensionIcon(ext) {
|
|
355
|
+
const normalizedExt = ext.startsWith('.') ? ext : `.${ext}`;
|
|
356
|
+
return EXTENSION_ICONS[normalizedExt] || null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Render icon SVG with fallback
|
|
361
|
+
* @param {string} iconName - Octicon name
|
|
362
|
+
* @param {string} colorClass - CSS color class
|
|
363
|
+
* @returns {string} SVG string
|
|
7
364
|
*/
|
|
365
|
+
function renderIcon(iconName, colorClass) {
|
|
366
|
+
const icon = octicons[iconName];
|
|
367
|
+
const fallback = octicons.file;
|
|
368
|
+
|
|
369
|
+
const iconToUse = icon || fallback;
|
|
370
|
+
const classes = `octicon-file ${colorClass}`;
|
|
371
|
+
|
|
372
|
+
// Render SVG with proper classes
|
|
373
|
+
return iconToUse.toSVG({ class: classes });
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ============================================================================
|
|
377
|
+
// Public API
|
|
378
|
+
// ============================================================================
|
|
8
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Get file icon SVG for a given filename
|
|
382
|
+
* @param {string} filename - File name
|
|
383
|
+
* @returns {string} SVG icon string
|
|
384
|
+
*/
|
|
9
385
|
function getFileIcon(filename) {
|
|
386
|
+
if (!filename) {
|
|
387
|
+
return renderIcon('file', 'text-gray');
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const normalizedName = filename.toLowerCase();
|
|
10
391
|
const ext = path.extname(filename).toLowerCase();
|
|
11
|
-
const name = filename.toLowerCase();
|
|
12
392
|
|
|
13
393
|
try {
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (name === 'tsconfig.json' || name === 'jsconfig.json') {
|
|
19
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-blue' });
|
|
20
|
-
}
|
|
21
|
-
if (name === '.eslintrc' || name === '.eslintrc.json' || name === '.eslintrc.js' || name === '.eslintrc.yml') {
|
|
22
|
-
return octicons.gear?.toSVG({ class: 'octicon-file text-purple' }) || octicons.file.toSVG({ class: 'octicon-file text-purple' });
|
|
23
|
-
}
|
|
24
|
-
if (name === '.prettierrc' || name === 'prettier.config.js' || name === '.prettierrc.json') {
|
|
25
|
-
return octicons.gear?.toSVG({ class: 'octicon-file text-blue' }) || octicons.file.toSVG({ class: 'octicon-file text-blue' });
|
|
26
|
-
}
|
|
27
|
-
if (name === 'webpack.config.js' || name === 'vite.config.js' || name === 'rollup.config.js' || name === 'next.config.js' || name === 'nuxt.config.js' || name === 'svelte.config.js') {
|
|
28
|
-
return octicons.gear?.toSVG({ class: 'octicon-file text-orange' }) || octicons.file.toSVG({ class: 'octicon-file text-orange' });
|
|
29
|
-
}
|
|
30
|
-
if (name === 'tailwind.config.js' || name === 'postcss.config.js' || name === 'babel.config.js' || name === '.babelrc') {
|
|
31
|
-
return octicons.gear?.toSVG({ class: 'octicon-file text-purple' }) || octicons.file.toSVG({ class: 'octicon-file text-purple' });
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Docker files
|
|
35
|
-
if (name === 'dockerfile' || name === 'dockerfile.dev' || name === '.dockerignore') {
|
|
36
|
-
return octicons.container?.toSVG({ class: 'octicon-file text-blue' }) || octicons.file.toSVG({ class: 'octicon-file text-blue' });
|
|
37
|
-
}
|
|
38
|
-
if (name === 'docker-compose.yml' || name === 'docker-compose.yaml') {
|
|
39
|
-
return octicons.container?.toSVG({ class: 'octicon-file text-blue' }) || octicons.file.toSVG({ class: 'octicon-file text-blue' });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Git files
|
|
43
|
-
if (name === '.gitignore' || name === '.gitattributes' || name === '.gitmodules') {
|
|
44
|
-
return octicons['git-branch']?.toSVG({ class: 'octicon-file text-orange' }) || octicons.file.toSVG({ class: 'octicon-file text-orange' });
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Documentation
|
|
48
|
-
if (name.startsWith('readme') || name === 'changelog.md' || name === 'history.md') {
|
|
49
|
-
return octicons.book.toSVG({ class: 'octicon-file text-blue' });
|
|
50
|
-
}
|
|
51
|
-
if (name === 'license' || name === 'license.txt' || name === 'license.md') {
|
|
52
|
-
return octicons.law?.toSVG({ class: 'octicon-file text-yellow' }) || octicons.file.toSVG({ class: 'octicon-file text-yellow' });
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Build files
|
|
56
|
-
if (name === 'makefile' || name === 'makefile.am' || name === 'cmakelists.txt') {
|
|
57
|
-
return octicons.tools?.toSVG({ class: 'octicon-file text-gray' }) || octicons.file.toSVG({ class: 'octicon-file text-gray' });
|
|
58
|
-
}
|
|
59
|
-
if (name.endsWith('.lock') || name === 'yarn.lock' || name === 'package-lock.json' || name === 'pipfile.lock') {
|
|
60
|
-
return octicons.lock?.toSVG({ class: 'octicon-file text-yellow' }) || octicons.file.toSVG({ class: 'octicon-file text-yellow' });
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// CI/CD files
|
|
64
|
-
if (name === '.travis.yml' || name === '.circleci' || name.startsWith('.github')) {
|
|
65
|
-
return octicons.gear?.toSVG({ class: 'octicon-file text-green' }) || octicons.file.toSVG({ class: 'octicon-file text-green' });
|
|
394
|
+
// Check special files first (exact name matches, prefixes, suffixes)
|
|
395
|
+
const specialIcon = getSpecialFileIcon(normalizedName);
|
|
396
|
+
if (specialIcon) {
|
|
397
|
+
return renderIcon(specialIcon.icon, specialIcon.color);
|
|
66
398
|
}
|
|
67
399
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
400
|
+
// Check extension-based icons
|
|
401
|
+
const extIcon = getExtensionIcon(ext);
|
|
402
|
+
if (extIcon) {
|
|
403
|
+
return renderIcon(extIcon.icon, extIcon.color);
|
|
71
404
|
}
|
|
72
405
|
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
case '.js':
|
|
76
|
-
case '.mjs':
|
|
77
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-yellow' });
|
|
78
|
-
case '.jsx':
|
|
79
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-blue' });
|
|
80
|
-
case '.ts':
|
|
81
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-blue' });
|
|
82
|
-
case '.tsx':
|
|
83
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-blue' });
|
|
84
|
-
case '.vue':
|
|
85
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-green' });
|
|
86
|
-
case '.svelte':
|
|
87
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-orange' });
|
|
88
|
-
case '.py':
|
|
89
|
-
case '.pyx':
|
|
90
|
-
case '.pyi':
|
|
91
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-blue' });
|
|
92
|
-
case '.java':
|
|
93
|
-
case '.class':
|
|
94
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-red' });
|
|
95
|
-
case '.c':
|
|
96
|
-
case '.h':
|
|
97
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-blue' });
|
|
98
|
-
case '.cpp':
|
|
99
|
-
case '.cxx':
|
|
100
|
-
case '.cc':
|
|
101
|
-
case '.hpp':
|
|
102
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-blue' });
|
|
103
|
-
case '.cs':
|
|
104
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-purple' });
|
|
105
|
-
case '.go':
|
|
106
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-blue' });
|
|
107
|
-
case '.rs':
|
|
108
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-orange' });
|
|
109
|
-
case '.php':
|
|
110
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-purple' });
|
|
111
|
-
case '.rb':
|
|
112
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-red' });
|
|
113
|
-
case '.swift':
|
|
114
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-orange' });
|
|
115
|
-
case '.kt':
|
|
116
|
-
case '.kts':
|
|
117
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-purple' });
|
|
118
|
-
case '.dart':
|
|
119
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-blue' });
|
|
120
|
-
case '.scala':
|
|
121
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-red' });
|
|
122
|
-
case '.clj':
|
|
123
|
-
case '.cljs':
|
|
124
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-green' });
|
|
125
|
-
case '.hs':
|
|
126
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-purple' });
|
|
127
|
-
case '.elm':
|
|
128
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-blue' });
|
|
129
|
-
case '.r':
|
|
130
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-blue' });
|
|
131
|
-
case '.html':
|
|
132
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-orange' });
|
|
133
|
-
case '.css':
|
|
134
|
-
case '.scss':
|
|
135
|
-
case '.sass':
|
|
136
|
-
case '.less':
|
|
137
|
-
return octicons.paintbrush?.toSVG({ class: 'octicon-file text-purple' }) || octicons.file.toSVG({ class: 'octicon-file text-purple' });
|
|
138
|
-
case '.json':
|
|
139
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-yellow' });
|
|
140
|
-
case '.xml':
|
|
141
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-orange' });
|
|
142
|
-
case '.yml':
|
|
143
|
-
case '.yaml':
|
|
144
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-purple' });
|
|
145
|
-
case '.md':
|
|
146
|
-
case '.markdown':
|
|
147
|
-
return octicons.book.toSVG({ class: 'octicon-file text-blue' });
|
|
148
|
-
case '.txt':
|
|
149
|
-
return octicons['file-text']?.toSVG({ class: 'octicon-file text-gray' }) || octicons.file.toSVG({ class: 'octicon-file text-gray' });
|
|
150
|
-
case '.pdf':
|
|
151
|
-
return octicons['file-binary']?.toSVG({ class: 'octicon-file text-red' }) || octicons.file.toSVG({ class: 'octicon-file text-red' });
|
|
152
|
-
case '.png':
|
|
153
|
-
case '.jpg':
|
|
154
|
-
case '.jpeg':
|
|
155
|
-
case '.gif':
|
|
156
|
-
case '.svg':
|
|
157
|
-
case '.webp':
|
|
158
|
-
return octicons['file-media']?.toSVG({ class: 'octicon-file text-purple' }) || octicons.file.toSVG({ class: 'octicon-file text-purple' });
|
|
159
|
-
case '.mp4':
|
|
160
|
-
case '.mov':
|
|
161
|
-
case '.avi':
|
|
162
|
-
case '.mkv':
|
|
163
|
-
return octicons['device-camera-video']?.toSVG({ class: 'octicon-file text-red' }) || octicons.file.toSVG({ class: 'octicon-file text-red' });
|
|
164
|
-
case '.mp3':
|
|
165
|
-
case '.wav':
|
|
166
|
-
case '.flac':
|
|
167
|
-
return octicons.unmute?.toSVG({ class: 'octicon-file text-purple' }) || octicons.file.toSVG({ class: 'octicon-file text-purple' });
|
|
168
|
-
case '.zip':
|
|
169
|
-
case '.tar':
|
|
170
|
-
case '.gz':
|
|
171
|
-
case '.rar':
|
|
172
|
-
case '.7z':
|
|
173
|
-
return octicons['file-zip']?.toSVG({ class: 'octicon-file text-yellow' }) || octicons.file.toSVG({ class: 'octicon-file text-yellow' });
|
|
174
|
-
case '.sh':
|
|
175
|
-
case '.bash':
|
|
176
|
-
case '.zsh':
|
|
177
|
-
case '.fish':
|
|
178
|
-
return octicons.terminal?.toSVG({ class: 'octicon-file text-green' }) || octicons.file.toSVG({ class: 'octicon-file text-green' });
|
|
179
|
-
case '.sql':
|
|
180
|
-
return octicons['file-code'].toSVG({ class: 'octicon-file text-orange' });
|
|
181
|
-
default:
|
|
182
|
-
return octicons.file.toSVG({ class: 'octicon-file text-gray' });
|
|
183
|
-
}
|
|
406
|
+
// Default fallback
|
|
407
|
+
return renderIcon('file', 'text-gray');
|
|
184
408
|
} catch (error) {
|
|
185
|
-
|
|
409
|
+
console.warn('Error generating file icon:', error);
|
|
410
|
+
return renderIcon('file', 'text-gray');
|
|
186
411
|
}
|
|
187
412
|
}
|
|
188
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Get language identifier from file extension
|
|
416
|
+
* @param {string} ext - File extension (with or without dot)
|
|
417
|
+
* @returns {string|undefined} Language identifier or undefined
|
|
418
|
+
*/
|
|
189
419
|
function getLanguageFromExtension(ext) {
|
|
190
420
|
if (!ext) return undefined;
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
'js': 'javascript',
|
|
195
|
-
'mjs': 'javascript',
|
|
196
|
-
'jsx': 'javascript',
|
|
197
|
-
'ts': 'typescript',
|
|
198
|
-
'tsx': 'typescript',
|
|
199
|
-
|
|
200
|
-
// Web
|
|
201
|
-
'html': 'html',
|
|
202
|
-
'htm': 'html',
|
|
203
|
-
'css': 'css',
|
|
204
|
-
'scss': 'scss',
|
|
205
|
-
'sass': 'sass',
|
|
206
|
-
'less': 'less',
|
|
207
|
-
|
|
208
|
-
// Data / config
|
|
209
|
-
'json': 'json',
|
|
210
|
-
'xml': 'xml',
|
|
211
|
-
'yaml': 'yaml',
|
|
212
|
-
'yml': 'yaml',
|
|
213
|
-
|
|
214
|
-
// Shell & scripts
|
|
215
|
-
'sh': 'bash',
|
|
216
|
-
'bash': 'bash',
|
|
217
|
-
'zsh': 'bash',
|
|
218
|
-
'fish': 'bash',
|
|
219
|
-
'ps1': 'powershell',
|
|
220
|
-
|
|
221
|
-
// Compiled / systems
|
|
222
|
-
'c': 'c',
|
|
223
|
-
'h': 'c',
|
|
224
|
-
'cpp': 'cpp',
|
|
225
|
-
'cc': 'cpp',
|
|
226
|
-
'cxx': 'cpp',
|
|
227
|
-
'hpp': 'cpp',
|
|
228
|
-
'rs': 'rust',
|
|
229
|
-
'go': 'go',
|
|
230
|
-
'java': 'java',
|
|
231
|
-
'kt': 'kotlin',
|
|
232
|
-
'swift': 'swift',
|
|
233
|
-
|
|
234
|
-
// Scripting
|
|
235
|
-
'py': 'python',
|
|
236
|
-
'php': 'php',
|
|
237
|
-
'rb': 'ruby',
|
|
238
|
-
'dart': 'dart',
|
|
239
|
-
'r': 'r',
|
|
240
|
-
'sql': 'sql',
|
|
241
|
-
'scala': 'scala',
|
|
242
|
-
'clj': 'clojure',
|
|
243
|
-
'lua': 'lua',
|
|
244
|
-
'pl': 'perl',
|
|
245
|
-
'groovy': 'groovy',
|
|
246
|
-
|
|
247
|
-
// Markup / frameworks
|
|
248
|
-
'md': 'markdown',
|
|
249
|
-
'markdown': 'markdown',
|
|
250
|
-
'vue': 'vue',
|
|
251
|
-
'svelte': 'svelte',
|
|
252
|
-
|
|
253
|
-
// Misc text
|
|
254
|
-
'txt': 'plaintext',
|
|
255
|
-
'log': 'plaintext',
|
|
256
|
-
|
|
257
|
-
// Special filename-style extensions
|
|
258
|
-
'dockerfile': 'dockerfile'
|
|
259
|
-
};
|
|
260
|
-
return langMap[normalized];
|
|
421
|
+
|
|
422
|
+
const normalized = getExtension(ext);
|
|
423
|
+
return LANGUAGE_MAP[normalized];
|
|
261
424
|
}
|
|
262
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Get color for a programming language (for stats display)
|
|
428
|
+
* @param {string} language - Language identifier
|
|
429
|
+
* @returns {string} Hex color code
|
|
430
|
+
*/
|
|
263
431
|
function getLanguageColor(language) {
|
|
264
|
-
|
|
265
|
-
javascript: '#f1e05a',
|
|
266
|
-
typescript: '#2b7489',
|
|
267
|
-
python: '#3572A5',
|
|
268
|
-
java: '#b07219',
|
|
269
|
-
html: '#e34c26',
|
|
270
|
-
css: '#563d7c',
|
|
271
|
-
json: '#292929',
|
|
272
|
-
markdown: '#083fa1',
|
|
273
|
-
go: '#00ADD8',
|
|
274
|
-
rust: '#dea584',
|
|
275
|
-
php: '#4F5D95',
|
|
276
|
-
ruby: '#701516',
|
|
277
|
-
other: '#cccccc'
|
|
278
|
-
};
|
|
279
|
-
return colors[language] || colors.other;
|
|
432
|
+
return LANGUAGE_COLORS[language] || LANGUAGE_COLORS.other;
|
|
280
433
|
}
|
|
281
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Format bytes to human-readable string
|
|
437
|
+
* @param {number} bytes - Number of bytes
|
|
438
|
+
* @returns {string} Formatted string (e.g., "1.5 MB")
|
|
439
|
+
*/
|
|
282
440
|
function formatBytes(bytes) {
|
|
283
441
|
if (bytes === 0) return '0 B';
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
|
|
442
|
+
|
|
443
|
+
const i = Math.floor(Math.log(bytes) / Math.log(BYTE_BASE));
|
|
444
|
+
const size = parseFloat((bytes / Math.pow(BYTE_BASE, i)).toFixed(2));
|
|
445
|
+
|
|
446
|
+
return `${size} ${BYTE_UNITS[i]}`;
|
|
288
447
|
}
|
|
289
448
|
|
|
290
449
|
/**
|
|
291
|
-
*
|
|
450
|
+
* Check if file is an image based on extension
|
|
451
|
+
* @param {string} filePathOrExt - File path or extension
|
|
452
|
+
* @returns {boolean} True if image file
|
|
292
453
|
*/
|
|
293
454
|
function isImageFile(filePathOrExt) {
|
|
294
455
|
const ext = getExtension(filePathOrExt);
|
|
295
|
-
|
|
296
|
-
return imageExtensions.includes(ext);
|
|
456
|
+
return IMAGE_EXTENSIONS.has(ext);
|
|
297
457
|
}
|
|
298
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Check if file is binary based on extension
|
|
461
|
+
* @param {string} filePathOrExt - File path or extension
|
|
462
|
+
* @returns {boolean} True if binary file
|
|
463
|
+
*/
|
|
299
464
|
function isBinaryFile(filePathOrExt) {
|
|
300
465
|
const ext = getExtension(filePathOrExt);
|
|
301
|
-
|
|
302
|
-
// Images (handled separately)
|
|
303
|
-
'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp', 'tiff', 'ico',
|
|
304
|
-
// Archives
|
|
305
|
-
'zip', 'tar', 'gz', 'rar', '7z',
|
|
306
|
-
// Executables
|
|
307
|
-
'exe', 'bin', 'app', 'deb', 'rpm',
|
|
308
|
-
// Documents
|
|
309
|
-
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
310
|
-
// Media
|
|
311
|
-
'mp4', 'mov', 'avi', 'mkv', 'mp3', 'wav', 'flac',
|
|
312
|
-
// Other
|
|
313
|
-
'class', 'so', 'dll', 'dylib'
|
|
314
|
-
];
|
|
315
|
-
return binaryExtensions.includes(ext);
|
|
466
|
+
return BINARY_EXTENSIONS.has(ext);
|
|
316
467
|
}
|
|
317
468
|
|
|
469
|
+
/**
|
|
470
|
+
* Check if file is text-based (not binary)
|
|
471
|
+
* @param {string} filePathOrExt - File path or extension
|
|
472
|
+
* @returns {boolean} True if text file
|
|
473
|
+
*/
|
|
318
474
|
function isTextFile(filePathOrExt) {
|
|
319
475
|
return !isBinaryFile(filePathOrExt);
|
|
320
476
|
}
|
|
321
477
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return filePathOrExt.toLowerCase();
|
|
326
|
-
}
|
|
327
|
-
// Extract extension from file path
|
|
328
|
-
return path.extname(filePathOrExt).toLowerCase().slice(1);
|
|
329
|
-
}
|
|
478
|
+
// ============================================================================
|
|
479
|
+
// Exports
|
|
480
|
+
// ============================================================================
|
|
330
481
|
|
|
331
482
|
module.exports = {
|
|
332
483
|
getFileIcon,
|
|
@@ -336,4 +487,4 @@ module.exports = {
|
|
|
336
487
|
isImageFile,
|
|
337
488
|
isBinaryFile,
|
|
338
489
|
isTextFile
|
|
339
|
-
};
|
|
490
|
+
};
|