pinokiod 7.5.40 → 7.5.41
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 +1 -1
- package/server/index.js +23 -0
- package/server/views/app.ejs +323 -10
- package/test/launch-settings-ui.test.js +122 -0
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -1498,6 +1498,28 @@ class Server {
|
|
|
1498
1498
|
community_url = ""
|
|
1499
1499
|
}
|
|
1500
1500
|
}
|
|
1501
|
+
const buildGitRemoteWebUrl = (value) => {
|
|
1502
|
+
const raw = typeof value === "string" ? value.trim() : ""
|
|
1503
|
+
if (!raw) {
|
|
1504
|
+
return ""
|
|
1505
|
+
}
|
|
1506
|
+
if (/^https?:\/\//i.test(raw)) {
|
|
1507
|
+
return raw.replace(/\.git$/i, "")
|
|
1508
|
+
}
|
|
1509
|
+
const sshUrlMatch = raw.match(/^ssh:\/\/(?:[^@/]+@)?([^/]+)\/(.+?)(?:\.git)?$/i)
|
|
1510
|
+
if (sshUrlMatch && sshUrlMatch[1] && sshUrlMatch[2]) {
|
|
1511
|
+
return `https://${sshUrlMatch[1]}/${sshUrlMatch[2]}`
|
|
1512
|
+
}
|
|
1513
|
+
const scpMatch = raw.match(/^(?:[^@]+@)?([^:/]+):(.+?)(?:\.git)?$/)
|
|
1514
|
+
if (scpMatch && scpMatch[1] && scpMatch[2]) {
|
|
1515
|
+
return `https://${scpMatch[1]}/${scpMatch[2]}`
|
|
1516
|
+
}
|
|
1517
|
+
return ""
|
|
1518
|
+
}
|
|
1519
|
+
const launcherRemote = this.kernel.api && typeof this.kernel.api.parentGitURI === "function"
|
|
1520
|
+
? (this.kernel.api.parentGitURI(this.kernel.path("api", name)) || "")
|
|
1521
|
+
: ""
|
|
1522
|
+
const launcherRemoteUrl = buildGitRemoteWebUrl(launcherRemote)
|
|
1501
1523
|
|
|
1502
1524
|
let editor_tab = `/pinokio/fileview/${encodeURIComponent(name)}`
|
|
1503
1525
|
const tabsStorageKey = `${name}:${type}`
|
|
@@ -1593,6 +1615,7 @@ class Server {
|
|
|
1593
1615
|
review_tab,
|
|
1594
1616
|
files_tab,
|
|
1595
1617
|
community_url,
|
|
1618
|
+
launcher_remote_url: launcherRemoteUrl,
|
|
1596
1619
|
// paths,
|
|
1597
1620
|
theme: this.theme,
|
|
1598
1621
|
agent: req.agent,
|
package/server/views/app.ejs
CHANGED
|
@@ -70,6 +70,173 @@ body.dark .app-header-identity {
|
|
|
70
70
|
0 1px 2px rgba(2, 6, 23, 0.35);
|
|
71
71
|
color: rgba(248, 250, 252, 0.94);
|
|
72
72
|
}
|
|
73
|
+
.app-header-info {
|
|
74
|
+
position: relative;
|
|
75
|
+
flex: 0 1 auto;
|
|
76
|
+
min-width: 0;
|
|
77
|
+
display: inline-flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
pointer-events: auto;
|
|
80
|
+
}
|
|
81
|
+
.app-header-info-trigger {
|
|
82
|
+
appearance: none;
|
|
83
|
+
border: 0;
|
|
84
|
+
background: transparent;
|
|
85
|
+
color: inherit;
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
gap: 8px;
|
|
89
|
+
min-width: 0;
|
|
90
|
+
max-width: min(260px, 32vw);
|
|
91
|
+
height: 22px;
|
|
92
|
+
padding: 0 4px;
|
|
93
|
+
margin: 0 -4px;
|
|
94
|
+
border-radius: 6px;
|
|
95
|
+
font: inherit;
|
|
96
|
+
line-height: 1;
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
99
|
+
}
|
|
100
|
+
.app-header-info-trigger:hover,
|
|
101
|
+
.app-header-info-trigger[aria-expanded="true"] {
|
|
102
|
+
background: rgba(15, 23, 42, 0.055);
|
|
103
|
+
}
|
|
104
|
+
body.dark .app-header-info-trigger:hover,
|
|
105
|
+
body.dark .app-header-info-trigger[aria-expanded="true"] {
|
|
106
|
+
background: rgba(255, 255, 255, 0.07);
|
|
107
|
+
}
|
|
108
|
+
.app-header-info-trigger:focus-visible {
|
|
109
|
+
outline: 2px solid rgba(10, 132, 255, 0.55);
|
|
110
|
+
outline-offset: 4px;
|
|
111
|
+
}
|
|
112
|
+
.app-header-info-popover {
|
|
113
|
+
position: absolute;
|
|
114
|
+
top: calc(100% + 9px);
|
|
115
|
+
left: 0;
|
|
116
|
+
width: min(390px, calc(100vw - 24px));
|
|
117
|
+
padding: 12px;
|
|
118
|
+
border: 1px solid rgba(15, 23, 42, 0.1);
|
|
119
|
+
border-radius: 10px;
|
|
120
|
+
background: rgba(255, 255, 255, 0.98);
|
|
121
|
+
box-shadow: 0 16px 42px rgba(15, 23, 42, 0.14);
|
|
122
|
+
color: #0f172a;
|
|
123
|
+
z-index: 10000020;
|
|
124
|
+
box-sizing: border-box;
|
|
125
|
+
}
|
|
126
|
+
body.dark .app-header-info-popover {
|
|
127
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
128
|
+
background: rgba(20, 22, 28, 0.98);
|
|
129
|
+
box-shadow: 0 18px 46px rgba(0, 0, 0, 0.48);
|
|
130
|
+
color: rgba(248, 250, 252, 0.94);
|
|
131
|
+
}
|
|
132
|
+
.app-header-info-popover.hidden {
|
|
133
|
+
display: none;
|
|
134
|
+
}
|
|
135
|
+
.app-header-info-popover-head {
|
|
136
|
+
margin-bottom: 10px;
|
|
137
|
+
color: rgba(71, 85, 105, 0.72);
|
|
138
|
+
font-size: 11px;
|
|
139
|
+
font-weight: 700;
|
|
140
|
+
line-height: 1;
|
|
141
|
+
text-transform: uppercase;
|
|
142
|
+
letter-spacing: 0.03em;
|
|
143
|
+
}
|
|
144
|
+
body.dark .app-header-info-popover-head {
|
|
145
|
+
color: rgba(203, 213, 225, 0.7);
|
|
146
|
+
}
|
|
147
|
+
.app-header-info-popover-main {
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: flex-start;
|
|
150
|
+
gap: 12px;
|
|
151
|
+
min-width: 0;
|
|
152
|
+
}
|
|
153
|
+
.app-header-info-popover-icon {
|
|
154
|
+
width: 58px;
|
|
155
|
+
height: 58px;
|
|
156
|
+
flex: 0 0 58px;
|
|
157
|
+
display: block;
|
|
158
|
+
object-fit: contain;
|
|
159
|
+
border-radius: 8px;
|
|
160
|
+
border: 1px solid rgba(15, 23, 42, 0.08);
|
|
161
|
+
background: rgba(248, 250, 252, 0.8);
|
|
162
|
+
}
|
|
163
|
+
body.dark .app-header-info-popover-icon {
|
|
164
|
+
border-color: rgba(255, 255, 255, 0.09);
|
|
165
|
+
background: rgba(255, 255, 255, 0.055);
|
|
166
|
+
}
|
|
167
|
+
.app-header-info-popover-copy {
|
|
168
|
+
min-width: 0;
|
|
169
|
+
flex: 1 1 auto;
|
|
170
|
+
}
|
|
171
|
+
.app-header-info-popover-title {
|
|
172
|
+
color: rgba(15, 23, 42, 0.92);
|
|
173
|
+
font-size: 15px;
|
|
174
|
+
font-weight: 700;
|
|
175
|
+
line-height: 1.2;
|
|
176
|
+
overflow-wrap: anywhere;
|
|
177
|
+
}
|
|
178
|
+
body.dark .app-header-info-popover-title {
|
|
179
|
+
color: rgba(248, 250, 252, 0.94);
|
|
180
|
+
}
|
|
181
|
+
.app-header-info-popover-description {
|
|
182
|
+
margin-top: 5px;
|
|
183
|
+
color: rgba(71, 85, 105, 0.82);
|
|
184
|
+
font-size: 12px;
|
|
185
|
+
font-weight: 500;
|
|
186
|
+
line-height: 1.4;
|
|
187
|
+
overflow-wrap: anywhere;
|
|
188
|
+
}
|
|
189
|
+
body.dark .app-header-info-popover-description {
|
|
190
|
+
color: rgba(203, 213, 225, 0.78);
|
|
191
|
+
}
|
|
192
|
+
.app-header-info-popover-link,
|
|
193
|
+
.app-header-info-popover-link--muted {
|
|
194
|
+
margin-top: 12px;
|
|
195
|
+
min-height: 32px;
|
|
196
|
+
display: flex;
|
|
197
|
+
align-items: center;
|
|
198
|
+
justify-content: space-between;
|
|
199
|
+
gap: 10px;
|
|
200
|
+
padding: 0 10px;
|
|
201
|
+
border-radius: 7px;
|
|
202
|
+
border: 1px solid rgba(15, 23, 42, 0.1);
|
|
203
|
+
background: rgba(15, 23, 42, 0.035);
|
|
204
|
+
color: rgba(15, 23, 42, 0.88);
|
|
205
|
+
font-size: 12px;
|
|
206
|
+
font-weight: 650;
|
|
207
|
+
line-height: 1.2;
|
|
208
|
+
text-decoration: none;
|
|
209
|
+
box-sizing: border-box;
|
|
210
|
+
}
|
|
211
|
+
.app-header-info-popover-link:hover {
|
|
212
|
+
background: rgba(15, 23, 42, 0.07);
|
|
213
|
+
}
|
|
214
|
+
.app-header-info-popover-link span {
|
|
215
|
+
min-width: 0;
|
|
216
|
+
overflow: hidden;
|
|
217
|
+
text-overflow: ellipsis;
|
|
218
|
+
white-space: nowrap;
|
|
219
|
+
}
|
|
220
|
+
.app-header-info-popover-link:focus-visible {
|
|
221
|
+
outline: 2px solid rgba(10, 132, 255, 0.55);
|
|
222
|
+
outline-offset: 2px;
|
|
223
|
+
}
|
|
224
|
+
body.dark .app-header-info-popover-link,
|
|
225
|
+
body.dark .app-header-info-popover-link--muted {
|
|
226
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
227
|
+
background: rgba(255, 255, 255, 0.06);
|
|
228
|
+
color: rgba(248, 250, 252, 0.9);
|
|
229
|
+
}
|
|
230
|
+
body.dark .app-header-info-popover-link:hover {
|
|
231
|
+
background: rgba(255, 255, 255, 0.09);
|
|
232
|
+
}
|
|
233
|
+
.app-header-info-popover-link--muted {
|
|
234
|
+
justify-content: flex-start;
|
|
235
|
+
color: rgba(100, 116, 139, 0.78);
|
|
236
|
+
}
|
|
237
|
+
body.dark .app-header-info-popover-link--muted {
|
|
238
|
+
color: rgba(148, 163, 184, 0.86);
|
|
239
|
+
}
|
|
73
240
|
.app-header-identity-icon {
|
|
74
241
|
width: 18px;
|
|
75
242
|
height: 18px;
|
|
@@ -83,6 +250,9 @@ body.dark .app-header-identity {
|
|
|
83
250
|
text-overflow: ellipsis;
|
|
84
251
|
white-space: nowrap;
|
|
85
252
|
}
|
|
253
|
+
.app-header-info-trigger .app-header-identity-title {
|
|
254
|
+
flex: 1 1 auto;
|
|
255
|
+
}
|
|
86
256
|
.app-header-identity-separator {
|
|
87
257
|
width: 3px;
|
|
88
258
|
height: 3px;
|
|
@@ -6699,12 +6869,35 @@ header.navheader h1 {
|
|
|
6699
6869
|
border: 1px solid rgba(0,0,0,0.08);
|
|
6700
6870
|
background: rgba(255, 255, 255, 0.78);
|
|
6701
6871
|
box-sizing: border-box;
|
|
6702
|
-
overflow:
|
|
6872
|
+
overflow: visible;
|
|
6703
6873
|
}
|
|
6704
6874
|
body.dark .mobile-bottom-nav__identity {
|
|
6705
6875
|
border-color: rgba(255,255,255,0.08);
|
|
6706
6876
|
background: rgba(255,255,255,0.06);
|
|
6707
6877
|
}
|
|
6878
|
+
.mobile-bottom-nav .app-header-info {
|
|
6879
|
+
flex: 0 1 42%;
|
|
6880
|
+
max-width: 42%;
|
|
6881
|
+
}
|
|
6882
|
+
.mobile-bottom-nav .app-header-info-trigger {
|
|
6883
|
+
width: 100%;
|
|
6884
|
+
max-width: 100%;
|
|
6885
|
+
height: auto;
|
|
6886
|
+
min-height: 18px;
|
|
6887
|
+
gap: 5px;
|
|
6888
|
+
padding: 0;
|
|
6889
|
+
margin: 0;
|
|
6890
|
+
}
|
|
6891
|
+
.mobile-bottom-nav .app-header-info-trigger:hover,
|
|
6892
|
+
.mobile-bottom-nav .app-header-info-trigger[aria-expanded="true"] {
|
|
6893
|
+
background: transparent;
|
|
6894
|
+
}
|
|
6895
|
+
.mobile-bottom-nav .app-header-info-popover {
|
|
6896
|
+
top: auto;
|
|
6897
|
+
bottom: calc(100% + 9px);
|
|
6898
|
+
left: -46px;
|
|
6899
|
+
width: min(390px, calc(100vw - 24px));
|
|
6900
|
+
}
|
|
6708
6901
|
.mobile-bottom-nav__identity-icon {
|
|
6709
6902
|
width: 16px;
|
|
6710
6903
|
height: 16px;
|
|
@@ -6714,7 +6907,7 @@ header.navheader h1 {
|
|
|
6714
6907
|
}
|
|
6715
6908
|
.mobile-bottom-nav__identity-title {
|
|
6716
6909
|
min-width: 0;
|
|
6717
|
-
max-width:
|
|
6910
|
+
max-width: 100%;
|
|
6718
6911
|
overflow: hidden;
|
|
6719
6912
|
text-overflow: ellipsis;
|
|
6720
6913
|
white-space: nowrap;
|
|
@@ -7864,6 +8057,10 @@ body.dark .pinokio-custom-terminal-header {
|
|
|
7864
8057
|
})()
|
|
7865
8058
|
</script>
|
|
7866
8059
|
</head>
|
|
8060
|
+
<%
|
|
8061
|
+
const appInfoRemoteUrl = typeof launcher_remote_url === "string" ? launcher_remote_url : ""
|
|
8062
|
+
const appInfoRemoteDisplayUrl = appInfoRemoteUrl.replace(/^https?:\/\//i, "")
|
|
8063
|
+
%>
|
|
7867
8064
|
<body class='<%= [theme, "app-page"].filter(Boolean).join(" ") %>' data-platform="<%=platform%>" data-agent="<%=agent%>" data-view="<%=type%>">
|
|
7868
8065
|
<header class='navheader grabbable'>
|
|
7869
8066
|
<h1>
|
|
@@ -7893,10 +8090,32 @@ body.dark .pinokio-custom-terminal-header {
|
|
|
7893
8090
|
</button>
|
|
7894
8091
|
<div class='sep'></div>
|
|
7895
8092
|
<div class='app-header-identity' title="<%=config.title || name%>">
|
|
7896
|
-
|
|
7897
|
-
<
|
|
7898
|
-
|
|
7899
|
-
|
|
8093
|
+
<div class="app-header-info" data-app-info-root>
|
|
8094
|
+
<button type="button" class="app-header-info-trigger" data-app-header-info-trigger aria-haspopup="dialog" aria-expanded="false" aria-label="App information">
|
|
8095
|
+
<% if (config.icon) { %>
|
|
8096
|
+
<img class='app-header-identity-icon' src="<%=config.icon%>" alt="" aria-hidden="true"/>
|
|
8097
|
+
<% } %>
|
|
8098
|
+
<span class='app-header-identity-title'><%=config.title || name%></span>
|
|
8099
|
+
</button>
|
|
8100
|
+
<div class="app-header-info-popover hidden" data-app-header-info-popover role="dialog" aria-label="App information">
|
|
8101
|
+
<div class="app-header-info-popover-head">Launcher</div>
|
|
8102
|
+
<div class="app-header-info-popover-main">
|
|
8103
|
+
<img class="app-header-info-popover-icon" src="<%=config.icon || '/pinokio-black.png'%>" alt="" aria-hidden="true"/>
|
|
8104
|
+
<div class="app-header-info-popover-copy">
|
|
8105
|
+
<div class="app-header-info-popover-title"><%=config.title || name%></div>
|
|
8106
|
+
<div class="app-header-info-popover-description"><%=config.description || 'No description provided.'%></div>
|
|
8107
|
+
</div>
|
|
8108
|
+
</div>
|
|
8109
|
+
<% if (appInfoRemoteUrl) { %>
|
|
8110
|
+
<a class="app-header-info-popover-link" href="<%=appInfoRemoteUrl%>" title="<%=appInfoRemoteUrl%>" rel="noopener noreferrer" data-app-info-external-link>
|
|
8111
|
+
<span><%=appInfoRemoteDisplayUrl%></span>
|
|
8112
|
+
<i class="fa-solid fa-arrow-up-right-from-square" aria-hidden="true"></i>
|
|
8113
|
+
</a>
|
|
8114
|
+
<% } else { %>
|
|
8115
|
+
<div class="app-header-info-popover-link--muted">No browser-openable remote detected</div>
|
|
8116
|
+
<% } %>
|
|
8117
|
+
</div>
|
|
8118
|
+
</div>
|
|
7900
8119
|
<span class='app-header-identity-separator' aria-hidden="true"></span>
|
|
7901
8120
|
<div class="resource-usage" data-resource-usage-root data-workspace="<%=name%>">
|
|
7902
8121
|
<button type="button" class="resource-usage-trigger" data-resource-usage-trigger aria-haspopup="dialog" aria-expanded="false" aria-label="Resource usage">
|
|
@@ -8419,10 +8638,32 @@ body.dark .pinokio-custom-terminal-header {
|
|
|
8419
8638
|
<img class='mobile-bottom-nav__logo' src="/pinokio-black.png" alt="" aria-hidden="true">
|
|
8420
8639
|
</button>
|
|
8421
8640
|
<div class='mobile-bottom-nav__identity' title="<%=config.title || name%>" aria-label="<%=config.title || name%>">
|
|
8422
|
-
|
|
8423
|
-
<
|
|
8424
|
-
|
|
8425
|
-
|
|
8641
|
+
<div class="app-header-info app-header-info--mobile" data-app-info-root>
|
|
8642
|
+
<button type="button" class="app-header-info-trigger" data-app-header-info-trigger aria-haspopup="dialog" aria-expanded="false" aria-label="App information">
|
|
8643
|
+
<% if (config.icon) { %>
|
|
8644
|
+
<img class='mobile-bottom-nav__identity-icon' src="<%=config.icon%>" alt="" aria-hidden="true"/>
|
|
8645
|
+
<% } %>
|
|
8646
|
+
<span class='mobile-bottom-nav__identity-title'><%=config.title || name%></span>
|
|
8647
|
+
</button>
|
|
8648
|
+
<div class="app-header-info-popover hidden" data-app-header-info-popover role="dialog" aria-label="App information">
|
|
8649
|
+
<div class="app-header-info-popover-head">Launcher</div>
|
|
8650
|
+
<div class="app-header-info-popover-main">
|
|
8651
|
+
<img class="app-header-info-popover-icon" src="<%=config.icon || '/pinokio-black.png'%>" alt="" aria-hidden="true"/>
|
|
8652
|
+
<div class="app-header-info-popover-copy">
|
|
8653
|
+
<div class="app-header-info-popover-title"><%=config.title || name%></div>
|
|
8654
|
+
<div class="app-header-info-popover-description"><%=config.description || 'No description provided.'%></div>
|
|
8655
|
+
</div>
|
|
8656
|
+
</div>
|
|
8657
|
+
<% if (appInfoRemoteUrl) { %>
|
|
8658
|
+
<a class="app-header-info-popover-link" href="<%=appInfoRemoteUrl%>" title="<%=appInfoRemoteUrl%>" rel="noopener noreferrer" data-app-info-external-link>
|
|
8659
|
+
<span><%=appInfoRemoteDisplayUrl%></span>
|
|
8660
|
+
<i class="fa-solid fa-arrow-up-right-from-square" aria-hidden="true"></i>
|
|
8661
|
+
</a>
|
|
8662
|
+
<% } else { %>
|
|
8663
|
+
<div class="app-header-info-popover-link--muted">No browser-openable remote detected</div>
|
|
8664
|
+
<% } %>
|
|
8665
|
+
</div>
|
|
8666
|
+
</div>
|
|
8426
8667
|
<span class='mobile-bottom-nav__identity-separator' aria-hidden="true"></span>
|
|
8427
8668
|
<div class="resource-usage" data-resource-usage-root data-resource-always-visible="true" data-workspace="<%=name%>">
|
|
8428
8669
|
<button type="button" class="resource-usage-trigger" data-resource-usage-trigger aria-haspopup="dialog" aria-expanded="false" aria-label="Resource usage">
|
|
@@ -16152,6 +16393,78 @@ const rerenderMenuSection = (container, html) => {
|
|
|
16152
16393
|
})()
|
|
16153
16394
|
</script>
|
|
16154
16395
|
<script>
|
|
16396
|
+
(() => {
|
|
16397
|
+
const appInfoRoots = Array.from(document.querySelectorAll("[data-app-info-root]"))
|
|
16398
|
+
if (appInfoRoots.length === 0) {
|
|
16399
|
+
return
|
|
16400
|
+
}
|
|
16401
|
+
|
|
16402
|
+
const closeAppInfoPopovers = (exceptRoot = null) => {
|
|
16403
|
+
appInfoRoots.forEach((root) => {
|
|
16404
|
+
if (root === exceptRoot) return
|
|
16405
|
+
const trigger = root.querySelector("[data-app-header-info-trigger]")
|
|
16406
|
+
const popover = root.querySelector("[data-app-header-info-popover]")
|
|
16407
|
+
if (trigger) trigger.setAttribute("aria-expanded", "false")
|
|
16408
|
+
if (popover) popover.classList.add("hidden")
|
|
16409
|
+
})
|
|
16410
|
+
}
|
|
16411
|
+
|
|
16412
|
+
appInfoRoots.forEach((root) => {
|
|
16413
|
+
const trigger = root.querySelector("[data-app-header-info-trigger]")
|
|
16414
|
+
const popover = root.querySelector("[data-app-header-info-popover]")
|
|
16415
|
+
if (!trigger || !popover) {
|
|
16416
|
+
return
|
|
16417
|
+
}
|
|
16418
|
+
trigger.addEventListener("click", (event) => {
|
|
16419
|
+
event.preventDefault()
|
|
16420
|
+
event.stopPropagation()
|
|
16421
|
+
const isOpen = !popover.classList.contains("hidden")
|
|
16422
|
+
closeAppInfoPopovers(root)
|
|
16423
|
+
popover.classList.toggle("hidden", isOpen)
|
|
16424
|
+
trigger.setAttribute("aria-expanded", isOpen ? "false" : "true")
|
|
16425
|
+
})
|
|
16426
|
+
root.querySelectorAll("[data-app-info-external-link]").forEach((link) => {
|
|
16427
|
+
link.addEventListener("click", (event) => {
|
|
16428
|
+
const href = link.getAttribute("href") || ""
|
|
16429
|
+
if (!href) {
|
|
16430
|
+
return
|
|
16431
|
+
}
|
|
16432
|
+
event.preventDefault()
|
|
16433
|
+
event.stopPropagation()
|
|
16434
|
+
const agent = document.body.getAttribute("data-agent")
|
|
16435
|
+
if (agent === "electron") {
|
|
16436
|
+
window.open(href, "_blank", "browser")
|
|
16437
|
+
} else {
|
|
16438
|
+
window.open(href, "_blank")
|
|
16439
|
+
}
|
|
16440
|
+
closeAppInfoPopovers()
|
|
16441
|
+
})
|
|
16442
|
+
})
|
|
16443
|
+
})
|
|
16444
|
+
|
|
16445
|
+
document.addEventListener("pointerdown", (event) => {
|
|
16446
|
+
const target = event.target
|
|
16447
|
+
if (target && target.closest && target.closest("[data-app-info-root]")) {
|
|
16448
|
+
return
|
|
16449
|
+
}
|
|
16450
|
+
closeAppInfoPopovers()
|
|
16451
|
+
}, true)
|
|
16452
|
+
window.addEventListener("blur", () => {
|
|
16453
|
+
window.setTimeout(() => {
|
|
16454
|
+
const activeElement = document.activeElement
|
|
16455
|
+
if (activeElement && activeElement.tagName === "IFRAME") {
|
|
16456
|
+
closeAppInfoPopovers()
|
|
16457
|
+
}
|
|
16458
|
+
}, 0)
|
|
16459
|
+
})
|
|
16460
|
+
document.addEventListener("keydown", (event) => {
|
|
16461
|
+
if (event.key === "Escape") {
|
|
16462
|
+
closeAppInfoPopovers()
|
|
16463
|
+
}
|
|
16464
|
+
})
|
|
16465
|
+
})()
|
|
16466
|
+
</script>
|
|
16467
|
+
<script>
|
|
16155
16468
|
(() => {
|
|
16156
16469
|
const appcanvas = document.querySelector(".appcanvas")
|
|
16157
16470
|
const aside = document.getElementById("app-sidebar")
|
|
@@ -537,6 +537,128 @@ test("app sidebar auto-peeks on collapsed idle run page and dismisses after clic
|
|
|
537
537
|
assert.equal(activeDom.window.localStorage.getItem(storageKey), "1")
|
|
538
538
|
})
|
|
539
539
|
|
|
540
|
+
test("app identity popover exposes launcher metadata and remote browser link", async () => {
|
|
541
|
+
const appView = await fs.readFile(path.resolve(root, "server/views/app.ejs"), "utf8")
|
|
542
|
+
const server = await fs.readFile(path.resolve(root, "server/index.js"), "utf8")
|
|
543
|
+
const chromeStart = server.indexOf(" async chrome(req, res, type, options) {")
|
|
544
|
+
const existingCommunityGitConfigIndex = server.indexOf("const gitRemote = await git.getConfig", chromeStart)
|
|
545
|
+
const helperIndex = server.indexOf("const buildGitRemoteWebUrl = (value) =>", chromeStart)
|
|
546
|
+
const launcherRemoteIndex = server.indexOf("const launcherRemote =", chromeStart)
|
|
547
|
+
const renderPropIndex = server.indexOf("launcher_remote_url: launcherRemoteUrl", launcherRemoteIndex)
|
|
548
|
+
|
|
549
|
+
assert.ok(chromeStart >= 0)
|
|
550
|
+
assert.ok(existingCommunityGitConfigIndex > chromeStart)
|
|
551
|
+
assert.ok(helperIndex > existingCommunityGitConfigIndex)
|
|
552
|
+
assert.ok(helperIndex < launcherRemoteIndex)
|
|
553
|
+
assert.ok(renderPropIndex > launcherRemoteIndex)
|
|
554
|
+
assert.match(server, /kernel\.api\.parentGitURI\(this\.kernel\.path\("api", name\)\)/)
|
|
555
|
+
assert.match(server, /launcherRemoteUrl = buildGitRemoteWebUrl\(launcherRemote\)/)
|
|
556
|
+
assert.match(server, /launcher_remote_url: launcherRemoteUrl/)
|
|
557
|
+
assert.match(server, /const buildGitRemoteWebUrl = \(value\) =>/)
|
|
558
|
+
assert.match(server, /return `https:\/\/\$\{sshUrlMatch\[1\]\}\/\$\{sshUrlMatch\[2\]\}`/)
|
|
559
|
+
assert.match(server, /return `https:\/\/\$\{scpMatch\[1\]\}\/\$\{scpMatch\[2\]\}`/)
|
|
560
|
+
assert.match(appView, /data-app-info-root/)
|
|
561
|
+
assert.match(appView, /data-app-header-info-trigger/)
|
|
562
|
+
assert.match(appView, /data-app-header-info-popover/)
|
|
563
|
+
assert.match(appView, /width: min\(390px, calc\(100vw - 24px\)\);/)
|
|
564
|
+
assert.match(appView, /class="app-header-info-popover-icon" src="<%=config\.icon \|\| '\/pinokio-black\.png'%>"/)
|
|
565
|
+
assert.match(appView, /const appInfoRemoteUrl = typeof launcher_remote_url === "string" \? launcher_remote_url : ""/)
|
|
566
|
+
assert.ok(appView.includes('const appInfoRemoteDisplayUrl = appInfoRemoteUrl.replace(/^https?:\\/\\//i, "")'))
|
|
567
|
+
assert.match(appView, /href="<%=appInfoRemoteUrl%>" title="<%=appInfoRemoteUrl%>" rel="noopener noreferrer" data-app-info-external-link/)
|
|
568
|
+
assert.match(appView, /<span><%=appInfoRemoteDisplayUrl%><\/span>/)
|
|
569
|
+
assert.match(appView, /text-overflow: ellipsis/)
|
|
570
|
+
assert.match(appView, /white-space: nowrap/)
|
|
571
|
+
assert.match(appView, /window\.open\(href, "_blank"\)/)
|
|
572
|
+
assert.match(appView, /window\.open\(href, "_blank", "browser"\)/)
|
|
573
|
+
assert.match(appView, /No browser-openable remote detected/)
|
|
574
|
+
assert.match(appView, /\.mobile-bottom-nav__identity \{[\s\S]*overflow: visible;/)
|
|
575
|
+
assert.match(appView, /\.mobile-bottom-nav \.app-header-info-popover \{[\s\S]*bottom: calc\(100% \+ 9px\);[\s\S]*width: min\(390px, calc\(100vw - 24px\)\);/)
|
|
576
|
+
assert.match(appView, /\.mobile-bottom-nav \.resource-usage-popover \{[\s\S]*bottom: calc\(100% \+ 9px\);/)
|
|
577
|
+
assert.doesNotMatch(appView, /Open launcher remote/)
|
|
578
|
+
assert.doesNotMatch(appView, /fetch\("\/go"/)
|
|
579
|
+
assert.doesNotMatch(appView, /app-header-info-popover-link"[^>]*features="browser"/)
|
|
580
|
+
assert.doesNotMatch(appView, /appInfoGitHistoryUrl|hydrateAppInfoGithubUrl|buildAppInfoGithubUrl|launcher_github_url|Open launcher on GitHub|No GitHub remote detected/)
|
|
581
|
+
|
|
582
|
+
const start = appView.indexOf('<script>\n(() => {\n const appInfoRoots = Array.from(document.querySelectorAll("[data-app-info-root]"))')
|
|
583
|
+
const end = appView.indexOf("</script>", start)
|
|
584
|
+
assert.notEqual(start, -1)
|
|
585
|
+
assert.notEqual(end, -1)
|
|
586
|
+
const appInfoScript = appView.slice(start + "<script>\n".length, end)
|
|
587
|
+
const dom = new JSDOM(`<!doctype html>
|
|
588
|
+
<body>
|
|
589
|
+
<div data-app-info-root>
|
|
590
|
+
<button type="button" data-app-header-info-trigger aria-expanded="false">Demo</button>
|
|
591
|
+
<div class="hidden" data-app-header-info-popover>
|
|
592
|
+
<a class="app-header-info-popover-link" href="https://gitlab.com/octocat/demo" title="https://gitlab.com/octocat/demo" data-app-info-external-link>gitlab.com/octocat/demo</a>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
<iframe></iframe>
|
|
596
|
+
</body>`, {
|
|
597
|
+
url: "http://127.0.0.1:42000/v/demo",
|
|
598
|
+
runScripts: "outside-only",
|
|
599
|
+
pretendToBeVisual: true
|
|
600
|
+
})
|
|
601
|
+
const openedUrls = []
|
|
602
|
+
dom.window.open = (url, target, features) => {
|
|
603
|
+
openedUrls.push({ url, target, features })
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
dom.window.eval(appInfoScript)
|
|
607
|
+
const trigger = dom.window.document.querySelector("[data-app-header-info-trigger]")
|
|
608
|
+
const popover = dom.window.document.querySelector("[data-app-header-info-popover]")
|
|
609
|
+
const click = () => trigger.dispatchEvent(new dom.window.MouseEvent("click", { bubbles: true, cancelable: true }))
|
|
610
|
+
|
|
611
|
+
assert.equal(popover.classList.contains("hidden"), true)
|
|
612
|
+
assert.equal(trigger.getAttribute("aria-expanded"), "false")
|
|
613
|
+
const remoteLink = dom.window.document.querySelector(".app-header-info-popover-link")
|
|
614
|
+
assert.ok(remoteLink)
|
|
615
|
+
assert.equal(remoteLink.getAttribute("href"), "https://gitlab.com/octocat/demo")
|
|
616
|
+
assert.equal(remoteLink.getAttribute("title"), "https://gitlab.com/octocat/demo")
|
|
617
|
+
assert.equal(remoteLink.hasAttribute("target"), false)
|
|
618
|
+
assert.equal(remoteLink.hasAttribute("features"), false)
|
|
619
|
+
assert.equal(remoteLink.textContent.trim(), "gitlab.com/octocat/demo")
|
|
620
|
+
|
|
621
|
+
click()
|
|
622
|
+
assert.equal(popover.classList.contains("hidden"), false)
|
|
623
|
+
assert.equal(trigger.getAttribute("aria-expanded"), "true")
|
|
624
|
+
remoteLink.dispatchEvent(new dom.window.MouseEvent("click", { bubbles: true, cancelable: true }))
|
|
625
|
+
assert.equal(openedUrls.length, 1)
|
|
626
|
+
assert.deepEqual(openedUrls[0], {
|
|
627
|
+
url: "https://gitlab.com/octocat/demo",
|
|
628
|
+
target: "_blank",
|
|
629
|
+
features: undefined
|
|
630
|
+
})
|
|
631
|
+
assert.equal(popover.classList.contains("hidden"), true)
|
|
632
|
+
assert.equal(trigger.getAttribute("aria-expanded"), "false")
|
|
633
|
+
|
|
634
|
+
click()
|
|
635
|
+
assert.equal(popover.classList.contains("hidden"), false)
|
|
636
|
+
assert.equal(trigger.getAttribute("aria-expanded"), "true")
|
|
637
|
+
|
|
638
|
+
click()
|
|
639
|
+
assert.equal(popover.classList.contains("hidden"), true)
|
|
640
|
+
assert.equal(trigger.getAttribute("aria-expanded"), "false")
|
|
641
|
+
|
|
642
|
+
click()
|
|
643
|
+
dom.window.document.body.dispatchEvent(new dom.window.Event("pointerdown", { bubbles: true }))
|
|
644
|
+
assert.equal(popover.classList.contains("hidden"), true)
|
|
645
|
+
assert.equal(trigger.getAttribute("aria-expanded"), "false")
|
|
646
|
+
|
|
647
|
+
click()
|
|
648
|
+
dom.window.document.dispatchEvent(new dom.window.KeyboardEvent("keydown", { key: "Escape", bubbles: true }))
|
|
649
|
+
assert.equal(popover.classList.contains("hidden"), true)
|
|
650
|
+
assert.equal(trigger.getAttribute("aria-expanded"), "false")
|
|
651
|
+
|
|
652
|
+
dom.window.document.body.setAttribute("data-agent", "electron")
|
|
653
|
+
click()
|
|
654
|
+
remoteLink.dispatchEvent(new dom.window.MouseEvent("click", { bubbles: true, cancelable: true }))
|
|
655
|
+
assert.deepEqual(openedUrls[1], {
|
|
656
|
+
url: "https://gitlab.com/octocat/demo",
|
|
657
|
+
target: "_blank",
|
|
658
|
+
features: "browser"
|
|
659
|
+
})
|
|
660
|
+
})
|
|
661
|
+
|
|
540
662
|
test("static guard: home run command selection opens without launching before selecting command", async () => {
|
|
541
663
|
const homeRunMenu = await fs.readFile(path.resolve(root, "server/views/partials/home_run_menu.ejs"), "utf8")
|
|
542
664
|
|