gh-here 3.0.3 → 3.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/.env +0 -0
- package/.playwright-mcp/fixed-alignment.png +0 -0
- package/.playwright-mcp/fixed-layout.png +0 -0
- package/.playwright-mcp/gh-here-home-header-table.png +0 -0
- package/.playwright-mcp/gh-here-home.png +0 -0
- package/.playwright-mcp/line-selection-multiline.png +0 -0
- package/.playwright-mcp/line-selection-test-after.png +0 -0
- package/.playwright-mcp/line-selection-test-before.png +0 -0
- package/.playwright-mcp/page-2026-01-03T17-58-21-336Z.png +0 -0
- package/lib/constants.js +25 -15
- 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 +10 -54
- package/lib/gitignore.js +70 -41
- package/lib/renderers.js +15 -19
- package/lib/server.js +70 -193
- package/lib/symbol-parser.js +600 -0
- package/package.json +1 -1
- package/public/app.js +134 -68
- 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/public/styles.css +2049 -296
- 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/js/draft-manager.js +0 -36
- package/public/js/editor-manager.js +0 -159
- 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/public/app.js
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Main application entry point
|
|
3
3
|
* Coordinates all modules and initializes the application
|
|
4
|
+
* @module app
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Imports (alpha-sorted)
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
import { ContentSearchHandler } from './js/content-search-handler.js';
|
|
12
|
+
import { copyToClipboard } from './js/clipboard-utils.js';
|
|
9
13
|
import { FileTreeNavigator } from './js/file-tree.js';
|
|
14
|
+
import { FileViewer } from './js/file-viewer.js';
|
|
15
|
+
import { FocusMode } from './js/focus-mode.js';
|
|
16
|
+
import { InlineSearch } from './js/inline-search.js';
|
|
17
|
+
import { KeyboardHandler } from './js/keyboard-handler.js';
|
|
10
18
|
import { NavigationHandler } from './js/navigation.js';
|
|
11
19
|
import { PathUtils } from './js/utils.js';
|
|
20
|
+
import { SearchHandler } from './js/search-handler.js';
|
|
12
21
|
import { showNotification } from './js/notification.js';
|
|
13
|
-
import {
|
|
22
|
+
import { SymbolOutline } from './js/symbol-outline.js';
|
|
23
|
+
import { ThemeManager } from './js/theme-manager.js';
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Application Class
|
|
27
|
+
// ============================================================================
|
|
14
28
|
|
|
15
29
|
class Application {
|
|
16
30
|
constructor() {
|
|
@@ -20,6 +34,11 @@ class Application {
|
|
|
20
34
|
this.fileTree = null;
|
|
21
35
|
this.navigationHandler = null;
|
|
22
36
|
this.lastSelectedLine = null;
|
|
37
|
+
this.fileViewer = null;
|
|
38
|
+
this.inlineSearch = null;
|
|
39
|
+
this.focusMode = null;
|
|
40
|
+
this.contentSearch = null;
|
|
41
|
+
this.symbolOutline = null;
|
|
23
42
|
}
|
|
24
43
|
|
|
25
44
|
init() {
|
|
@@ -32,6 +51,9 @@ class Application {
|
|
|
32
51
|
// Re-initialize components after client-side navigation
|
|
33
52
|
document.addEventListener('content-loaded', () => {
|
|
34
53
|
try {
|
|
54
|
+
// Cleanup existing components before re-initializing
|
|
55
|
+
this.cleanupComponents();
|
|
56
|
+
|
|
35
57
|
// Re-initialize theme manager listeners (button might be re-rendered)
|
|
36
58
|
if (this.themeManager) {
|
|
37
59
|
this.themeManager.setupListeners();
|
|
@@ -57,8 +79,11 @@ class Application {
|
|
|
57
79
|
|
|
58
80
|
this.setupGlobalEventListeners();
|
|
59
81
|
this.setupGitignoreToggle();
|
|
60
|
-
this.setupFileOperations();
|
|
61
82
|
this.highlightLinesFromHash();
|
|
83
|
+
this.initializeFileViewer();
|
|
84
|
+
this.initializeInlineSearch();
|
|
85
|
+
this.initializeFocusMode();
|
|
86
|
+
this.initializeContentSearch();
|
|
62
87
|
} catch (error) {
|
|
63
88
|
console.error('Error re-initializing components:', error);
|
|
64
89
|
}
|
|
@@ -84,8 +109,84 @@ class Application {
|
|
|
84
109
|
|
|
85
110
|
this.setupGlobalEventListeners();
|
|
86
111
|
this.setupGitignoreToggle();
|
|
87
|
-
this.setupFileOperations();
|
|
88
112
|
this.highlightLinesFromHash();
|
|
113
|
+
this.initializeFileViewer();
|
|
114
|
+
this.initializeInlineSearch();
|
|
115
|
+
this.initializeFocusMode();
|
|
116
|
+
this.initializeContentSearch();
|
|
117
|
+
this.initializeSymbolOutline();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ========================================================================
|
|
121
|
+
// Component Initialization
|
|
122
|
+
// ========================================================================
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check if current page is a file view page
|
|
126
|
+
*/
|
|
127
|
+
isFileViewPage() {
|
|
128
|
+
const fileContent = document.querySelector('.file-content');
|
|
129
|
+
return fileContent?.querySelector('pre code.hljs.with-line-numbers') !== null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Initialize Monaco-based file viewer
|
|
134
|
+
*/
|
|
135
|
+
initializeFileViewer() {
|
|
136
|
+
if (!this.isFileViewPage()) return;
|
|
137
|
+
|
|
138
|
+
// Wait for Monaco to be ready
|
|
139
|
+
if (typeof require === 'undefined' || !window.monacoReady) {
|
|
140
|
+
// Wait a bit and try again
|
|
141
|
+
setTimeout(() => this.initializeFileViewer(), 100);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!this.fileViewer) {
|
|
146
|
+
this.fileViewer = new FileViewer();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Initialize inline search component
|
|
152
|
+
* Note: Skipped when Monaco viewer is active (Monaco has built-in search)
|
|
153
|
+
*/
|
|
154
|
+
initializeInlineSearch() {
|
|
155
|
+
if (!this.isFileViewPage()) return;
|
|
156
|
+
|
|
157
|
+
// Skip if Monaco viewer is active
|
|
158
|
+
if (document.querySelector('.monaco-file-viewer')) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!this.inlineSearch) {
|
|
163
|
+
this.inlineSearch = new InlineSearch();
|
|
164
|
+
}
|
|
165
|
+
this.inlineSearch.init();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
initializeFocusMode() {
|
|
169
|
+
if (!this.focusMode) {
|
|
170
|
+
this.focusMode = new FocusMode();
|
|
171
|
+
}
|
|
172
|
+
this.focusMode.init();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Initialize symbol outline panel
|
|
177
|
+
*/
|
|
178
|
+
initializeSymbolOutline() {
|
|
179
|
+
// Cleanup previous instance
|
|
180
|
+
if (this.symbolOutline) {
|
|
181
|
+
this.symbolOutline.destroy();
|
|
182
|
+
this.symbolOutline = null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Only initialize on file view pages
|
|
186
|
+
if (!this.isFileViewPage()) return;
|
|
187
|
+
|
|
188
|
+
this.symbolOutline = new SymbolOutline();
|
|
189
|
+
this.symbolOutline.init();
|
|
89
190
|
}
|
|
90
191
|
|
|
91
192
|
setupGlobalEventListeners() {
|
|
@@ -193,68 +294,6 @@ class Application {
|
|
|
193
294
|
}
|
|
194
295
|
|
|
195
296
|
|
|
196
|
-
setupFileOperations() {
|
|
197
|
-
document.addEventListener('click', async e => {
|
|
198
|
-
if (e.target.closest('.delete-btn')) {
|
|
199
|
-
const btn = e.target.closest('.delete-btn');
|
|
200
|
-
const itemPath = btn.dataset.path;
|
|
201
|
-
const itemName = btn.dataset.name;
|
|
202
|
-
const isDirectory = btn.dataset.isDirectory === 'true';
|
|
203
|
-
|
|
204
|
-
const message = `Are you sure you want to delete ${isDirectory ? 'folder' : 'file'} "${itemName}"?`;
|
|
205
|
-
if (!confirm(message)) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
const response = await fetch('/api/delete', {
|
|
211
|
-
method: 'POST',
|
|
212
|
-
headers: { 'Content-Type': 'application/json' },
|
|
213
|
-
body: JSON.stringify({ path: itemPath })
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
if (!response.ok) {
|
|
217
|
-
throw new Error('Delete failed');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
showNotification(`${isDirectory ? 'Folder' : 'File'} deleted successfully`, 'success');
|
|
221
|
-
setTimeout(() => window.location.reload(), 600);
|
|
222
|
-
} catch (error) {
|
|
223
|
-
showNotification('Failed to delete item', 'error');
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (e.target.closest('.rename-btn')) {
|
|
228
|
-
const btn = e.target.closest('.rename-btn');
|
|
229
|
-
const itemPath = btn.dataset.path;
|
|
230
|
-
const currentName = btn.dataset.name;
|
|
231
|
-
const isDirectory = btn.dataset.isDirectory === 'true';
|
|
232
|
-
|
|
233
|
-
const newName = prompt(`Rename ${isDirectory ? 'folder' : 'file'}:`, currentName);
|
|
234
|
-
if (!newName || newName.trim() === currentName) {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
const response = await fetch('/api/rename', {
|
|
240
|
-
method: 'POST',
|
|
241
|
-
headers: { 'Content-Type': 'application/json' },
|
|
242
|
-
body: JSON.stringify({ path: itemPath, newName: newName.trim() })
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
if (!response.ok) {
|
|
246
|
-
throw new Error('Rename failed');
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
showNotification(`Renamed to "${newName.trim()}"`, 'success');
|
|
250
|
-
setTimeout(() => window.location.reload(), 600);
|
|
251
|
-
} catch (error) {
|
|
252
|
-
showNotification('Failed to rename item', 'error');
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
297
|
showDiffViewer(filePath) {
|
|
259
298
|
// Simplified - redirect to diff view
|
|
260
299
|
const url = new URL(window.location.href);
|
|
@@ -332,6 +371,33 @@ class Application {
|
|
|
332
371
|
firstLine.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
333
372
|
}
|
|
334
373
|
}
|
|
374
|
+
|
|
375
|
+
initializeContentSearch() {
|
|
376
|
+
if (!this.contentSearch) {
|
|
377
|
+
this.contentSearch = new ContentSearchHandler();
|
|
378
|
+
}
|
|
379
|
+
this.contentSearch.init();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Cleanup components before navigation/re-initialization
|
|
384
|
+
*/
|
|
385
|
+
cleanupComponents() {
|
|
386
|
+
// Cleanup file viewer
|
|
387
|
+
if (this.fileViewer && typeof this.fileViewer.destroy === 'function') {
|
|
388
|
+
this.fileViewer.destroy();
|
|
389
|
+
this.fileViewer = null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Cleanup inline search
|
|
393
|
+
if (this.inlineSearch && typeof this.inlineSearch.destroy === 'function') {
|
|
394
|
+
this.inlineSearch.destroy();
|
|
395
|
+
this.inlineSearch = null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Focus mode persists across navigations, no cleanup needed
|
|
399
|
+
// Content search persists across navigations, no cleanup needed
|
|
400
|
+
}
|
|
335
401
|
}
|
|
336
402
|
|
|
337
403
|
const app = new Application();
|
package/public/js/constants.js
CHANGED
|
@@ -1,60 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Application constants and configuration
|
|
3
|
+
* @module constants
|
|
3
4
|
*/
|
|
4
5
|
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Configuration
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
5
10
|
export const CONFIG = {
|
|
6
|
-
MONACO_CDN: 'https://unpkg.com/monaco-editor@0.45.0/min/vs',
|
|
7
|
-
MONACO_VERSION: '0.45.0',
|
|
8
|
-
EDITOR_HEIGHT: 600,
|
|
9
11
|
DEFAULT_PORT: 5555,
|
|
12
|
+
MONACO_CDN: 'https://unpkg.com/monaco-editor@0.45.0/min/vs',
|
|
10
13
|
NOTIFICATION_DURATION: 4000
|
|
11
14
|
};
|
|
12
15
|
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Enums
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
13
20
|
export const THEME = {
|
|
14
21
|
DARK: 'dark',
|
|
15
22
|
LIGHT: 'light'
|
|
16
23
|
};
|
|
17
24
|
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Storage Keys
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
18
29
|
export const STORAGE_KEYS = {
|
|
19
|
-
THEME: 'gh-here-theme'
|
|
20
|
-
DRAFT_PREFIX: 'gh-here-draft-'
|
|
30
|
+
THEME: 'gh-here-theme'
|
|
21
31
|
};
|
|
22
32
|
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Keyboard Shortcuts (alpha-sorted)
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
export const KEYBOARD_SHORTCUTS = {
|
|
38
|
+
ESCAPE: 'Escape',
|
|
39
|
+
GO_UP: 'h',
|
|
40
|
+
HELP: '?',
|
|
41
|
+
NAV_DOWN: 'j',
|
|
42
|
+
NAV_UP: 'k',
|
|
43
|
+
OPEN: 'o',
|
|
44
|
+
REFRESH: 'r',
|
|
45
|
+
SEARCH: ['/', 's'],
|
|
46
|
+
SHOW_DIFF: 'd',
|
|
47
|
+
THEME_TOGGLE: 't',
|
|
48
|
+
TOGGLE_GITIGNORE: 'i'
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Monaco Editor Options (for FileViewer read-only mode)
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
23
55
|
export const EDITOR_OPTIONS = {
|
|
24
|
-
minimap: { enabled: false },
|
|
25
|
-
lineNumbers: 'on',
|
|
26
|
-
wordWrap: 'off',
|
|
27
|
-
scrollBeyondLastLine: false,
|
|
28
|
-
fontSize: 12,
|
|
29
|
-
lineHeight: 20,
|
|
30
|
-
fontFamily: "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace",
|
|
31
|
-
padding: { top: 16, bottom: 16 },
|
|
32
|
-
renderLineHighlight: 'line',
|
|
33
|
-
selectOnLineNumbers: true,
|
|
34
56
|
automaticLayout: true,
|
|
57
|
+
bracketPairColorization: { enabled: true },
|
|
35
58
|
folding: true,
|
|
36
59
|
foldingHighlight: true,
|
|
37
60
|
foldingStrategy: 'auto',
|
|
38
|
-
|
|
39
|
-
|
|
61
|
+
fontFamily: "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace",
|
|
62
|
+
fontSize: 12,
|
|
40
63
|
guides: {
|
|
41
64
|
bracketPairs: true,
|
|
42
65
|
indentation: true
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
CREATE_FILE: 'c',
|
|
54
|
-
EDIT_FILE: 'e',
|
|
55
|
-
SHOW_DIFF: 'd',
|
|
56
|
-
TOGGLE_GITIGNORE: 'i',
|
|
57
|
-
NAV_DOWN: 'j',
|
|
58
|
-
NAV_UP: 'k',
|
|
59
|
-
OPEN: 'o'
|
|
66
|
+
},
|
|
67
|
+
lineHeight: 20,
|
|
68
|
+
lineNumbers: 'on',
|
|
69
|
+
minimap: { enabled: false },
|
|
70
|
+
padding: { top: 16, bottom: 16 },
|
|
71
|
+
renderLineHighlight: 'line',
|
|
72
|
+
scrollBeyondLastLine: false,
|
|
73
|
+
selectOnLineNumbers: true,
|
|
74
|
+
showFoldingControls: 'mouseover',
|
|
75
|
+
wordWrap: 'off'
|
|
60
76
|
};
|