@leftium/gg 0.0.39 → 0.0.40
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/dist/eruda/plugin.js +158 -4
- package/package.json +1 -1
package/dist/eruda/plugin.js
CHANGED
|
@@ -205,6 +205,41 @@ export function createGgPlugin(options, gg) {
|
|
|
205
205
|
}
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
|
+
function toggleNamespaces(namespaces, enable) {
|
|
209
|
+
const currentPattern = filterPattern || 'gg:*';
|
|
210
|
+
let parts = currentPattern
|
|
211
|
+
.split(',')
|
|
212
|
+
.map((p) => p.trim())
|
|
213
|
+
.filter(Boolean);
|
|
214
|
+
namespaces.forEach((namespace) => {
|
|
215
|
+
const ns = namespace.trim();
|
|
216
|
+
if (enable) {
|
|
217
|
+
// Remove any exclusion for this namespace
|
|
218
|
+
parts = parts.filter((p) => p !== `-${ns}`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
// Add exclusion if not already present
|
|
222
|
+
const exclusion = `-${ns}`;
|
|
223
|
+
if (!parts.includes(exclusion)) {
|
|
224
|
+
parts.push(exclusion);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
filterPattern = parts.join(',');
|
|
229
|
+
// Simplify pattern
|
|
230
|
+
filterPattern = simplifyPattern(filterPattern);
|
|
231
|
+
// Sync enabledNamespaces from the NEW pattern
|
|
232
|
+
const allNamespaces = getAllCapturedNamespaces();
|
|
233
|
+
enabledNamespaces.clear();
|
|
234
|
+
const effectivePattern = filterPattern || 'gg:*';
|
|
235
|
+
allNamespaces.forEach((ns) => {
|
|
236
|
+
if (namespaceMatchesPattern(ns, effectivePattern)) {
|
|
237
|
+
enabledNamespaces.add(ns);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
// Persist the new pattern
|
|
241
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
242
|
+
}
|
|
208
243
|
function soloNamespace(namespace) {
|
|
209
244
|
const ns = namespace.trim();
|
|
210
245
|
// Toggle: if already soloed on this namespace, restore all
|
|
@@ -377,6 +412,20 @@ export function createGgPlugin(options, gg) {
|
|
|
377
412
|
.gg-log-ns[data-file]:hover::after {
|
|
378
413
|
opacity: 1;
|
|
379
414
|
}
|
|
415
|
+
/* Clickable namespace segments */
|
|
416
|
+
.gg-ns-segment {
|
|
417
|
+
cursor: pointer;
|
|
418
|
+
padding: 1px 2px;
|
|
419
|
+
border-radius: 2px;
|
|
420
|
+
transition: background 0.1s;
|
|
421
|
+
}
|
|
422
|
+
/* Only show segment hover styling when in filter mode */
|
|
423
|
+
.gg-log-grid.filter-mode .gg-ns-segment:hover {
|
|
424
|
+
background: rgba(0,0,0,0.1);
|
|
425
|
+
text-decoration: underline;
|
|
426
|
+
text-decoration-style: solid;
|
|
427
|
+
text-underline-offset: 2px;
|
|
428
|
+
}
|
|
380
429
|
.gg-details {
|
|
381
430
|
grid-column: 1 / -1;
|
|
382
431
|
border-top: none;
|
|
@@ -870,6 +919,19 @@ export function createGgPlugin(options, gg) {
|
|
|
870
919
|
renderLogs();
|
|
871
920
|
return;
|
|
872
921
|
}
|
|
922
|
+
// Handle "other" checkbox
|
|
923
|
+
if (target.classList.contains('gg-other-checkbox')) {
|
|
924
|
+
const otherNamespacesJson = target.getAttribute('data-other-namespaces');
|
|
925
|
+
if (!otherNamespacesJson)
|
|
926
|
+
return;
|
|
927
|
+
const otherNamespaces = JSON.parse(otherNamespacesJson);
|
|
928
|
+
// Toggle all "other" namespaces at once
|
|
929
|
+
toggleNamespaces(otherNamespaces, target.checked);
|
|
930
|
+
// localStorage already saved in toggleNamespaces()
|
|
931
|
+
renderFilterUI();
|
|
932
|
+
renderLogs();
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
873
935
|
// Handle individual namespace checkboxes
|
|
874
936
|
if (target.classList.contains('gg-ns-checkbox')) {
|
|
875
937
|
const namespace = target.getAttribute('data-namespace');
|
|
@@ -908,24 +970,50 @@ export function createGgPlugin(options, gg) {
|
|
|
908
970
|
let checkboxesHTML = '';
|
|
909
971
|
if (simple && allNamespaces.length > 0) {
|
|
910
972
|
const allChecked = enabledCount === totalCount;
|
|
973
|
+
// Count frequency of each namespace in the buffer
|
|
974
|
+
const allEntries = buffer.getEntries();
|
|
975
|
+
const nsCounts = new Map();
|
|
976
|
+
allEntries.forEach((entry) => {
|
|
977
|
+
nsCounts.set(entry.namespace, (nsCounts.get(entry.namespace) || 0) + 1);
|
|
978
|
+
});
|
|
979
|
+
// Sort ALL namespaces by frequency (most common first)
|
|
980
|
+
const sortedAllNamespaces = [...allNamespaces].sort((a, b) => (nsCounts.get(b) || 0) - (nsCounts.get(a) || 0));
|
|
981
|
+
// Take top 5 most common (regardless of enabled state)
|
|
982
|
+
const displayedNamespaces = sortedAllNamespaces.slice(0, 5);
|
|
983
|
+
// Calculate "other" namespaces (not in top 5)
|
|
984
|
+
const displayedSet = new Set(displayedNamespaces);
|
|
985
|
+
const otherNamespaces = allNamespaces.filter((ns) => !displayedSet.has(ns));
|
|
986
|
+
const otherEnabledCount = otherNamespaces.filter((ns) => enabledNamespaces.has(ns)).length;
|
|
987
|
+
const otherTotalCount = otherNamespaces.length;
|
|
988
|
+
const otherChecked = otherEnabledCount > 0;
|
|
989
|
+
const otherCount = otherNamespaces.reduce((sum, ns) => sum + (nsCounts.get(ns) || 0), 0);
|
|
911
990
|
checkboxesHTML = `
|
|
912
991
|
<div class="gg-filter-checkboxes">
|
|
913
992
|
<label class="gg-filter-checkbox" style="font-weight: bold;">
|
|
914
993
|
<input type="checkbox" class="gg-all-checkbox" ${allChecked ? 'checked' : ''}>
|
|
915
994
|
<span>ALL</span>
|
|
916
995
|
</label>
|
|
917
|
-
${
|
|
996
|
+
${displayedNamespaces
|
|
918
997
|
.map((ns) => {
|
|
919
998
|
// Check if namespace matches the current pattern
|
|
920
999
|
const checked = namespaceMatchesPattern(ns, effectivePattern);
|
|
1000
|
+
const count = nsCounts.get(ns) || 0;
|
|
921
1001
|
return `
|
|
922
1002
|
<label class="gg-filter-checkbox">
|
|
923
1003
|
<input type="checkbox" class="gg-ns-checkbox" data-namespace="${escapeHtml(ns)}" ${checked ? 'checked' : ''}>
|
|
924
|
-
<span>${escapeHtml(ns)}</span>
|
|
1004
|
+
<span>${escapeHtml(ns)} (${count})</span>
|
|
925
1005
|
</label>
|
|
926
1006
|
`;
|
|
927
1007
|
})
|
|
928
1008
|
.join('')}
|
|
1009
|
+
${otherTotalCount > 0
|
|
1010
|
+
? `
|
|
1011
|
+
<label class="gg-filter-checkbox" style="opacity: 0.7;">
|
|
1012
|
+
<input type="checkbox" class="gg-other-checkbox" ${otherChecked ? 'checked' : ''} data-other-namespaces='${JSON.stringify(otherNamespaces)}'>
|
|
1013
|
+
<span>other (${otherCount})</span>
|
|
1014
|
+
</label>
|
|
1015
|
+
`
|
|
1016
|
+
: ''}
|
|
929
1017
|
</div>
|
|
930
1018
|
`;
|
|
931
1019
|
}
|
|
@@ -1282,6 +1370,38 @@ export function createGgPlugin(options, gg) {
|
|
|
1282
1370
|
}
|
|
1283
1371
|
return;
|
|
1284
1372
|
}
|
|
1373
|
+
// Handle clicking namespace segments
|
|
1374
|
+
if (target?.classList?.contains('gg-ns-segment')) {
|
|
1375
|
+
// When filter is collapsed, open in editor instead of filtering
|
|
1376
|
+
if (!filterExpanded) {
|
|
1377
|
+
const nsContainer = target.closest('.gg-log-ns');
|
|
1378
|
+
if (nsContainer?.hasAttribute('data-file')) {
|
|
1379
|
+
handleNamespaceClick(nsContainer);
|
|
1380
|
+
}
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
// When filter is expanded, apply hierarchical filtering
|
|
1384
|
+
const filter = target.getAttribute('data-filter');
|
|
1385
|
+
if (!filter)
|
|
1386
|
+
return;
|
|
1387
|
+
// Toggle behavior: if already at this filter, restore all
|
|
1388
|
+
if (filterPattern === filter) {
|
|
1389
|
+
filterPattern = 'gg:*';
|
|
1390
|
+
enabledNamespaces.clear();
|
|
1391
|
+
getAllCapturedNamespaces().forEach((ns) => enabledNamespaces.add(ns));
|
|
1392
|
+
}
|
|
1393
|
+
else {
|
|
1394
|
+
filterPattern = filter;
|
|
1395
|
+
enabledNamespaces.clear();
|
|
1396
|
+
getAllCapturedNamespaces()
|
|
1397
|
+
.filter((ns) => namespaceMatchesPattern(ns, filter))
|
|
1398
|
+
.forEach((ns) => enabledNamespaces.add(ns));
|
|
1399
|
+
}
|
|
1400
|
+
localStorage.setItem(FILTER_KEY, filterPattern);
|
|
1401
|
+
renderFilterUI();
|
|
1402
|
+
renderLogs();
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1285
1405
|
// Handle clicking namespace to open in editor (when filter collapsed)
|
|
1286
1406
|
if (target?.classList?.contains('gg-log-ns') &&
|
|
1287
1407
|
target.hasAttribute('data-file') &&
|
|
@@ -1462,11 +1582,45 @@ export function createGgPlugin(options, gg) {
|
|
|
1462
1582
|
logContainer.html('<div style="padding: 20px; text-align: center; opacity: 0.5;">No logs captured yet. Call gg() to see output here.</div>');
|
|
1463
1583
|
return;
|
|
1464
1584
|
}
|
|
1465
|
-
const logsHTML = `<div class="gg-log-grid" style="grid-template-columns: ${gridColumns()};">${entries
|
|
1585
|
+
const logsHTML = `<div class="gg-log-grid${filterExpanded ? ' filter-mode' : ''}" style="grid-template-columns: ${gridColumns()};">${entries
|
|
1466
1586
|
.map((entry, index) => {
|
|
1467
1587
|
const color = entry.color || '#0066cc';
|
|
1468
1588
|
const diff = `+${humanize(entry.diff)}`;
|
|
1469
1589
|
const ns = escapeHtml(entry.namespace);
|
|
1590
|
+
// Split namespace into clickable segments on multiple delimiters: : @ / - _
|
|
1591
|
+
const parts = entry.namespace.split(/([:\/@\-_])/);
|
|
1592
|
+
const nsSegments = [];
|
|
1593
|
+
const delimiters = [];
|
|
1594
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1595
|
+
if (i % 2 === 0) {
|
|
1596
|
+
// Even indices are segments
|
|
1597
|
+
if (parts[i])
|
|
1598
|
+
nsSegments.push(parts[i]);
|
|
1599
|
+
}
|
|
1600
|
+
else {
|
|
1601
|
+
// Odd indices are delimiters
|
|
1602
|
+
delimiters.push(parts[i]);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
let nsHTML = '';
|
|
1606
|
+
for (let i = 0; i < nsSegments.length; i++) {
|
|
1607
|
+
const segment = escapeHtml(nsSegments[i]);
|
|
1608
|
+
// Build filter pattern: reconstruct namespace up to this point
|
|
1609
|
+
let filterPattern = '';
|
|
1610
|
+
for (let j = 0; j <= i; j++) {
|
|
1611
|
+
filterPattern += nsSegments[j];
|
|
1612
|
+
if (j < i) {
|
|
1613
|
+
filterPattern += delimiters[j];
|
|
1614
|
+
}
|
|
1615
|
+
else if (j < nsSegments.length - 1) {
|
|
1616
|
+
filterPattern += delimiters[j] + '*';
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
nsHTML += `<span class="gg-ns-segment" data-filter="${escapeHtml(filterPattern)}">${segment}</span>`;
|
|
1620
|
+
if (i < nsSegments.length - 1) {
|
|
1621
|
+
nsHTML += escapeHtml(delimiters[i]);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1470
1624
|
// Format each arg individually - objects are expandable
|
|
1471
1625
|
let argsHTML = '';
|
|
1472
1626
|
let detailsHTML = '';
|
|
@@ -1569,7 +1723,7 @@ export function createGgPlugin(options, gg) {
|
|
|
1569
1723
|
`<div class="gg-log-header">` +
|
|
1570
1724
|
iconsCol +
|
|
1571
1725
|
`<div class="gg-log-diff${soloClass}" style="color: ${color};"${soloAttr}>${diff}</div>` +
|
|
1572
|
-
`<div class="gg-log-ns${soloClass}" style="color: ${color};"${
|
|
1726
|
+
`<div class="gg-log-ns${soloClass}" style="color: ${color};" data-namespace="${escapeHtml(entry.namespace)}"${fileAttr}${lineAttr}${colAttr}${fileTitle}>${nsHTML}</div>` +
|
|
1573
1727
|
`<div class="gg-log-handle"></div>` +
|
|
1574
1728
|
`</div>` +
|
|
1575
1729
|
`<div class="gg-log-content"${!entry.level && entry.src?.trim() && !/^['"`]/.test(entry.src) ? ` data-src="${escapeHtml(entry.src)}"` : ''}>${argsHTML}${stackHTML}</div>` +
|