pinokiod 5.1.34 → 5.1.36
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/kernel/git.js +46 -1
- package/package.json +1 -1
- package/server/index.js +82 -0
- package/server/public/nav.js +30 -8
- package/server/public/style.css +14 -1
- package/server/public/tab-link-popover.css +34 -0
- package/server/public/tab-link-popover.js +104 -12
- package/server/public/urldropdown.js +272 -84
- package/server/views/app.ejs +66 -11
- package/server/views/checkpoints.ejs +129 -5
- package/server/views/env_editor.ejs +15 -8
package/kernel/git.js
CHANGED
|
@@ -84,6 +84,17 @@ class Git {
|
|
|
84
84
|
const offM = pad2(abs % 60)
|
|
85
85
|
return `${y}-${m}-${day}T${hh}:${mm}:${ss}${sign}${offH}:${offM}`
|
|
86
86
|
}
|
|
87
|
+
normalizeRepoPath(rawPath) {
|
|
88
|
+
if (typeof rawPath !== "string") return "."
|
|
89
|
+
let value = rawPath.trim()
|
|
90
|
+
if (!value) return "."
|
|
91
|
+
value = value.replace(/\\/g, "/").replace(/\/{2,}/g, "/")
|
|
92
|
+
if (value === "." || value === "./") return "."
|
|
93
|
+
if (value.startsWith("./")) {
|
|
94
|
+
value = value.slice(2)
|
|
95
|
+
}
|
|
96
|
+
return value || "."
|
|
97
|
+
}
|
|
87
98
|
upsertCommitMeta(repoUrlNorm, sha, meta) {
|
|
88
99
|
if (!repoUrlNorm || typeof repoUrlNorm !== "string") return false
|
|
89
100
|
if (!this.isCommitSha(sha)) return false
|
|
@@ -231,7 +242,7 @@ class Git {
|
|
|
231
242
|
const repos = []
|
|
232
243
|
for (const repo of rawRepos) {
|
|
233
244
|
if (!repo) continue
|
|
234
|
-
const pathVal =
|
|
245
|
+
const pathVal = this.normalizeRepoPath(repo.path)
|
|
235
246
|
let remote = typeof repo.remote === "string" && repo.remote.length > 0 ? repo.remote : null
|
|
236
247
|
if (!remote) remote = typeof repo.repo === "string" && repo.repo.length > 0 ? repo.repo : null
|
|
237
248
|
const commit = typeof repo.commit === "string" && repo.commit.length > 0 ? repo.commit : null
|
|
@@ -559,6 +570,40 @@ class Git {
|
|
|
559
570
|
await this.saveManifest()
|
|
560
571
|
return true
|
|
561
572
|
}
|
|
573
|
+
async deleteCheckpoint(remoteKey, checkpointId) {
|
|
574
|
+
if (!remoteKey || checkpointId == null) return { ok: false, error: "invalid" }
|
|
575
|
+
const apps = this.apps()
|
|
576
|
+
const entry = apps[remoteKey]
|
|
577
|
+
if (!entry || !Array.isArray(entry.checkpoints)) return { ok: false, error: "not_found" }
|
|
578
|
+
const idStr = String(checkpointId)
|
|
579
|
+
const idx = entry.checkpoints.findIndex((c) => c && String(c.id) === idStr)
|
|
580
|
+
if (idx < 0) return { ok: false, error: "not_found" }
|
|
581
|
+
const record = entry.checkpoints[idx]
|
|
582
|
+
entry.checkpoints.splice(idx, 1)
|
|
583
|
+
await this.saveManifest()
|
|
584
|
+
const hash = record && record.hash ? String(record.hash) : null
|
|
585
|
+
let fileDeleted = false
|
|
586
|
+
if (hash) {
|
|
587
|
+
let stillUsed = false
|
|
588
|
+
for (const entry of Object.values(apps)) {
|
|
589
|
+
if (!entry || !Array.isArray(entry.checkpoints)) continue
|
|
590
|
+
if (entry.checkpoints.some((c) => c && String(c.hash) === hash)) {
|
|
591
|
+
stillUsed = true
|
|
592
|
+
break
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (!stillUsed) {
|
|
596
|
+
const filePath = this.checkpointFilePath(hash)
|
|
597
|
+
if (filePath) {
|
|
598
|
+
try {
|
|
599
|
+
await fs.promises.rm(filePath, { force: true })
|
|
600
|
+
fileDeleted = true
|
|
601
|
+
} catch (_) {}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return { ok: true, hash, fileDeleted }
|
|
606
|
+
}
|
|
562
607
|
async logCheckpointRestore(event) {
|
|
563
608
|
const logEntry = {
|
|
564
609
|
ts: Date.now(),
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -5205,6 +5205,38 @@ class Server {
|
|
|
5205
5205
|
}
|
|
5206
5206
|
res.json({ ok: true, redirect: `/p/${encodeURIComponent(folder)}` })
|
|
5207
5207
|
}))
|
|
5208
|
+
this.app.post("/checkpoints/delete", ex(async (req, res) => {
|
|
5209
|
+
const remoteRaw = typeof req.body.remote === 'string'
|
|
5210
|
+
? req.body.remote.trim()
|
|
5211
|
+
: (typeof req.query.remote === 'string' ? req.query.remote.trim() : '')
|
|
5212
|
+
const remoteKeyRaw = typeof req.body.remoteKey === 'string'
|
|
5213
|
+
? req.body.remoteKey.trim()
|
|
5214
|
+
: (typeof req.query.remoteKey === 'string' ? req.query.remoteKey.trim() : '')
|
|
5215
|
+
const snapshotRaw = Object.prototype.hasOwnProperty.call(req.body || {}, "snapshotId")
|
|
5216
|
+
? req.body.snapshotId
|
|
5217
|
+
: (Object.prototype.hasOwnProperty.call(req.query || {}, "snapshotId") ? req.query.snapshotId : "")
|
|
5218
|
+
const snapshotId = snapshotRaw === 'latest'
|
|
5219
|
+
? ''
|
|
5220
|
+
: (snapshotRaw == null ? '' : String(snapshotRaw))
|
|
5221
|
+
const remoteKey = remoteKeyRaw || (remoteRaw ? this.kernel.git.normalizeRemote(remoteRaw) : '')
|
|
5222
|
+
if (!snapshotId || !remoteKey) {
|
|
5223
|
+
res.status(400).json({ ok: false, error: "Missing parameters" })
|
|
5224
|
+
return
|
|
5225
|
+
}
|
|
5226
|
+
const result = await this.kernel.git.deleteCheckpoint(remoteKey, snapshotId)
|
|
5227
|
+
if (!result || !result.ok) {
|
|
5228
|
+
res.status(404).json({ ok: false, error: "Snapshot not found" })
|
|
5229
|
+
return
|
|
5230
|
+
}
|
|
5231
|
+
res.json({
|
|
5232
|
+
ok: true,
|
|
5233
|
+
deleted: {
|
|
5234
|
+
id: snapshotId,
|
|
5235
|
+
hash: result.hash || null,
|
|
5236
|
+
fileDeleted: !!result.fileDeleted
|
|
5237
|
+
}
|
|
5238
|
+
})
|
|
5239
|
+
}))
|
|
5208
5240
|
this.app.get("/checkpoints/restore/:workspace/:snapshotId", ex(async (req, res) => {
|
|
5209
5241
|
const workspace = typeof req.params.workspace === 'string' ? req.params.workspace : ''
|
|
5210
5242
|
const snapshotId = req.params.snapshotId
|
|
@@ -8652,6 +8684,56 @@ class Server {
|
|
|
8652
8684
|
}))
|
|
8653
8685
|
|
|
8654
8686
|
|
|
8687
|
+
this.app.get("/info/apps", ex(async (req, res) => {
|
|
8688
|
+
const apps = []
|
|
8689
|
+
try {
|
|
8690
|
+
const apipath = this.kernel.path("api")
|
|
8691
|
+
const entries = await fs.promises.readdir(apipath, { withFileTypes: true })
|
|
8692
|
+
for (const entry of entries) {
|
|
8693
|
+
let type
|
|
8694
|
+
try {
|
|
8695
|
+
type = await Util.file_type(apipath, entry)
|
|
8696
|
+
} catch (typeErr) {
|
|
8697
|
+
console.warn('Failed to inspect api entry', entry.name, typeErr)
|
|
8698
|
+
continue
|
|
8699
|
+
}
|
|
8700
|
+
if (!type || !type.directory) {
|
|
8701
|
+
continue
|
|
8702
|
+
}
|
|
8703
|
+
try {
|
|
8704
|
+
const meta = await this.kernel.api.meta(entry.name)
|
|
8705
|
+
apps.push({
|
|
8706
|
+
name: entry.name,
|
|
8707
|
+
title: meta && meta.title ? meta.title : entry.name,
|
|
8708
|
+
description: meta && meta.description ? meta.description : '',
|
|
8709
|
+
icon: meta && meta.icon ? meta.icon : "/pinokio-black.png"
|
|
8710
|
+
})
|
|
8711
|
+
} catch (metaError) {
|
|
8712
|
+
console.warn('Failed to load app metadata', entry.name, metaError)
|
|
8713
|
+
apps.push({
|
|
8714
|
+
name: entry.name,
|
|
8715
|
+
title: entry.name,
|
|
8716
|
+
description: '',
|
|
8717
|
+
icon: "/pinokio-black.png"
|
|
8718
|
+
})
|
|
8719
|
+
}
|
|
8720
|
+
}
|
|
8721
|
+
} catch (enumerationError) {
|
|
8722
|
+
console.warn('Failed to enumerate api apps for url dropdown', enumerationError)
|
|
8723
|
+
}
|
|
8724
|
+
|
|
8725
|
+
apps.sort((a, b) => {
|
|
8726
|
+
const at = (a.title || a.name || '').toLowerCase()
|
|
8727
|
+
const bt = (b.title || b.name || '').toLowerCase()
|
|
8728
|
+
if (at < bt) return -1
|
|
8729
|
+
if (at > bt) return 1
|
|
8730
|
+
return (a.name || '').localeCompare(b.name || '')
|
|
8731
|
+
})
|
|
8732
|
+
|
|
8733
|
+
res.json({ apps })
|
|
8734
|
+
}))
|
|
8735
|
+
|
|
8736
|
+
|
|
8655
8737
|
this.app.get("/info/procs", ex(async (req, res) => {
|
|
8656
8738
|
await this.kernel.processes.refresh()
|
|
8657
8739
|
|
package/server/public/nav.js
CHANGED
|
@@ -25,8 +25,25 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
const homeIcon = homeLink ? homeLink.querySelector("img.icon") : null;
|
|
29
|
+
const ensureHomeExpandIcon = () => {
|
|
30
|
+
if (!homeLink || !homeIcon) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
let icon = homeLink.querySelector(".home-expand-icon");
|
|
34
|
+
if (!icon) {
|
|
35
|
+
icon = document.createElement("i");
|
|
36
|
+
icon.className = "fa-solid fa-expand home-expand-icon";
|
|
37
|
+
icon.setAttribute("aria-hidden", "true");
|
|
38
|
+
homeLink.appendChild(icon);
|
|
39
|
+
}
|
|
40
|
+
return icon;
|
|
41
|
+
};
|
|
42
|
+
ensureHomeExpandIcon();
|
|
43
|
+
|
|
28
44
|
// Helper functions used during initial restore must be defined before use
|
|
29
|
-
const MIN_MARGIN =
|
|
45
|
+
const MIN_MARGIN = 0;
|
|
46
|
+
const LEGACY_MARGIN = 8;
|
|
30
47
|
|
|
31
48
|
function clampPosition(left, top, sizeOverride) {
|
|
32
49
|
const rect = header.getBoundingClientRect();
|
|
@@ -168,19 +185,24 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
168
185
|
// Restore persisted or respect DOM state on load (per path, per session)
|
|
169
186
|
const persisted = readPersisted();
|
|
170
187
|
const restoreFromStorage = !!(persisted && persisted.minimized);
|
|
188
|
+
const hasStoredPosition = !!(persisted && Number.isFinite(persisted.left) && Number.isFinite(persisted.top));
|
|
189
|
+
const isLegacyDefault = hasStoredPosition
|
|
190
|
+
&& Math.abs(persisted.left - LEGACY_MARGIN) < 0.5
|
|
191
|
+
&& Math.abs(persisted.top - LEGACY_MARGIN) < 0.5;
|
|
192
|
+
const useStoredPosition = restoreFromStorage && hasStoredPosition && !isLegacyDefault;
|
|
171
193
|
const domIsMinimized = header.classList.contains("minimized");
|
|
172
194
|
if (restoreFromStorage || domIsMinimized) {
|
|
173
195
|
header.classList.add("minimized");
|
|
174
196
|
// Use minimized size for clamping/positioning
|
|
175
197
|
const size = measureRect((clone) => { clone.classList.add("minimized"); });
|
|
176
|
-
const fallbackLeft =
|
|
177
|
-
const fallbackTop =
|
|
178
|
-
const left =
|
|
179
|
-
const top =
|
|
198
|
+
const fallbackLeft = MIN_MARGIN;
|
|
199
|
+
const fallbackTop = MIN_MARGIN;
|
|
200
|
+
const left = useStoredPosition ? persisted.left : fallbackLeft;
|
|
201
|
+
const top = useStoredPosition ? persisted.top : fallbackTop;
|
|
180
202
|
const clamped = clampPosition(left, top, size);
|
|
181
203
|
state.lastLeft = clamped.left;
|
|
182
204
|
state.lastTop = clamped.top;
|
|
183
|
-
state.hasCustomPosition =
|
|
205
|
+
state.hasCustomPosition = useStoredPosition;
|
|
184
206
|
state.minimized = true;
|
|
185
207
|
// Apply immediately and once after layout settles
|
|
186
208
|
applyPosition(clamped.left, clamped.top);
|
|
@@ -234,8 +256,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
234
256
|
clone.classList.add("minimized");
|
|
235
257
|
});
|
|
236
258
|
|
|
237
|
-
const defaultLeft =
|
|
238
|
-
const defaultTop =
|
|
259
|
+
const defaultLeft = MIN_MARGIN;
|
|
260
|
+
const defaultTop = MIN_MARGIN;
|
|
239
261
|
const targetLeft = state.hasCustomPosition ? state.lastLeft : defaultLeft;
|
|
240
262
|
const targetTop = state.hasCustomPosition ? state.lastTop : defaultTop;
|
|
241
263
|
|
package/server/public/style.css
CHANGED
|
@@ -2978,7 +2978,6 @@ header.navheader.minimized {
|
|
|
2978
2978
|
height: auto;
|
|
2979
2979
|
max-height: none;
|
|
2980
2980
|
padding: 4px 8px;
|
|
2981
|
-
border-radius: 12px;
|
|
2982
2981
|
background: var(--light-nav-bg);
|
|
2983
2982
|
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.14), 0 2px 6px rgba(0, 0, 0, 0.08);
|
|
2984
2983
|
display: inline-flex;
|
|
@@ -3052,9 +3051,23 @@ header.navheader.minimized .home {
|
|
|
3052
3051
|
padding: 0;
|
|
3053
3052
|
}
|
|
3054
3053
|
header.navheader.minimized .home .icon {
|
|
3054
|
+
display: none;
|
|
3055
3055
|
width: 24px;
|
|
3056
3056
|
height: 24px;
|
|
3057
3057
|
}
|
|
3058
|
+
header.navheader .home .home-expand-icon {
|
|
3059
|
+
display: none;
|
|
3060
|
+
}
|
|
3061
|
+
header.navheader.minimized .home .home-expand-icon {
|
|
3062
|
+
display: inline-flex;
|
|
3063
|
+
width: 24px;
|
|
3064
|
+
height: 24px;
|
|
3065
|
+
align-items: center;
|
|
3066
|
+
justify-content: center;
|
|
3067
|
+
font-size: 16px;
|
|
3068
|
+
line-height: 1;
|
|
3069
|
+
color: inherit;
|
|
3070
|
+
}
|
|
3058
3071
|
|
|
3059
3072
|
|
|
3060
3073
|
header.navheader.transitioning {
|
|
@@ -132,3 +132,37 @@ body.dark .tab-link-popover .tab-link-popover-footer:hover,
|
|
|
132
132
|
body.dark .tab-link-popover .tab-link-popover-footer:focus-visible {
|
|
133
133
|
background: rgba(147, 197, 253, 0.35);
|
|
134
134
|
}
|
|
135
|
+
.appcanvas > aside .menu-container .tab-link-popover-host {
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
gap: 8px;
|
|
139
|
+
}
|
|
140
|
+
.appcanvas > aside .menu-container .tab-link-popover-trigger {
|
|
141
|
+
display: inline-flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
justify-content: center;
|
|
144
|
+
width: 18px;
|
|
145
|
+
height: 18px;
|
|
146
|
+
border-radius: 6px;
|
|
147
|
+
flex: 0 0 auto;
|
|
148
|
+
color: inherit;
|
|
149
|
+
opacity: 0.55;
|
|
150
|
+
cursor: pointer;
|
|
151
|
+
transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease;
|
|
152
|
+
}
|
|
153
|
+
.appcanvas > aside .menu-container .tab-link-popover-trigger i {
|
|
154
|
+
font-size: 14px;
|
|
155
|
+
}
|
|
156
|
+
.appcanvas > aside .menu-container .frame-link:hover .tab-link-popover-trigger,
|
|
157
|
+
.appcanvas > aside .menu-container .frame-link:focus-within .tab-link-popover-trigger {
|
|
158
|
+
opacity: 0.85;
|
|
159
|
+
}
|
|
160
|
+
.appcanvas > aside .menu-container .tab-link-popover-trigger:hover,
|
|
161
|
+
.appcanvas > aside .menu-container .tab-link-popover-trigger:focus-visible {
|
|
162
|
+
background: rgba(15, 23, 42, 0.08);
|
|
163
|
+
opacity: 1;
|
|
164
|
+
}
|
|
165
|
+
body.dark .appcanvas > aside .menu-container .tab-link-popover-trigger:hover,
|
|
166
|
+
body.dark .appcanvas > aside .menu-container .tab-link-popover-trigger:focus-visible {
|
|
167
|
+
background: rgba(148, 163, 184, 0.2);
|
|
168
|
+
}
|
|
@@ -10,6 +10,40 @@
|
|
|
10
10
|
let tabLinkRouterHttpsActive = null
|
|
11
11
|
let tabLinkPeerInfoPromise = null
|
|
12
12
|
let tabLinkPeerInfoExpiry = 0
|
|
13
|
+
const TAB_LINK_TRIGGER_CLASS = "tab-link-popover-trigger"
|
|
14
|
+
const TAB_LINK_TRIGGER_HOST_CLASS = "tab-link-popover-host"
|
|
15
|
+
|
|
16
|
+
const shouldAttachTabLinkTrigger = (link) => {
|
|
17
|
+
if (!link || !link.classList || !link.classList.contains("frame-link")) {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
if (!link.hasAttribute("href")) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
const href = link.getAttribute("href")
|
|
24
|
+
return typeof href === "string" && href.trim().length > 0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const createTabLinkTrigger = () => {
|
|
28
|
+
const trigger = document.createElement("span")
|
|
29
|
+
trigger.className = TAB_LINK_TRIGGER_CLASS
|
|
30
|
+
trigger.setAttribute("role", "button")
|
|
31
|
+
trigger.setAttribute("tabindex", "0")
|
|
32
|
+
trigger.setAttribute("aria-label", "Open in browser")
|
|
33
|
+
trigger.innerHTML = '<i class="fa-solid fa-arrow-up-right-from-square"></i>'
|
|
34
|
+
return trigger
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ensureTabLinkTrigger = (link) => {
|
|
38
|
+
if (!shouldAttachTabLinkTrigger(link)) {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
if (link.querySelector(`.${TAB_LINK_TRIGGER_CLASS}`)) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
link.classList.add(TAB_LINK_TRIGGER_HOST_CLASS)
|
|
45
|
+
link.appendChild(createTabLinkTrigger())
|
|
46
|
+
}
|
|
13
47
|
|
|
14
48
|
const ensureTabLinkPopoverEl = () => {
|
|
15
49
|
if (!tabLinkPopoverEl) {
|
|
@@ -38,7 +72,12 @@
|
|
|
38
72
|
if (targetMode === "_self") {
|
|
39
73
|
window.location.assign(url)
|
|
40
74
|
} else {
|
|
41
|
-
|
|
75
|
+
const agent = document.body ? document.body.getAttribute("data-agent") : null
|
|
76
|
+
if (agent === "electron") {
|
|
77
|
+
window.open(url, "_blank", "browser")
|
|
78
|
+
} else {
|
|
79
|
+
window.open(url, "_blank")
|
|
80
|
+
}
|
|
42
81
|
// fetch("/go", {
|
|
43
82
|
// method: "POST",
|
|
44
83
|
// headers: {
|
|
@@ -1611,27 +1650,80 @@
|
|
|
1611
1650
|
if (!container) {
|
|
1612
1651
|
return
|
|
1613
1652
|
}
|
|
1653
|
+
if (container.dataset.tabLinkPopoverReady === "1") {
|
|
1654
|
+
return
|
|
1655
|
+
}
|
|
1656
|
+
container.dataset.tabLinkPopoverReady = "1"
|
|
1614
1657
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
if (!link || !container.contains(link)) {
|
|
1658
|
+
const ensureTriggers = (root) => {
|
|
1659
|
+
if (!root || !root.querySelectorAll) {
|
|
1618
1660
|
return
|
|
1619
1661
|
}
|
|
1620
|
-
|
|
1662
|
+
root.querySelectorAll(".frame-link").forEach((link) => {
|
|
1663
|
+
ensureTabLinkTrigger(link)
|
|
1664
|
+
})
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
ensureTriggers(container)
|
|
1668
|
+
|
|
1669
|
+
const observer = new MutationObserver((mutations) => {
|
|
1670
|
+
mutations.forEach((mutation) => {
|
|
1671
|
+
mutation.addedNodes.forEach((node) => {
|
|
1672
|
+
if (!node || node.nodeType !== 1) {
|
|
1673
|
+
return
|
|
1674
|
+
}
|
|
1675
|
+
if (node.classList && node.classList.contains("frame-link")) {
|
|
1676
|
+
ensureTabLinkTrigger(node)
|
|
1677
|
+
}
|
|
1678
|
+
if (node.querySelectorAll) {
|
|
1679
|
+
node.querySelectorAll(".frame-link").forEach((link) => {
|
|
1680
|
+
ensureTabLinkTrigger(link)
|
|
1681
|
+
})
|
|
1682
|
+
}
|
|
1683
|
+
})
|
|
1684
|
+
})
|
|
1621
1685
|
})
|
|
1686
|
+
observer.observe(container, { childList: true, subtree: true })
|
|
1622
1687
|
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
if (!origin || !container.contains(origin)) {
|
|
1688
|
+
const togglePopoverForLink = (link) => {
|
|
1689
|
+
if (!link) {
|
|
1626
1690
|
return
|
|
1627
1691
|
}
|
|
1628
|
-
const related = event.relatedTarget
|
|
1629
1692
|
const popover = tabLinkPopoverEl || document.getElementById(TAB_LINK_POPOVER_ID)
|
|
1630
|
-
if (
|
|
1693
|
+
if (tabLinkActiveLink === link && popover && popover.classList.contains("visible")) {
|
|
1694
|
+
hideTabLinkPopover({ immediate: true })
|
|
1631
1695
|
return
|
|
1632
1696
|
}
|
|
1633
|
-
|
|
1634
|
-
}
|
|
1697
|
+
renderTabLinkPopover(link, { requireAlternate: false })
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
const handleTriggerClick = (event) => {
|
|
1701
|
+
const trigger = event.target.closest(`.${TAB_LINK_TRIGGER_CLASS}`)
|
|
1702
|
+
if (!trigger || !container.contains(trigger)) {
|
|
1703
|
+
return
|
|
1704
|
+
}
|
|
1705
|
+
event.preventDefault()
|
|
1706
|
+
event.stopPropagation()
|
|
1707
|
+
const link = trigger.closest(".frame-link")
|
|
1708
|
+
togglePopoverForLink(link)
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
const handleTriggerKeydown = (event) => {
|
|
1712
|
+
if (event.key !== "Enter" && event.key !== " ") {
|
|
1713
|
+
return
|
|
1714
|
+
}
|
|
1715
|
+
const trigger = event.target.closest(`.${TAB_LINK_TRIGGER_CLASS}`)
|
|
1716
|
+
if (!trigger || !container.contains(trigger)) {
|
|
1717
|
+
return
|
|
1718
|
+
}
|
|
1719
|
+
event.preventDefault()
|
|
1720
|
+
event.stopPropagation()
|
|
1721
|
+
const link = trigger.closest(".frame-link")
|
|
1722
|
+
togglePopoverForLink(link)
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
container.addEventListener("click", handleTriggerClick, true)
|
|
1726
|
+
container.addEventListener("keydown", handleTriggerKeydown, true)
|
|
1635
1727
|
}
|
|
1636
1728
|
|
|
1637
1729
|
const handleGlobalPointer = (event) => {
|