@leftium/gg 0.0.33 → 0.0.35
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 +123 -36
- package/dist/GgConsole.svelte +12 -0
- package/dist/GgConsole.svelte.d.ts +4 -0
- package/dist/OpenInEditorLink.svelte +17 -7
- package/dist/OpenInEditorLink.svelte.d.ts +8 -2
- package/dist/debug/browser.d.ts +10 -0
- package/dist/debug/browser.js +102 -0
- package/dist/debug/common.d.ts +41 -0
- package/dist/debug/common.js +191 -0
- package/dist/debug/index.d.ts +9 -0
- package/dist/debug/index.js +11 -0
- package/dist/debug/node.d.ts +10 -0
- package/dist/debug/node.js +137 -0
- package/dist/eruda/loader.js +0 -11
- package/dist/eruda/plugin.js +310 -29
- package/dist/eruda/types.d.ts +11 -5
- package/dist/gg-call-sites-plugin.d.ts +84 -0
- package/dist/gg-call-sites-plugin.js +600 -165
- package/dist/gg.d.ts +80 -0
- package/dist/gg.js +459 -110
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/vite.d.ts +37 -0
- package/dist/vite.js +46 -0
- package/package.json +20 -12
- package/dist/debug-bundled.d.ts +0 -2
- package/dist/debug-bundled.js +0 -3
- package/dist/debug.d.ts +0 -2
- package/dist/debug.js +0 -15
- package/patches/debug@4.4.3.patch +0 -35
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js-specific debug implementation.
|
|
3
|
+
*
|
|
4
|
+
* Output: process.stderr via util.formatWithOptions.
|
|
5
|
+
* Persistence: process.env.DEBUG
|
|
6
|
+
* Format (patched): +123ms namespace message (ANSI colored)
|
|
7
|
+
*/
|
|
8
|
+
import { setup, humanize } from './common.js';
|
|
9
|
+
import tty from 'tty';
|
|
10
|
+
import util from 'util';
|
|
11
|
+
/**
|
|
12
|
+
* Basic ANSI colors (6) — used when 256-color support is not detected.
|
|
13
|
+
* Extended 256-color palette matches debug@4 for color-hash stability.
|
|
14
|
+
*/
|
|
15
|
+
const basicColors = [6, 2, 3, 4, 5, 1];
|
|
16
|
+
const extendedColors = [
|
|
17
|
+
20, 21, 26, 27, 32, 33, 38, 39, 40, 41, 42, 43, 44, 45,
|
|
18
|
+
56, 57, 62, 63, 68, 69, 74, 75, 76, 77, 78, 79, 80, 81,
|
|
19
|
+
92, 93, 98, 99, 112, 113, 128, 129, 134, 135, 148, 149,
|
|
20
|
+
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171,
|
|
21
|
+
172, 173, 178, 179, 184, 185, 196, 197, 198, 199, 200, 201,
|
|
22
|
+
202, 203, 204, 205, 206, 207, 208, 209, 214, 215, 220, 221
|
|
23
|
+
];
|
|
24
|
+
/** Detect 256-color support via supports-color (optional) or heuristic */
|
|
25
|
+
function detectColors() {
|
|
26
|
+
try {
|
|
27
|
+
// Try supports-color if available (same as debug@4)
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
|
+
const supportsColor = require('supports-color');
|
|
30
|
+
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
|
|
31
|
+
return extendedColors;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Not installed — fall through
|
|
36
|
+
}
|
|
37
|
+
return basicColors;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build inspectOpts from DEBUG_* environment variables.
|
|
41
|
+
* Supports: DEBUG_COLORS, DEBUG_DEPTH, DEBUG_SHOW_HIDDEN, DEBUG_HIDE_DATE
|
|
42
|
+
*/
|
|
43
|
+
const inspectOpts = Object.keys(process.env)
|
|
44
|
+
.filter((key) => /^debug_/i.test(key))
|
|
45
|
+
.reduce((obj, key) => {
|
|
46
|
+
const prop = key
|
|
47
|
+
.substring(6)
|
|
48
|
+
.toLowerCase()
|
|
49
|
+
.replace(/_([a-z])/g, (_, k) => k.toUpperCase());
|
|
50
|
+
let val = process.env[key];
|
|
51
|
+
if (/^(yes|on|true|enabled)$/i.test(val))
|
|
52
|
+
val = true;
|
|
53
|
+
else if (/^(no|off|false|disabled)$/i.test(val))
|
|
54
|
+
val = false;
|
|
55
|
+
else if (val === 'null')
|
|
56
|
+
val = null;
|
|
57
|
+
else
|
|
58
|
+
val = Number(val);
|
|
59
|
+
obj[prop] = val;
|
|
60
|
+
return obj;
|
|
61
|
+
}, {});
|
|
62
|
+
function useColors() {
|
|
63
|
+
return 'colors' in inspectOpts
|
|
64
|
+
? Boolean(inspectOpts.colors)
|
|
65
|
+
: tty.isatty(process.stderr.fd);
|
|
66
|
+
}
|
|
67
|
+
function getDate() {
|
|
68
|
+
if (inspectOpts.hideDate)
|
|
69
|
+
return '';
|
|
70
|
+
return new Date().toISOString() + ' ';
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Format args with ANSI colors and gg's patched prefix order:
|
|
74
|
+
* +123ms namespace message
|
|
75
|
+
*/
|
|
76
|
+
function formatArgs(args) {
|
|
77
|
+
const name = this.namespace;
|
|
78
|
+
const useCol = this.useColors;
|
|
79
|
+
if (useCol) {
|
|
80
|
+
const c = Number(this.color);
|
|
81
|
+
const colorCode = '\u001B[3' + (c < 8 ? String(c) : '8;5;' + c);
|
|
82
|
+
const h = ('+' + humanize(this.diff)).padStart(6);
|
|
83
|
+
const prefix = `${colorCode};1m${h} ${name} \u001B[0m`;
|
|
84
|
+
args[0] = prefix + String(args[0]).split('\n').join('\n' + prefix);
|
|
85
|
+
// Append empty color reset (preserves arg count parity from original debug)
|
|
86
|
+
args.push(colorCode + '' + '\u001B[0m');
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
args[0] = getDate() + name + ' ' + args[0];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function log(...args) {
|
|
93
|
+
process.stderr.write(util.formatWithOptions(inspectOpts, ...args) + '\n');
|
|
94
|
+
}
|
|
95
|
+
function save(namespaces) {
|
|
96
|
+
if (namespaces) {
|
|
97
|
+
process.env.DEBUG = namespaces;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
delete process.env.DEBUG;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function load() {
|
|
104
|
+
return process.env.DEBUG || '';
|
|
105
|
+
}
|
|
106
|
+
function init(instance) {
|
|
107
|
+
// Each instance gets its own inspectOpts copy (for per-instance color override)
|
|
108
|
+
instance.inspectOpts = { ...inspectOpts };
|
|
109
|
+
}
|
|
110
|
+
const env = {
|
|
111
|
+
formatArgs,
|
|
112
|
+
save,
|
|
113
|
+
load,
|
|
114
|
+
useColors,
|
|
115
|
+
colors: detectColors(),
|
|
116
|
+
log,
|
|
117
|
+
init,
|
|
118
|
+
formatters: {
|
|
119
|
+
/** %o → util.inspect, single line */
|
|
120
|
+
o(v) {
|
|
121
|
+
const opts = this.inspectOpts || {};
|
|
122
|
+
opts.colors = this.useColors;
|
|
123
|
+
return util.inspect(v, opts)
|
|
124
|
+
.split('\n')
|
|
125
|
+
.map((str) => str.trim())
|
|
126
|
+
.join(' ');
|
|
127
|
+
},
|
|
128
|
+
/** %O → util.inspect, multi-line */
|
|
129
|
+
O(v) {
|
|
130
|
+
const opts = this.inspectOpts || {};
|
|
131
|
+
opts.colors = this.useColors;
|
|
132
|
+
return util.inspect(v, opts);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const debug = setup(env);
|
|
137
|
+
export default debug;
|
package/dist/eruda/loader.js
CHANGED
|
@@ -79,17 +79,6 @@ export async function loadEruda(options) {
|
|
|
79
79
|
// Ensure tool is always visible in case user customizes
|
|
80
80
|
tool: ['console', 'elements', 'network', 'resources', 'info', 'snippets', 'sources']
|
|
81
81
|
});
|
|
82
|
-
// Auto-enable localStorage.debug if requested and unset
|
|
83
|
-
if (options.autoEnable !== false) {
|
|
84
|
-
try {
|
|
85
|
-
if (!localStorage.getItem('debug')) {
|
|
86
|
-
localStorage.setItem('debug', 'gg:*');
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
// localStorage might not be available
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
82
|
// Register gg plugin
|
|
94
83
|
// Import gg and pass it to the plugin directly
|
|
95
84
|
const { gg, runGgDiagnostics } = await import('../gg.js');
|
package/dist/eruda/plugin.js
CHANGED
|
@@ -19,8 +19,12 @@ export function createGgPlugin(options, gg) {
|
|
|
19
19
|
let filterExpanded = false;
|
|
20
20
|
let filterPattern = '';
|
|
21
21
|
const enabledNamespaces = new Set();
|
|
22
|
+
// Last rendered entries (for hover tooltip arg lookup)
|
|
23
|
+
let renderedEntries = [];
|
|
22
24
|
// Settings UI state
|
|
23
25
|
let settingsExpanded = false;
|
|
26
|
+
// Filter pattern persistence key (independent of localStorage.debug)
|
|
27
|
+
const FILTER_KEY = 'gg-filter';
|
|
24
28
|
// Namespace click action: 'open' uses Vite dev middleware, 'copy' copies formatted string, 'open-url' navigates to URI
|
|
25
29
|
const NS_ACTION_KEY = 'gg-ns-action';
|
|
26
30
|
const EDITOR_BIN_KEY = 'gg-editor-bin';
|
|
@@ -78,13 +82,6 @@ export function createGgPlugin(options, gg) {
|
|
|
78
82
|
};
|
|
79
83
|
let nsClickAction = localStorage.getItem(NS_ACTION_KEY) || (DEV ? 'open' : 'copy');
|
|
80
84
|
let editorBin = localStorage.getItem(EDITOR_BIN_KEY) || '';
|
|
81
|
-
// Separate format strings for copy vs URL modes (each persisted independently)
|
|
82
|
-
// Migrate from old single-key format (gg-editor-format) if present
|
|
83
|
-
const legacyFormat = localStorage.getItem('gg-editor-format');
|
|
84
|
-
if (legacyFormat && !localStorage.getItem(COPY_FORMAT_KEY)) {
|
|
85
|
-
localStorage.setItem(COPY_FORMAT_KEY, legacyFormat);
|
|
86
|
-
localStorage.removeItem('gg-editor-format');
|
|
87
|
-
}
|
|
88
85
|
let copyFormat = localStorage.getItem(COPY_FORMAT_KEY) || copyPresets['Raw path'];
|
|
89
86
|
let urlFormat = localStorage.getItem(URL_FORMAT_KEY) || uriPresets['VS Code'];
|
|
90
87
|
let projectRoot = localStorage.getItem(PROJECT_ROOT_KEY) || '';
|
|
@@ -107,24 +104,27 @@ export function createGgPlugin(options, gg) {
|
|
|
107
104
|
name: 'GG',
|
|
108
105
|
init($container) {
|
|
109
106
|
$el = $container;
|
|
107
|
+
// Load filter state BEFORE registering _onLog hook, because setting _onLog
|
|
108
|
+
// triggers replay of earlyLogBuffer and each entry checks filterPattern
|
|
109
|
+
filterPattern = localStorage.getItem(FILTER_KEY) || 'gg:*';
|
|
110
110
|
// Register the capture hook on gg
|
|
111
111
|
if (gg) {
|
|
112
112
|
gg._onLog = (entry) => {
|
|
113
|
+
// Track namespaces for filter UI update (check BEFORE pushing to buffer)
|
|
114
|
+
const hadNamespace = getAllCapturedNamespaces().includes(entry.namespace);
|
|
113
115
|
buffer.push(entry);
|
|
114
116
|
// Add new namespace to enabledNamespaces if it matches the current pattern
|
|
115
117
|
const effectivePattern = filterPattern || 'gg:*';
|
|
116
118
|
if (namespaceMatchesPattern(entry.namespace, effectivePattern)) {
|
|
117
119
|
enabledNamespaces.add(entry.namespace);
|
|
118
120
|
}
|
|
119
|
-
// Update filter UI if
|
|
120
|
-
if (
|
|
121
|
+
// Update filter UI if new namespace appeared (updates button summary count)
|
|
122
|
+
if (!hadNamespace) {
|
|
121
123
|
renderFilterUI();
|
|
122
124
|
}
|
|
123
125
|
renderLogs();
|
|
124
126
|
};
|
|
125
127
|
}
|
|
126
|
-
// Load initial filter state
|
|
127
|
-
filterPattern = localStorage.getItem('debug') || '';
|
|
128
128
|
// Probe for openInEditorPlugin (status 222) and auto-populate $ROOT in dev mode
|
|
129
129
|
if (DEV) {
|
|
130
130
|
fetch('/__open-in-editor?file=+')
|
|
@@ -420,21 +420,38 @@ export function createGgPlugin(options, gg) {
|
|
|
420
420
|
word-break: break-word;
|
|
421
421
|
padding: 4px 0;
|
|
422
422
|
position: relative;
|
|
423
|
+
-webkit-user-select: text !important;
|
|
424
|
+
user-select: text !important;
|
|
425
|
+
cursor: text;
|
|
426
|
+
}
|
|
427
|
+
.gg-log-content * {
|
|
428
|
+
-webkit-user-select: text !important;
|
|
429
|
+
user-select: text !important;
|
|
430
|
+
}
|
|
431
|
+
.gg-log-diff, .gg-log-ns {
|
|
432
|
+
-webkit-user-select: text !important;
|
|
433
|
+
user-select: text !important;
|
|
423
434
|
}
|
|
424
|
-
|
|
435
|
+
.gg-details, .gg-details * {
|
|
436
|
+
-webkit-user-select: text !important;
|
|
437
|
+
user-select: text !important;
|
|
438
|
+
cursor: text;
|
|
439
|
+
}
|
|
440
|
+
/* Fast custom tooltip for src expression on primitive-only rows (no expandable objects) */
|
|
425
441
|
.gg-log-content[data-src] {
|
|
426
442
|
cursor: help;
|
|
427
443
|
}
|
|
428
|
-
.gg-
|
|
429
|
-
|
|
444
|
+
/* Show icon only on primitive rows (no .gg-expand child) */
|
|
445
|
+
.gg-log-content[data-src]:not(:has(.gg-expand))::before {
|
|
446
|
+
content: '\uD83D\uDD0D';
|
|
430
447
|
font-size: 10px;
|
|
431
448
|
margin-right: 4px;
|
|
432
449
|
opacity: 0.4;
|
|
433
450
|
}
|
|
434
|
-
.gg-log-content[data-src]:hover::before {
|
|
451
|
+
.gg-log-content[data-src]:not(:has(.gg-expand)):hover::before {
|
|
435
452
|
opacity: 1;
|
|
436
453
|
}
|
|
437
|
-
.gg-log-content[data-src]::after {
|
|
454
|
+
.gg-log-content[data-src]:not(:has(.gg-expand))::after {
|
|
438
455
|
content: attr(data-src);
|
|
439
456
|
position: absolute;
|
|
440
457
|
top: 100%;
|
|
@@ -454,10 +471,103 @@ export function createGgPlugin(options, gg) {
|
|
|
454
471
|
overflow: hidden;
|
|
455
472
|
text-overflow: ellipsis;
|
|
456
473
|
}
|
|
457
|
-
.gg-log-content[data-src]:hover::after {
|
|
474
|
+
.gg-log-content[data-src]:not(:has(.gg-expand)):hover::after {
|
|
475
|
+
opacity: 1;
|
|
476
|
+
}
|
|
477
|
+
/* Expression icon inline with expandable object labels */
|
|
478
|
+
.gg-src-icon {
|
|
479
|
+
font-size: 10px;
|
|
480
|
+
margin-right: 2px;
|
|
481
|
+
opacity: 0.4;
|
|
482
|
+
cursor: pointer;
|
|
483
|
+
}
|
|
484
|
+
.gg-expand:hover .gg-src-icon,
|
|
485
|
+
.gg-src-icon:hover {
|
|
486
|
+
opacity: 1;
|
|
487
|
+
}
|
|
488
|
+
/* Hover tooltip for expandable objects/arrays */
|
|
489
|
+
.gg-hover-tooltip {
|
|
490
|
+
display: none;
|
|
491
|
+
position: fixed;
|
|
492
|
+
background: #1e1e1e;
|
|
493
|
+
color: #d4d4d4;
|
|
494
|
+
font-size: 11px;
|
|
495
|
+
font-family: monospace;
|
|
496
|
+
padding: 8px 10px;
|
|
497
|
+
border-radius: 4px;
|
|
498
|
+
white-space: pre;
|
|
499
|
+
pointer-events: none;
|
|
500
|
+
z-index: 100000;
|
|
501
|
+
max-width: min(90vw, 500px);
|
|
502
|
+
max-height: 300px;
|
|
503
|
+
overflow: auto;
|
|
504
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
505
|
+
line-height: 1.4;
|
|
506
|
+
}
|
|
507
|
+
.gg-hover-tooltip-src {
|
|
508
|
+
color: #9cdcfe;
|
|
509
|
+
font-style: italic;
|
|
510
|
+
margin-bottom: 4px;
|
|
511
|
+
padding-bottom: 4px;
|
|
512
|
+
border-bottom: 1px solid #444;
|
|
513
|
+
}
|
|
514
|
+
/* Expression header inside expanded details */
|
|
515
|
+
.gg-details-src {
|
|
516
|
+
color: #555;
|
|
517
|
+
font-style: italic;
|
|
518
|
+
font-family: monospace;
|
|
519
|
+
font-size: 11px;
|
|
520
|
+
margin-bottom: 6px;
|
|
521
|
+
padding-bottom: 4px;
|
|
522
|
+
border-bottom: 1px solid #ddd;
|
|
523
|
+
}
|
|
524
|
+
/* Level-based styling for warn/error entries */
|
|
525
|
+
.gg-level-warn .gg-log-diff,
|
|
526
|
+
.gg-level-warn .gg-log-ns,
|
|
527
|
+
.gg-level-warn .gg-log-content {
|
|
528
|
+
background: rgba(255, 200, 0, 0.08);
|
|
529
|
+
}
|
|
530
|
+
.gg-level-warn .gg-log-content {
|
|
531
|
+
border-left: 3px solid #e6a700;
|
|
532
|
+
padding-left: 6px;
|
|
533
|
+
}
|
|
534
|
+
.gg-level-error .gg-log-diff,
|
|
535
|
+
.gg-level-error .gg-log-ns,
|
|
536
|
+
.gg-level-error .gg-log-content {
|
|
537
|
+
background: rgba(255, 50, 50, 0.08);
|
|
538
|
+
}
|
|
539
|
+
.gg-level-error .gg-log-content {
|
|
540
|
+
border-left: 3px solid #cc0000;
|
|
541
|
+
padding-left: 6px;
|
|
542
|
+
}
|
|
543
|
+
/* Stack trace toggle */
|
|
544
|
+
.gg-stack-toggle {
|
|
545
|
+
cursor: pointer;
|
|
546
|
+
font-size: 11px;
|
|
547
|
+
opacity: 0.6;
|
|
548
|
+
margin-left: 8px;
|
|
549
|
+
user-select: none;
|
|
550
|
+
}
|
|
551
|
+
.gg-stack-toggle:hover {
|
|
458
552
|
opacity: 1;
|
|
459
553
|
}
|
|
460
|
-
.gg-
|
|
554
|
+
.gg-stack-content {
|
|
555
|
+
display: none;
|
|
556
|
+
font-size: 11px;
|
|
557
|
+
font-family: monospace;
|
|
558
|
+
white-space: pre;
|
|
559
|
+
padding: 6px 8px;
|
|
560
|
+
margin-top: 4px;
|
|
561
|
+
background: #f0f0f0;
|
|
562
|
+
border-radius: 3px;
|
|
563
|
+
overflow-x: auto;
|
|
564
|
+
color: #666;
|
|
565
|
+
line-height: 1.4;
|
|
566
|
+
}
|
|
567
|
+
.gg-stack-content.expanded {
|
|
568
|
+
display: block;
|
|
569
|
+
}
|
|
570
|
+
.gg-filter-panel {
|
|
461
571
|
background: #f5f5f5;
|
|
462
572
|
padding: 10px;
|
|
463
573
|
margin-bottom: 8px;
|
|
@@ -683,7 +793,7 @@ export function createGgPlugin(options, gg) {
|
|
|
683
793
|
}
|
|
684
794
|
function applyPatternFromInput(value) {
|
|
685
795
|
filterPattern = value;
|
|
686
|
-
localStorage.setItem(
|
|
796
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
687
797
|
// Sync enabledNamespaces from the new pattern
|
|
688
798
|
const allNamespaces = getAllCapturedNamespaces();
|
|
689
799
|
enabledNamespaces.clear();
|
|
@@ -746,7 +856,7 @@ export function createGgPlugin(options, gg) {
|
|
|
746
856
|
filterPattern = `gg:*,${exclusions}`;
|
|
747
857
|
enabledNamespaces.clear();
|
|
748
858
|
}
|
|
749
|
-
localStorage.setItem(
|
|
859
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
750
860
|
renderFilterUI();
|
|
751
861
|
renderLogs();
|
|
752
862
|
return;
|
|
@@ -893,6 +1003,37 @@ export function createGgPlugin(options, gg) {
|
|
|
893
1003
|
else if (nsClickAction === 'copy' || nsClickAction === 'open-url') {
|
|
894
1004
|
optionsSection = renderFormatSection(nsClickAction === 'open-url');
|
|
895
1005
|
}
|
|
1006
|
+
// Native Console section
|
|
1007
|
+
const currentDebugValue = localStorage.getItem('debug');
|
|
1008
|
+
const debugDisplay = currentDebugValue !== null ? `'${escapeHtml(currentDebugValue)}'` : 'not set';
|
|
1009
|
+
const currentFilter = filterPattern || 'gg:*';
|
|
1010
|
+
const debugMatchesFilter = currentDebugValue === currentFilter;
|
|
1011
|
+
const debugIncludesGg = currentDebugValue !== null &&
|
|
1012
|
+
(currentDebugValue.includes('gg:') || currentDebugValue === '*');
|
|
1013
|
+
const nativeConsoleSection = `
|
|
1014
|
+
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #ddd;">
|
|
1015
|
+
<div class="gg-settings-label">Native Console Output</div>
|
|
1016
|
+
<div style="font-size: 11px; opacity: 0.7; margin-bottom: 8px;">
|
|
1017
|
+
gg messages always appear in this GG panel. To also see them in the browser's native console, set <code>localStorage.debug</code> below.
|
|
1018
|
+
For server-side: <code>DEBUG=gg:* npm run dev</code>
|
|
1019
|
+
</div>
|
|
1020
|
+
<div style="font-family: monospace; font-size: 12px; margin-bottom: 6px;">
|
|
1021
|
+
localStorage.debug = ${debugDisplay}
|
|
1022
|
+
${debugIncludesGg ? '<span style="color: green;">✅</span>' : '<span style="color: #999;">⚫ gg:* not included</span>'}
|
|
1023
|
+
</div>
|
|
1024
|
+
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
|
|
1025
|
+
<button class="gg-sync-debug-btn" style="padding: 4px 10px; font-size: 12px; cursor: pointer; border: 1px solid #ccc; border-radius: 3px; background: ${debugMatchesFilter ? '#e8e8e8' : '#fff'};"${debugMatchesFilter ? ' disabled' : ''}>
|
|
1026
|
+
${debugMatchesFilter ? 'In sync' : `Set to '${escapeHtml(currentFilter)}'`}
|
|
1027
|
+
</button>
|
|
1028
|
+
<button class="gg-clear-debug-btn" style="padding: 4px 10px; font-size: 12px; cursor: pointer; border: 1px solid #ccc; border-radius: 3px; background: #fff;"${currentDebugValue === null ? ' disabled' : ''}>
|
|
1029
|
+
Clear
|
|
1030
|
+
</button>
|
|
1031
|
+
</div>
|
|
1032
|
+
<div style="font-size: 10px; opacity: 0.5; margin-top: 4px;">
|
|
1033
|
+
Changes take effect on next page reload.
|
|
1034
|
+
</div>
|
|
1035
|
+
</div>
|
|
1036
|
+
`;
|
|
896
1037
|
settingsPanel.innerHTML = `
|
|
897
1038
|
${callSitesWarning}
|
|
898
1039
|
<div class="gg-settings-label">When namespace clicked:</div>
|
|
@@ -911,6 +1052,7 @@ export function createGgPlugin(options, gg) {
|
|
|
911
1052
|
</label>
|
|
912
1053
|
</div>
|
|
913
1054
|
${optionsSection}
|
|
1055
|
+
${nativeConsoleSection}
|
|
914
1056
|
`;
|
|
915
1057
|
}
|
|
916
1058
|
else {
|
|
@@ -970,7 +1112,7 @@ export function createGgPlugin(options, gg) {
|
|
|
970
1112
|
target.blur();
|
|
971
1113
|
}
|
|
972
1114
|
});
|
|
973
|
-
// Preset button clicks + editor bin button clicks
|
|
1115
|
+
// Preset button clicks + editor bin button clicks + native console buttons
|
|
974
1116
|
settingsPanel.addEventListener('click', (e) => {
|
|
975
1117
|
const target = e.target;
|
|
976
1118
|
if (target.classList.contains('gg-preset-btn')) {
|
|
@@ -988,6 +1130,17 @@ export function createGgPlugin(options, gg) {
|
|
|
988
1130
|
renderSettingsUI();
|
|
989
1131
|
}
|
|
990
1132
|
}
|
|
1133
|
+
// Native Console: sync localStorage.debug to current gg-filter
|
|
1134
|
+
if (target.classList.contains('gg-sync-debug-btn')) {
|
|
1135
|
+
const currentFilter = localStorage.getItem(FILTER_KEY) || 'gg:*';
|
|
1136
|
+
localStorage.setItem('debug', currentFilter);
|
|
1137
|
+
renderSettingsUI();
|
|
1138
|
+
}
|
|
1139
|
+
// Native Console: clear localStorage.debug
|
|
1140
|
+
if (target.classList.contains('gg-clear-debug-btn')) {
|
|
1141
|
+
localStorage.removeItem('debug');
|
|
1142
|
+
renderSettingsUI();
|
|
1143
|
+
}
|
|
991
1144
|
});
|
|
992
1145
|
}
|
|
993
1146
|
function wireUpButtons() {
|
|
@@ -1107,6 +1260,19 @@ export function createGgPlugin(options, gg) {
|
|
|
1107
1260
|
}
|
|
1108
1261
|
return;
|
|
1109
1262
|
}
|
|
1263
|
+
// Handle stack trace toggle
|
|
1264
|
+
if (target?.classList?.contains('gg-stack-toggle')) {
|
|
1265
|
+
const stackId = target.getAttribute('data-stack-id');
|
|
1266
|
+
if (!stackId)
|
|
1267
|
+
return;
|
|
1268
|
+
const stackEl = containerEl.querySelector(`.gg-stack-content[data-stack-id="${stackId}"]`);
|
|
1269
|
+
if (stackEl) {
|
|
1270
|
+
const isExpanded = stackEl.classList.contains('expanded');
|
|
1271
|
+
stackEl.classList.toggle('expanded');
|
|
1272
|
+
target.textContent = isExpanded ? '▶ stack' : '▼ stack';
|
|
1273
|
+
}
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1110
1276
|
// Handle clicking namespace to open in editor (when filter collapsed)
|
|
1111
1277
|
if (target?.classList?.contains('gg-log-ns') &&
|
|
1112
1278
|
target.hasAttribute('data-file') &&
|
|
@@ -1127,7 +1293,7 @@ export function createGgPlugin(options, gg) {
|
|
|
1127
1293
|
else {
|
|
1128
1294
|
soloNamespace(namespace);
|
|
1129
1295
|
}
|
|
1130
|
-
localStorage.setItem(
|
|
1296
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
1131
1297
|
renderFilterUI();
|
|
1132
1298
|
renderLogs();
|
|
1133
1299
|
return;
|
|
@@ -1138,7 +1304,7 @@ export function createGgPlugin(options, gg) {
|
|
|
1138
1304
|
if (!namespace)
|
|
1139
1305
|
return;
|
|
1140
1306
|
soloNamespace(namespace);
|
|
1141
|
-
localStorage.setItem(
|
|
1307
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
1142
1308
|
renderFilterUI();
|
|
1143
1309
|
renderLogs();
|
|
1144
1310
|
return;
|
|
@@ -1150,11 +1316,75 @@ export function createGgPlugin(options, gg) {
|
|
|
1150
1316
|
filterPattern = 'gg:*';
|
|
1151
1317
|
enabledNamespaces.clear();
|
|
1152
1318
|
getAllCapturedNamespaces().forEach((ns) => enabledNamespaces.add(ns));
|
|
1153
|
-
localStorage.setItem(
|
|
1319
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
1154
1320
|
renderFilterUI();
|
|
1155
1321
|
renderLogs();
|
|
1156
1322
|
}
|
|
1157
1323
|
});
|
|
1324
|
+
// Hover tooltip for expandable objects/arrays.
|
|
1325
|
+
// The tooltip div is re-created after each renderLogs() call
|
|
1326
|
+
// since logContainer.html() destroys children. Event listeners query it dynamically.
|
|
1327
|
+
containerEl.addEventListener('mouseover', (e) => {
|
|
1328
|
+
const target = e.target?.closest?.('.gg-expand');
|
|
1329
|
+
if (!target)
|
|
1330
|
+
return;
|
|
1331
|
+
const entryIdx = target.getAttribute('data-entry');
|
|
1332
|
+
const argIdx = target.getAttribute('data-arg');
|
|
1333
|
+
if (entryIdx === null || argIdx === null)
|
|
1334
|
+
return;
|
|
1335
|
+
const entry = renderedEntries[Number(entryIdx)];
|
|
1336
|
+
if (!entry)
|
|
1337
|
+
return;
|
|
1338
|
+
const arg = entry.args[Number(argIdx)];
|
|
1339
|
+
if (arg === undefined)
|
|
1340
|
+
return;
|
|
1341
|
+
const tip = containerEl.querySelector('.gg-hover-tooltip');
|
|
1342
|
+
if (!tip)
|
|
1343
|
+
return;
|
|
1344
|
+
const srcExpr = target.getAttribute('data-src');
|
|
1345
|
+
// Build tooltip content using DOM API (safe, no HTML injection)
|
|
1346
|
+
tip.textContent = '';
|
|
1347
|
+
if (srcExpr) {
|
|
1348
|
+
const srcDiv = document.createElement('div');
|
|
1349
|
+
srcDiv.className = 'gg-hover-tooltip-src';
|
|
1350
|
+
srcDiv.textContent = srcExpr;
|
|
1351
|
+
tip.appendChild(srcDiv);
|
|
1352
|
+
}
|
|
1353
|
+
const pre = document.createElement('pre');
|
|
1354
|
+
pre.style.margin = '0';
|
|
1355
|
+
pre.textContent = JSON.stringify(arg, null, 2);
|
|
1356
|
+
tip.appendChild(pre);
|
|
1357
|
+
tip.style.display = 'block';
|
|
1358
|
+
// Position below the hovered element using viewport coords (fixed positioning)
|
|
1359
|
+
const targetRect = target.getBoundingClientRect();
|
|
1360
|
+
let left = targetRect.left;
|
|
1361
|
+
let top = targetRect.bottom + 4;
|
|
1362
|
+
// Keep tooltip within viewport
|
|
1363
|
+
const tipRect = tip.getBoundingClientRect();
|
|
1364
|
+
if (left + tipRect.width > window.innerWidth) {
|
|
1365
|
+
left = window.innerWidth - tipRect.width - 8;
|
|
1366
|
+
}
|
|
1367
|
+
if (left < 4)
|
|
1368
|
+
left = 4;
|
|
1369
|
+
// If tooltip would go below viewport, show above instead
|
|
1370
|
+
if (top + tipRect.height > window.innerHeight) {
|
|
1371
|
+
top = targetRect.top - tipRect.height - 4;
|
|
1372
|
+
}
|
|
1373
|
+
tip.style.left = `${left}px`;
|
|
1374
|
+
tip.style.top = `${top}px`;
|
|
1375
|
+
});
|
|
1376
|
+
containerEl.addEventListener('mouseout', (e) => {
|
|
1377
|
+
const target = e.target?.closest?.('.gg-expand');
|
|
1378
|
+
if (!target)
|
|
1379
|
+
return;
|
|
1380
|
+
// Only hide if we're not moving to another child of the same .gg-expand
|
|
1381
|
+
const related = e.relatedTarget;
|
|
1382
|
+
if (related?.closest?.('.gg-expand') === target)
|
|
1383
|
+
return;
|
|
1384
|
+
const tip = containerEl.querySelector('.gg-hover-tooltip');
|
|
1385
|
+
if (tip)
|
|
1386
|
+
tip.style.display = 'none';
|
|
1387
|
+
});
|
|
1158
1388
|
expanderAttached = true;
|
|
1159
1389
|
}
|
|
1160
1390
|
function wireUpResize() {
|
|
@@ -1214,6 +1444,7 @@ export function createGgPlugin(options, gg) {
|
|
|
1214
1444
|
const allEntries = buffer.getEntries();
|
|
1215
1445
|
// Apply filtering
|
|
1216
1446
|
const entries = allEntries.filter((entry) => enabledNamespaces.has(entry.namespace));
|
|
1447
|
+
renderedEntries = entries;
|
|
1217
1448
|
const countText = entries.length === allEntries.length
|
|
1218
1449
|
? `${entries.length} entries`
|
|
1219
1450
|
: `${entries.length} / ${allEntries.length} entries`;
|
|
@@ -1230,7 +1461,29 @@ export function createGgPlugin(options, gg) {
|
|
|
1230
1461
|
// Format each arg individually - objects are expandable
|
|
1231
1462
|
let argsHTML = '';
|
|
1232
1463
|
let detailsHTML = '';
|
|
1233
|
-
|
|
1464
|
+
// Source expression for this entry (used in hover tooltips and expanded details)
|
|
1465
|
+
const srcExpr = entry.src?.trim() && !/^['"`]/.test(entry.src) ? escapeHtml(entry.src) : '';
|
|
1466
|
+
// HTML table rendering for gg.table() entries
|
|
1467
|
+
if (entry.tableData && entry.tableData.keys.length > 0) {
|
|
1468
|
+
const { keys, rows: tableRows } = entry.tableData;
|
|
1469
|
+
const headerCells = keys
|
|
1470
|
+
.map((k) => `<th style="padding: 2px 8px; border: 1px solid #ccc; background: #f0f0f0; font-size: 11px; white-space: nowrap;">${escapeHtml(k)}</th>`)
|
|
1471
|
+
.join('');
|
|
1472
|
+
const bodyRowsHtml = tableRows
|
|
1473
|
+
.map((row) => {
|
|
1474
|
+
const cells = keys
|
|
1475
|
+
.map((k) => {
|
|
1476
|
+
const val = row[k];
|
|
1477
|
+
const display = val === undefined ? '' : escapeHtml(String(val));
|
|
1478
|
+
return `<td style="padding: 2px 8px; border: 1px solid #ddd; font-size: 11px; white-space: nowrap;">${display}</td>`;
|
|
1479
|
+
})
|
|
1480
|
+
.join('');
|
|
1481
|
+
return `<tr>${cells}</tr>`;
|
|
1482
|
+
})
|
|
1483
|
+
.join('');
|
|
1484
|
+
argsHTML = `<table style="border-collapse: collapse; margin: 2px 0; font-family: monospace;"><thead><tr>${headerCells}</tr></thead><tbody>${bodyRowsHtml}</tbody></table>`;
|
|
1485
|
+
}
|
|
1486
|
+
else if (entry.args.length > 0) {
|
|
1234
1487
|
argsHTML = entry.args
|
|
1235
1488
|
.map((arg, argIdx) => {
|
|
1236
1489
|
if (typeof arg === 'object' && arg !== null) {
|
|
@@ -1238,9 +1491,14 @@ export function createGgPlugin(options, gg) {
|
|
|
1238
1491
|
const preview = Array.isArray(arg) ? `Array(${arg.length})` : 'Object';
|
|
1239
1492
|
const jsonStr = escapeHtml(JSON.stringify(arg, null, 2));
|
|
1240
1493
|
const uniqueId = `${index}-${argIdx}`;
|
|
1494
|
+
// Expression header inside expanded details
|
|
1495
|
+
const srcHeader = srcExpr ? `<div class="gg-details-src">${srcExpr}</div>` : '';
|
|
1241
1496
|
// Store details separately to render after the row
|
|
1242
|
-
detailsHTML += `<div class="gg-details" data-index="${uniqueId}" style="display: none; margin: 4px 0 8px 0; padding: 8px; background: #f8f8f8; border-left: 3px solid ${color}; font-size: 11px; overflow-x: auto;"
|
|
1243
|
-
|
|
1497
|
+
detailsHTML += `<div class="gg-details" data-index="${uniqueId}" style="display: none; margin: 4px 0 8px 0; padding: 8px; background: #f8f8f8; border-left: 3px solid ${color}; font-size: 11px; overflow-x: auto;">${srcHeader}<pre style="margin: 0;">${jsonStr}</pre></div>`;
|
|
1498
|
+
// data-entry/data-arg for hover tooltip lookup, data-src for expression context
|
|
1499
|
+
const srcAttr = srcExpr ? ` data-src="${srcExpr}"` : '';
|
|
1500
|
+
const srcIcon = srcExpr ? `<span class="gg-src-icon">\uD83D\uDD0D</span>` : '';
|
|
1501
|
+
return `<span style="color: #888; cursor: pointer; text-decoration: underline;" class="gg-expand" data-index="${uniqueId}" data-entry="${index}" data-arg="${argIdx}"${srcAttr}>${srcIcon}${preview}</span>`;
|
|
1244
1502
|
}
|
|
1245
1503
|
else {
|
|
1246
1504
|
// Parse ANSI codes first, then convert URLs to clickable links
|
|
@@ -1281,20 +1539,41 @@ export function createGgPlugin(options, gg) {
|
|
|
1281
1539
|
}
|
|
1282
1540
|
}
|
|
1283
1541
|
const fileTitle = fileTitleText ? ` title="${escapeHtml(fileTitleText)}"` : '';
|
|
1542
|
+
// Level class for warn/error styling
|
|
1543
|
+
const levelClass = entry.level === 'warn'
|
|
1544
|
+
? ' gg-level-warn'
|
|
1545
|
+
: entry.level === 'error'
|
|
1546
|
+
? ' gg-level-error'
|
|
1547
|
+
: '';
|
|
1548
|
+
// Stack trace toggle (for error/trace entries with captured stacks)
|
|
1549
|
+
let stackHTML = '';
|
|
1550
|
+
if (entry.stack) {
|
|
1551
|
+
const stackId = `stack-${index}`;
|
|
1552
|
+
stackHTML =
|
|
1553
|
+
`<span class="gg-stack-toggle" data-stack-id="${stackId}">▶ stack</span>` +
|
|
1554
|
+
`<div class="gg-stack-content" data-stack-id="${stackId}">${escapeHtml(entry.stack)}</div>`;
|
|
1555
|
+
}
|
|
1284
1556
|
// Desktop: grid layout, Mobile: stacked layout
|
|
1285
|
-
return (`<div class="gg-log-entry">` +
|
|
1557
|
+
return (`<div class="gg-log-entry${levelClass}">` +
|
|
1286
1558
|
`<div class="gg-log-header">` +
|
|
1287
1559
|
iconsCol +
|
|
1288
1560
|
`<div class="gg-log-diff${soloClass}" style="color: ${color};"${soloAttr}>${diff}</div>` +
|
|
1289
1561
|
`<div class="gg-log-ns${soloClass}" style="color: ${color};"${soloAttr}${fileAttr}${lineAttr}${colAttr}${fileTitle}>${ns}</div>` +
|
|
1290
1562
|
`<div class="gg-log-handle"></div>` +
|
|
1291
1563
|
`</div>` +
|
|
1292
|
-
`<div class="gg-log-content"${entry.src?.trim() && !/^['"`]/.test(entry.src) ? ` data-src="${escapeHtml(entry.src)}"` : ''}>${argsHTML}</div>` +
|
|
1564
|
+
`<div class="gg-log-content"${!entry.level && entry.src?.trim() && !/^['"`]/.test(entry.src) ? ` data-src="${escapeHtml(entry.src)}"` : ''}>${argsHTML}${stackHTML}</div>` +
|
|
1293
1565
|
detailsHTML +
|
|
1294
1566
|
`</div>`);
|
|
1295
1567
|
})
|
|
1296
1568
|
.join('')}</div>`;
|
|
1297
1569
|
logContainer.html(logsHTML);
|
|
1570
|
+
// Append hover tooltip div (destroyed by .html() each render, so re-create)
|
|
1571
|
+
const containerDom = logContainer.get(0);
|
|
1572
|
+
if (containerDom) {
|
|
1573
|
+
const tip = document.createElement('div');
|
|
1574
|
+
tip.className = 'gg-hover-tooltip';
|
|
1575
|
+
containerDom.appendChild(tip);
|
|
1576
|
+
}
|
|
1298
1577
|
// Re-wire expanders after rendering
|
|
1299
1578
|
wireUpExpanders();
|
|
1300
1579
|
// Auto-scroll to bottom
|
|
@@ -1326,6 +1605,7 @@ export function createGgPlugin(options, gg) {
|
|
|
1326
1605
|
*/
|
|
1327
1606
|
function stripAnsi(text) {
|
|
1328
1607
|
// Remove all ANSI escape codes
|
|
1608
|
+
// eslint-disable-next-line no-control-regex
|
|
1329
1609
|
return text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
1330
1610
|
}
|
|
1331
1611
|
// Standard ANSI 3/4-bit color palette
|
|
@@ -1376,6 +1656,7 @@ export function createGgPlugin(options, gg) {
|
|
|
1376
1656
|
*/
|
|
1377
1657
|
function parseAnsiToHtml(text) {
|
|
1378
1658
|
// ANSI escape sequence regex
|
|
1659
|
+
// eslint-disable-next-line no-control-regex
|
|
1379
1660
|
const ansiRegex = /\x1b\[([0-9;]+)m/g;
|
|
1380
1661
|
let html = '';
|
|
1381
1662
|
let lastIndex = 0;
|