gh-here 1.1.1 → 2.1.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/.channels_cache_v2.json +10882 -0
- package/.claude/settings.local.json +10 -17
- package/.users_cache.json +16187 -0
- package/SAMPLE.md +287 -0
- package/bin/gh-here.js +8 -9
- package/blog-post.md +100 -0
- package/lib/renderers.js +29 -8
- package/lib/server.js +2 -2
- package/package.json +1 -1
- package/public/styles.css +60 -10
package/SAMPLE.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
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/bin/gh-here.js
CHANGED
|
@@ -9,7 +9,8 @@ const { setupRoutes } = require('../lib/server');
|
|
|
9
9
|
|
|
10
10
|
// Parse command line arguments
|
|
11
11
|
const args = process.argv.slice(2);
|
|
12
|
-
const
|
|
12
|
+
const noOpen = args.includes('--no-open');
|
|
13
|
+
const openBrowser = !noOpen; // Default is to open browser
|
|
13
14
|
const helpRequested = args.includes('--help') || args.includes('-h');
|
|
14
15
|
|
|
15
16
|
// Check for port specification
|
|
@@ -37,17 +38,17 @@ gh-here - GitHub-like local file browser
|
|
|
37
38
|
Usage: npx gh-here [options]
|
|
38
39
|
|
|
39
40
|
Options:
|
|
40
|
-
--open
|
|
41
|
+
--no-open Do not open browser automatically
|
|
41
42
|
--browser=<name> Specify browser (safari, chrome, firefox, arc)
|
|
42
43
|
--port=<number> Specify port number (default: 5555)
|
|
43
44
|
--help, -h Show this help message
|
|
44
45
|
|
|
45
46
|
Examples:
|
|
46
|
-
npx gh-here Start server
|
|
47
|
-
npx gh-here --open
|
|
48
|
-
npx gh-here --port=8080 Start server on port 8080
|
|
49
|
-
npx gh-here --
|
|
50
|
-
npx gh-here --
|
|
47
|
+
npx gh-here Start server and open browser
|
|
48
|
+
npx gh-here --no-open Start server without opening browser
|
|
49
|
+
npx gh-here --port=8080 Start server on port 8080 and open browser
|
|
50
|
+
npx gh-here --browser=safari Start server and open in Safari
|
|
51
|
+
npx gh-here --browser=arc Start server and open in Arc
|
|
51
52
|
`);
|
|
52
53
|
process.exit(0);
|
|
53
54
|
}
|
|
@@ -181,8 +182,6 @@ async function startServer() {
|
|
|
181
182
|
if (openBrowser) {
|
|
182
183
|
console.log(`🌍 Opening browser...`);
|
|
183
184
|
setTimeout(() => openBrowserToUrl(url), 1000);
|
|
184
|
-
} else {
|
|
185
|
-
console.log(`💡 Tip: Use --open flag to launch browser automatically`);
|
|
186
185
|
}
|
|
187
186
|
});
|
|
188
187
|
} catch (error) {
|
package/blog-post.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Why gh-here is the Perfect Tool for Terminal-Heavy Developers
|
|
2
|
+
|
|
3
|
+
If you're like most developers in 2025, you probably spend a lot of time in the terminal. You're using Claude Code to generate and refactor code, running commands left and right, and iterating quickly on projects that haven't even been pushed to GitHub yet. But sometimes you need to step back and get a bird's-eye view of your codebase—and that's where `gh-here` shines.
|
|
4
|
+
|
|
5
|
+
## The Gap Between Terminal and IDE
|
|
6
|
+
|
|
7
|
+
Picture this scenario: You're deep in a terminal session, using Claude Code to implement a new feature. You've got files scattered across multiple directories, you're testing things with curl, and you're in that flow state where opening a heavy IDE would just break your momentum. But you need to:
|
|
8
|
+
|
|
9
|
+
- Quickly browse through your project structure
|
|
10
|
+
- Check what files you've modified
|
|
11
|
+
- Preview a README or markdown file
|
|
12
|
+
- Navigate between related files without losing context
|
|
13
|
+
- See language distribution across your codebase
|
|
14
|
+
|
|
15
|
+
This is the exact gap that `gh-here` fills.
|
|
16
|
+
|
|
17
|
+
## Built for Modern Development Workflows
|
|
18
|
+
|
|
19
|
+
### 1. **Perfect for AI-Assisted Development**
|
|
20
|
+
When you're working with Claude Code or GitHub Copilot, you're often generating and modifying files rapidly. `gh-here` gives you an instant, GitHub-like interface to review what's been changed without committing to version control or opening a full IDE.
|
|
21
|
+
|
|
22
|
+
### 2. **Ideal for Pre-Push Workflows**
|
|
23
|
+
Not everything needs to hit GitHub immediately. Whether you're experimenting with a proof of concept or working on a feature branch, `gh-here` lets you navigate and review your local changes in a familiar, web-based interface.
|
|
24
|
+
|
|
25
|
+
### 3. **Terminal-Native but GUI-Friendly**
|
|
26
|
+
Launch it with a single command (`gh-here`) from any directory, and instantly get a clean, GitHub-inspired interface in your browser. No configuration, no setup—just point and browse.
|
|
27
|
+
|
|
28
|
+
## Real-World Use Cases
|
|
29
|
+
|
|
30
|
+
**Scenario 1: Code Review Before Push**
|
|
31
|
+
```bash
|
|
32
|
+
$ claude "implement user authentication system"
|
|
33
|
+
# ... lots of generated files and changes
|
|
34
|
+
$ gh-here
|
|
35
|
+
# Browse through generated files, check git status, review changes
|
|
36
|
+
$ git add . && git commit -m "Add user authentication"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Scenario 2: Project Exploration**
|
|
40
|
+
```bash
|
|
41
|
+
$ git clone some-interesting-repo
|
|
42
|
+
$ cd some-interesting-repo
|
|
43
|
+
$ gh-here
|
|
44
|
+
# Instantly get an overview: languages used, file structure, README preview
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Scenario 3: Documentation Review**
|
|
48
|
+
```bash
|
|
49
|
+
$ claude "update all the documentation"
|
|
50
|
+
$ gh-here
|
|
51
|
+
# Preview all the markdown files in GitHub-style rendering
|
|
52
|
+
# Check if everything looks good before committing
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Why Not Just Use GitHub or an IDE?
|
|
56
|
+
|
|
57
|
+
**GitHub**: Your code isn't pushed yet, or you're working on experimental changes you don't want in version control.
|
|
58
|
+
|
|
59
|
+
**IDE**: Too heavy for quick browsing, breaks your terminal flow, takes time to open and index.
|
|
60
|
+
|
|
61
|
+
**File Explorer**: No syntax highlighting, no git integration, no markdown rendering, no language stats.
|
|
62
|
+
|
|
63
|
+
**Terminal**: Great for editing, terrible for browsing and getting visual context.
|
|
64
|
+
|
|
65
|
+
## The Sweet Spot
|
|
66
|
+
|
|
67
|
+
`gh-here` hits the perfect sweet spot:
|
|
68
|
+
- **Lightweight**: Starts instantly, no heavy indexing
|
|
69
|
+
- **Familiar**: GitHub-style interface that every developer knows
|
|
70
|
+
- **Integrated**: Shows git status, handles various file types, renders markdown
|
|
71
|
+
- **Flexible**: Works with any directory, whether it's a git repo or not
|
|
72
|
+
- **Terminal-friendly**: Launch with one command, works alongside your existing workflow
|
|
73
|
+
|
|
74
|
+
## Features That Just Make Sense
|
|
75
|
+
|
|
76
|
+
- **Language statistics**: Instantly see what technologies your project uses
|
|
77
|
+
- **Git integration**: Visual git status indicators, diff viewing
|
|
78
|
+
- **File type support**: Syntax highlighting, image viewing, markdown rendering
|
|
79
|
+
- **Search functionality**: Quick file search with keyboard shortcuts
|
|
80
|
+
- **Breadcrumb navigation**: Always know where you are in your project
|
|
81
|
+
|
|
82
|
+
## Perfect for 2025 Development
|
|
83
|
+
|
|
84
|
+
As development workflows become more AI-assisted and terminal-centric, tools like `gh-here` become essential. You're not always in an IDE, you're not always ready to push to GitHub, but you always need to understand your codebase structure and changes.
|
|
85
|
+
|
|
86
|
+
It's the missing link between your terminal workflow and the visual context you need to stay productive.
|
|
87
|
+
|
|
88
|
+
## Get Started
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm install -g gh-here
|
|
92
|
+
cd your-project
|
|
93
|
+
gh-here
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
That's it. Your local codebase is now browsable in a clean, GitHub-style interface at `localhost:5556`.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
*Built for developers who live in the terminal but occasionally need a better view of their code.*
|
package/lib/renderers.js
CHANGED
|
@@ -8,6 +8,23 @@ const { exec } = require('child_process');
|
|
|
8
8
|
const { getFileIcon, getLanguageFromExtension, getLanguageColor, formatBytes, isImageFile, isBinaryFile, isTextFile } = require('./file-utils');
|
|
9
9
|
const { getGitStatusIcon, getGitStatusDescription } = require('./git');
|
|
10
10
|
|
|
11
|
+
// Configure marked to use highlight.js for syntax highlighting
|
|
12
|
+
marked.use({
|
|
13
|
+
renderer: {
|
|
14
|
+
code(code, language) {
|
|
15
|
+
if (language && hljs.getLanguage(language)) {
|
|
16
|
+
try {
|
|
17
|
+
return `<pre><code class="hljs language-${language}">${hljs.highlight(code, { language }).value}</code></pre>`;
|
|
18
|
+
} catch (err) {
|
|
19
|
+
// Fall back to auto-detection if language-specific highlighting fails
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Auto-detect language if not specified or language not found
|
|
23
|
+
return `<pre><code class="hljs">${hljs.highlightAuto(code).value}</code></pre>`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
11
28
|
/**
|
|
12
29
|
* HTML rendering module
|
|
13
30
|
* Handles all HTML template generation for different views
|
|
@@ -18,7 +35,8 @@ function renderDirectory(currentPath, items, showGitignored = false, gitBranch =
|
|
|
18
35
|
const breadcrumbs = generateBreadcrumbs(currentPath, gitBranch, workingDirName);
|
|
19
36
|
const readmeFile = findReadmeFile(items);
|
|
20
37
|
const readmePreview = readmeFile ? generateReadmePreview(currentPath, readmeFile) : '';
|
|
21
|
-
|
|
38
|
+
// Only show language stats on root/top-level directory
|
|
39
|
+
const languageStats = (!currentPath || currentPath === '.') ? generateLanguageStats(items) : '';
|
|
22
40
|
|
|
23
41
|
const itemsHtml = items.map(item => `
|
|
24
42
|
<tr class="file-row" data-name="${item.name.toLowerCase()}" data-type="${item.isDirectory ? 'dir' : 'file'}" data-path="${item.path}">
|
|
@@ -113,6 +131,7 @@ function renderDirectory(currentPath, items, showGitignored = false, gitBranch =
|
|
|
113
131
|
${octicons['chevron-down'].toSVG({ class: 'octicon-chevron' })}
|
|
114
132
|
</button>
|
|
115
133
|
` : ''}
|
|
134
|
+
${languageStats}
|
|
116
135
|
</div>
|
|
117
136
|
<div class="repo-controls-right">
|
|
118
137
|
<div class="search-container">
|
|
@@ -130,7 +149,6 @@ function renderDirectory(currentPath, items, showGitignored = false, gitBranch =
|
|
|
130
149
|
<main>
|
|
131
150
|
<div class="repo-canvas">
|
|
132
151
|
<div class="repo-canvas-content">
|
|
133
|
-
${languageStats}
|
|
134
152
|
<div class="file-table-container">
|
|
135
153
|
<table class="file-table" id="file-table">
|
|
136
154
|
<thead>
|
|
@@ -242,8 +260,9 @@ function generateLanguageStats(items) {
|
|
|
242
260
|
`;
|
|
243
261
|
}
|
|
244
262
|
|
|
245
|
-
async function renderFileDiff(filePath, ext, gitInfo, gitRepoRoot) {
|
|
246
|
-
const
|
|
263
|
+
async function renderFileDiff(filePath, ext, gitInfo, gitRepoRoot, workingDir = null) {
|
|
264
|
+
const workingDirName = workingDir ? path.basename(workingDir) : null;
|
|
265
|
+
const breadcrumbs = generateBreadcrumbs(filePath, null, workingDirName);
|
|
247
266
|
|
|
248
267
|
// Get git diff for the file
|
|
249
268
|
return new Promise((resolve, reject) => {
|
|
@@ -370,8 +389,9 @@ function renderRawDiff(diffOutput, ext) {
|
|
|
370
389
|
}
|
|
371
390
|
|
|
372
391
|
|
|
373
|
-
async function renderFile(filePath, content, ext, viewMode = 'rendered', gitStatus = null, workingDir) {
|
|
374
|
-
const
|
|
392
|
+
async function renderFile(filePath, content, ext, viewMode = 'rendered', gitStatus = null, workingDir = null) {
|
|
393
|
+
const workingDirName = workingDir ? path.basename(workingDir) : null;
|
|
394
|
+
const breadcrumbs = generateBreadcrumbs(filePath, null, workingDirName);
|
|
375
395
|
let displayContent;
|
|
376
396
|
let viewToggle = '';
|
|
377
397
|
|
|
@@ -551,8 +571,9 @@ async function renderFile(filePath, content, ext, viewMode = 'rendered', gitStat
|
|
|
551
571
|
`;
|
|
552
572
|
}
|
|
553
573
|
|
|
554
|
-
function renderNewFile(currentPath) {
|
|
555
|
-
const
|
|
574
|
+
function renderNewFile(currentPath, workingDir = null) {
|
|
575
|
+
const workingDirName = workingDir ? path.basename(workingDir) : null;
|
|
576
|
+
const breadcrumbs = generateBreadcrumbs(currentPath, null, workingDirName);
|
|
556
577
|
|
|
557
578
|
return `
|
|
558
579
|
<!DOCTYPE html>
|
package/lib/server.js
CHANGED
|
@@ -108,7 +108,7 @@ function setupRoutes(app, workingDir, isGitRepo, gitRepoRoot) {
|
|
|
108
108
|
const absolutePath = path.resolve(fullPath);
|
|
109
109
|
const gitInfo = gitStatus[absolutePath];
|
|
110
110
|
if (gitInfo) {
|
|
111
|
-
const diffHtml = await renderFileDiff(currentPath, ext, gitInfo, gitRepoRoot);
|
|
111
|
+
const diffHtml = await renderFileDiff(currentPath, ext, gitInfo, gitRepoRoot, workingDir);
|
|
112
112
|
return res.send(diffHtml);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
@@ -123,7 +123,7 @@ function setupRoutes(app, workingDir, isGitRepo, gitRepoRoot) {
|
|
|
123
123
|
// Route for creating new files
|
|
124
124
|
app.get('/new', (req, res) => {
|
|
125
125
|
const currentPath = req.query.path || '';
|
|
126
|
-
res.send(renderNewFile(currentPath));
|
|
126
|
+
res.send(renderNewFile(currentPath, workingDir));
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
// API endpoint to get file content for editing
|
package/package.json
CHANGED
package/public/styles.css
CHANGED
|
@@ -499,29 +499,54 @@ main {
|
|
|
499
499
|
|
|
500
500
|
/* Old search styles removed - using new GitHub-style search */
|
|
501
501
|
|
|
502
|
-
/* Language stats */
|
|
502
|
+
/* Language stats in control bar */
|
|
503
503
|
.language-stats {
|
|
504
504
|
display: flex;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
505
|
+
align-items: center;
|
|
506
|
+
gap: 8px;
|
|
507
|
+
margin-left: 16px;
|
|
508
|
+
max-width: 300px;
|
|
509
|
+
overflow: hidden;
|
|
510
|
+
position: relative;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.language-stats:hover {
|
|
514
|
+
overflow: visible;
|
|
515
|
+
z-index: 10;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.language-stats:hover::before {
|
|
519
|
+
content: '';
|
|
520
|
+
position: absolute;
|
|
521
|
+
top: -4px;
|
|
522
|
+
left: -8px;
|
|
523
|
+
right: -8px;
|
|
524
|
+
bottom: -4px;
|
|
525
|
+
background: var(--bg-secondary);
|
|
526
|
+
border: 1px solid var(--border-primary);
|
|
527
|
+
border-radius: 6px;
|
|
528
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
529
|
+
z-index: -1;
|
|
530
|
+
pointer-events: none;
|
|
510
531
|
}
|
|
511
532
|
|
|
512
533
|
.lang-stat {
|
|
513
534
|
display: flex;
|
|
514
535
|
align-items: center;
|
|
515
|
-
gap:
|
|
516
|
-
font-size:
|
|
536
|
+
gap: 4px;
|
|
537
|
+
font-size: 11px;
|
|
517
538
|
color: var(--text-secondary);
|
|
539
|
+
white-space: nowrap;
|
|
540
|
+
flex-shrink: 0;
|
|
541
|
+
padding: 2px 0;
|
|
518
542
|
}
|
|
519
543
|
|
|
520
544
|
.lang-dot {
|
|
521
|
-
width:
|
|
522
|
-
height:
|
|
545
|
+
width: 8px;
|
|
546
|
+
height: 8px;
|
|
523
547
|
border-radius: 50%;
|
|
524
548
|
display: inline-block;
|
|
549
|
+
flex-shrink: 0;
|
|
525
550
|
}
|
|
526
551
|
|
|
527
552
|
.lang-name {
|
|
@@ -2010,6 +2035,31 @@ main {
|
|
|
2010
2035
|
height: 14px;
|
|
2011
2036
|
}
|
|
2012
2037
|
|
|
2038
|
+
/* General modal overlay */
|
|
2039
|
+
.modal-overlay {
|
|
2040
|
+
position: fixed;
|
|
2041
|
+
top: 0;
|
|
2042
|
+
left: 0;
|
|
2043
|
+
right: 0;
|
|
2044
|
+
bottom: 0;
|
|
2045
|
+
background: rgba(0, 0, 0, 0.7);
|
|
2046
|
+
display: flex;
|
|
2047
|
+
align-items: center;
|
|
2048
|
+
justify-content: center;
|
|
2049
|
+
z-index: 1000;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
.modal-content {
|
|
2053
|
+
background: var(--bg-primary);
|
|
2054
|
+
border: 1px solid var(--border-primary);
|
|
2055
|
+
border-radius: 8px;
|
|
2056
|
+
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
|
2057
|
+
padding: 20px;
|
|
2058
|
+
max-width: 90vw;
|
|
2059
|
+
max-height: 90vh;
|
|
2060
|
+
overflow-y: auto;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2013
2063
|
/* Clean commit modal */
|
|
2014
2064
|
.commit-modal-overlay {
|
|
2015
2065
|
position: fixed;
|