gh-here 3.0.2 → 3.0.3
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/package.json +1 -1
- package/public/app.js +73 -5
- package/public/js/navigation.js +5 -0
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -19,6 +19,7 @@ class Application {
|
|
|
19
19
|
this.keyboardHandler = null;
|
|
20
20
|
this.fileTree = null;
|
|
21
21
|
this.navigationHandler = null;
|
|
22
|
+
this.lastSelectedLine = null;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
init() {
|
|
@@ -35,15 +36,15 @@ class Application {
|
|
|
35
36
|
if (this.themeManager) {
|
|
36
37
|
this.themeManager.setupListeners();
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
// Re-initialize components that need fresh DOM references
|
|
40
41
|
this.searchHandler = new SearchHandler();
|
|
41
42
|
this.keyboardHandler = new KeyboardHandler(this.searchHandler);
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
// Re-initialize file tree when sidebar becomes visible
|
|
44
45
|
const sidebar = document.querySelector('.file-tree-sidebar');
|
|
45
46
|
const treeContainer = document.getElementById('file-tree');
|
|
46
|
-
|
|
47
|
+
|
|
47
48
|
if (sidebar && treeContainer && !sidebar.classList.contains('hidden')) {
|
|
48
49
|
// Sidebar is visible - initialize or re-initialize file tree
|
|
49
50
|
if (!this.fileTree || !this.fileTree.isInitialized || this.fileTree.treeContainer !== treeContainer) {
|
|
@@ -53,10 +54,11 @@ class Application {
|
|
|
53
54
|
// Sidebar is hidden - don't initialize but keep reference for when it becomes visible
|
|
54
55
|
this.fileTree = null;
|
|
55
56
|
}
|
|
56
|
-
|
|
57
|
+
|
|
57
58
|
this.setupGlobalEventListeners();
|
|
58
59
|
this.setupGitignoreToggle();
|
|
59
60
|
this.setupFileOperations();
|
|
61
|
+
this.highlightLinesFromHash();
|
|
60
62
|
} catch (error) {
|
|
61
63
|
console.error('Error re-initializing components:', error);
|
|
62
64
|
}
|
|
@@ -72,7 +74,7 @@ class Application {
|
|
|
72
74
|
// Initialize components
|
|
73
75
|
this.searchHandler = new SearchHandler();
|
|
74
76
|
this.keyboardHandler = new KeyboardHandler(this.searchHandler);
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
// Initialize file tree if sidebar is visible (not hidden)
|
|
77
79
|
const sidebar = document.querySelector('.file-tree-sidebar');
|
|
78
80
|
const treeContainer = document.getElementById('file-tree');
|
|
@@ -83,6 +85,7 @@ class Application {
|
|
|
83
85
|
this.setupGlobalEventListeners();
|
|
84
86
|
this.setupGitignoreToggle();
|
|
85
87
|
this.setupFileOperations();
|
|
88
|
+
this.highlightLinesFromHash();
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
setupGlobalEventListeners() {
|
|
@@ -104,6 +107,16 @@ class Application {
|
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
handleGlobalClick(e) {
|
|
110
|
+
// Line number selection (like GitHub)
|
|
111
|
+
const lineNumber = e.target.closest('.line-number');
|
|
112
|
+
if (lineNumber) {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
e.stopPropagation();
|
|
115
|
+
const lineNum = parseInt(lineNumber.textContent.trim(), 10);
|
|
116
|
+
this.handleLineSelection(lineNum, e.shiftKey);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
// Copy path button
|
|
108
121
|
const copyPathBtn = e.target.closest('.copy-path-btn, .file-path-copy-btn');
|
|
109
122
|
if (copyPathBtn) {
|
|
@@ -264,6 +277,61 @@ class Application {
|
|
|
264
277
|
showNotification('Failed to copy raw content', 'error');
|
|
265
278
|
}
|
|
266
279
|
}
|
|
280
|
+
|
|
281
|
+
handleLineSelection(lineNum, shiftKey) {
|
|
282
|
+
// If shift is held and we have a previous selection, select range
|
|
283
|
+
if (shiftKey && this.lastSelectedLine) {
|
|
284
|
+
const start = Math.min(this.lastSelectedLine, lineNum);
|
|
285
|
+
const end = Math.max(this.lastSelectedLine, lineNum);
|
|
286
|
+
this.highlightLines(start, end);
|
|
287
|
+
this.updateUrlHash(start, end);
|
|
288
|
+
} else {
|
|
289
|
+
// Single line selection
|
|
290
|
+
this.highlightLines(lineNum, lineNum);
|
|
291
|
+
this.updateUrlHash(lineNum, lineNum);
|
|
292
|
+
this.lastSelectedLine = lineNum;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
highlightLines(start, end) {
|
|
297
|
+
// Clear all existing selections and highlight new range in one pass
|
|
298
|
+
document.querySelectorAll('.line-container').forEach(el => {
|
|
299
|
+
const lineNum = parseInt(el.dataset.line, 10);
|
|
300
|
+
if (lineNum >= start && lineNum <= end) {
|
|
301
|
+
el.classList.add('selected');
|
|
302
|
+
} else {
|
|
303
|
+
el.classList.remove('selected');
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
updateUrlHash(start, end) {
|
|
309
|
+
const hash = start === end ? `L${start}` : `L${start}-L${end}`;
|
|
310
|
+
// Use history API to update URL without scrolling - preserve path and query params
|
|
311
|
+
const url = new URL(window.location);
|
|
312
|
+
url.hash = hash;
|
|
313
|
+
history.replaceState(null, null, url);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
highlightLinesFromHash() {
|
|
317
|
+
const hash = window.location.hash.slice(1); // Remove #
|
|
318
|
+
if (!hash.startsWith('L')) return;
|
|
319
|
+
|
|
320
|
+
const match = hash.match(/^L(\d+)(?:-L(\d+))?$/);
|
|
321
|
+
if (!match) return;
|
|
322
|
+
|
|
323
|
+
const start = parseInt(match[1], 10);
|
|
324
|
+
const end = match[2] ? parseInt(match[2], 10) : start;
|
|
325
|
+
|
|
326
|
+
this.highlightLines(start, end);
|
|
327
|
+
this.lastSelectedLine = start;
|
|
328
|
+
|
|
329
|
+
// Scroll to the first selected line
|
|
330
|
+
const firstLine = document.querySelector(`.line-container[data-line="${start}"]`);
|
|
331
|
+
if (firstLine) {
|
|
332
|
+
firstLine.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
267
335
|
}
|
|
268
336
|
|
|
269
337
|
const app = new Application();
|
package/public/js/navigation.js
CHANGED
|
@@ -35,6 +35,11 @@ export class NavigationHandler {
|
|
|
35
35
|
const link = e.target.closest('a');
|
|
36
36
|
if (!link) return;
|
|
37
37
|
|
|
38
|
+
// Skip line number links (hash-only navigation like #L10)
|
|
39
|
+
if (link.classList.contains('line-number')) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
// Skip if clicking inside quick actions or other interactive elements
|
|
39
44
|
if (e.target.closest('.quick-actions, button, .file-action-btn')) {
|
|
40
45
|
return;
|