@matdata/yasgui 5.17.0 → 5.19.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/build/ts/src/Tab.js +7 -1
- package/build/ts/src/Tab.js.map +1 -1
- package/build/ts/src/TabContextMenu.d.ts +1 -0
- package/build/ts/src/TabContextMenu.js +42 -0
- package/build/ts/src/TabContextMenu.js.map +1 -1
- package/build/ts/src/TabSettingsModal.d.ts +2 -1
- package/build/ts/src/TabSettingsModal.js +44 -25
- package/build/ts/src/TabSettingsModal.js.map +1 -1
- package/build/ts/src/index.d.ts +6 -0
- package/build/ts/src/index.js +42 -0
- package/build/ts/src/index.js.map +1 -1
- package/build/ts/src/prefixUtils.d.ts +1 -0
- package/build/ts/src/prefixUtils.js +23 -0
- package/build/ts/src/prefixUtils.js.map +1 -0
- package/build/ts/src/queryManagement/QueryBrowser.d.ts +8 -0
- package/build/ts/src/queryManagement/QueryBrowser.js +283 -116
- package/build/ts/src/queryManagement/QueryBrowser.js.map +1 -1
- package/build/ts/src/queryManagement/QueryHistoryModal.d.ts +35 -0
- package/build/ts/src/queryManagement/QueryHistoryModal.js +266 -0
- package/build/ts/src/queryManagement/QueryHistoryModal.js.map +1 -0
- package/build/ts/src/queryManagement/WorkspaceSettingsForm.js +8 -5
- package/build/ts/src/queryManagement/WorkspaceSettingsForm.js.map +1 -1
- package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.d.ts +1 -0
- package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.js +33 -0
- package/build/ts/src/queryManagement/backends/GitWorkspaceBackend.js.map +1 -1
- package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.d.ts +1 -0
- package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.js +3 -0
- package/build/ts/src/queryManagement/backends/SparqlWorkspaceBackend.js.map +1 -1
- package/build/ts/src/queryManagement/backends/WorkspaceBackend.d.ts +1 -0
- package/build/ts/src/queryManagement/countManagedQueries.d.ts +2 -0
- package/build/ts/src/queryManagement/countManagedQueries.js +35 -0
- package/build/ts/src/queryManagement/countManagedQueries.js.map +1 -0
- package/build/ts/src/tabNavigationHistory.d.ts +3 -0
- package/build/ts/src/tabNavigationHistory.js +13 -0
- package/build/ts/src/tabNavigationHistory.js.map +1 -0
- package/build/ts/src/version.d.ts +1 -1
- package/build/ts/src/version.js +1 -1
- package/build/yasgui.min.css +1 -1
- package/build/yasgui.min.css.map +3 -3
- package/build/yasgui.min.js +245 -245
- package/build/yasgui.min.js.map +4 -4
- package/package.json +1 -1
- package/src/Tab.ts +10 -0
- package/src/TabContextMenu.ts +42 -0
- package/src/TabSettingsModal.ts +48 -29
- package/src/index.ts +50 -0
- package/src/prefixUtils.ts +26 -0
- package/src/queryManagement/QueryBrowser.scss +85 -0
- package/src/queryManagement/QueryBrowser.ts +429 -213
- package/src/queryManagement/QueryHistoryModal.scss +239 -0
- package/src/queryManagement/QueryHistoryModal.ts +346 -0
- package/src/queryManagement/WorkspaceSettingsForm.ts +8 -5
- package/src/queryManagement/backends/GitWorkspaceBackend.ts +39 -0
- package/src/queryManagement/backends/SparqlWorkspaceBackend.ts +5 -0
- package/src/queryManagement/backends/WorkspaceBackend.ts +8 -0
- package/src/queryManagement/countManagedQueries.ts +28 -0
- package/src/tabNavigationHistory.ts +17 -0
- package/src/themes.scss +44 -0
- package/src/version.ts +1 -1
package/package.json
CHANGED
package/src/Tab.ts
CHANGED
|
@@ -436,6 +436,15 @@ export class Tab extends EventEmitter {
|
|
|
436
436
|
private handleKeyDown = (event: KeyboardEvent) => {
|
|
437
437
|
if (event.defaultPrevented) return;
|
|
438
438
|
|
|
439
|
+
// Ctrl+, → backward (less recently used), Ctrl+Alt+, → forward (more recently used)
|
|
440
|
+
// Ctrl+Tab / Ctrl+Shift+Tab cannot be used: the browser intercepts them before keydown fires
|
|
441
|
+
const isTabSwitchShortcut = (event.ctrlKey || event.metaKey) && !event.shiftKey && event.key === ",";
|
|
442
|
+
if (isTabSwitchShortcut) {
|
|
443
|
+
event.preventDefault();
|
|
444
|
+
this.yasgui.selectRecentlyUsedTab(event.altKey ? "forward" : "backward");
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
439
448
|
const saveModalOpen = !!document.querySelector(".saveManagedQueryModalOverlay.open");
|
|
440
449
|
if (!saveModalOpen) {
|
|
441
450
|
const isSaveShortcut =
|
|
@@ -1278,6 +1287,7 @@ export class Tab extends EventEmitter {
|
|
|
1278
1287
|
createShareableLink: () => this.getShareableLink(),
|
|
1279
1288
|
// Use global showSnippetsBar setting if it exists
|
|
1280
1289
|
showSnippetsBar: this.yasgui.config.showSnippetsBar !== false,
|
|
1290
|
+
showMapButton: false,
|
|
1281
1291
|
requestConfig: () => {
|
|
1282
1292
|
const processedReqConfig: YasguiRequestConfig = {
|
|
1283
1293
|
//setting defaults
|
package/src/TabContextMenu.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { default as Yasgui, getRandomId } from "./";
|
|
|
3
3
|
import Tab from "./Tab";
|
|
4
4
|
import { TabListEl } from "./TabElements";
|
|
5
5
|
import { cloneDeep } from "lodash-es";
|
|
6
|
+
import { getWorkspaceBackend } from "./queryManagement/backends/getWorkspaceBackend";
|
|
7
|
+
import type { GitQueryRef, SparqlQueryRef } from "./queryManagement/types";
|
|
6
8
|
import "./TabContextMenu.scss";
|
|
7
9
|
export interface TabContextConfig {
|
|
8
10
|
name: string;
|
|
@@ -17,6 +19,7 @@ export default class TabContextMenu {
|
|
|
17
19
|
private duplicateTabEl!: HTMLElement;
|
|
18
20
|
private saveManagedQueryEl!: HTMLElement;
|
|
19
21
|
private saveAsRqFileEl!: HTMLElement;
|
|
22
|
+
private copyManagedQueryUriEl!: HTMLElement;
|
|
20
23
|
private closeTabEl!: HTMLElement;
|
|
21
24
|
private closeOtherTabsEl!: HTMLElement;
|
|
22
25
|
private reOpenOldTab!: HTMLElement;
|
|
@@ -55,6 +58,8 @@ export default class TabContextMenu {
|
|
|
55
58
|
|
|
56
59
|
this.saveAsRqFileEl = this.getMenuItemEl("Save as .rq file");
|
|
57
60
|
|
|
61
|
+
this.copyManagedQueryUriEl = this.getMenuItemEl("Copy URI");
|
|
62
|
+
|
|
58
63
|
this.closeTabEl = this.getMenuItemEl("Close Tab");
|
|
59
64
|
|
|
60
65
|
this.closeOtherTabsEl = this.getMenuItemEl("Close other tabs");
|
|
@@ -67,6 +72,7 @@ export default class TabContextMenu {
|
|
|
67
72
|
dropDownList.appendChild(this.duplicateTabEl);
|
|
68
73
|
dropDownList.appendChild(this.saveManagedQueryEl);
|
|
69
74
|
dropDownList.appendChild(this.saveAsRqFileEl);
|
|
75
|
+
dropDownList.appendChild(this.copyManagedQueryUriEl);
|
|
70
76
|
// Add divider
|
|
71
77
|
dropDownList.appendChild(document.createElement("hr"));
|
|
72
78
|
dropDownList.appendChild(this.closeTabEl);
|
|
@@ -125,6 +131,42 @@ export default class TabContextMenu {
|
|
|
125
131
|
this.closeConfigMenu();
|
|
126
132
|
};
|
|
127
133
|
|
|
134
|
+
// Copy URI for managed query tabs
|
|
135
|
+
const meta = tab?.getManagedQueryMetadata();
|
|
136
|
+
if (meta) {
|
|
137
|
+
const workspaceConfig = this.yasgui.persistentConfig?.getWorkspaces().find((w) => w.id === meta.workspaceId);
|
|
138
|
+
if (workspaceConfig) {
|
|
139
|
+
const queryId =
|
|
140
|
+
meta.backendType === "git"
|
|
141
|
+
? (meta.queryRef as GitQueryRef).path
|
|
142
|
+
: (meta.queryRef as SparqlQueryRef).managedQueryIri;
|
|
143
|
+
if (queryId) {
|
|
144
|
+
const backend = getWorkspaceBackend(workspaceConfig, {
|
|
145
|
+
persistentConfig: this.yasgui.persistentConfig,
|
|
146
|
+
});
|
|
147
|
+
const uri = backend.getQueryUri?.(queryId);
|
|
148
|
+
if (uri) {
|
|
149
|
+
this.copyManagedQueryUriEl.onclick = async () => {
|
|
150
|
+
try {
|
|
151
|
+
await navigator.clipboard.writeText(uri);
|
|
152
|
+
} catch {
|
|
153
|
+
window.prompt("Copy this URI:", uri);
|
|
154
|
+
}
|
|
155
|
+
this.closeConfigMenu();
|
|
156
|
+
};
|
|
157
|
+
} else {
|
|
158
|
+
addClass(this.copyManagedQueryUriEl, "disabled");
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
addClass(this.copyManagedQueryUriEl, "disabled");
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
addClass(this.copyManagedQueryUriEl, "disabled");
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
addClass(this.copyManagedQueryUriEl, "disabled");
|
|
168
|
+
}
|
|
169
|
+
|
|
128
170
|
// Close tab functionality
|
|
129
171
|
this.closeTabEl.onclick = () => tab?.close();
|
|
130
172
|
|
package/src/TabSettingsModal.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as ConfigExportImport from "./ConfigExportImport";
|
|
|
5
5
|
import { VERSION } from "./version";
|
|
6
6
|
import * as OAuth2Utils from "./OAuth2Utils";
|
|
7
7
|
import PersistentConfig from "./PersistentConfig";
|
|
8
|
+
import { deduplicatePrefixes } from "./prefixUtils";
|
|
8
9
|
import { WorkspaceSettingsForm } from "./queryManagement/WorkspaceSettingsForm";
|
|
9
10
|
|
|
10
11
|
const AcceptOptionsMap: { key: string; value: string }[] = [
|
|
@@ -37,6 +38,7 @@ export default class TabSettingsModal {
|
|
|
37
38
|
private settingsButton!: HTMLButtonElement;
|
|
38
39
|
private themeToggleButton!: HTMLButtonElement;
|
|
39
40
|
private prefixButton!: HTMLButtonElement;
|
|
41
|
+
private mapButton!: HTMLButtonElement;
|
|
40
42
|
private prefixTextarea!: HTMLTextAreaElement;
|
|
41
43
|
private autoCaptureCheckbox!: HTMLInputElement;
|
|
42
44
|
private hamburgerMenuButton!: HTMLButtonElement;
|
|
@@ -88,6 +90,15 @@ export default class TabSettingsModal {
|
|
|
88
90
|
controlBarEl.appendChild(this.prefixButton);
|
|
89
91
|
this.prefixButton.onclick = () => this.insertPrefixesIntoQuery();
|
|
90
92
|
|
|
93
|
+
// Map button
|
|
94
|
+
this.mapButton = document.createElement("button");
|
|
95
|
+
this.mapButton.setAttribute("aria-label", "Open map");
|
|
96
|
+
this.mapButton.title = "Open map";
|
|
97
|
+
this.mapButton.innerHTML = '<i class="fas fa-map-location-dot"></i>';
|
|
98
|
+
addClass(this.mapButton, "tabContextButton", "desktopOnly");
|
|
99
|
+
controlBarEl.appendChild(this.mapButton);
|
|
100
|
+
this.mapButton.onclick = (event: MouseEvent) => this.openMapWidget(event);
|
|
101
|
+
|
|
91
102
|
// Hamburger menu button and dropdown (mobile only)
|
|
92
103
|
const hamburgerContainer = document.createElement("div");
|
|
93
104
|
addClass(hamburgerContainer, "hamburgerContainer", "mobileOnly");
|
|
@@ -147,6 +158,15 @@ export default class TabSettingsModal {
|
|
|
147
158
|
};
|
|
148
159
|
this.hamburgerDropdown.appendChild(prefixItem);
|
|
149
160
|
|
|
161
|
+
const mapItem = document.createElement("button");
|
|
162
|
+
addClass(mapItem, "hamburgerMenuItem");
|
|
163
|
+
mapItem.innerHTML = '<i class="fas fa-map-location-dot"></i><span>Open map</span>';
|
|
164
|
+
mapItem.onclick = (event) => {
|
|
165
|
+
this.openMapWidget(event);
|
|
166
|
+
this.closeHamburgerMenu();
|
|
167
|
+
};
|
|
168
|
+
this.hamburgerDropdown.appendChild(mapItem);
|
|
169
|
+
|
|
150
170
|
// Theme toggle menu item (if enabled)
|
|
151
171
|
if (this.tab.yasgui.config.showThemeToggle) {
|
|
152
172
|
const themeItem = document.createElement("button");
|
|
@@ -847,7 +867,12 @@ export default class TabSettingsModal {
|
|
|
847
867
|
|
|
848
868
|
private renderEndpointsList(container: HTMLElement) {
|
|
849
869
|
container.innerHTML = "";
|
|
850
|
-
const configs = this.tab.yasgui.persistentConfig.getEndpointConfigs();
|
|
870
|
+
const configs = [...this.tab.yasgui.persistentConfig.getEndpointConfigs()];
|
|
871
|
+
configs.sort((a, b) => {
|
|
872
|
+
const aKey = (a.label || a.endpoint).toLowerCase();
|
|
873
|
+
const bKey = (b.label || b.endpoint).toLowerCase();
|
|
874
|
+
return aKey.localeCompare(bKey);
|
|
875
|
+
});
|
|
851
876
|
|
|
852
877
|
// Create table (even if empty, we'll show add form)
|
|
853
878
|
const table = document.createElement("table");
|
|
@@ -1687,7 +1712,7 @@ export default class TabSettingsModal {
|
|
|
1687
1712
|
const autoCapture = this.autoCaptureCheckbox.checked;
|
|
1688
1713
|
|
|
1689
1714
|
// Parse and deduplicate prefixes
|
|
1690
|
-
const deduplicated =
|
|
1715
|
+
const deduplicated = deduplicatePrefixes(prefixText);
|
|
1691
1716
|
this.tab.yasgui.persistentConfig.setPrefixes(deduplicated);
|
|
1692
1717
|
this.tab.yasgui.persistentConfig.setAutoCaptureEnabled(autoCapture);
|
|
1693
1718
|
|
|
@@ -1798,32 +1823,6 @@ export default class TabSettingsModal {
|
|
|
1798
1823
|
this.close();
|
|
1799
1824
|
}
|
|
1800
1825
|
|
|
1801
|
-
private deduplicatePrefixes(prefixText: string): string {
|
|
1802
|
-
const lines = prefixText.split("\n");
|
|
1803
|
-
const seen = new Map<string, string>();
|
|
1804
|
-
const result: string[] = [];
|
|
1805
|
-
|
|
1806
|
-
for (const line of lines) {
|
|
1807
|
-
const trimmed = line.trim();
|
|
1808
|
-
if (!trimmed) continue;
|
|
1809
|
-
|
|
1810
|
-
// Extract prefix label from line
|
|
1811
|
-
const match = trimmed.match(/^\s*PREFIX\s+(\w+):\s*<(.+)>\s*$/i);
|
|
1812
|
-
if (match) {
|
|
1813
|
-
const label = match[1].toLowerCase();
|
|
1814
|
-
if (!seen.has(label)) {
|
|
1815
|
-
seen.set(label, trimmed);
|
|
1816
|
-
result.push(trimmed);
|
|
1817
|
-
}
|
|
1818
|
-
} else {
|
|
1819
|
-
// Keep non-prefix lines as-is
|
|
1820
|
-
result.push(trimmed);
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
|
-
return result.join("\n");
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
1826
|
private insertPrefixesIntoQuery() {
|
|
1828
1827
|
const yasqe = this.tab.getYasqe();
|
|
1829
1828
|
if (!yasqe) return;
|
|
@@ -1866,6 +1865,13 @@ export default class TabSettingsModal {
|
|
|
1866
1865
|
yasqe.focus();
|
|
1867
1866
|
}
|
|
1868
1867
|
|
|
1868
|
+
private openMapWidget(event?: MouseEvent) {
|
|
1869
|
+
const yasqe = this.tab.getYasqe();
|
|
1870
|
+
if (!yasqe) return;
|
|
1871
|
+
const anchorPoint = event ? { x: event.clientX, y: event.clientY } : undefined;
|
|
1872
|
+
yasqe.toggleMapWidget(anchorPoint);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1869
1875
|
public capturePrefixesFromQuery() {
|
|
1870
1876
|
const autoCapture = this.tab.yasgui.persistentConfig.getAutoCaptureEnabled();
|
|
1871
1877
|
if (!autoCapture) return;
|
|
@@ -1885,7 +1891,7 @@ export default class TabSettingsModal {
|
|
|
1885
1891
|
// Merge with existing prefixes
|
|
1886
1892
|
const existingPrefixes = this.tab.yasgui.persistentConfig.getPrefixes();
|
|
1887
1893
|
const combined = existingPrefixes + "\n" + newPrefixLines.join("\n");
|
|
1888
|
-
const deduplicated =
|
|
1894
|
+
const deduplicated = deduplicatePrefixes(combined);
|
|
1889
1895
|
|
|
1890
1896
|
this.tab.yasgui.persistentConfig.setPrefixes(deduplicated);
|
|
1891
1897
|
}
|
|
@@ -2078,6 +2084,19 @@ export default class TabSettingsModal {
|
|
|
2078
2084
|
{ keys: ["Esc"], description: "Exit fullscreen mode" },
|
|
2079
2085
|
],
|
|
2080
2086
|
},
|
|
2087
|
+
{
|
|
2088
|
+
category: "YASGUI Tabs",
|
|
2089
|
+
shortcuts: [
|
|
2090
|
+
{
|
|
2091
|
+
keys: ["Ctrl+Alt+Tab", "Cmd+Alt+Tab"],
|
|
2092
|
+
description: "Switch to previously used YASGUI tab",
|
|
2093
|
+
},
|
|
2094
|
+
{
|
|
2095
|
+
keys: ["Ctrl+Alt+Shift+Tab", "Cmd+Alt+Shift+Tab"],
|
|
2096
|
+
description: "Switch to next YASGUI tab in recently used order",
|
|
2097
|
+
},
|
|
2098
|
+
],
|
|
2099
|
+
},
|
|
2081
2100
|
];
|
|
2082
2101
|
|
|
2083
2102
|
shortcutsData.forEach((section) => {
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { addClass, removeClass } from "@matdata/yasgui-utils";
|
|
|
13
13
|
import GeoPlugin from "yasgui-geo-tg";
|
|
14
14
|
import GraphPlugin from "@matdata/yasgui-graph-plugin";
|
|
15
15
|
import TablePlugin from "@matdata/yasgui-table-plugin";
|
|
16
|
+
import { moveTabIdToFront, removeTabId } from "./tabNavigationHistory";
|
|
16
17
|
import "@matdata/yasgui-graph-plugin/dist/yasgui-graph-plugin.min.css";
|
|
17
18
|
import "@matdata/yasgui-table-plugin/dist/yasgui-table-plugin.min.css";
|
|
18
19
|
import { ThemeManager, Theme } from "./ThemeManager";
|
|
@@ -155,6 +156,9 @@ export class Yasgui extends EventEmitter {
|
|
|
155
156
|
public persistentConfig: PersistentConfig;
|
|
156
157
|
public themeManager: ThemeManager;
|
|
157
158
|
public queryBrowser: QueryBrowser;
|
|
159
|
+
private recentTabIds: string[] = [];
|
|
160
|
+
private navigationSnapshot: string[] | null = null;
|
|
161
|
+
private navigationCursor = 0;
|
|
158
162
|
public static Tab = Tab;
|
|
159
163
|
constructor(parent: HTMLElement, config: PartialConfig) {
|
|
160
164
|
super();
|
|
@@ -185,6 +189,9 @@ export class Yasgui extends EventEmitter {
|
|
|
185
189
|
this.tabPanelsEl = document.createElement("div");
|
|
186
190
|
|
|
187
191
|
this.queryBrowser = new QueryBrowser(this);
|
|
192
|
+
this.on("tabClose", (_yasgui, tab) => {
|
|
193
|
+
this.removeTabFromRecentHistory(tab.getId());
|
|
194
|
+
});
|
|
188
195
|
|
|
189
196
|
this.rootEl.appendChild(this.tabElements.drawTabsList());
|
|
190
197
|
this.rootEl.appendChild(this.tabPanelsEl);
|
|
@@ -227,6 +234,7 @@ export class Yasgui extends EventEmitter {
|
|
|
227
234
|
const newTab = this.addTab(true);
|
|
228
235
|
this.persistentConfig.setActive(newTab.getId());
|
|
229
236
|
this.emit("tabChange", this, newTab);
|
|
237
|
+
this.recordTabInRecentHistory(newTab.getId());
|
|
230
238
|
} else {
|
|
231
239
|
for (const tabId of tabs) {
|
|
232
240
|
this._tabs[tabId] = new Tab(this, this.persistentConfig.getTab(tabId));
|
|
@@ -237,6 +245,7 @@ export class Yasgui extends EventEmitter {
|
|
|
237
245
|
const activeTabId = this.persistentConfig.getActiveId();
|
|
238
246
|
if (activeTabId) {
|
|
239
247
|
this.markTabSelected(activeTabId);
|
|
248
|
+
this.recordTabInRecentHistory(activeTabId);
|
|
240
249
|
if (executeIdAfterInit && executeIdAfterInit === activeTabId) {
|
|
241
250
|
(this.getTab(activeTabId) as Tab).query().catch(() => {});
|
|
242
251
|
}
|
|
@@ -300,6 +309,7 @@ export class Yasgui extends EventEmitter {
|
|
|
300
309
|
const tab = this.getTab();
|
|
301
310
|
if (tab && tab.getId() !== tabId) {
|
|
302
311
|
if (this.markTabSelected(tabId)) {
|
|
312
|
+
this.recordTabInRecentHistory(tabId);
|
|
303
313
|
//emit
|
|
304
314
|
this.emit("tabSelect", this, tabId);
|
|
305
315
|
this.persistentConfig.setActive(tabId);
|
|
@@ -307,6 +317,45 @@ export class Yasgui extends EventEmitter {
|
|
|
307
317
|
}
|
|
308
318
|
return tab;
|
|
309
319
|
}
|
|
320
|
+
private recordTabInRecentHistory(tabId: string) {
|
|
321
|
+
// Any non-navigation selection commits the navigation and ends navigation mode.
|
|
322
|
+
this.navigationSnapshot = null;
|
|
323
|
+
this.recentTabIds = moveTabIdToFront(this.recentTabIds, tabId);
|
|
324
|
+
}
|
|
325
|
+
private removeTabFromRecentHistory(tabId: string) {
|
|
326
|
+
this.recentTabIds = removeTabId(this.recentTabIds, tabId);
|
|
327
|
+
}
|
|
328
|
+
public selectRecentlyUsedTab(direction: "backward" | "forward" = "backward"): void {
|
|
329
|
+
const activeTab = this.getTab();
|
|
330
|
+
if (!activeTab) return;
|
|
331
|
+
const activeTabId = activeTab.getId();
|
|
332
|
+
|
|
333
|
+
// Initialize (or re-initialize) the navigation snapshot when starting a new navigation
|
|
334
|
+
// sequence or when the active tab no longer matches the cursor position.
|
|
335
|
+
// Snapshotting the history order lets repeated presses cycle through all tabs
|
|
336
|
+
// instead of ping-ponging between the two most-recently-used ones.
|
|
337
|
+
if (this.navigationSnapshot === null || this.navigationSnapshot[this.navigationCursor] !== activeTabId) {
|
|
338
|
+
this.navigationSnapshot = [...this.recentTabIds];
|
|
339
|
+
const activeIdx = this.navigationSnapshot.indexOf(activeTabId);
|
|
340
|
+
this.navigationCursor = activeIdx >= 0 ? activeIdx : 0;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (direction === "backward") {
|
|
344
|
+
this.navigationCursor = Math.min(this.navigationCursor + 1, this.navigationSnapshot.length - 1);
|
|
345
|
+
} else {
|
|
346
|
+
this.navigationCursor = Math.max(this.navigationCursor - 1, 0);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const nextTabId = this.navigationSnapshot[this.navigationCursor];
|
|
350
|
+
if (!nextTabId || nextTabId === activeTabId) return;
|
|
351
|
+
|
|
352
|
+
// Select the tab without updating recentTabIds. The history is committed when the
|
|
353
|
+
// user performs any non-navigation action that calls recordTabInRecentHistory.
|
|
354
|
+
if (this.markTabSelected(nextTabId)) {
|
|
355
|
+
this.emit("tabSelect", this, nextTabId);
|
|
356
|
+
this.persistentConfig.setActive(nextTabId);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
310
359
|
/**
|
|
311
360
|
* Checks if two persistent tab configuration are the same based.
|
|
312
361
|
* It isnt a strict equality, as falsy values (e.g. a header that isnt set in one tabjson) isnt taken into consideration
|
|
@@ -417,6 +466,7 @@ export class Yasgui extends EventEmitter {
|
|
|
417
466
|
if (setActive) {
|
|
418
467
|
this.persistentConfig.setActive(tabId);
|
|
419
468
|
this._tabs[tabId].show();
|
|
469
|
+
this.recordTabInRecentHistory(tabId);
|
|
420
470
|
}
|
|
421
471
|
return this._tabs[tabId];
|
|
422
472
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function deduplicatePrefixes(prefixText: string): string {
|
|
2
|
+
const lines = prefixText.split("\n");
|
|
3
|
+
const seen = new Set<string>();
|
|
4
|
+
const result: string[] = [];
|
|
5
|
+
|
|
6
|
+
for (const line of lines) {
|
|
7
|
+
const trimmed = line.trim();
|
|
8
|
+
if (!trimmed) continue;
|
|
9
|
+
|
|
10
|
+
// Allow an empty prefix label so the default prefix form `PREFIX : <...>` is
|
|
11
|
+
// deduplicated together with named prefixes, while keeping the original
|
|
12
|
+
// word-character restriction for named labels.
|
|
13
|
+
const match = trimmed.match(/^\s*PREFIX\s+(\w*):\s*<([^>]+)>\s*$/i);
|
|
14
|
+
if (match) {
|
|
15
|
+
const label = match[1].toLowerCase();
|
|
16
|
+
if (!seen.has(label)) {
|
|
17
|
+
seen.add(label);
|
|
18
|
+
result.push(trimmed);
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
result.push(trimmed);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return result.join("\n");
|
|
26
|
+
}
|
|
@@ -325,6 +325,91 @@
|
|
|
325
325
|
white-space: nowrap;
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
&__context-menu {
|
|
329
|
+
position: fixed;
|
|
330
|
+
z-index: 10001;
|
|
331
|
+
list-style: none;
|
|
332
|
+
padding: 4px 0;
|
|
333
|
+
margin: 0;
|
|
334
|
+
min-width: 140px;
|
|
335
|
+
background: var(--yasgui-bg-primary, white);
|
|
336
|
+
border: 1px solid var(--yasgui-border-color, #e0e0e0);
|
|
337
|
+
border-radius: 4px;
|
|
338
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
&__context-menu-item {
|
|
342
|
+
padding: 7px 16px;
|
|
343
|
+
cursor: pointer;
|
|
344
|
+
white-space: nowrap;
|
|
345
|
+
color: var(--yasgui-text-primary, #000);
|
|
346
|
+
font-size: 13px;
|
|
347
|
+
line-height: 1.4;
|
|
348
|
+
|
|
349
|
+
&:hover {
|
|
350
|
+
background: var(--yasgui-bg-secondary, #f5f5f5);
|
|
351
|
+
color: var(--yasgui-button-hover, #000);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
&--danger {
|
|
355
|
+
color: var(--yasgui-error-color, #c00);
|
|
356
|
+
|
|
357
|
+
&:hover {
|
|
358
|
+
background: var(--yasgui-bg-secondary, #f5f5f5);
|
|
359
|
+
color: var(--yasgui-error-color, #c00);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
&__rename-input {
|
|
365
|
+
flex: 1;
|
|
366
|
+
min-width: 0;
|
|
367
|
+
font: inherit;
|
|
368
|
+
font-size: 13px;
|
|
369
|
+
color: var(--yasgui-text-primary, #000);
|
|
370
|
+
background: var(--yasgui-bg-primary, white);
|
|
371
|
+
border: 1px solid var(--yasgui-accent-color, #337ab7);
|
|
372
|
+
border-radius: 3px;
|
|
373
|
+
padding: 1px 4px;
|
|
374
|
+
outline: none;
|
|
375
|
+
box-shadow: 0 0 0 2px rgba(51, 122, 183, 0.25);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
&__inline-confirm {
|
|
379
|
+
flex: 1;
|
|
380
|
+
font-size: 13px;
|
|
381
|
+
color: var(--yasgui-text-primary, #000);
|
|
382
|
+
white-space: nowrap;
|
|
383
|
+
overflow: hidden;
|
|
384
|
+
text-overflow: ellipsis;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
&__inline-confirm-btn {
|
|
388
|
+
appearance: none;
|
|
389
|
+
background: transparent;
|
|
390
|
+
border: 1px solid var(--yasgui-border-color, #e0e0e0);
|
|
391
|
+
border-radius: 3px;
|
|
392
|
+
padding: 1px 8px;
|
|
393
|
+
font: inherit;
|
|
394
|
+
font-size: 12px;
|
|
395
|
+
cursor: pointer;
|
|
396
|
+
color: var(--yasgui-text-primary, #000);
|
|
397
|
+
flex-shrink: 0;
|
|
398
|
+
|
|
399
|
+
&:hover {
|
|
400
|
+
background: var(--yasgui-bg-secondary, #f5f5f5);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
&--danger {
|
|
404
|
+
color: var(--yasgui-error-color, #c00);
|
|
405
|
+
border-color: var(--yasgui-error-color, #c00);
|
|
406
|
+
|
|
407
|
+
&:hover {
|
|
408
|
+
background: rgba(204, 0, 0, 0.08);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
328
413
|
&__tree-meta {
|
|
329
414
|
padding: 4px 8px;
|
|
330
415
|
font-size: 13px;
|