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.
Files changed (47) hide show
  1. package/.env +0 -0
  2. package/lib/constants.js +21 -16
  3. package/lib/content-search.js +212 -0
  4. package/lib/error-handler.js +39 -28
  5. package/lib/file-utils.js +438 -287
  6. package/lib/git.js +11 -55
  7. package/lib/gitignore.js +70 -41
  8. package/lib/renderers.js +17 -33
  9. package/lib/server.js +73 -196
  10. package/lib/symbol-parser.js +600 -0
  11. package/package.json +1 -1
  12. package/public/app.js +135 -68
  13. package/public/css/components/buttons.css +423 -0
  14. package/public/css/components/forms.css +171 -0
  15. package/public/css/components/modals.css +286 -0
  16. package/public/css/components/notifications.css +36 -0
  17. package/public/css/file-table.css +318 -0
  18. package/public/css/file-tree.css +269 -0
  19. package/public/css/file-viewer.css +1259 -0
  20. package/public/css/layout.css +372 -0
  21. package/public/css/main.css +35 -0
  22. package/public/css/reset.css +64 -0
  23. package/public/css/search.css +694 -0
  24. package/public/css/symbol-outline.css +279 -0
  25. package/public/css/variables.css +135 -0
  26. package/public/js/constants.js +50 -34
  27. package/public/js/content-search-handler.js +551 -0
  28. package/public/js/file-viewer.js +437 -0
  29. package/public/js/focus-mode.js +280 -0
  30. package/public/js/inline-search.js +659 -0
  31. package/public/js/modal-manager.js +14 -28
  32. package/public/js/symbol-outline.js +454 -0
  33. package/public/js/utils.js +152 -94
  34. package/.claude/settings.local.json +0 -30
  35. package/SAMPLE.md +0 -287
  36. package/lib/validation.js +0 -77
  37. package/public/app.js.backup +0 -1902
  38. package/public/highlight.css +0 -121
  39. package/public/js/draft-manager.js +0 -36
  40. package/public/js/editor-manager.js +0 -159
  41. package/public/styles.css +0 -2727
  42. package/test.js +0 -138
  43. package/tests/draftManager.test.js +0 -241
  44. package/tests/fileTypeDetection.test.js +0 -111
  45. package/tests/httpService.test.js +0 -268
  46. package/tests/languageDetection.test.js +0 -145
  47. package/tests/pathUtils.test.js +0 -136
@@ -1,102 +1,92 @@
1
1
  /**
2
- * Utility functions for path and URL manipulation
2
+ * Utility functions for path manipulation, language detection, and HTML escaping
3
+ * @module utils
3
4
  */
4
5
 
5
- export const PathUtils = {
6
- getCurrentPath() {
7
- const currentUrl = new URL(window.location.href);
8
- return currentUrl.searchParams.get('path') || '';
9
- },
10
-
11
- getParentPath(currentPath) {
12
- if (!currentPath || currentPath === '') {
13
- return null;
14
- }
6
+ // ============================================================================
7
+ // Language Map (alpha-sorted by key)
8
+ // ============================================================================
15
9
 
16
- const pathParts = currentPath.split('/').filter(p => p);
17
- if (pathParts.length === 0) {
18
- return null;
19
- }
20
-
21
- pathParts.pop();
22
- return pathParts.join('/');
23
- },
10
+ const LANGUAGE_MAP = {
11
+ bash: 'shell',
12
+ c: 'c',
13
+ cc: 'cpp',
14
+ clj: 'clojure',
15
+ cpp: 'cpp',
16
+ css: 'css',
17
+ cxx: 'cpp',
18
+ dart: 'dart',
19
+ fish: 'shell',
20
+ go: 'go',
21
+ groovy: 'groovy',
22
+ h: 'c',
23
+ hpp: 'cpp',
24
+ htm: 'html',
25
+ html: 'html',
26
+ java: 'java',
27
+ js: 'javascript',
28
+ json: 'json',
29
+ jsx: 'javascript',
30
+ kt: 'kotlin',
31
+ less: 'less',
32
+ log: 'plaintext',
33
+ lua: 'lua',
34
+ md: 'markdown',
35
+ mjs: 'javascript',
36
+ php: 'php',
37
+ pl: 'perl',
38
+ ps1: 'powershell',
39
+ py: 'python',
40
+ r: 'r',
41
+ rb: 'ruby',
42
+ rs: 'rust',
43
+ sass: 'sass',
44
+ scala: 'scala',
45
+ scss: 'scss',
46
+ sh: 'shell',
47
+ sql: 'sql',
48
+ swift: 'swift',
49
+ ts: 'typescript',
50
+ tsx: 'typescript',
51
+ txt: 'plaintext',
52
+ xml: 'xml',
53
+ yaml: 'yaml',
54
+ yml: 'yaml',
55
+ zsh: 'shell'
56
+ };
24
57
 
25
- buildFilePath(currentPath, filename) {
26
- return currentPath ? `${currentPath}/${filename}` : filename;
27
- },
58
+ // ============================================================================
59
+ // HTML Utilities
60
+ // ============================================================================
28
61
 
29
- getFileName(filePath) {
30
- return filePath.split('/').pop() || 'file.txt';
31
- },
32
-
33
- buildPathUrl(basePath, targetPath) {
34
- return targetPath ? `${basePath}?path=${encodeURIComponent(targetPath)}` : basePath;
35
- },
62
+ /**
63
+ * Escapes HTML special characters to prevent XSS attacks
64
+ * @param {string} text - Text to escape
65
+ * @returns {string} Escaped HTML string
66
+ */
67
+ export function escapeHtml(text) {
68
+ if (typeof text !== 'string') return '';
69
+ const div = document.createElement('div');
70
+ div.textContent = text;
71
+ return div.innerHTML;
72
+ }
36
73
 
37
- getDirectoryPath(filePath) {
38
- const parts = filePath.split('/').filter(p => p);
39
- if (parts.length <= 1) {
40
- return '';
41
- }
42
- return parts.slice(0, -1).join('/');
43
- }
44
- };
74
+ // ============================================================================
75
+ // Language Detection
76
+ // ============================================================================
45
77
 
46
78
  /**
47
- * Language detection utility
79
+ * Detects programming language from filename extension
80
+ * @param {string} filename - Filename to analyze
81
+ * @returns {string} Language identifier for syntax highlighting
48
82
  */
49
83
  export function getLanguageFromExtension(filename) {
50
- const ext = filename.split('.').pop().toLowerCase();
51
- const languageMap = {
52
- js: 'javascript',
53
- mjs: 'javascript',
54
- jsx: 'javascript',
55
- ts: 'typescript',
56
- tsx: 'typescript',
57
- html: 'html',
58
- htm: 'html',
59
- css: 'css',
60
- scss: 'scss',
61
- sass: 'sass',
62
- less: 'less',
63
- json: 'json',
64
- xml: 'xml',
65
- yaml: 'yaml',
66
- yml: 'yaml',
67
- py: 'python',
68
- java: 'java',
69
- go: 'go',
70
- rs: 'rust',
71
- php: 'php',
72
- rb: 'ruby',
73
- swift: 'swift',
74
- kt: 'kotlin',
75
- dart: 'dart',
76
- c: 'c',
77
- cpp: 'cpp',
78
- cc: 'cpp',
79
- cxx: 'cpp',
80
- h: 'c',
81
- hpp: 'cpp',
82
- sh: 'shell',
83
- bash: 'shell',
84
- zsh: 'shell',
85
- fish: 'shell',
86
- ps1: 'powershell',
87
- sql: 'sql',
88
- r: 'r',
89
- scala: 'scala',
90
- clj: 'clojure',
91
- lua: 'lua',
92
- pl: 'perl',
93
- groovy: 'groovy',
94
- md: 'markdown',
95
- txt: 'plaintext',
96
- log: 'plaintext'
97
- };
98
-
84
+ if (!filename) return 'plaintext';
85
+
99
86
  const basename = filename.toLowerCase();
87
+ const ext = filename.split('.').pop()?.toLowerCase();
88
+
89
+ // Special filenames
100
90
  if (basename === 'dockerfile' || basename.startsWith('dockerfile.')) {
101
91
  return 'dockerfile';
102
92
  }
@@ -110,14 +100,82 @@ export function getLanguageFromExtension(filename) {
110
100
  return 'json';
111
101
  }
112
102
 
113
- return languageMap[ext] || 'plaintext';
103
+ return LANGUAGE_MAP[ext] || 'plaintext';
114
104
  }
115
105
 
106
+ // ============================================================================
107
+ // Path Utilities
108
+ // ============================================================================
109
+
116
110
  /**
117
- * HTML escaping utility
111
+ * Path manipulation utilities
118
112
  */
119
- export function escapeHtml(text) {
120
- const div = document.createElement('div');
121
- div.textContent = text;
122
- return div.innerHTML;
123
- }
113
+ export const PathUtils = {
114
+ /**
115
+ * Builds a file path from directory and filename
116
+ * @param {string} currentPath - Current directory path
117
+ * @param {string} filename - Filename to append
118
+ * @returns {string} Combined file path
119
+ */
120
+ buildFilePath(currentPath, filename) {
121
+ return currentPath ? `${currentPath}/${filename}` : filename;
122
+ },
123
+
124
+ /**
125
+ * Builds a URL with path query parameter
126
+ * @param {string} basePath - Base URL path
127
+ * @param {string} targetPath - Target file/directory path
128
+ * @returns {string} URL with encoded path parameter
129
+ */
130
+ buildPathUrl(basePath, targetPath) {
131
+ return targetPath ? `${basePath}?path=${encodeURIComponent(targetPath)}` : basePath;
132
+ },
133
+
134
+ /**
135
+ * Gets the current path from URL query parameters
136
+ * @returns {string} Current path or empty string
137
+ */
138
+ getCurrentPath() {
139
+ const currentUrl = new URL(window.location.href);
140
+ return currentUrl.searchParams.get('path') || '';
141
+ },
142
+
143
+ /**
144
+ * Gets the directory portion of a file path
145
+ * @param {string} filePath - Full file path
146
+ * @returns {string} Directory path
147
+ */
148
+ getDirectoryPath(filePath) {
149
+ const parts = filePath.split('/').filter(p => p);
150
+ if (parts.length <= 1) {
151
+ return '';
152
+ }
153
+ return parts.slice(0, -1).join('/');
154
+ },
155
+
156
+ /**
157
+ * Gets the filename from a full path
158
+ * @param {string} filePath - Full file path
159
+ * @returns {string} Filename
160
+ */
161
+ getFileName(filePath) {
162
+ return filePath.split('/').pop() || 'file.txt';
163
+ },
164
+
165
+ /**
166
+ * Gets the parent directory path
167
+ * @param {string} currentPath - Current path
168
+ * @returns {string|null} Parent path or null if at root
169
+ */
170
+ getParentPath(currentPath) {
171
+ if (!currentPath || currentPath === '') {
172
+ return null;
173
+ }
174
+ const pathParts = currentPath.split('/').filter(p => p);
175
+ if (pathParts.length === 0) {
176
+ return null;
177
+ }
178
+ pathParts.pop();
179
+ return pathParts.join('/');
180
+ }
181
+ };
@@ -1,30 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(chmod:*)",
5
- "Bash(npm install)",
6
- "Bash(npm start)",
7
- "Bash(pkill:*)",
8
- "Bash(node:*)",
9
- "Bash(curl:*)",
10
- "Bash(npm install:*)",
11
- "Bash(git init:*)",
12
- "Bash(git add:*)",
13
- "Bash(git commit:*)",
14
- "Bash(git rm:*)",
15
- "Bash(ssh:*)",
16
- "Bash(git restore:*)",
17
- "Bash(npx gh-here:*)",
18
- "Bash(git checkout:*)",
19
- "Bash(git push:*)",
20
- "Bash(git pull:*)",
21
- "Bash(npm publish:*)",
22
- "Bash(timeout:*)",
23
- "Bash(npm version:*)",
24
- "Bash(gh release create:*)",
25
- "Bash(tree:*)"
26
- ],
27
- "deny": [],
28
- "ask": []
29
- }
30
- }
package/SAMPLE.md DELETED
@@ -1,287 +0,0 @@
1
- # Syntax Highlighting Test
2
-
3
- This file contains code samples in various languages to test syntax highlighting in markdown rendering.
4
-
5
- ## JavaScript
6
-
7
- ```javascript
8
- function greet(name) {
9
- const message = `Hello, ${name}!`;
10
- console.log(message);
11
- return message;
12
- }
13
-
14
- class Person {
15
- constructor(name, age) {
16
- this.name = name;
17
- this.age = age;
18
- }
19
-
20
- introduce() {
21
- return `My name is ${this.name} and I'm ${this.age} years old.`;
22
- }
23
- }
24
- ```
25
-
26
- ## Python
27
-
28
- ```python
29
- def calculate_fibonacci(n):
30
- """Calculate the nth Fibonacci number."""
31
- if n <= 1:
32
- return n
33
- return calculate_fibonacci(n - 1) + calculate_fibonacci(n - 2)
34
-
35
- class Animal:
36
- def __init__(self, name, species):
37
- self.name = name
38
- self.species = species
39
-
40
- def make_sound(self):
41
- return f"{self.name} the {self.species} makes a sound"
42
- ```
43
-
44
- ## Go
45
-
46
- ```go
47
- package main
48
-
49
- import (
50
- "fmt"
51
- "time"
52
- )
53
-
54
- type User struct {
55
- Name string
56
- Email string
57
- CreatedAt time.Time
58
- }
59
-
60
- func (u *User) SendEmail(message string) error {
61
- fmt.Printf("Sending email to %s: %s\n", u.Email, message)
62
- return nil
63
- }
64
-
65
- func main() {
66
- user := &User{
67
- Name: "John Doe",
68
- Email: "john@example.com",
69
- CreatedAt: time.Now(),
70
- }
71
- user.SendEmail("Welcome!")
72
- }
73
- ```
74
-
75
- ## Rust
76
-
77
- ```rust
78
- use std::collections::HashMap;
79
-
80
- fn main() {
81
- let mut scores = HashMap::new();
82
- scores.insert(String::from("Blue"), 10);
83
- scores.insert(String::from("Red"), 50);
84
-
85
- for (key, value) in &scores {
86
- println!("{}: {}", key, value);
87
- }
88
- }
89
-
90
- struct Rectangle {
91
- width: u32,
92
- height: u32,
93
- }
94
-
95
- impl Rectangle {
96
- fn area(&self) -> u32 {
97
- self.width * self.height
98
- }
99
- }
100
- ```
101
-
102
- ## TypeScript
103
-
104
- ```typescript
105
- interface User {
106
- id: number;
107
- name: string;
108
- email: string;
109
- }
110
-
111
- async function fetchUser(id: number): Promise<User> {
112
- const response = await fetch(`/api/users/${id}`);
113
- const data: User = await response.json();
114
- return data;
115
- }
116
-
117
- class UserService {
118
- private users: Map<number, User> = new Map();
119
-
120
- addUser(user: User): void {
121
- this.users.set(user.id, user);
122
- }
123
-
124
- getUser(id: number): User | undefined {
125
- return this.users.get(id);
126
- }
127
- }
128
- ```
129
-
130
- ## Bash
131
-
132
- ```bash
133
- #!/bin/bash
134
-
135
- # Function to backup files
136
- backup_files() {
137
- local source_dir=$1
138
- local backup_dir=$2
139
-
140
- if [ ! -d "$backup_dir" ]; then
141
- mkdir -p "$backup_dir"
142
- fi
143
-
144
- tar -czf "$backup_dir/backup-$(date +%Y%m%d).tar.gz" "$source_dir"
145
- echo "Backup completed successfully!"
146
- }
147
-
148
- backup_files "/home/user/documents" "/home/user/backups"
149
- ```
150
-
151
- ## SQL
152
-
153
- ```sql
154
- -- Create users table
155
- CREATE TABLE users (
156
- id SERIAL PRIMARY KEY,
157
- username VARCHAR(50) UNIQUE NOT NULL,
158
- email VARCHAR(100) UNIQUE NOT NULL,
159
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
160
- );
161
-
162
- -- Insert some data
163
- INSERT INTO users (username, email) VALUES
164
- ('john_doe', 'john@example.com'),
165
- ('jane_smith', 'jane@example.com');
166
-
167
- -- Query with JOIN
168
- SELECT u.username, o.order_date, o.total
169
- FROM users u
170
- INNER JOIN orders o ON u.id = o.user_id
171
- WHERE o.total > 100
172
- ORDER BY o.order_date DESC;
173
- ```
174
-
175
- ## JSON
176
-
177
- ```json
178
- {
179
- "name": "gh-here",
180
- "version": "2.0.0",
181
- "description": "A local GitHub-like file browser",
182
- "dependencies": {
183
- "express": "^4.18.2",
184
- "highlight.js": "^11.9.0",
185
- "marked": "^12.0.0"
186
- },
187
- "scripts": {
188
- "start": "node bin/gh-here.js",
189
- "test": "jest"
190
- }
191
- }
192
- ```
193
-
194
- ## CSS
195
-
196
- ```css
197
- :root {
198
- --primary-color: #0366d6;
199
- --background-color: #ffffff;
200
- --text-color: #24292e;
201
- }
202
-
203
- .container {
204
- max-width: 1200px;
205
- margin: 0 auto;
206
- padding: 20px;
207
- }
208
-
209
- .button {
210
- display: inline-block;
211
- padding: 10px 20px;
212
- background-color: var(--primary-color);
213
- color: white;
214
- border-radius: 5px;
215
- transition: background-color 0.3s ease;
216
- }
217
-
218
- .button:hover {
219
- background-color: #0256c7;
220
- cursor: pointer;
221
- }
222
- ```
223
-
224
- ## HTML
225
-
226
- ```html
227
- <!DOCTYPE html>
228
- <html lang="en">
229
- <head>
230
- <meta charset="UTF-8">
231
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
232
- <title>Sample Page</title>
233
- <link rel="stylesheet" href="styles.css">
234
- </head>
235
- <body>
236
- <header>
237
- <h1>Welcome to My Site</h1>
238
- <nav>
239
- <ul>
240
- <li><a href="#home">Home</a></li>
241
- <li><a href="#about">About</a></li>
242
- <li><a href="#contact">Contact</a></li>
243
- </ul>
244
- </nav>
245
- </header>
246
- <main>
247
- <p>This is the main content area.</p>
248
- </main>
249
- <script src="script.js"></script>
250
- </body>
251
- </html>
252
- ```
253
-
254
- ## Java
255
-
256
- ```java
257
- import java.util.ArrayList;
258
- import java.util.List;
259
-
260
- public class BankAccount {
261
- private String accountNumber;
262
- private double balance;
263
- private List<Transaction> transactions;
264
-
265
- public BankAccount(String accountNumber, double initialBalance) {
266
- this.accountNumber = accountNumber;
267
- this.balance = initialBalance;
268
- this.transactions = new ArrayList<>();
269
- }
270
-
271
- public void deposit(double amount) {
272
- if (amount > 0) {
273
- balance += amount;
274
- transactions.add(new Transaction("DEPOSIT", amount));
275
- }
276
- }
277
-
278
- public boolean withdraw(double amount) {
279
- if (amount > 0 && balance >= amount) {
280
- balance -= amount;
281
- transactions.add(new Transaction("WITHDRAWAL", amount));
282
- return true;
283
- }
284
- return false;
285
- }
286
- }
287
- ```
package/lib/validation.js DELETED
@@ -1,77 +0,0 @@
1
- /**
2
- * Input validation and security utilities
3
- */
4
-
5
- const path = require('path');
6
-
7
- /**
8
- * Validates that a path is within the allowed working directory
9
- * Prevents path traversal attacks
10
- */
11
- function validatePath(requestPath, workingDir) {
12
- const fullPath = path.resolve(path.join(workingDir, requestPath || ''));
13
- return fullPath.startsWith(workingDir);
14
- }
15
-
16
- /**
17
- * Sanitizes file paths to prevent injection
18
- */
19
- function sanitizePath(filePath) {
20
- if (!filePath) {
21
- return '';
22
- }
23
- return filePath.replace(/\.\./g, '').replace(/[<>:"|?*]/g, '');
24
- }
25
-
26
- /**
27
- * Validates commit message
28
- */
29
- function validateCommitMessage(message) {
30
- if (!message || typeof message !== 'string') {
31
- return false;
32
- }
33
- return message.trim().length > 0 && message.trim().length <= 5000;
34
- }
35
-
36
- /**
37
- * Validates filename
38
- */
39
- function validateFilename(filename) {
40
- if (!filename || typeof filename !== 'string') {
41
- return false;
42
- }
43
-
44
- const sanitized = filename.trim();
45
-
46
- if (sanitized.length === 0 || sanitized.length > 255) {
47
- return false;
48
- }
49
-
50
- const invalidChars = /[<>:"|?*\x00-\x1F]/;
51
- if (invalidChars.test(sanitized)) {
52
- return false;
53
- }
54
-
55
- return true;
56
- }
57
-
58
- /**
59
- * Validates an array of file paths
60
- */
61
- function validateFilePaths(files) {
62
- if (!Array.isArray(files) || files.length === 0) {
63
- return false;
64
- }
65
-
66
- return files.every(file => {
67
- return typeof file === 'string' && file.trim().length > 0;
68
- });
69
- }
70
-
71
- module.exports = {
72
- validatePath,
73
- sanitizePath,
74
- validateCommitMessage,
75
- validateFilename,
76
- validateFilePaths
77
- };