@herb-tools/dev-tools 0.7.5 → 0.8.1
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/README.md +0 -1
- package/dist/herb-dev-tools.esm.js +116 -15
- package/dist/herb-dev-tools.esm.js.map +1 -1
- package/dist/herb-dev-tools.umd.js +115 -14
- package/dist/herb-dev-tools.umd.js.map +1 -1
- package/dist/types/herb-overlay.d.ts +6 -0
- package/package.json +2 -2
- package/src/herb-overlay.ts +125 -14
- package/src/styles.css +79 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herb-tools/dev-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Development tools for visual debugging in HTML+ERB templates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@herb-tools/core": "0.
|
|
33
|
+
"@herb-tools/core": "0.8.1"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"package.json",
|
package/src/herb-overlay.ts
CHANGED
|
@@ -15,10 +15,30 @@ export class HerbOverlay {
|
|
|
15
15
|
private showingComponentOutlines = false;
|
|
16
16
|
private menuOpen = false;
|
|
17
17
|
private projectPath = '';
|
|
18
|
+
private preferredEditor = 'auto';
|
|
19
|
+
private defaultEditorFromServer = 'vscode';
|
|
18
20
|
private currentlyHoveredERBElement: HTMLElement | null = null;
|
|
19
21
|
private errorOverlay: ErrorOverlay | null = null;
|
|
20
22
|
|
|
21
23
|
private static readonly SETTINGS_KEY = 'herb-dev-tools-settings';
|
|
24
|
+
private static readonly EDITOR_OPTIONS = [
|
|
25
|
+
{ value: 'auto', label: 'Auto (from server via RAILS_EDITOR or EDITOR)' },
|
|
26
|
+
{ value: 'atom', label: 'Atom' },
|
|
27
|
+
{ value: 'cursor', label: 'Cursor' },
|
|
28
|
+
{ value: 'emacs', label: 'Emacs' },
|
|
29
|
+
{ value: 'idea', label: 'IntelliJ IDEA' },
|
|
30
|
+
{ value: 'macvim', label: 'MacVim' },
|
|
31
|
+
{ value: 'nova', label: 'Nova' },
|
|
32
|
+
{ value: 'nvim', label: 'Neovim' },
|
|
33
|
+
{ value: 'rubymine', label: 'RubyMine' },
|
|
34
|
+
{ value: 'sublime', label: 'Sublime Text' },
|
|
35
|
+
{ value: 'textmate', label: 'TextMate' },
|
|
36
|
+
{ value: 'vim', label: 'Vim' },
|
|
37
|
+
{ value: 'vscode', label: 'Visual Studio Code' },
|
|
38
|
+
{ value: 'vscodium', label: 'VSCodium' },
|
|
39
|
+
{ value: 'windsurf', label: 'Windsurf' },
|
|
40
|
+
{ value: 'zed', label: 'Zed' },
|
|
41
|
+
];
|
|
22
42
|
|
|
23
43
|
constructor(private options: HerbDevToolsOptions = {}) {
|
|
24
44
|
if (options.autoInit !== false) {
|
|
@@ -28,10 +48,12 @@ export class HerbOverlay {
|
|
|
28
48
|
|
|
29
49
|
private init() {
|
|
30
50
|
this.loadProjectPath();
|
|
51
|
+
this.loadDefaultEditor();
|
|
31
52
|
this.loadSettings();
|
|
32
53
|
this.injectMenu();
|
|
33
54
|
this.setupMenuToggle();
|
|
34
55
|
this.setupToggleSwitches();
|
|
56
|
+
this.setupEditorDropdown();
|
|
35
57
|
this.initializeErrorOverlay();
|
|
36
58
|
this.setupTurboListeners();
|
|
37
59
|
this.applySettings();
|
|
@@ -50,6 +72,19 @@ export class HerbOverlay {
|
|
|
50
72
|
}
|
|
51
73
|
}
|
|
52
74
|
|
|
75
|
+
private loadDefaultEditor() {
|
|
76
|
+
const metaTag = document.querySelector('meta[name="herb-default-editor"]') as HTMLMetaElement;
|
|
77
|
+
|
|
78
|
+
if (metaTag?.content) {
|
|
79
|
+
const defaultEditor = metaTag.content.toLowerCase();
|
|
80
|
+
const isValidEditor = HerbOverlay.EDITOR_OPTIONS.some(option => option.value === defaultEditor);
|
|
81
|
+
|
|
82
|
+
if (isValidEditor) {
|
|
83
|
+
this.defaultEditorFromServer = defaultEditor;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
53
88
|
private loadSettings() {
|
|
54
89
|
const savedSettings = localStorage.getItem(HerbOverlay.SETTINGS_KEY);
|
|
55
90
|
if (savedSettings) {
|
|
@@ -63,6 +98,9 @@ export class HerbOverlay {
|
|
|
63
98
|
this.showingPartialOutlines = settings.showingPartialOutlines || false;
|
|
64
99
|
this.showingComponentOutlines = settings.showingComponentOutlines || false;
|
|
65
100
|
this.menuOpen = settings.menuOpen || false;
|
|
101
|
+
if (settings.preferredEditor) {
|
|
102
|
+
this.preferredEditor = settings.preferredEditor;
|
|
103
|
+
}
|
|
66
104
|
} catch (e) {
|
|
67
105
|
console.warn('Failed to load Herb dev tools settings:', e);
|
|
68
106
|
}
|
|
@@ -78,7 +116,8 @@ export class HerbOverlay {
|
|
|
78
116
|
showingViewOutlines: this.showingViewOutlines,
|
|
79
117
|
showingPartialOutlines: this.showingPartialOutlines,
|
|
80
118
|
showingComponentOutlines: this.showingComponentOutlines,
|
|
81
|
-
menuOpen: this.menuOpen
|
|
119
|
+
menuOpen: this.menuOpen,
|
|
120
|
+
preferredEditor: this.preferredEditor
|
|
82
121
|
};
|
|
83
122
|
|
|
84
123
|
localStorage.setItem(HerbOverlay.SETTINGS_KEY, JSON.stringify(settings));
|
|
@@ -170,6 +209,17 @@ export class HerbOverlay {
|
|
|
170
209
|
</label>
|
|
171
210
|
</div>
|
|
172
211
|
|
|
212
|
+
<div class="herb-editor-section">
|
|
213
|
+
<label class="herb-editor-label">
|
|
214
|
+
<span class="herb-editor-text">Editor</span>
|
|
215
|
+
<select id="herbEditorSelect" class="herb-editor-select">
|
|
216
|
+
${HerbOverlay.EDITOR_OPTIONS.map(editor =>
|
|
217
|
+
`<option value="${editor.value}">${editor.label}</option>`
|
|
218
|
+
).join('')}
|
|
219
|
+
</select>
|
|
220
|
+
</label>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
173
223
|
<div class="herb-disable-all-section">
|
|
174
224
|
<button id="herbDisableAll" class="herb-disable-all-btn">Disable All</button>
|
|
175
225
|
</div>
|
|
@@ -247,6 +297,7 @@ export class HerbOverlay {
|
|
|
247
297
|
this.injectMenu();
|
|
248
298
|
this.setupMenuToggle();
|
|
249
299
|
this.setupToggleSwitches();
|
|
300
|
+
this.setupEditorDropdown();
|
|
250
301
|
this.applySettings();
|
|
251
302
|
this.updateMenuButtonState();
|
|
252
303
|
}
|
|
@@ -336,6 +387,32 @@ export class HerbOverlay {
|
|
|
336
387
|
}
|
|
337
388
|
}
|
|
338
389
|
|
|
390
|
+
private setupEditorDropdown() {
|
|
391
|
+
const editorSelect = document.getElementById('herbEditorSelect') as HTMLSelectElement;
|
|
392
|
+
|
|
393
|
+
if (editorSelect) {
|
|
394
|
+
const autoOption = editorSelect.querySelector('option[value="auto"]') as HTMLOptionElement;
|
|
395
|
+
|
|
396
|
+
if (autoOption) {
|
|
397
|
+
const editorLabel = HerbOverlay.EDITOR_OPTIONS.find(opt => opt.value === this.defaultEditorFromServer)?.label || this.defaultEditorFromServer;
|
|
398
|
+
const metaTag = document.querySelector('meta[name="herb-default-editor"]') as HTMLMetaElement;
|
|
399
|
+
|
|
400
|
+
if (metaTag?.content) {
|
|
401
|
+
autoOption.textContent = `Auto (from server): ${editorLabel}`;
|
|
402
|
+
} else {
|
|
403
|
+
autoOption.textContent = `Auto (default): ${editorLabel}`;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
editorSelect.value = this.preferredEditor;
|
|
408
|
+
|
|
409
|
+
editorSelect.addEventListener('change', () => {
|
|
410
|
+
this.preferredEditor = editorSelect.value;
|
|
411
|
+
this.saveSettings();
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
339
416
|
private toggleViewOutlines(show?: boolean) {
|
|
340
417
|
this.showingViewOutlines = show !== undefined ? show : !this.showingViewOutlines;
|
|
341
418
|
const viewOutlines = document.querySelectorAll('[data-herb-debug-outline-type="view"], [data-herb-debug-outline-type*="view"]');
|
|
@@ -924,22 +1001,56 @@ export class HerbOverlay {
|
|
|
924
1001
|
}
|
|
925
1002
|
}
|
|
926
1003
|
|
|
1004
|
+
private getEditorUrl(editor: string, absolutePath: string, line: number, column: number): string {
|
|
1005
|
+
switch (editor) {
|
|
1006
|
+
case 'cursor':
|
|
1007
|
+
return `cursor://file/${absolutePath}:${line}:${column}`;
|
|
1008
|
+
case 'vscode':
|
|
1009
|
+
return `vscode://file/${absolutePath}:${line}:${column}`;
|
|
1010
|
+
case 'vscodium':
|
|
1011
|
+
return `vscodium://file/${absolutePath}:${line}:${column}`;
|
|
1012
|
+
case 'zed':
|
|
1013
|
+
return `zed://file/${absolutePath}:${line}:${column}`;
|
|
1014
|
+
case 'windsurf':
|
|
1015
|
+
return `windsurf://file/${absolutePath}:${line}:${column}`;
|
|
1016
|
+
case 'sublime':
|
|
1017
|
+
return `subl://open?url=file://${absolutePath}&line=${line}&column=${column}`;
|
|
1018
|
+
case 'atom':
|
|
1019
|
+
return `atom://core/open/file?filename=${absolutePath}&line=${line}&column=${column}`;
|
|
1020
|
+
case 'textmate':
|
|
1021
|
+
return `txmt://open?url=file://${absolutePath}&line=${line}&column=${column}`;
|
|
1022
|
+
case 'emacs':
|
|
1023
|
+
return `emacs://open?url=file://${absolutePath}&line=${line}&column=${column}`;
|
|
1024
|
+
case 'idea':
|
|
1025
|
+
return `idea://open?file=${absolutePath}&line=${line}&column=${column}`;
|
|
1026
|
+
case 'rubymine':
|
|
1027
|
+
return `x-mine://open?file=${absolutePath}&line=${line}&column=${column}`;
|
|
1028
|
+
case 'nova':
|
|
1029
|
+
return `nova://open?path=${absolutePath}&line=${line}&column=${column}`;
|
|
1030
|
+
case 'macvim':
|
|
1031
|
+
return `mvim://open?url=file://${absolutePath}&line=${line}&column=${column}`;
|
|
1032
|
+
case 'vim':
|
|
1033
|
+
return `vim://open?url=file://${absolutePath}&line=${line}&column=${column}`;
|
|
1034
|
+
case 'nvim':
|
|
1035
|
+
return `nvim://open?url=file://${absolutePath}&line=${line}&column=${column}`;
|
|
1036
|
+
default:
|
|
1037
|
+
return '';
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
927
1041
|
private openFileInEditor(file: string, line: number, column: number) {
|
|
928
1042
|
const absolutePath = file.startsWith('/') ? file : (this.projectPath ? `${this.projectPath}/${file}` : file);
|
|
929
1043
|
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
try {
|
|
941
|
-
window.open(editors[0], '_self');
|
|
942
|
-
} catch (error) {
|
|
1044
|
+
const editorToUse = this.preferredEditor === 'auto' ? this.defaultEditorFromServer : this.preferredEditor;
|
|
1045
|
+
const url = this.getEditorUrl(editorToUse, absolutePath, line, column);
|
|
1046
|
+
|
|
1047
|
+
if (url) {
|
|
1048
|
+
try {
|
|
1049
|
+
window.open(url, '_self');
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
console.log(`Open in editor: ${absolutePath}:${line}:${column}`);
|
|
1052
|
+
}
|
|
1053
|
+
} else {
|
|
943
1054
|
console.log(`Open in editor: ${absolutePath}:${line}:${column}`);
|
|
944
1055
|
}
|
|
945
1056
|
}
|
package/src/styles.css
CHANGED
|
@@ -429,6 +429,85 @@
|
|
|
429
429
|
background: #7c3aed;
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
+
.herb-editor-section {
|
|
433
|
+
padding: 16px 20px;
|
|
434
|
+
border-bottom: 1px solid #f3f4f6;
|
|
435
|
+
background: linear-gradient(135deg, #fafbfc 0%, #f8f9fa 100%);
|
|
436
|
+
position: relative;
|
|
437
|
+
overflow: hidden;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.herb-editor-section::before {
|
|
441
|
+
content: '';
|
|
442
|
+
position: absolute;
|
|
443
|
+
top: 0;
|
|
444
|
+
left: 0;
|
|
445
|
+
right: 0;
|
|
446
|
+
height: 2px;
|
|
447
|
+
background: linear-gradient(90deg, transparent, rgba(139, 92, 246, 0.2), transparent);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.herb-editor-label {
|
|
451
|
+
display: flex;
|
|
452
|
+
flex-direction: column;
|
|
453
|
+
gap: 10px;
|
|
454
|
+
cursor: default;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.herb-editor-text {
|
|
458
|
+
font-size: 12px;
|
|
459
|
+
font-weight: 600;
|
|
460
|
+
color: #6b7280;
|
|
461
|
+
text-transform: uppercase;
|
|
462
|
+
letter-spacing: 0.5px;
|
|
463
|
+
display: flex;
|
|
464
|
+
align-items: center;
|
|
465
|
+
gap: 6px;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.herb-editor-select {
|
|
469
|
+
width: 100%;
|
|
470
|
+
padding: 10px 36px 10px 12px;
|
|
471
|
+
background: white;
|
|
472
|
+
border: 1.5px solid #e5e7eb;
|
|
473
|
+
border-radius: 8px;
|
|
474
|
+
font-size: 13.5px;
|
|
475
|
+
font-weight: 500;
|
|
476
|
+
color: #1f2937;
|
|
477
|
+
cursor: pointer;
|
|
478
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
479
|
+
font-family: "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
480
|
+
appearance: none;
|
|
481
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.herb-editor-select option {
|
|
485
|
+
font-family: "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
486
|
+
font-size: 14px;
|
|
487
|
+
font-weight: 400;
|
|
488
|
+
padding: 8px 12px;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.herb-editor-select:hover {
|
|
492
|
+
border-color: #8b5cf6;
|
|
493
|
+
background-color: #fafafa;
|
|
494
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(139, 92, 246, 0.1);
|
|
495
|
+
transform: translateY(-1px);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.herb-editor-select:focus {
|
|
499
|
+
outline: none;
|
|
500
|
+
border-color: #8b5cf6;
|
|
501
|
+
background-color: white;
|
|
502
|
+
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15), 0 2px 8px rgba(139, 92, 246, 0.1);
|
|
503
|
+
transform: translateY(-1px);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.herb-editor-select:active {
|
|
507
|
+
transform: translateY(0);
|
|
508
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
509
|
+
}
|
|
510
|
+
|
|
432
511
|
.herb-disable-all-section {
|
|
433
512
|
padding: 16px 20px;
|
|
434
513
|
border-top: 1px solid #f3f4f6;
|