@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.
@@ -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
- ${allNamespaces
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};"${soloAttr}${fileAttr}${lineAttr}${colAttr}${fileTitle}>${ns}</div>` +
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>` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leftium/gg",
3
- "version": "0.0.39",
3
+ "version": "0.0.40",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Leftium/gg.git"