@matdata/yasgui 5.8.1 → 5.9.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@matdata/yasgui",
3
3
  "description": "Yet Another SPARQL GUI",
4
- "version": "5.8.1",
4
+ "version": "5.9.0",
5
5
  "main": "build/yasgui.min.js",
6
6
  "types": "build/ts/src/index.d.ts",
7
7
  "license": "MIT",
@@ -15,6 +15,7 @@ export interface PersistedJson {
15
15
  theme?: "light" | "dark";
16
16
  orientation?: "vertical" | "horizontal";
17
17
  showSnippetsBar?: boolean;
18
+ disabledDevButtons?: string[]; // Endpoints of developer-configured buttons that are disabled by user
18
19
  }
19
20
  function getDefaults(): PersistedJson {
20
21
  return {
@@ -234,4 +235,37 @@ export default class PersistentConfig {
234
235
  Object.assign(this.persistedJson, config);
235
236
  this.toStorage();
236
237
  }
238
+
239
+ /**
240
+ * Get list of disabled developer-configured endpoint buttons
241
+ */
242
+ public getDisabledDevButtons(): string[] {
243
+ return this.persistedJson.disabledDevButtons || [];
244
+ }
245
+
246
+ /**
247
+ * Set list of disabled developer-configured endpoint buttons
248
+ */
249
+ public setDisabledDevButtons(endpoints: string[]) {
250
+ this.persistedJson.disabledDevButtons = endpoints;
251
+ this.toStorage();
252
+ }
253
+
254
+ /**
255
+ * Toggle a developer button enabled/disabled state
256
+ */
257
+ public toggleDevButton(endpoint: string, enabled: boolean) {
258
+ const disabled = this.getDisabledDevButtons();
259
+ if (enabled) {
260
+ // Remove from disabled list
261
+ const filtered = disabled.filter((e) => e !== endpoint);
262
+ this.setDisabledDevButtons(filtered);
263
+ } else {
264
+ // Add to disabled list
265
+ if (!disabled.includes(endpoint)) {
266
+ disabled.push(endpoint);
267
+ this.setDisabledDevButtons(disabled);
268
+ }
269
+ }
270
+ }
237
271
  }
package/src/Tab.ts CHANGED
@@ -12,16 +12,23 @@ import { getRandomId, default as Yasgui, YasguiRequestConfig } from "./";
12
12
  import * as OAuth2Utils from "./OAuth2Utils";
13
13
 
14
14
  // Layout orientation toggle icons
15
- const HORIZONTAL_LAYOUT_ICON = `<svg viewBox="0 0 24 24" class="svgImg">
15
+ const HORIZONTAL_LAYOUT_ICON = `<svg viewBox="0 0 24 24">
16
16
  <rect x="2" y="4" width="9" height="16" stroke="currentColor" stroke-width="2" fill="none"/>
17
17
  <rect x="13" y="4" width="9" height="16" stroke="currentColor" stroke-width="2" fill="none"/>
18
18
  </svg>`;
19
19
 
20
- const VERTICAL_LAYOUT_ICON = `<svg viewBox="0 0 24 24" class="svgImg">
20
+ const VERTICAL_LAYOUT_ICON = `<svg viewBox="0 0 24 24">
21
21
  <rect x="2" y="2" width="20" height="8" stroke="currentColor" stroke-width="2" fill="none"/>
22
22
  <rect x="2" y="12" width="20" height="10" stroke="currentColor" stroke-width="2" fill="none"/>
23
23
  </svg>`;
24
24
 
25
+ // Overflow dropdown icon (three horizontal dots / ellipsis menu)
26
+ const OVERFLOW_ICON = `<svg viewBox="0 0 24 24" fill="currentColor">
27
+ <circle cx="5" cy="12" r="2"/>
28
+ <circle cx="12" cy="12" r="2"/>
29
+ <circle cx="19" cy="12" r="2"/>
30
+ </svg>`;
31
+
25
32
  export interface PersistedJsonYasr extends YasrPersistentConfig {
26
33
  responseSummary: Parser.ResponseSummary;
27
34
  }
@@ -75,6 +82,10 @@ export class Tab extends EventEmitter {
75
82
  private yasrWrapperEl: HTMLDivElement | undefined;
76
83
  private endpointSelect: EndpointSelect | undefined;
77
84
  private endpointButtonsContainer: HTMLDivElement | undefined;
85
+ private endpointOverflowButton: HTMLButtonElement | undefined;
86
+ private endpointOverflowDropdown: HTMLDivElement | undefined;
87
+ private endpointButtonConfigs: Array<{ endpoint: string; label: string }> = [];
88
+ private resizeObserver: ResizeObserver | undefined;
78
89
  private settingsModal?: TabSettingsModal;
79
90
  private currentOrientation: "vertical" | "horizontal";
80
91
  private orientationToggleButton?: HTMLButtonElement;
@@ -337,6 +348,8 @@ export class Tab extends EventEmitter {
337
348
  // Refresh components to adjust to new layout
338
349
  if (tab.yasqe) {
339
350
  tab.yasqe.refresh();
351
+ // Trigger snippets overflow detection after layout change
352
+ tab.yasqe.refreshSnippetsBar();
340
353
  }
341
354
  if (tab.yasr) {
342
355
  tab.yasr.refresh();
@@ -385,16 +398,202 @@ export class Tab extends EventEmitter {
385
398
  }
386
399
 
387
400
  this.refreshEndpointButtons();
401
+ this.initEndpointButtonsResizeObserver();
402
+ }
403
+
404
+ private initEndpointButtonsResizeObserver() {
405
+ if (!this.controlBarEl || !this.endpointButtonsContainer) return;
406
+
407
+ // Clean up existing observer
408
+ if (this.resizeObserver) {
409
+ this.resizeObserver.disconnect();
410
+ }
411
+
412
+ // Create resize observer to detect when we need to show overflow
413
+ this.resizeObserver = new ResizeObserver(() => {
414
+ this.updateEndpointButtonsOverflow();
415
+ });
416
+
417
+ this.resizeObserver.observe(this.controlBarEl);
418
+ }
419
+
420
+ private updateEndpointButtonsOverflow() {
421
+ if (!this.endpointButtonsContainer || !this.controlBarEl) return;
422
+
423
+ // Get all actual endpoint buttons (not the overflow button)
424
+ const buttons = Array.from(
425
+ this.endpointButtonsContainer.querySelectorAll(".endpointButton:not(.endpointOverflowBtn)"),
426
+ ) as HTMLButtonElement[];
427
+
428
+ if (buttons.length === 0) {
429
+ this.hideOverflowButton();
430
+ return;
431
+ }
432
+
433
+ // Get the container's available width
434
+ const containerRect = this.controlBarEl.getBoundingClientRect();
435
+ const containerWidth = containerRect.width;
436
+
437
+ // Calculate the space used by other elements (endpoint select, settings buttons, etc.)
438
+ const endpointButtonsRect = this.endpointButtonsContainer.getBoundingClientRect();
439
+ const buttonsContainerLeft = endpointButtonsRect.left - containerRect.left;
440
+
441
+ // Estimate available space for endpoint buttons (leave some margin for overflow button)
442
+ const overflowButtonWidth = 40; // Approximate width of overflow button
443
+ const availableWidth = containerWidth - buttonsContainerLeft - overflowButtonWidth - 20; // 20px margin
444
+
445
+ // Make all buttons temporarily visible to measure
446
+ buttons.forEach((btn) => btn.classList.remove("endpointButtonHidden"));
447
+
448
+ // Check if buttons overflow
449
+ let totalWidth = 0;
450
+ let overflowIndex = -1;
451
+
452
+ for (let i = 0; i < buttons.length; i++) {
453
+ const btn = buttons[i];
454
+ const btnWidth = btn.offsetWidth + 4; // Include gap
455
+ totalWidth += btnWidth;
456
+
457
+ if (totalWidth > availableWidth && overflowIndex === -1) {
458
+ overflowIndex = i;
459
+ }
460
+ }
461
+
462
+ if (overflowIndex === -1) {
463
+ // All buttons fit, hide overflow button
464
+ this.hideOverflowButton();
465
+ buttons.forEach((btn) => btn.classList.remove("endpointButtonHidden"));
466
+ } else {
467
+ // Some buttons need to go into overflow
468
+ buttons.forEach((btn, index) => {
469
+ if (index >= overflowIndex) {
470
+ btn.classList.add("endpointButtonHidden");
471
+ } else {
472
+ btn.classList.remove("endpointButtonHidden");
473
+ }
474
+ });
475
+ this.showOverflowButton(overflowIndex);
476
+ }
477
+ }
478
+
479
+ private showOverflowButton(overflowStartIndex: number) {
480
+ if (!this.endpointButtonsContainer) return;
481
+
482
+ // Create overflow button if it doesn't exist
483
+ if (!this.endpointOverflowButton) {
484
+ this.endpointOverflowButton = document.createElement("button");
485
+ addClass(this.endpointOverflowButton, "endpointOverflowBtn");
486
+ this.endpointOverflowButton.innerHTML = OVERFLOW_ICON;
487
+ this.endpointOverflowButton.title = "More endpoints";
488
+ this.endpointOverflowButton.setAttribute("aria-label", "More endpoint options");
489
+ this.endpointOverflowButton.setAttribute("aria-haspopup", "true");
490
+ this.endpointOverflowButton.setAttribute("aria-expanded", "false");
491
+
492
+ this.endpointOverflowButton.addEventListener("click", (e) => {
493
+ e.stopPropagation();
494
+ this.toggleOverflowDropdown();
495
+ });
496
+
497
+ this.endpointButtonsContainer.appendChild(this.endpointOverflowButton);
498
+ }
499
+
500
+ // Update the overflow button's data with which buttons are hidden
501
+ this.endpointOverflowButton.dataset.overflowStart = String(overflowStartIndex);
502
+ this.endpointOverflowButton.style.display = "flex";
503
+ }
504
+
505
+ private hideOverflowButton() {
506
+ if (this.endpointOverflowButton) {
507
+ this.endpointOverflowButton.style.display = "none";
508
+ }
509
+ this.closeOverflowDropdown();
510
+ }
511
+
512
+ private toggleOverflowDropdown() {
513
+ if (this.endpointOverflowDropdown && this.endpointOverflowDropdown.style.display !== "none") {
514
+ this.closeOverflowDropdown();
515
+ } else {
516
+ this.openOverflowDropdown();
517
+ }
518
+ }
519
+
520
+ private openOverflowDropdown() {
521
+ if (!this.endpointOverflowButton || !this.endpointButtonsContainer) return;
522
+
523
+ const overflowStartIndex = parseInt(this.endpointOverflowButton.dataset.overflowStart || "0", 10);
524
+ const overflowButtons = this.endpointButtonConfigs.slice(overflowStartIndex);
525
+
526
+ if (overflowButtons.length === 0) return;
527
+
528
+ // Create dropdown if it doesn't exist
529
+ if (!this.endpointOverflowDropdown) {
530
+ this.endpointOverflowDropdown = document.createElement("div");
531
+ addClass(this.endpointOverflowDropdown, "endpointOverflowDropdown");
532
+ this.endpointButtonsContainer.appendChild(this.endpointOverflowDropdown);
533
+ }
534
+
535
+ // Clear and populate dropdown
536
+ this.endpointOverflowDropdown.innerHTML = "";
537
+
538
+ overflowButtons.forEach((buttonConfig) => {
539
+ const item = document.createElement("button");
540
+ addClass(item, "endpointOverflowItem");
541
+ item.textContent = buttonConfig.label;
542
+ item.title = `Set endpoint to ${buttonConfig.endpoint}`;
543
+ item.setAttribute("aria-label", `Set endpoint to ${buttonConfig.endpoint}`);
544
+
545
+ item.addEventListener("click", () => {
546
+ this.setEndpoint(buttonConfig.endpoint);
547
+ this.closeOverflowDropdown();
548
+ });
549
+
550
+ this.endpointOverflowDropdown!.appendChild(item);
551
+ });
552
+
553
+ // Position and show dropdown
554
+ this.endpointOverflowDropdown.style.display = "block";
555
+ this.endpointOverflowButton.setAttribute("aria-expanded", "true");
556
+
557
+ // Add click-outside listener to close dropdown
558
+ const closeHandler = (e: MouseEvent) => {
559
+ if (
560
+ this.endpointOverflowDropdown &&
561
+ !this.endpointOverflowDropdown.contains(e.target as Node) &&
562
+ e.target !== this.endpointOverflowButton
563
+ ) {
564
+ this.closeOverflowDropdown();
565
+ document.removeEventListener("click", closeHandler);
566
+ }
567
+ };
568
+
569
+ // Delay adding listener to avoid immediate close
570
+ setTimeout(() => {
571
+ document.addEventListener("click", closeHandler);
572
+ }, 0);
573
+ }
574
+
575
+ private closeOverflowDropdown() {
576
+ if (this.endpointOverflowDropdown) {
577
+ this.endpointOverflowDropdown.style.display = "none";
578
+ }
579
+ if (this.endpointOverflowButton) {
580
+ this.endpointOverflowButton.setAttribute("aria-expanded", "false");
581
+ }
388
582
  }
389
583
 
390
584
  public refreshEndpointButtons() {
391
585
  if (!this.endpointButtonsContainer) return;
392
586
 
393
- // Clear existing buttons
587
+ // Clear existing buttons (but keep overflow button reference)
394
588
  this.endpointButtonsContainer.innerHTML = "";
589
+ this.endpointOverflowButton = undefined;
590
+ this.endpointOverflowDropdown = undefined;
395
591
 
396
- // Get config buttons (for backwards compatibility)
397
- const configButtons = this.yasgui.config.endpointButtons || [];
592
+ // Get config buttons (for backwards compatibility) and filter out disabled ones
593
+ const disabledButtons = this.yasgui.persistentConfig.getDisabledDevButtons();
594
+ const configButtons = (this.yasgui.config.endpointButtons || []).filter(
595
+ (button) => !disabledButtons.includes(button.endpoint),
596
+ );
398
597
 
399
598
  // Get endpoint configs where showAsButton is true
400
599
  const endpointConfigs = this.yasgui.persistentConfig.getEndpointConfigs();
@@ -407,6 +606,9 @@ export class Tab extends EventEmitter {
407
606
 
408
607
  const allButtons = [...configButtons, ...endpointButtons, ...customButtons];
409
608
 
609
+ // Store button configs for overflow dropdown
610
+ this.endpointButtonConfigs = allButtons;
611
+
410
612
  if (allButtons.length === 0) {
411
613
  // Hide container if no buttons
412
614
  this.endpointButtonsContainer.style.display = "none";
@@ -429,6 +631,11 @@ export class Tab extends EventEmitter {
429
631
 
430
632
  this.endpointButtonsContainer!.appendChild(button);
431
633
  });
634
+
635
+ // Trigger overflow check after rendering
636
+ requestAnimationFrame(() => {
637
+ this.updateEndpointButtonsOverflow();
638
+ });
432
639
  }
433
640
 
434
641
  public setEndpoint(endpoint: string, endpointHistory?: string[]) {
@@ -440,12 +647,14 @@ export class Tab extends EventEmitter {
440
647
  if (this.persistentJson.requestConfig.endpoint !== endpoint) {
441
648
  this.persistentJson.requestConfig.endpoint = endpoint;
442
649
  this.emit("change", this, this.persistentJson);
443
- this.emit("endpointChange", this, endpoint);
444
650
 
445
651
  // Auto-track this endpoint in endpoint configs (if not already present)
446
652
  if (endpoint && !this.yasgui.persistentConfig.getEndpointConfig(endpoint)) {
447
653
  this.yasgui.persistentConfig.addOrUpdateEndpoint(endpoint, {});
448
654
  }
655
+
656
+ // Emit after endpoint is tracked
657
+ this.emit("endpointChange", this, endpoint);
449
658
  }
450
659
  if (this.endpointSelect instanceof EndpointSelect) {
451
660
  this.endpointSelect.setEndpoint(endpoint, endpointHistory);
@@ -1056,6 +1265,8 @@ WHERE {
1056
1265
  // Refresh editors after resizing
1057
1266
  if (this.yasqe) {
1058
1267
  this.yasqe.refresh();
1268
+ // Trigger snippets overflow detection after horizontal resize
1269
+ this.yasqe.refreshSnippetsBar();
1059
1270
  }
1060
1271
  if (this.yasr) {
1061
1272
  this.yasr.refresh();
@@ -1087,6 +1298,12 @@ WHERE {
1087
1298
  document.documentElement.removeEventListener("mousemove", this.doVerticalDrag, false);
1088
1299
  document.documentElement.removeEventListener("mouseup", this.stopVerticalDrag, false);
1089
1300
 
1301
+ // Clean up resize observer for endpoint buttons overflow
1302
+ if (this.resizeObserver) {
1303
+ this.resizeObserver.disconnect();
1304
+ this.resizeObserver = undefined;
1305
+ }
1306
+
1090
1307
  this.removeAllListeners();
1091
1308
  this.settingsModal?.destroy();
1092
1309
  this.endpointSelect?.destroy();
@@ -1,42 +1,51 @@
1
1
  .yasgui.context-menu {
2
2
  position: absolute;
3
3
  z-index: 10;
4
- background: white;
5
- min-width: 160px;
6
- font-size: 14px;
7
- border: 1px solid rgba(0, 0, 0, 0.15);
4
+ background: var(--yasgui-bg-primary);
5
+ width: fit-content;
6
+ height: fit-content;
7
+ min-width: fit-content;
8
+ min-height: fit-content;
9
+ min-width: 140px;
10
+ max-width: 250px;
11
+ font-size: 12px;
12
+ border: 1px solid var(--yasgui-border-color);
8
13
  border-bottom-right-radius: 4px;
9
14
  border-bottom-left-radius: 4px;
10
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
15
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
11
16
  background-clip: padding-box;
12
17
  hr {
13
- margin: 8px auto;
18
+ margin: 6px auto;
14
19
  border: none;
15
- border-top: 1px solid #aaaaaa;
16
- border-bottom: 1px solid #ffffff;
20
+ border-top: 1px solid var(--yasgui-border-color);
17
21
  }
18
22
  .context-menu-list {
19
23
  padding: 0;
24
+ margin: 0;
25
+ list-style: none;
20
26
  }
21
27
  .context-menu-item {
22
28
  display: block;
23
- padding: 3px 20px;
29
+ padding: 6px 16px;
24
30
  clear: both;
25
31
  font-weight: 400;
26
- line-height: 1.42857;
27
- color: #333;
32
+ line-height: 1.4;
33
+ color: var(--yasgui-text-primary);
28
34
  white-space: nowrap;
29
35
  cursor: pointer;
30
36
  &:hover {
31
37
  text-decoration: none;
32
- color: black;
33
- background-color: #f5f5f5;
38
+ color: var(--yasgui-button-hover);
39
+ background-color: var(--yasgui-bg-secondary);
34
40
  }
35
41
  &.disabled {
36
42
  text-decoration: none;
37
- color: gray;
38
- background-color: #e5e5e5;
43
+ color: var(--yasgui-text-muted);
44
+ background-color: transparent;
39
45
  cursor: not-allowed;
46
+ &:hover {
47
+ background-color: transparent;
48
+ }
40
49
  }
41
50
  }
42
51
  }
@@ -75,7 +75,10 @@ export class TabListEl {
75
75
  };
76
76
  addClass(this.tabEl, "tab");
77
77
  this.tabEl.addEventListener("keydown", (e: KeyboardEvent) => {
78
- if (e.code === "Delete") handleDeleteTab();
78
+ // Don't handle Delete key if we're renaming the tab (input field is active)
79
+ if (e.code === "Delete" && !this.tabEl.classList.contains("renaming")) {
80
+ handleDeleteTab();
81
+ }
79
82
  });
80
83
 
81
84
  const handleDeleteTab = (e?: MouseEvent) => {
@@ -88,6 +91,7 @@ export class TabListEl {
88
91
  tabLinkEl.href = "#" + this.tabId;
89
92
  tabLinkEl.id = "tab-" + this.tabId; // use the id for the tabpanel which is tabId to set the actual tab id
90
93
  tabLinkEl.setAttribute("aria-controls", this.tabId); // respective tabPanel id
94
+ tabLinkEl.draggable = false; // Prevent default link dragging that interferes with text selection
91
95
  tabLinkEl.addEventListener("blur", () => {
92
96
  if (!this.tabEl) return;
93
97
  if (this.tabEl.classList.contains("active")) {
@@ -140,6 +144,13 @@ export class TabListEl {
140
144
  this.yasgui.getTab(this.tabId)?.setName(renameEl.value);
141
145
  removeClass(this.tabEl, "renaming");
142
146
  };
147
+ // Prevent sortablejs from detecting drag events on the input field
148
+ renameEl.addEventListener("mousedown", (e) => {
149
+ e.stopPropagation();
150
+ });
151
+ renameEl.addEventListener("dragstart", (e) => {
152
+ e.stopPropagation();
153
+ });
143
154
  tabLinkEl.appendChild(this.renameEl);
144
155
  tabLinkEl.oncontextmenu = (ev: MouseEvent) => {
145
156
  // Close possible old
@@ -212,6 +223,8 @@ export class TabList {
212
223
  }
213
224
  private handleKeydownArrowKeys = (e: KeyboardEvent) => {
214
225
  if (e.code === "ArrowLeft" || e.code === "ArrowRight") {
226
+ // Don't handle arrow keys if we're typing in the rename input field
227
+ if (document.activeElement?.tagName === "INPUT") return;
215
228
  if (!this._tabsListEl) return;
216
229
  const numOfChildren = this._tabsListEl.childElementCount;
217
230
  if (typeof this.tabEntryIndex !== "number") return;
@@ -251,7 +264,8 @@ export class TabList {
251
264
  this.yasgui.emit("tabOrderChanged", this.yasgui, tabs);
252
265
  this.yasgui.persistentConfig.setTabOrder(tabs);
253
266
  },
254
- filter: ".addTab",
267
+ filter: ".addTab, input, .renaming",
268
+ preventOnFilter: false,
255
269
  onMove: (ev: any, _origEv: any) => {
256
270
  return hasClass(ev.related, "tab");
257
271
  },
@@ -3,8 +3,8 @@
3
3
  background: transparent;
4
4
  color: inherit;
5
5
  border: none;
6
- padding: 6px 8px;
7
- margin-left: 10px;
6
+ padding: 6px;
7
+ margin-left: 0;
8
8
  display: flex;
9
9
  align-items: center;
10
10
  justify-content: center;
@@ -23,25 +23,6 @@
23
23
  }
24
24
  }
25
25
 
26
- .tabPrefixButton {
27
- cursor: pointer;
28
- background: transparent;
29
- color: inherit;
30
- border: none;
31
- padding: 6px 12px;
32
- font-size: 12px;
33
- font-weight: 600;
34
- margin-left: 10px;
35
-
36
- &:hover {
37
- opacity: 0.7;
38
- }
39
-
40
- &:active {
41
- opacity: 0.5;
42
- }
43
- }
44
-
45
26
  .tabSettingsModalOverlay {
46
27
  display: none;
47
28
  position: fixed;
@@ -706,6 +687,56 @@
706
687
  }
707
688
  }
708
689
 
690
+ // Developer buttons table styling
691
+ .devButtonsTable {
692
+ margin-top: 15px;
693
+ margin-bottom: 30px;
694
+ }
695
+
696
+ .devButtonsTableElement {
697
+ width: 100%;
698
+ border-collapse: collapse;
699
+ font-size: 14px;
700
+
701
+ th,
702
+ td {
703
+ padding: 10px;
704
+ text-align: left;
705
+ border-bottom: 1px solid var(--yasgui-border-color, #e0e0e0);
706
+ }
707
+
708
+ th {
709
+ font-weight: 600;
710
+ color: var(--yasgui-text-primary, #000);
711
+ background: var(--yasgui-bg-secondary, #f9f9f9);
712
+ }
713
+
714
+ td {
715
+ color: var(--yasgui-text-primary, #333);
716
+ }
717
+
718
+ .devButtonLabelCell {
719
+ font-weight: 500;
720
+ }
721
+
722
+ .endpointCell {
723
+ max-width: 350px;
724
+ overflow: hidden;
725
+ text-overflow: ellipsis;
726
+ white-space: nowrap;
727
+ }
728
+
729
+ .centerCell {
730
+ text-align: center;
731
+ }
732
+
733
+ input[type="checkbox"] {
734
+ width: 18px;
735
+ height: 18px;
736
+ cursor: pointer;
737
+ }
738
+ }
739
+
709
740
  // Endpoints table styling
710
741
  .endpointsTable {
711
742
  margin-top: 20px;