pinokiod 3.107.0 → 3.108.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/kernel/bin/git.js +12 -0
- package/kernel/environment.js +76 -0
- package/kernel/scripts/git/fork +21 -0
- package/kernel/scripts/git/push +1 -1
- package/package.json +1 -1
- package/server/index.js +34 -21
- package/server/views/app.ejs +960 -74
- package/server/views/pro.ejs +1 -1
package/server/views/app.ejs
CHANGED
|
@@ -1378,6 +1378,15 @@ body.dark #fs-status {
|
|
|
1378
1378
|
z-index: 20;
|
|
1379
1379
|
}
|
|
1380
1380
|
|
|
1381
|
+
.fs-status-dropdown.git-fork .fs-dropdown-menu,
|
|
1382
|
+
.fs-status-dropdown.git-publish .fs-dropdown-menu {
|
|
1383
|
+
left: auto;
|
|
1384
|
+
right: 0;
|
|
1385
|
+
width: min(420px, calc(100vw - 24px));
|
|
1386
|
+
max-width: min(420px, calc(100vw - 24px));
|
|
1387
|
+
white-space: normal;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1381
1390
|
body.dark .fs-dropdown-menu {
|
|
1382
1391
|
background: rgba(30, 41, 59, 0.96);
|
|
1383
1392
|
border: 1px solid rgba(148, 163, 184, 0.35);
|
|
@@ -2114,6 +2123,157 @@ body.dark {
|
|
|
2114
2123
|
background: rgba(127, 91, 243, 1) !important;
|
|
2115
2124
|
}
|
|
2116
2125
|
|
|
2126
|
+
.pinokio-modal-body--fork {
|
|
2127
|
+
display: flex;
|
|
2128
|
+
flex-direction: column;
|
|
2129
|
+
gap: 16px;
|
|
2130
|
+
padding: 0;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
.pinokio-fork-modal {
|
|
2134
|
+
display: flex;
|
|
2135
|
+
flex-direction: column;
|
|
2136
|
+
gap: 16px;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
.pinokio-fork-help {
|
|
2140
|
+
margin: 0;
|
|
2141
|
+
font-size: 0.875rem;
|
|
2142
|
+
color: rgba(255, 255, 255, 0.75);
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
.pinokio-fork-item {
|
|
2146
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
2147
|
+
border-radius: 10px;
|
|
2148
|
+
padding: 14px 16px;
|
|
2149
|
+
display: flex;
|
|
2150
|
+
flex-direction: column;
|
|
2151
|
+
gap: 10px;
|
|
2152
|
+
background: rgba(15, 18, 24, 0.65);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
.pinokio-fork-item[data-disabled='true'] {
|
|
2156
|
+
opacity: 0.55;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
.pinokio-fork-item-header {
|
|
2160
|
+
display: flex;
|
|
2161
|
+
align-items: center;
|
|
2162
|
+
gap: 10px;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
.pinokio-fork-item-header label {
|
|
2166
|
+
flex: 1;
|
|
2167
|
+
display: flex;
|
|
2168
|
+
align-items: center;
|
|
2169
|
+
gap: 8px;
|
|
2170
|
+
cursor: pointer;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
.pinokio-fork-item-title {
|
|
2174
|
+
font-weight: 600;
|
|
2175
|
+
font-size: 0.95rem;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
.pinokio-fork-item-url {
|
|
2179
|
+
font-size: 0.85rem;
|
|
2180
|
+
color: rgba(255, 255, 255, 0.65);
|
|
2181
|
+
word-break: break-word;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
.pinokio-fork-item-url.empty {
|
|
2185
|
+
font-style: italic;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
.pinokio-fork-name-input {
|
|
2189
|
+
display: flex;
|
|
2190
|
+
flex-direction: column;
|
|
2191
|
+
gap: 6px;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
.pinokio-fork-name-input label {
|
|
2195
|
+
font-size: 0.8rem;
|
|
2196
|
+
font-weight: 500;
|
|
2197
|
+
text-transform: uppercase;
|
|
2198
|
+
letter-spacing: 0.04em;
|
|
2199
|
+
color: rgba(255, 255, 255, 0.7);
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
.pinokio-modal-input.pinokio-modal-input--error {
|
|
2203
|
+
border-color: #ff6b6b;
|
|
2204
|
+
box-shadow: 0 0 0 1px rgba(255, 107, 107, 0.25);
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
.pinokio-fork-checkbox-row {
|
|
2208
|
+
display: flex;
|
|
2209
|
+
align-items: center;
|
|
2210
|
+
gap: 8px;
|
|
2211
|
+
font-size: 0.85rem;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
.pinokio-fork-checkbox-row label {
|
|
2215
|
+
cursor: pointer;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
.pinokio-fork-org-input {
|
|
2219
|
+
display: flex;
|
|
2220
|
+
flex-direction: column;
|
|
2221
|
+
gap: 6px;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
.pinokio-fork-org-input.hidden {
|
|
2225
|
+
display: none !important;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
.pinokio-fork-org-input label {
|
|
2229
|
+
font-size: 0.8rem;
|
|
2230
|
+
font-weight: 500;
|
|
2231
|
+
text-transform: uppercase;
|
|
2232
|
+
letter-spacing: 0.04em;
|
|
2233
|
+
color: rgba(255, 255, 255, 0.7);
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
.pinokio-fork-org-hint {
|
|
2237
|
+
margin: 0;
|
|
2238
|
+
font-size: 0.75rem;
|
|
2239
|
+
color: rgba(255, 255, 255, 0.55);
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
.pinokio-fork-dropdown-item,
|
|
2243
|
+
.pinokio-publish-dropdown-item {
|
|
2244
|
+
display: flex;
|
|
2245
|
+
flex-direction: column;
|
|
2246
|
+
align-items: flex-start;
|
|
2247
|
+
gap: 4px;
|
|
2248
|
+
text-align: left;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
.pinokio-fork-dropdown-title,
|
|
2252
|
+
.pinokio-publish-dropdown-title {
|
|
2253
|
+
font-weight: 600;
|
|
2254
|
+
font-size: 0.9rem;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
.pinokio-fork-dropdown-remote,
|
|
2258
|
+
.pinokio-publish-dropdown-remote {
|
|
2259
|
+
font-size: 0.75rem;
|
|
2260
|
+
color: rgba(255, 255, 255, 0.6);
|
|
2261
|
+
word-break: break-word;
|
|
2262
|
+
white-space: normal;
|
|
2263
|
+
overflow-wrap: anywhere;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
.pinokio-fork-dropdown-remote.empty,
|
|
2267
|
+
.pinokio-publish-dropdown-remote.empty {
|
|
2268
|
+
font-style: italic;
|
|
2269
|
+
color: rgba(255, 255, 255, 0.45);
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
.fs-dropdown-item--disabled {
|
|
2273
|
+
opacity: 0.5;
|
|
2274
|
+
cursor: not-allowed;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2117
2277
|
.pinokio-git-history-list {
|
|
2118
2278
|
display: flex;
|
|
2119
2279
|
flex-direction: column;
|
|
@@ -2526,13 +2686,16 @@ body.dark {
|
|
|
2526
2686
|
font-size: 1rem;
|
|
2527
2687
|
}
|
|
2528
2688
|
*/
|
|
2529
|
-
#fs-push-btn
|
|
2689
|
+
#fs-push-btn,
|
|
2690
|
+
#fs-fork-btn {
|
|
2530
2691
|
min-width: 0;
|
|
2531
2692
|
}
|
|
2532
|
-
#fs-push-btn .fs-status-label
|
|
2693
|
+
#fs-push-btn .fs-status-label,
|
|
2694
|
+
#fs-fork-btn .fs-status-label {
|
|
2533
2695
|
font-size: 0;
|
|
2534
2696
|
}
|
|
2535
|
-
#fs-push-btn .fs-status-label i
|
|
2697
|
+
#fs-push-btn .fs-status-label i,
|
|
2698
|
+
#fs-fork-btn .fs-status-label i {
|
|
2536
2699
|
font-size: 1rem;
|
|
2537
2700
|
}
|
|
2538
2701
|
#fs-status .fs-status-btn .disk-usage,
|
|
@@ -2679,7 +2842,7 @@ body.dark {
|
|
|
2679
2842
|
<% } %>
|
|
2680
2843
|
<div class='container'>
|
|
2681
2844
|
<% if (type === "browse") { %>
|
|
2682
|
-
<div id='fs-status' data-workspace="<%=name%>" data-create-uri="<%=git_create_url%>" data-history-uri="<%=git_history_url%>" data-status-uri="<%=git_status_url%>" data-uri="<%=git_monitor_url%>" data-push-uri="<%=git_push_url%>">
|
|
2845
|
+
<div id='fs-status' data-workspace="<%=name%>" data-create-uri="<%=git_create_url%>" data-history-uri="<%=git_history_url%>" data-status-uri="<%=git_status_url%>" data-uri="<%=git_monitor_url%>" data-push-uri="<%=git_push_url%>" data-fork-uri="<%=git_fork_url%>">
|
|
2683
2846
|
<a target="<%=src%>" href="<%=src%>" class='fs-status-btn frame-link' data-index="0" data-mode="refresh" data-type="n">
|
|
2684
2847
|
<span class='fs-status-label'>
|
|
2685
2848
|
<i class="fa-regular fa-folder-open"></i>
|
|
@@ -2733,12 +2896,24 @@ body.dark {
|
|
|
2733
2896
|
</button>
|
|
2734
2897
|
<div class='fs-dropdown-menu submenu hidden' id='fs-changes-menu'></div>
|
|
2735
2898
|
</div>
|
|
2736
|
-
<
|
|
2737
|
-
<
|
|
2738
|
-
<
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2899
|
+
<div class='fs-status-dropdown git-fork'>
|
|
2900
|
+
<button id='fs-fork-btn' class='fs-status-btn revealer' data-group='#fs-fork-menu' type='button'>
|
|
2901
|
+
<span class='fs-status-label'>
|
|
2902
|
+
<i class="fa-solid fa-code-branch"></i>
|
|
2903
|
+
<span class='fs-status-title'>Fork</span>
|
|
2904
|
+
</span>
|
|
2905
|
+
</button>
|
|
2906
|
+
<div class='fs-dropdown-menu submenu hidden' id='fs-fork-menu'></div>
|
|
2907
|
+
</div>
|
|
2908
|
+
<div class='fs-status-dropdown git-publish'>
|
|
2909
|
+
<button id='fs-push-btn' class='fs-status-btn revealer' data-group='#fs-push-menu' type='button'>
|
|
2910
|
+
<span class='fs-status-label'>
|
|
2911
|
+
<i class="fa-brands fa-github"></i>
|
|
2912
|
+
<span class='fs-status-title'>Publish</span>
|
|
2913
|
+
</span>
|
|
2914
|
+
</button>
|
|
2915
|
+
<div class='fs-dropdown-menu submenu hidden' id='fs-push-menu'></div>
|
|
2916
|
+
</div>
|
|
2742
2917
|
</div>
|
|
2743
2918
|
<% } %>
|
|
2744
2919
|
<main class='browserview'>
|
|
@@ -2772,17 +2947,20 @@ body.dark {
|
|
|
2772
2947
|
let ignorePersistedSelection = pluginLaunchActive
|
|
2773
2948
|
let lastForegroundSignature = null
|
|
2774
2949
|
const iframe_onerror = (iframe) => {
|
|
2950
|
+
if (iframe && iframe.dataset && iframe.dataset.forceVisible === 'true') {
|
|
2951
|
+
return
|
|
2952
|
+
}
|
|
2775
2953
|
let originalSrc = iframe.src
|
|
2776
2954
|
iframe.onload = function() {
|
|
2777
2955
|
try {
|
|
2778
2956
|
// Try to access the iframe's document
|
|
2957
|
+
const iframeDoc = iframe.contentDocument || (iframe.contentWindow ? iframe.contentWindow.document : null)
|
|
2779
2958
|
const currentSrc = iframe.src
|
|
2780
2959
|
// Check if it's a chrome error page or empty
|
|
2781
2960
|
if (currentSrc !== originalSrc &&
|
|
2782
2961
|
(currentSrc.includes('chrome-error://') ||
|
|
2783
2962
|
currentSrc === 'about:blank' ||
|
|
2784
2963
|
currentSrc.includes('data:'))) {
|
|
2785
|
-
iframeDoc.classList.add("hidden")
|
|
2786
2964
|
Swal.fire({
|
|
2787
2965
|
html: `<i class="fa-solid fa-circle-notch fa-spin"></i> Loading...`,
|
|
2788
2966
|
customClass: {
|
|
@@ -2798,22 +2976,9 @@ body.dark {
|
|
|
2798
2976
|
}, 3000)
|
|
2799
2977
|
}
|
|
2800
2978
|
} catch (e) {
|
|
2801
|
-
iframe.classList.add("hidden")
|
|
2802
2979
|
// Cross-origin restriction - assume it loaded successfully
|
|
2803
2980
|
// if no error was thrown during the initial load
|
|
2804
|
-
|
|
2805
|
-
html: `<i class="fa-solid fa-circle-notch fa-spin"></i> Loading...`,
|
|
2806
|
-
customClass: {
|
|
2807
|
-
container: "loader-container",
|
|
2808
|
-
popup: "loader-popup",
|
|
2809
|
-
htmlContainer: "loader-dialog",
|
|
2810
|
-
footer: "hidden",
|
|
2811
|
-
actions: "hidden"
|
|
2812
|
-
}
|
|
2813
|
-
});
|
|
2814
|
-
setTimeout(() => {
|
|
2815
|
-
location.href = location.href
|
|
2816
|
-
}, 3000);
|
|
2981
|
+
console.warn('Iframe load warning', e)
|
|
2817
2982
|
}
|
|
2818
2983
|
}
|
|
2819
2984
|
}
|
|
@@ -2821,7 +2986,7 @@ body.dark {
|
|
|
2821
2986
|
document.querySelectorAll(".menu-container .selected").forEach((el) => {
|
|
2822
2987
|
el.classList.remove("selected")
|
|
2823
2988
|
})
|
|
2824
|
-
document.querySelectorAll("iframe").forEach((el) => {
|
|
2989
|
+
document.querySelectorAll("main.browserview iframe").forEach((el) => {
|
|
2825
2990
|
el.classList.add("hidden")
|
|
2826
2991
|
})
|
|
2827
2992
|
let frame = document.createElement("iframe")
|
|
@@ -4588,7 +4753,7 @@ body.dark {
|
|
|
4588
4753
|
if (!sourceWindow) {
|
|
4589
4754
|
return null
|
|
4590
4755
|
}
|
|
4591
|
-
const frames = Array.from(document.querySelectorAll("iframe"))
|
|
4756
|
+
const frames = Array.from(document.querySelectorAll("main.browserview iframe"))
|
|
4592
4757
|
for (const frame of frames) {
|
|
4593
4758
|
if (frame.contentWindow === sourceWindow) {
|
|
4594
4759
|
return frame.name || null
|
|
@@ -4829,7 +4994,7 @@ body.dark {
|
|
|
4829
4994
|
<% } %>
|
|
4830
4995
|
|
|
4831
4996
|
// hide all frames
|
|
4832
|
-
document.querySelectorAll("iframe").forEach((el) => {
|
|
4997
|
+
document.querySelectorAll("main.browserview iframe").forEach((el) => {
|
|
4833
4998
|
el.classList.add("hidden")
|
|
4834
4999
|
})
|
|
4835
5000
|
|
|
@@ -5975,9 +6140,9 @@ body.dark {
|
|
|
5975
6140
|
} else {
|
|
5976
6141
|
global_selector = null
|
|
5977
6142
|
}
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
6143
|
+
const frameExists = Array.from(document.querySelectorAll("main.browserview iframe")).some((frame) => {
|
|
6144
|
+
return frame.name === rawName
|
|
6145
|
+
})
|
|
5981
6146
|
if (!frameExists) {
|
|
5982
6147
|
create_iframe(rawName, event.data.launch.href)
|
|
5983
6148
|
}
|
|
@@ -6082,6 +6247,13 @@ body.dark {
|
|
|
6082
6247
|
const changesMenu = document.getElementById('fs-changes-menu')
|
|
6083
6248
|
const changesBtn = document.getElementById('fs-changes-btn')
|
|
6084
6249
|
const badgeElement = changesBtn ? changesBtn.querySelector('.badge') : null
|
|
6250
|
+
const forkBtn = document.getElementById('fs-fork-btn')
|
|
6251
|
+
const forkDropdownContainer = document.querySelector('#fs-status .git-fork')
|
|
6252
|
+
const forkMenu = document.getElementById('fs-fork-menu')
|
|
6253
|
+
const pushBtn = document.getElementById('fs-push-btn')
|
|
6254
|
+
const publishMenu = document.getElementById('fs-push-menu')
|
|
6255
|
+
|
|
6256
|
+
let latestGitIntegration = null
|
|
6085
6257
|
|
|
6086
6258
|
const readDataAttr = (node, attr) => {
|
|
6087
6259
|
if (!node) {
|
|
@@ -6097,6 +6269,9 @@ body.dark {
|
|
|
6097
6269
|
const statusUri = readDataAttr(fsStatusEl, 'data-status-uri')
|
|
6098
6270
|
const monitorUri = readDataAttr(fsStatusEl, 'data-uri')
|
|
6099
6271
|
const workspaceName = readDataAttr(fsStatusEl, 'data-workspace')
|
|
6272
|
+
const historyUri = readDataAttr(fsStatusEl, 'data-history-uri')
|
|
6273
|
+
const defaultPushUri = readDataAttr(fsStatusEl, 'data-push-uri')
|
|
6274
|
+
const defaultForkUri = readDataAttr(fsStatusEl, 'data-fork-uri')
|
|
6100
6275
|
|
|
6101
6276
|
const encodeRepoPath = (value) => {
|
|
6102
6277
|
if (typeof value !== 'string' || value.length === 0) {
|
|
@@ -6105,6 +6280,191 @@ body.dark {
|
|
|
6105
6280
|
return value.split('/').map(encodeURIComponent).join('/')
|
|
6106
6281
|
}
|
|
6107
6282
|
|
|
6283
|
+
function escapeHtml(value) {
|
|
6284
|
+
if (value === null || value === undefined) {
|
|
6285
|
+
return ''
|
|
6286
|
+
}
|
|
6287
|
+
return String(value).replace(/[&<>"']/g, (match) => {
|
|
6288
|
+
switch (match) {
|
|
6289
|
+
case '&':
|
|
6290
|
+
return '&'
|
|
6291
|
+
case '<':
|
|
6292
|
+
return '<'
|
|
6293
|
+
case '>':
|
|
6294
|
+
return '>'
|
|
6295
|
+
case '"':
|
|
6296
|
+
return '"'
|
|
6297
|
+
case '\'':
|
|
6298
|
+
return '''
|
|
6299
|
+
default:
|
|
6300
|
+
return match
|
|
6301
|
+
}
|
|
6302
|
+
})
|
|
6303
|
+
}
|
|
6304
|
+
|
|
6305
|
+
function getRepoListSnapshot() {
|
|
6306
|
+
if (Array.isArray(lastRepoList) && lastRepoList.length > 0) {
|
|
6307
|
+
return lastRepoList.slice()
|
|
6308
|
+
}
|
|
6309
|
+
return Array.from(repoStatusCache.values())
|
|
6310
|
+
}
|
|
6311
|
+
|
|
6312
|
+
function parseRemoteSlug(remoteUrl) {
|
|
6313
|
+
if (!remoteUrl || typeof remoteUrl !== 'string') {
|
|
6314
|
+
return { owner: null, name: null, full: null }
|
|
6315
|
+
}
|
|
6316
|
+
const trimmed = remoteUrl.trim()
|
|
6317
|
+
if (!trimmed) {
|
|
6318
|
+
return { owner: null, name: null, full: null }
|
|
6319
|
+
}
|
|
6320
|
+
const withoutGit = trimmed.replace(/\.git$/i, '')
|
|
6321
|
+
|
|
6322
|
+
const parsePathSegments = (pathValue) => {
|
|
6323
|
+
if (!pathValue) {
|
|
6324
|
+
return []
|
|
6325
|
+
}
|
|
6326
|
+
const cleaned = pathValue.replace(/^\/+/, '')
|
|
6327
|
+
return cleaned.split('/').filter(Boolean)
|
|
6328
|
+
}
|
|
6329
|
+
|
|
6330
|
+
let pathSegment = ''
|
|
6331
|
+
if (withoutGit.startsWith('git@')) {
|
|
6332
|
+
const colonSplit = withoutGit.split(':')
|
|
6333
|
+
pathSegment = colonSplit.length > 1 ? colonSplit.slice(1).join(':') : ''
|
|
6334
|
+
} else {
|
|
6335
|
+
try {
|
|
6336
|
+
const prefixed = withoutGit.includes('://') ? withoutGit : `https://${withoutGit}`
|
|
6337
|
+
const url = new URL(prefixed)
|
|
6338
|
+
pathSegment = url.pathname
|
|
6339
|
+
} catch (error) {
|
|
6340
|
+
pathSegment = withoutGit
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
|
|
6344
|
+
const segments = parsePathSegments(pathSegment)
|
|
6345
|
+
if (segments.length >= 2) {
|
|
6346
|
+
const owner = segments[segments.length - 2]
|
|
6347
|
+
const name = segments[segments.length - 1]
|
|
6348
|
+
return { owner, name, full: `${owner}/${name}` }
|
|
6349
|
+
}
|
|
6350
|
+
|
|
6351
|
+
if (segments.length === 1) {
|
|
6352
|
+
const name = segments[0]
|
|
6353
|
+
return { owner: null, name, full: name }
|
|
6354
|
+
}
|
|
6355
|
+
|
|
6356
|
+
return { owner: null, name: null, full: null }
|
|
6357
|
+
}
|
|
6358
|
+
|
|
6359
|
+
function deriveForkDefaultName(repo) {
|
|
6360
|
+
if (!repo) {
|
|
6361
|
+
return workspaceName || 'fork'
|
|
6362
|
+
}
|
|
6363
|
+
const remoteSlug = parseRemoteSlug(repo.url || '')
|
|
6364
|
+
if (remoteSlug && remoteSlug.name) {
|
|
6365
|
+
return remoteSlug.name
|
|
6366
|
+
}
|
|
6367
|
+
if (typeof repo.name === 'string' && repo.name.length > 0) {
|
|
6368
|
+
const segments = repo.name.split('/').filter(Boolean)
|
|
6369
|
+
if (segments.length > 0) {
|
|
6370
|
+
return segments[segments.length - 1]
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
if (typeof repo.repoParam === 'string' && repo.repoParam.length > 0) {
|
|
6374
|
+
const repoSegments = repo.repoParam.split('/').filter(Boolean)
|
|
6375
|
+
if (repoSegments.length > 0) {
|
|
6376
|
+
return repoSegments[repoSegments.length - 1]
|
|
6377
|
+
}
|
|
6378
|
+
}
|
|
6379
|
+
return workspaceName || 'fork'
|
|
6380
|
+
}
|
|
6381
|
+
|
|
6382
|
+
function hasRemoteConfigured(repo) {
|
|
6383
|
+
return Boolean(repo && typeof repo.url === 'string' && repo.url.trim().length > 0)
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6386
|
+
function resolveForkUri(repo) {
|
|
6387
|
+
if (repo && typeof repo.git_fork_url === 'string' && repo.git_fork_url.length > 0) {
|
|
6388
|
+
return repo.git_fork_url
|
|
6389
|
+
}
|
|
6390
|
+
return defaultForkUri || null
|
|
6391
|
+
}
|
|
6392
|
+
|
|
6393
|
+
function resolvePushUri(repo) {
|
|
6394
|
+
if (repo && typeof repo.git_push_url === 'string' && repo.git_push_url.length > 0) {
|
|
6395
|
+
return repo.git_push_url
|
|
6396
|
+
}
|
|
6397
|
+
return defaultPushUri || null
|
|
6398
|
+
}
|
|
6399
|
+
|
|
6400
|
+
function updateForkButton() {
|
|
6401
|
+
if (!forkBtn) {
|
|
6402
|
+
return
|
|
6403
|
+
}
|
|
6404
|
+
const labelEl = forkBtn.querySelector('.fs-status-title')
|
|
6405
|
+
const repos = getRepoListSnapshot()
|
|
6406
|
+
const forkTargets = Array.isArray(repos)
|
|
6407
|
+
? repos.filter((repo) => hasRemoteConfigured(repo) && resolveForkUri(repo))
|
|
6408
|
+
: []
|
|
6409
|
+
|
|
6410
|
+
const detachHandlers = () => {
|
|
6411
|
+
forkBtn.removeEventListener('click', handlePushLogin)
|
|
6412
|
+
}
|
|
6413
|
+
|
|
6414
|
+
const enableDropdown = () => {
|
|
6415
|
+
forkBtn.classList.add('revealer')
|
|
6416
|
+
forkBtn.setAttribute('data-group', '#fs-fork-menu')
|
|
6417
|
+
}
|
|
6418
|
+
|
|
6419
|
+
const disableDropdown = () => {
|
|
6420
|
+
forkBtn.classList.remove('revealer')
|
|
6421
|
+
forkBtn.removeAttribute('data-group')
|
|
6422
|
+
closeStatusDropdowns()
|
|
6423
|
+
}
|
|
6424
|
+
|
|
6425
|
+
detachHandlers()
|
|
6426
|
+
|
|
6427
|
+
const isConnected = Boolean(latestGitIntegration && latestGitIntegration.connected)
|
|
6428
|
+
|
|
6429
|
+
renderForkDropdown(repos, {
|
|
6430
|
+
emptyMessage: isConnected
|
|
6431
|
+
? 'No remotes available to fork'
|
|
6432
|
+
: 'Connect GitHub to enable forking',
|
|
6433
|
+
})
|
|
6434
|
+
|
|
6435
|
+
if (!isConnected) {
|
|
6436
|
+
disableDropdown()
|
|
6437
|
+
if (labelEl) {
|
|
6438
|
+
labelEl.textContent = 'Login'
|
|
6439
|
+
}
|
|
6440
|
+
forkBtn.disabled = false
|
|
6441
|
+
forkBtn.classList.remove('fs-status-btn--disabled')
|
|
6442
|
+
forkBtn.setAttribute('title', 'Connect GitHub to fork this workspace')
|
|
6443
|
+
forkBtn.addEventListener('click', handlePushLogin)
|
|
6444
|
+
return
|
|
6445
|
+
}
|
|
6446
|
+
|
|
6447
|
+
if (labelEl) {
|
|
6448
|
+
labelEl.textContent = 'Fork'
|
|
6449
|
+
}
|
|
6450
|
+
|
|
6451
|
+
forkBtn.disabled = false
|
|
6452
|
+
forkBtn.classList.remove('fs-status-btn--disabled')
|
|
6453
|
+
enableDropdown()
|
|
6454
|
+
|
|
6455
|
+
if (!Array.isArray(repos) || repos.length === 0) {
|
|
6456
|
+
forkBtn.setAttribute('title', 'No Git repositories detected')
|
|
6457
|
+
return
|
|
6458
|
+
}
|
|
6459
|
+
|
|
6460
|
+
if (forkTargets.length === 0) {
|
|
6461
|
+
forkBtn.setAttribute('title', 'No remotes available to fork')
|
|
6462
|
+
return
|
|
6463
|
+
}
|
|
6464
|
+
|
|
6465
|
+
forkBtn.removeAttribute('title')
|
|
6466
|
+
}
|
|
6467
|
+
|
|
6108
6468
|
const updateCombinedBadge = (total) => {
|
|
6109
6469
|
if (!badgeElement) {
|
|
6110
6470
|
return
|
|
@@ -6136,6 +6496,143 @@ body.dark {
|
|
|
6136
6496
|
changesMenu.append(messageEl)
|
|
6137
6497
|
}
|
|
6138
6498
|
|
|
6499
|
+
const setForkMenuMessage = (message) => {
|
|
6500
|
+
if (!forkMenu) {
|
|
6501
|
+
return
|
|
6502
|
+
}
|
|
6503
|
+
const messageEl = document.createElement('div')
|
|
6504
|
+
messageEl.className = 'fs-dropdown-empty'
|
|
6505
|
+
messageEl.textContent = message
|
|
6506
|
+
forkMenu.innerHTML = ''
|
|
6507
|
+
forkMenu.append(messageEl)
|
|
6508
|
+
}
|
|
6509
|
+
|
|
6510
|
+
const setPublishMenuMessage = (message) => {
|
|
6511
|
+
if (!publishMenu) {
|
|
6512
|
+
return
|
|
6513
|
+
}
|
|
6514
|
+
const messageEl = document.createElement('div')
|
|
6515
|
+
messageEl.className = 'fs-dropdown-empty'
|
|
6516
|
+
messageEl.textContent = message
|
|
6517
|
+
publishMenu.innerHTML = ''
|
|
6518
|
+
publishMenu.append(messageEl)
|
|
6519
|
+
}
|
|
6520
|
+
|
|
6521
|
+
const renderForkDropdown = (repos, options = {}) => {
|
|
6522
|
+
if (!forkMenu) {
|
|
6523
|
+
return
|
|
6524
|
+
}
|
|
6525
|
+
|
|
6526
|
+
const opts = typeof options === 'object' && options !== null ? options : {}
|
|
6527
|
+
const list = Array.isArray(repos) ? repos : []
|
|
6528
|
+
|
|
6529
|
+
forkMenu.innerHTML = ''
|
|
6530
|
+
|
|
6531
|
+
if (list.length === 0) {
|
|
6532
|
+
setForkMenuMessage(opts.emptyMessage || 'No repositories available')
|
|
6533
|
+
return
|
|
6534
|
+
}
|
|
6535
|
+
|
|
6536
|
+
const fragment = document.createDocumentFragment()
|
|
6537
|
+
|
|
6538
|
+
list.forEach((repo) => {
|
|
6539
|
+
const key = repo && typeof repo.repoParam === 'string' ? repo.repoParam : ''
|
|
6540
|
+
const name = repo && repo.name ? repo.name : (key || 'Repository')
|
|
6541
|
+
const remoteUrl = repo && repo.url ? repo.url : ''
|
|
6542
|
+
const forkUri = resolveForkUri(repo)
|
|
6543
|
+
const hasRemote = hasRemoteConfigured(repo)
|
|
6544
|
+
|
|
6545
|
+
const item = document.createElement('button')
|
|
6546
|
+
item.type = 'button'
|
|
6547
|
+
item.className = 'fs-dropdown-item pinokio-fork-dropdown-item'
|
|
6548
|
+
item.dataset.repo = key
|
|
6549
|
+
|
|
6550
|
+
const remoteClass = remoteUrl
|
|
6551
|
+
? 'pinokio-fork-dropdown-remote'
|
|
6552
|
+
: 'pinokio-fork-dropdown-remote empty'
|
|
6553
|
+
const remoteDisplay = remoteUrl ? escapeHtml(remoteUrl) : 'No remote detected'
|
|
6554
|
+
|
|
6555
|
+
item.innerHTML = `
|
|
6556
|
+
<div class="pinokio-fork-dropdown-title">${escapeHtml(name)}</div>
|
|
6557
|
+
<div class="${remoteClass}">${remoteDisplay}</div>
|
|
6558
|
+
`
|
|
6559
|
+
|
|
6560
|
+
if (!hasRemote || !forkUri) {
|
|
6561
|
+
item.disabled = true
|
|
6562
|
+
item.classList.add('fs-dropdown-item--disabled')
|
|
6563
|
+
} else {
|
|
6564
|
+
item.addEventListener('click', (event) => {
|
|
6565
|
+
event.preventDefault()
|
|
6566
|
+
event.stopPropagation()
|
|
6567
|
+
closeStatusDropdowns()
|
|
6568
|
+
showForkModalForRepo(key)
|
|
6569
|
+
})
|
|
6570
|
+
}
|
|
6571
|
+
|
|
6572
|
+
fragment.appendChild(item)
|
|
6573
|
+
})
|
|
6574
|
+
|
|
6575
|
+
forkMenu.appendChild(fragment)
|
|
6576
|
+
}
|
|
6577
|
+
|
|
6578
|
+
const renderPublishDropdown = (repos, options = {}) => {
|
|
6579
|
+
if (!publishMenu) {
|
|
6580
|
+
return
|
|
6581
|
+
}
|
|
6582
|
+
|
|
6583
|
+
const opts = typeof options === 'object' && options !== null ? options : {}
|
|
6584
|
+
const list = Array.isArray(repos) ? repos : []
|
|
6585
|
+
|
|
6586
|
+
publishMenu.innerHTML = ''
|
|
6587
|
+
|
|
6588
|
+
if (list.length === 0) {
|
|
6589
|
+
setPublishMenuMessage(opts.emptyMessage || 'No repositories available')
|
|
6590
|
+
return
|
|
6591
|
+
}
|
|
6592
|
+
|
|
6593
|
+
const fragment = document.createDocumentFragment()
|
|
6594
|
+
|
|
6595
|
+
list.forEach((repo) => {
|
|
6596
|
+
const key = repo && typeof repo.repoParam === 'string' ? repo.repoParam : ''
|
|
6597
|
+
const name = repo && repo.name ? repo.name : (key || 'Repository')
|
|
6598
|
+
const remoteUrl = repo && repo.url ? repo.url : ''
|
|
6599
|
+
const pushUri = resolvePushUri(repo)
|
|
6600
|
+
const hasRemote = hasRemoteConfigured(repo)
|
|
6601
|
+
|
|
6602
|
+
const item = document.createElement('button')
|
|
6603
|
+
item.type = 'button'
|
|
6604
|
+
item.className = 'fs-dropdown-item pinokio-publish-dropdown-item'
|
|
6605
|
+
item.dataset.repo = key
|
|
6606
|
+
item.dataset.name = name
|
|
6607
|
+
|
|
6608
|
+
const remoteClass = remoteUrl
|
|
6609
|
+
? 'pinokio-publish-dropdown-remote'
|
|
6610
|
+
: 'pinokio-publish-dropdown-remote empty'
|
|
6611
|
+
const remoteDisplay = remoteUrl ? escapeHtml(remoteUrl) : 'No remote detected'
|
|
6612
|
+
|
|
6613
|
+
item.innerHTML = `
|
|
6614
|
+
<div class="pinokio-publish-dropdown-title">${escapeHtml(name)}</div>
|
|
6615
|
+
<div class="${remoteClass}">${remoteDisplay}</div>
|
|
6616
|
+
`
|
|
6617
|
+
|
|
6618
|
+
if (!hasRemote || !pushUri) {
|
|
6619
|
+
item.disabled = true
|
|
6620
|
+
item.classList.add('fs-dropdown-item--disabled')
|
|
6621
|
+
} else {
|
|
6622
|
+
item.addEventListener('click', (event) => {
|
|
6623
|
+
event.preventDefault()
|
|
6624
|
+
event.stopPropagation()
|
|
6625
|
+
closeStatusDropdowns()
|
|
6626
|
+
showPublishModal({ repoParam: key, repoName: name })
|
|
6627
|
+
})
|
|
6628
|
+
}
|
|
6629
|
+
|
|
6630
|
+
fragment.appendChild(item)
|
|
6631
|
+
})
|
|
6632
|
+
|
|
6633
|
+
publishMenu.appendChild(fragment)
|
|
6634
|
+
}
|
|
6635
|
+
|
|
6139
6636
|
const attachRepoDropdownHandlers = () => {
|
|
6140
6637
|
if (!changesMenu) {
|
|
6141
6638
|
return
|
|
@@ -6233,6 +6730,8 @@ body.dark {
|
|
|
6233
6730
|
changes: Array.isArray(data.changes) ? data.changes : [],
|
|
6234
6731
|
git_commit_url: data.git_commit_url || null,
|
|
6235
6732
|
git_history_url: workspaceName ? `/info/git/HEAD/${encodeRepoPath(workspaceName)}` : readDataAttr(fsStatusEl, 'data-history-uri'),
|
|
6733
|
+
git_fork_url: defaultForkUri,
|
|
6734
|
+
git_push_url: defaultPushUri,
|
|
6236
6735
|
url: null,
|
|
6237
6736
|
}
|
|
6238
6737
|
|
|
@@ -6246,12 +6745,14 @@ body.dark {
|
|
|
6246
6745
|
renderChangesDropdown(lastRepoList)
|
|
6247
6746
|
updateCombinedBadge(fallbackRepo.changeCount)
|
|
6248
6747
|
updatePublishButton()
|
|
6748
|
+
updateForkButton()
|
|
6249
6749
|
return true
|
|
6250
6750
|
} catch (error) {
|
|
6251
6751
|
console.error('check_git fallback error:', error)
|
|
6252
6752
|
setChangesMenuMessage('Unable to load repositories')
|
|
6253
6753
|
updateCombinedBadge(0)
|
|
6254
6754
|
updateChangesButtonState(false)
|
|
6755
|
+
updateForkButton()
|
|
6255
6756
|
return false
|
|
6256
6757
|
}
|
|
6257
6758
|
}
|
|
@@ -6316,6 +6817,7 @@ body.dark {
|
|
|
6316
6817
|
: sortedRepos.reduce((sum, repo) => sum + (repo.changeCount || 0), 0)
|
|
6317
6818
|
updateCombinedBadge(total)
|
|
6318
6819
|
updateChangesButtonState(true)
|
|
6820
|
+
updateForkButton()
|
|
6319
6821
|
}
|
|
6320
6822
|
|
|
6321
6823
|
updatePublishButton()
|
|
@@ -6526,6 +7028,12 @@ body.dark {
|
|
|
6526
7028
|
updatedRepo.git_commit_url = freshData.git_commit_url || null
|
|
6527
7029
|
updatedRepo.name = updatedRepo.name || repoDisplayName || repoParam
|
|
6528
7030
|
updatedRepo.git_history_url = updatedRepo.git_history_url || (repoData && repoData.git_history_url) || (repoParam ? `/info/git/HEAD/${encodeRepoPath(repoParam)}` : null)
|
|
7031
|
+
if (!updatedRepo.git_fork_url) {
|
|
7032
|
+
updatedRepo.git_fork_url = repoData && repoData.git_fork_url ? repoData.git_fork_url : resolveForkUri(updatedRepo)
|
|
7033
|
+
}
|
|
7034
|
+
if (!updatedRepo.git_push_url) {
|
|
7035
|
+
updatedRepo.git_push_url = repoData && repoData.git_push_url ? repoData.git_push_url : resolvePushUri(updatedRepo)
|
|
7036
|
+
}
|
|
6529
7037
|
repoStatusCache.set(repoParam, updatedRepo)
|
|
6530
7038
|
repoData = updatedRepo
|
|
6531
7039
|
const listEntry = lastRepoList.find((entry) => entry.repoParam === repoParam)
|
|
@@ -6536,6 +7044,12 @@ body.dark {
|
|
|
6536
7044
|
if (updatedRepo.git_history_url) {
|
|
6537
7045
|
listEntry.git_history_url = updatedRepo.git_history_url
|
|
6538
7046
|
}
|
|
7047
|
+
if (!listEntry.git_fork_url && updatedRepo.git_fork_url) {
|
|
7048
|
+
listEntry.git_fork_url = updatedRepo.git_fork_url
|
|
7049
|
+
}
|
|
7050
|
+
if (!listEntry.git_push_url && updatedRepo.git_push_url) {
|
|
7051
|
+
listEntry.git_push_url = updatedRepo.git_push_url
|
|
7052
|
+
}
|
|
6539
7053
|
}
|
|
6540
7054
|
const total = Array.from(repoStatusCache.values()).reduce((sum, repo) => sum + (repo.changeCount || 0), 0)
|
|
6541
7055
|
updateCombinedBadge(total)
|
|
@@ -7030,6 +7544,12 @@ body.dark {
|
|
|
7030
7544
|
closeBtn.addEventListener('click', () => Swal.close())
|
|
7031
7545
|
}
|
|
7032
7546
|
|
|
7547
|
+
if (iframe) {
|
|
7548
|
+
iframe.dataset.forceVisible = 'true'
|
|
7549
|
+
iframe.classList.remove('hidden')
|
|
7550
|
+
iframe.removeAttribute('hidden')
|
|
7551
|
+
}
|
|
7552
|
+
|
|
7033
7553
|
disconnectHandler = (event) => {
|
|
7034
7554
|
if (!iframe || event.source !== iframe.contentWindow) return
|
|
7035
7555
|
if (!event.data || event.data.type !== 'pinokio:socket-closed') return
|
|
@@ -7055,16 +7575,62 @@ body.dark {
|
|
|
7055
7575
|
}
|
|
7056
7576
|
}
|
|
7057
7577
|
|
|
7058
|
-
const showPublishModal = () => {
|
|
7059
|
-
|
|
7578
|
+
const showPublishModal = (target) => {
|
|
7579
|
+
let repoParam = null
|
|
7580
|
+
let repoLabel = null
|
|
7581
|
+
|
|
7582
|
+
if (typeof target === 'string') {
|
|
7583
|
+
repoParam = target
|
|
7584
|
+
} else if (target && typeof target === 'object') {
|
|
7585
|
+
if (typeof target.repoParam === 'string') {
|
|
7586
|
+
repoParam = target.repoParam
|
|
7587
|
+
}
|
|
7588
|
+
if (typeof target.repoName === 'string' && target.repoName.trim().length > 0) {
|
|
7589
|
+
repoLabel = target.repoName.trim()
|
|
7590
|
+
}
|
|
7591
|
+
}
|
|
7592
|
+
|
|
7593
|
+
if (!repoParam) {
|
|
7594
|
+
repoParam = activeRepoKey || null
|
|
7595
|
+
}
|
|
7596
|
+
|
|
7597
|
+
let repoData = null
|
|
7598
|
+
if (repoParam) {
|
|
7599
|
+
repoData = repoStatusCache.get(repoParam) || null
|
|
7600
|
+
if (!repoData && lastRepoList.length > 0) {
|
|
7601
|
+
const fallbackMatch = lastRepoList.find((repo) => repo.repoParam === repoParam)
|
|
7602
|
+
if (fallbackMatch) {
|
|
7603
|
+
repoData = fallbackMatch
|
|
7604
|
+
}
|
|
7605
|
+
}
|
|
7606
|
+
}
|
|
7607
|
+
|
|
7608
|
+
if (!repoLabel && repoData && typeof repoData.name === 'string') {
|
|
7609
|
+
repoLabel = repoData.name
|
|
7610
|
+
}
|
|
7611
|
+
if (!repoLabel && repoParam) {
|
|
7612
|
+
repoLabel = repoParam
|
|
7613
|
+
}
|
|
7614
|
+
if (!repoLabel) {
|
|
7615
|
+
repoLabel = workspaceName || 'Repository'
|
|
7616
|
+
}
|
|
7617
|
+
|
|
7618
|
+
const pushUri = resolvePushUri(repoData)
|
|
7060
7619
|
if (!pushUri) {
|
|
7061
7620
|
Swal.fire({
|
|
7062
7621
|
title: 'Error',
|
|
7063
|
-
text: 'Publish URL not available.',
|
|
7622
|
+
text: 'Publish URL not available for this repository.',
|
|
7064
7623
|
icon: 'error'
|
|
7065
7624
|
})
|
|
7066
7625
|
return
|
|
7067
7626
|
}
|
|
7627
|
+
|
|
7628
|
+
const remoteDisplay = repoData && repoData.url ? escapeHtml(repoData.url) : null
|
|
7629
|
+
const subtitle = `${escapeHtml(repoLabel)} — review your latest changes before publishing.`
|
|
7630
|
+
const remoteHtml = remoteDisplay ? `<div class="pinokio-fork-item-url">${remoteDisplay}</div>` : ''
|
|
7631
|
+
const timestampedUri = pushUri.includes('?')
|
|
7632
|
+
? `${pushUri}&ts=${Date.now()}`
|
|
7633
|
+
: `${pushUri}?ts=${Date.now()}`
|
|
7068
7634
|
|
|
7069
7635
|
const modalHtml = `
|
|
7070
7636
|
<div class="pinokio-modal-surface">
|
|
@@ -7072,11 +7638,12 @@ body.dark {
|
|
|
7072
7638
|
<div class="pinokio-modal-icon"><i class="fa-brands fa-github"></i></div>
|
|
7073
7639
|
<div class="pinokio-modal-heading">
|
|
7074
7640
|
<div class="pinokio-modal-title">Publish to GitHub</div>
|
|
7075
|
-
<div class="pinokio-modal-subtitle"
|
|
7641
|
+
<div class="pinokio-modal-subtitle">${subtitle}</div>
|
|
7642
|
+
${remoteHtml}
|
|
7076
7643
|
</div>
|
|
7077
7644
|
</div>
|
|
7078
7645
|
<div class="pinokio-modal-body pinokio-modal-body--iframe">
|
|
7079
|
-
<iframe src="${
|
|
7646
|
+
<iframe src="${timestampedUri}"></iframe>
|
|
7080
7647
|
</div>
|
|
7081
7648
|
<div class="pinokio-modal-footer pinokio-modal-footer--publish" data-publish-footer>
|
|
7082
7649
|
<button type="button" class="pinokio-publish-close-btn" data-publish-close>Close</button>
|
|
@@ -7329,65 +7896,384 @@ body.dark {
|
|
|
7329
7896
|
focusConfirm: false
|
|
7330
7897
|
})
|
|
7331
7898
|
}
|
|
7899
|
+
|
|
7900
|
+
async function showForkModalForRepo(repoParam) {
|
|
7901
|
+
const key = typeof repoParam === 'string' && repoParam.length > 0 ? repoParam : (activeRepoKey || null)
|
|
7902
|
+
if (!key) {
|
|
7903
|
+
Swal.fire({
|
|
7904
|
+
title: 'No repository selected',
|
|
7905
|
+
text: 'Select a repository to fork from the dropdown.',
|
|
7906
|
+
icon: 'info'
|
|
7907
|
+
})
|
|
7908
|
+
return
|
|
7909
|
+
}
|
|
7910
|
+
|
|
7911
|
+
let repo = repoStatusCache.get(key)
|
|
7912
|
+
if (!repo) {
|
|
7913
|
+
await check_git()
|
|
7914
|
+
repo = repoStatusCache.get(key)
|
|
7915
|
+
}
|
|
7916
|
+
|
|
7917
|
+
if (!repo) {
|
|
7918
|
+
Swal.fire({
|
|
7919
|
+
title: 'Repository unavailable',
|
|
7920
|
+
text: 'The selected repository could not be found. Refresh Git status and try again.',
|
|
7921
|
+
icon: 'error'
|
|
7922
|
+
})
|
|
7923
|
+
return
|
|
7924
|
+
}
|
|
7925
|
+
|
|
7926
|
+
if (!hasRemoteConfigured(repo)) {
|
|
7927
|
+
Swal.fire({
|
|
7928
|
+
title: 'Remote required',
|
|
7929
|
+
text: 'This repository does not have a remote configured, so it cannot be forked.',
|
|
7930
|
+
icon: 'info'
|
|
7931
|
+
})
|
|
7932
|
+
return
|
|
7933
|
+
}
|
|
7934
|
+
|
|
7935
|
+
const forkUri = resolveForkUri(repo)
|
|
7936
|
+
if (!forkUri) {
|
|
7937
|
+
Swal.fire({
|
|
7938
|
+
title: 'Fork script unavailable',
|
|
7939
|
+
text: 'Unable to locate the fork script for this repository.',
|
|
7940
|
+
icon: 'error'
|
|
7941
|
+
})
|
|
7942
|
+
return
|
|
7943
|
+
}
|
|
7944
|
+
|
|
7945
|
+
const defaultName = deriveForkDefaultName(repo)
|
|
7946
|
+
const remoteDisplay = repo.url ? escapeHtml(repo.url) : 'No remote detected'
|
|
7947
|
+
const remoteClass = repo.url ? 'pinokio-fork-item-url' : 'pinokio-fork-item-url empty'
|
|
7948
|
+
const nameInputId = 'pinokio-fork-name-input'
|
|
7949
|
+
const orgToggleId = 'pinokio-fork-org-toggle'
|
|
7950
|
+
const orgInputId = 'pinokio-fork-org-input'
|
|
7951
|
+
|
|
7952
|
+
const modalHtml = `
|
|
7953
|
+
<div class="pinokio-modal-surface">
|
|
7954
|
+
<div class="pinokio-modal-header">
|
|
7955
|
+
<div class="pinokio-modal-icon"><i class="fa-brands fa-github"></i></div>
|
|
7956
|
+
<div class="pinokio-modal-heading">
|
|
7957
|
+
<div class="pinokio-modal-title">Fork repository</div>
|
|
7958
|
+
<div class="pinokio-modal-subtitle">${escapeHtml(repo.name || key)}</div>
|
|
7959
|
+
</div>
|
|
7960
|
+
</div>
|
|
7961
|
+
<div class="pinokio-modal-body">
|
|
7962
|
+
<div class="pinokio-fork-modal">
|
|
7963
|
+
<div class="pinokio-fork-item" data-disabled="false">
|
|
7964
|
+
<div class="pinokio-fork-item-url ${remoteClass}">${remoteDisplay}</div>
|
|
7965
|
+
<div class="pinokio-fork-name-input">
|
|
7966
|
+
<label for="${nameInputId}">Fork name</label>
|
|
7967
|
+
<input id="${nameInputId}" class="pinokio-modal-input" value="${escapeHtml(defaultName || '')}">
|
|
7968
|
+
</div>
|
|
7969
|
+
<div class="pinokio-fork-checkbox-row">
|
|
7970
|
+
<input type="checkbox" id="${orgToggleId}" data-fork-org-toggle>
|
|
7971
|
+
<label for="${orgToggleId}">Fork into organization</label>
|
|
7972
|
+
</div>
|
|
7973
|
+
<div class="pinokio-fork-org-input hidden" data-fork-org-section>
|
|
7974
|
+
<label for="${orgInputId}">Organization</label>
|
|
7975
|
+
<input id="${orgInputId}" class="pinokio-modal-input" placeholder="my-org">
|
|
7976
|
+
<p class="pinokio-fork-org-hint">Provide an organization where you have permission to create repositories.</p>
|
|
7977
|
+
</div>
|
|
7978
|
+
</div>
|
|
7979
|
+
</div>
|
|
7980
|
+
</div>
|
|
7981
|
+
</div>
|
|
7982
|
+
`
|
|
7983
|
+
|
|
7984
|
+
Swal.fire({
|
|
7985
|
+
html: modalHtml,
|
|
7986
|
+
customClass: {
|
|
7987
|
+
popup: 'pinokio-modern-modal',
|
|
7988
|
+
htmlContainer: 'pinokio-modern-html',
|
|
7989
|
+
closeButton: 'pinokio-modern-close',
|
|
7990
|
+
confirmButton: 'pinokio-modern-confirm',
|
|
7991
|
+
cancelButton: 'pinokio-modern-cancel'
|
|
7992
|
+
},
|
|
7993
|
+
backdrop: 'rgba(9,11,15,0.65)',
|
|
7994
|
+
width: 'min(520px, 90vw)',
|
|
7995
|
+
buttonsStyling: false,
|
|
7996
|
+
showCloseButton: true,
|
|
7997
|
+
showCancelButton: true,
|
|
7998
|
+
showConfirmButton: true,
|
|
7999
|
+
confirmButtonText: 'Fork on GitHub',
|
|
8000
|
+
cancelButtonText: 'Cancel',
|
|
8001
|
+
focusConfirm: false,
|
|
8002
|
+
didOpen: (popup) => {
|
|
8003
|
+
const input = popup.querySelector(`#${nameInputId}`)
|
|
8004
|
+
if (input) {
|
|
8005
|
+
input.addEventListener('input', () => {
|
|
8006
|
+
input.classList.remove('pinokio-modal-input--error')
|
|
8007
|
+
})
|
|
8008
|
+
input.focus()
|
|
8009
|
+
input.select()
|
|
8010
|
+
}
|
|
8011
|
+
const orgToggle = popup.querySelector('#' + orgToggleId)
|
|
8012
|
+
const orgSection = popup.querySelector('[data-fork-org-section]')
|
|
8013
|
+
const orgInput = popup.querySelector('#' + orgInputId)
|
|
8014
|
+
const syncOrgSection = () => {
|
|
8015
|
+
if (!orgSection) {
|
|
8016
|
+
return
|
|
8017
|
+
}
|
|
8018
|
+
if (orgToggle && orgToggle.checked) {
|
|
8019
|
+
orgSection.classList.remove('hidden')
|
|
8020
|
+
if (orgInput) {
|
|
8021
|
+
requestAnimationFrame(() => orgInput.focus())
|
|
8022
|
+
}
|
|
8023
|
+
} else {
|
|
8024
|
+
orgSection.classList.add('hidden')
|
|
8025
|
+
if (orgInput) {
|
|
8026
|
+
orgInput.classList.remove('pinokio-modal-input--error')
|
|
8027
|
+
}
|
|
8028
|
+
}
|
|
8029
|
+
}
|
|
8030
|
+
if (orgToggle) {
|
|
8031
|
+
orgToggle.addEventListener('change', syncOrgSection)
|
|
8032
|
+
syncOrgSection()
|
|
8033
|
+
}
|
|
8034
|
+
if (orgInput) {
|
|
8035
|
+
orgInput.addEventListener('input', () => {
|
|
8036
|
+
orgInput.classList.remove('pinokio-modal-input--error')
|
|
8037
|
+
})
|
|
8038
|
+
}
|
|
8039
|
+
},
|
|
8040
|
+
preConfirm: () => {
|
|
8041
|
+
const input = document.getElementById(nameInputId)
|
|
8042
|
+
const forkName = input ? input.value.trim() : ''
|
|
8043
|
+
if (!forkName) {
|
|
8044
|
+
if (input) {
|
|
8045
|
+
input.classList.add('pinokio-modal-input--error')
|
|
8046
|
+
setTimeout(() => input.focus(), 0)
|
|
8047
|
+
}
|
|
8048
|
+
Swal.showValidationMessage('Enter a repository name for the fork')
|
|
8049
|
+
return false
|
|
8050
|
+
}
|
|
8051
|
+
const orgToggle = document.getElementById(orgToggleId)
|
|
8052
|
+
const orgInput = document.getElementById(orgInputId)
|
|
8053
|
+
let orgValue = null
|
|
8054
|
+
if (orgToggle && orgToggle.checked) {
|
|
8055
|
+
orgValue = orgInput ? orgInput.value.trim() : ''
|
|
8056
|
+
if (!orgValue) {
|
|
8057
|
+
if (orgInput) {
|
|
8058
|
+
orgInput.classList.add('pinokio-modal-input--error')
|
|
8059
|
+
setTimeout(() => orgInput.focus(), 0)
|
|
8060
|
+
}
|
|
8061
|
+
Swal.showValidationMessage('Enter the organization name or uncheck the option')
|
|
8062
|
+
return false
|
|
8063
|
+
}
|
|
8064
|
+
}
|
|
8065
|
+
return {
|
|
8066
|
+
job: {
|
|
8067
|
+
repoParam: key,
|
|
8068
|
+
repoName: repo.name || key,
|
|
8069
|
+
forkUri,
|
|
8070
|
+
forkName,
|
|
8071
|
+
remoteUrl: repo.url || '',
|
|
8072
|
+
org: orgValue,
|
|
8073
|
+
}
|
|
8074
|
+
}
|
|
8075
|
+
}
|
|
8076
|
+
}).then((result) => {
|
|
8077
|
+
if (result.isConfirmed && result.value && result.value.job) {
|
|
8078
|
+
showForkShellModal(result.value.job)
|
|
8079
|
+
}
|
|
8080
|
+
})
|
|
8081
|
+
}
|
|
8082
|
+
|
|
8083
|
+
function showForkShellModal(job) {
|
|
8084
|
+
if (!job || !job.forkUri) {
|
|
8085
|
+
return
|
|
8086
|
+
}
|
|
8087
|
+
|
|
8088
|
+
const lifecycle = createPublishModalLifecycle()
|
|
8089
|
+
const forkNameValue = typeof job.forkName === 'string' && job.forkName.trim().length > 0
|
|
8090
|
+
? job.forkName.trim()
|
|
8091
|
+
: deriveForkDefaultName({ name: job.repoName, url: job.remoteUrl })
|
|
8092
|
+
const queryParts = [`name=${encodeURIComponent(forkNameValue)}`]
|
|
8093
|
+
if (job.org) {
|
|
8094
|
+
queryParts.push(`org=${encodeURIComponent(job.org)}`)
|
|
8095
|
+
}
|
|
8096
|
+
queryParts.push(`ts=${Date.now()}`)
|
|
8097
|
+
const separator = job.forkUri.includes('?') ? '&' : '?'
|
|
8098
|
+
const finalUri = `${job.forkUri}${separator}${queryParts.join('&')}`
|
|
8099
|
+
|
|
8100
|
+
const remoteDisplay = job.remoteUrl ? escapeHtml(job.remoteUrl) : 'No remote detected'
|
|
8101
|
+
const titleDisplay = job.repoName ? escapeHtml(job.repoName) : 'Repository'
|
|
8102
|
+
const forkDisplay = escapeHtml(forkNameValue)
|
|
8103
|
+
const subtitleParts = [titleDisplay, forkDisplay]
|
|
8104
|
+
if (job.org) {
|
|
8105
|
+
subtitleParts.push(`Org: ${escapeHtml(job.org)}`)
|
|
8106
|
+
}
|
|
8107
|
+
const subtitleHtml = subtitleParts.join(' · ')
|
|
8108
|
+
Swal.fire({
|
|
8109
|
+
html: `
|
|
8110
|
+
<div class="pinokio-modal-surface">
|
|
8111
|
+
<div class="pinokio-modal-header">
|
|
8112
|
+
<div class="pinokio-modal-icon"><i class="fa-brands fa-github"></i></div>
|
|
8113
|
+
<div class="pinokio-modal-heading">
|
|
8114
|
+
<div class="pinokio-modal-title">Fork on GitHub</div>
|
|
8115
|
+
<div class="pinokio-modal-subtitle">${subtitleHtml}</div>
|
|
8116
|
+
<div class="pinokio-fork-item-url">${remoteDisplay}</div>
|
|
8117
|
+
</div>
|
|
8118
|
+
</div>
|
|
8119
|
+
<div class="pinokio-modal-body pinokio-modal-body--iframe">
|
|
8120
|
+
<iframe src="${finalUri}"></iframe>
|
|
8121
|
+
</div>
|
|
8122
|
+
<div class="pinokio-modal-footer pinokio-modal-footer--publish" data-publish-footer>
|
|
8123
|
+
<button type="button" class="pinokio-publish-close-btn" data-publish-close>Close</button>
|
|
8124
|
+
</div>
|
|
8125
|
+
</div>
|
|
8126
|
+
`,
|
|
8127
|
+
customClass: {
|
|
8128
|
+
popup: 'pinokio-modern-modal',
|
|
8129
|
+
htmlContainer: 'pinokio-modern-html',
|
|
8130
|
+
closeButton: 'pinokio-modern-close'
|
|
8131
|
+
},
|
|
8132
|
+
backdrop: 'rgba(9,11,15,0.65)',
|
|
8133
|
+
width: 'min(760px, 94vw)',
|
|
8134
|
+
buttonsStyling: false,
|
|
8135
|
+
showConfirmButton: false,
|
|
8136
|
+
showCloseButton: true,
|
|
8137
|
+
focusConfirm: false,
|
|
8138
|
+
didOpen: lifecycle.didOpen,
|
|
8139
|
+
willClose: lifecycle.willClose
|
|
8140
|
+
}).then(() => {
|
|
8141
|
+
setTimeout(() => {
|
|
8142
|
+
updateForkButton()
|
|
8143
|
+
check_git()
|
|
8144
|
+
}, 250)
|
|
8145
|
+
})
|
|
8146
|
+
}
|
|
7332
8147
|
|
|
7333
|
-
|
|
8148
|
+
function handlePushLogin() {
|
|
7334
8149
|
window.location.href = '/github'
|
|
7335
8150
|
}
|
|
7336
8151
|
|
|
7337
8152
|
// Function to update publish/create button
|
|
7338
8153
|
const updatePublishButton = async () => {
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
8154
|
+
if (!pushBtn) {
|
|
8155
|
+
latestGitIntegration = null
|
|
8156
|
+
updateForkButton()
|
|
8157
|
+
return
|
|
8158
|
+
}
|
|
8159
|
+
|
|
8160
|
+
const labelEl = pushBtn.querySelector('.fs-status-title')
|
|
8161
|
+
const setLabel = (text) => {
|
|
8162
|
+
if (labelEl) {
|
|
8163
|
+
labelEl.textContent = text
|
|
8164
|
+
}
|
|
8165
|
+
}
|
|
8166
|
+
const detachHandlers = () => {
|
|
8167
|
+
pushBtn.removeEventListener('click', showPublishModal)
|
|
8168
|
+
pushBtn.removeEventListener('click', showCreateModal)
|
|
8169
|
+
pushBtn.removeEventListener('click', handlePushLogin)
|
|
8170
|
+
}
|
|
8171
|
+
const enableDropdown = () => {
|
|
8172
|
+
pushBtn.classList.add('revealer')
|
|
8173
|
+
pushBtn.setAttribute('data-group', '#fs-push-menu')
|
|
8174
|
+
}
|
|
8175
|
+
const disableDropdown = () => {
|
|
8176
|
+
pushBtn.classList.remove('revealer')
|
|
8177
|
+
pushBtn.removeAttribute('data-group')
|
|
8178
|
+
closeStatusDropdowns()
|
|
8179
|
+
}
|
|
8180
|
+
|
|
8181
|
+
const repos = getRepoListSnapshot()
|
|
8182
|
+
const publishTargets = Array.isArray(repos)
|
|
8183
|
+
? repos.filter((repo) => hasRemoteConfigured(repo) && resolvePushUri(repo))
|
|
8184
|
+
: []
|
|
8185
|
+
|
|
8186
|
+
detachHandlers()
|
|
8187
|
+
|
|
8188
|
+
const syncForkButton = () => {
|
|
8189
|
+
updateForkButton()
|
|
8190
|
+
}
|
|
8191
|
+
|
|
8192
|
+
if (!historyUri) {
|
|
8193
|
+
setPublishMenuMessage('Git integration unavailable')
|
|
8194
|
+
setLabel('Publish')
|
|
8195
|
+
disableDropdown()
|
|
8196
|
+
pushBtn.disabled = publishTargets.length === 0
|
|
8197
|
+
if (pushBtn.disabled) {
|
|
8198
|
+
pushBtn.classList.add('fs-status-btn--disabled')
|
|
8199
|
+
} else {
|
|
8200
|
+
pushBtn.classList.remove('fs-status-btn--disabled')
|
|
8201
|
+
}
|
|
8202
|
+
pushBtn.setAttribute('title', 'Git integration is not available')
|
|
8203
|
+
latestGitIntegration = null
|
|
8204
|
+
syncForkButton()
|
|
8205
|
+
return
|
|
8206
|
+
}
|
|
8207
|
+
|
|
8208
|
+
if (!publishMenu) {
|
|
8209
|
+
setLabel('Publish')
|
|
8210
|
+
disableDropdown()
|
|
8211
|
+
} else if (!publishMenu.hasChildNodes()) {
|
|
8212
|
+
setPublishMenuMessage('Loading repositories...')
|
|
8213
|
+
}
|
|
7343
8214
|
|
|
7344
8215
|
try {
|
|
7345
8216
|
const response = await fetch(historyUri)
|
|
7346
|
-
|
|
7347
|
-
|
|
7348
|
-
const setLabel = (text) => {
|
|
7349
|
-
if (labelEl) {
|
|
7350
|
-
labelEl.textContent = text
|
|
7351
|
-
}
|
|
7352
|
-
}
|
|
7353
|
-
const resetHandlers = () => {
|
|
7354
|
-
pushBtn.removeEventListener('click', showPublishModal)
|
|
7355
|
-
pushBtn.removeEventListener('click', showCreateModal)
|
|
7356
|
-
pushBtn.removeEventListener('click', handlePushLogin)
|
|
8217
|
+
if (!response.ok) {
|
|
8218
|
+
throw new Error(`HTTP ${response.status}`)
|
|
7357
8219
|
}
|
|
8220
|
+
const data = await response.json()
|
|
8221
|
+
latestGitIntegration = data
|
|
8222
|
+
|
|
8223
|
+
const isConnected = Boolean(data && data.connected)
|
|
8224
|
+
const emptyMessage = isConnected
|
|
8225
|
+
? 'No Git repositories detected'
|
|
8226
|
+
: 'Connect GitHub to publish this workspace'
|
|
8227
|
+
renderPublishDropdown(repos, { emptyMessage })
|
|
7358
8228
|
|
|
7359
|
-
|
|
7360
|
-
if (!data.connected) {
|
|
7361
|
-
// Not logged in - show "Login" button
|
|
8229
|
+
if (!isConnected) {
|
|
7362
8230
|
setLabel('Login')
|
|
7363
|
-
|
|
8231
|
+
pushBtn.disabled = false
|
|
8232
|
+
pushBtn.classList.remove('fs-status-btn--disabled')
|
|
8233
|
+
pushBtn.setAttribute('title', 'Connect GitHub to publish this workspace')
|
|
8234
|
+
disableDropdown()
|
|
7364
8235
|
pushBtn.addEventListener('click', handlePushLogin)
|
|
8236
|
+
syncForkButton()
|
|
7365
8237
|
return
|
|
7366
8238
|
}
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
8239
|
+
|
|
8240
|
+
if (!Array.isArray(repos) || repos.length === 0) {
|
|
8241
|
+
setLabel('Publish')
|
|
8242
|
+
pushBtn.disabled = true
|
|
8243
|
+
pushBtn.classList.add('fs-status-btn--disabled')
|
|
8244
|
+
pushBtn.setAttribute('title', 'No Git repositories detected')
|
|
8245
|
+
disableDropdown()
|
|
8246
|
+
syncForkButton()
|
|
8247
|
+
return
|
|
8248
|
+
}
|
|
8249
|
+
|
|
8250
|
+
pushBtn.disabled = false
|
|
8251
|
+
pushBtn.classList.remove('fs-status-btn--disabled')
|
|
8252
|
+
|
|
8253
|
+
if (publishTargets.length === 0) {
|
|
7371
8254
|
setLabel('Create')
|
|
7372
|
-
|
|
8255
|
+
pushBtn.setAttribute('title', 'Create a GitHub repository for this project')
|
|
8256
|
+
disableDropdown()
|
|
7373
8257
|
pushBtn.addEventListener('click', showCreateModal)
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
setLabel('Publish')
|
|
7377
|
-
resetHandlers()
|
|
7378
|
-
pushBtn.addEventListener('click', showPublishModal)
|
|
8258
|
+
syncForkButton()
|
|
8259
|
+
return
|
|
7379
8260
|
}
|
|
8261
|
+
|
|
8262
|
+
setLabel('Publish')
|
|
8263
|
+
pushBtn.removeAttribute('title')
|
|
8264
|
+
enableDropdown()
|
|
8265
|
+
syncForkButton()
|
|
7380
8266
|
} catch (error) {
|
|
7381
8267
|
console.error('Error checking remotes:', error)
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
pushBtn.removeEventListener('click', showCreateModal)
|
|
7389
|
-
pushBtn.removeEventListener('click', handlePushLogin)
|
|
8268
|
+
setPublishMenuMessage('Git integration unavailable')
|
|
8269
|
+
setLabel('Login')
|
|
8270
|
+
pushBtn.disabled = false
|
|
8271
|
+
pushBtn.classList.remove('fs-status-btn--disabled')
|
|
8272
|
+
pushBtn.setAttribute('title', 'Connect GitHub to publish this workspace')
|
|
8273
|
+
disableDropdown()
|
|
7390
8274
|
pushBtn.addEventListener('click', handlePushLogin)
|
|
8275
|
+
latestGitIntegration = null
|
|
8276
|
+
updateForkButton()
|
|
7391
8277
|
}
|
|
7392
8278
|
}
|
|
7393
8279
|
|