pinokiod 3.170.0 → 3.181.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/favicon.js +91 -34
- package/kernel/peer.js +73 -0
- package/kernel/util.js +13 -2
- package/package.json +1 -1
- package/server/index.js +249 -26
- package/server/public/common.js +244 -0
- package/server/public/files-app/app.css +73 -1
- package/server/public/files-app/app.js +255 -2
- package/server/public/layout.js +115 -1
- package/server/public/nav.js +227 -64
- package/server/public/style.css +27 -3
- package/server/public/tab-idle-notifier.js +3 -0
- package/server/routes/files.js +96 -0
- package/server/socket.js +71 -4
- package/server/views/app.ejs +603 -53
- package/server/views/connect.ejs +9 -0
- package/server/views/file_browser.ejs +9 -2
- package/server/views/index.ejs +11 -2
- package/server/views/init/index.ejs +9 -2
- package/server/views/layout.ejs +7 -5
- package/server/views/net.ejs +9 -0
- package/server/views/network.ejs +9 -0
- package/server/views/review.ejs +4 -3
- package/server/views/screenshots.ejs +9 -0
- package/server/views/settings.ejs +9 -0
- package/server/views/terminals.ejs +12 -3
- package/server/views/tools.ejs +10 -1
package/server/views/app.ejs
CHANGED
|
@@ -905,6 +905,9 @@ body.dark .tab-link-popover .tab-link-popover-header {
|
|
|
905
905
|
background: transparent;
|
|
906
906
|
cursor: pointer;
|
|
907
907
|
}
|
|
908
|
+
.tab-link-popover .tab-link-popover-item.qr-inline { flex-direction: row; align-items: center; gap: 10px; }
|
|
909
|
+
.tab-link-popover .tab-link-popover-item.qr-inline .textcol { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
|
|
910
|
+
.tab-link-popover .tab-link-popover-item.qr-inline .qr { width: 64px; height: 64px; image-rendering: pixelated; flex: 0 0 auto; margin-left: auto; }
|
|
908
911
|
.tab-link-popover .tab-link-popover-item:hover,
|
|
909
912
|
.tab-link-popover .tab-link-popover-item:focus-visible {
|
|
910
913
|
background: rgba(15, 23, 42, 0.06);
|
|
@@ -1759,6 +1762,10 @@ body.dark .swal2-html-container label {
|
|
|
1759
1762
|
--pinokio-custom-commit-close-color: rgba(71, 85, 105, 0.75);
|
|
1760
1763
|
--pinokio-custom-commit-close-hover-bg: rgba(148, 163, 184, 0.18);
|
|
1761
1764
|
--pinokio-custom-commit-close-hover-color: #0f172a;
|
|
1765
|
+
--pinokio-custom-commit-action-bg: rgba(59, 130, 246, 0.12);
|
|
1766
|
+
--pinokio-custom-commit-action-border: rgba(59, 130, 246, 0.35);
|
|
1767
|
+
--pinokio-custom-commit-action-color: #1d4ed8;
|
|
1768
|
+
--pinokio-custom-commit-action-hover: rgba(59, 130, 246, 0.18);
|
|
1762
1769
|
--pinokio-commit-item-bg: rgba(226, 232, 240, 0.6);
|
|
1763
1770
|
--pinokio-commit-item-hover-bg: rgba(59, 130, 246, 0.12);
|
|
1764
1771
|
--pinokio-commit-item-text: #0f172a;
|
|
@@ -1837,6 +1844,10 @@ body.dark {
|
|
|
1837
1844
|
--pinokio-custom-commit-close-color: rgba(226, 232, 240, 0.7);
|
|
1838
1845
|
--pinokio-custom-commit-close-hover-bg: rgba(148, 163, 184, 0.18);
|
|
1839
1846
|
--pinokio-custom-commit-close-hover-color: #f8fafc;
|
|
1847
|
+
--pinokio-custom-commit-action-bg: rgba(56, 189, 248, 0.12);
|
|
1848
|
+
--pinokio-custom-commit-action-border: rgba(56, 189, 248, 0.35);
|
|
1849
|
+
--pinokio-custom-commit-action-color: #7dd3fc;
|
|
1850
|
+
--pinokio-custom-commit-action-hover: rgba(56, 189, 248, 0.18);
|
|
1840
1851
|
--pinokio-commit-item-bg: rgba(15, 23, 42, 0.35);
|
|
1841
1852
|
--pinokio-commit-item-hover-bg: rgba(37, 99, 235, 0.16);
|
|
1842
1853
|
--pinokio-commit-item-text: #f8fafc;
|
|
@@ -2173,6 +2184,70 @@ body.dark {
|
|
|
2173
2184
|
flex-wrap: wrap;
|
|
2174
2185
|
}
|
|
2175
2186
|
|
|
2187
|
+
.pinokio-history-latest-banner {
|
|
2188
|
+
display: flex;
|
|
2189
|
+
justify-content: space-between;
|
|
2190
|
+
align-items: center;
|
|
2191
|
+
padding: 12px 20px;
|
|
2192
|
+
margin: 12px 20px 0;
|
|
2193
|
+
background: rgba(59, 130, 246, 0.08);
|
|
2194
|
+
border: 1px solid rgba(59, 130, 246, 0.18);
|
|
2195
|
+
border-radius: 10px;
|
|
2196
|
+
color: var(--pinokio-modal-title-color);
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
.pinokio-history-latest-text {
|
|
2200
|
+
font-size: 13px;
|
|
2201
|
+
font-weight: 600;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
.pinokio-history-latest-btn {
|
|
2205
|
+
display: inline-flex;
|
|
2206
|
+
align-items: center;
|
|
2207
|
+
gap: 6px;
|
|
2208
|
+
background: rgba(59, 130, 246, 0.18);
|
|
2209
|
+
border: 1px solid rgba(59, 130, 246, 0.35);
|
|
2210
|
+
color: #1d4ed8;
|
|
2211
|
+
padding: 6px 14px;
|
|
2212
|
+
border-radius: 6px;
|
|
2213
|
+
font-weight: 600;
|
|
2214
|
+
font-size: 13px;
|
|
2215
|
+
cursor: pointer;
|
|
2216
|
+
transition: background 0.15s ease, border 0.15s ease, transform 0.15s ease;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
.pinokio-history-latest-btn:hover {
|
|
2220
|
+
background: rgba(59, 130, 246, 0.24);
|
|
2221
|
+
transform: translateY(-1px);
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
.pinokio-history-latest-btn:focus-visible {
|
|
2225
|
+
outline: 2px solid rgba(59, 130, 246, 0.45);
|
|
2226
|
+
outline-offset: 3px;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
.pinokio-history-latest-banner--disabled {
|
|
2230
|
+
opacity: 0.6;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
.pinokio-history-latest-banner--disabled .pinokio-history-latest-btn {
|
|
2234
|
+
pointer-events: none;
|
|
2235
|
+
cursor: not-allowed;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
.pinokio-history-actions {
|
|
2239
|
+
display: inline-flex;
|
|
2240
|
+
align-items: center;
|
|
2241
|
+
gap: 10px;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
.pinokio-history-branch-select.pinokio-modal-input {
|
|
2245
|
+
width: auto;
|
|
2246
|
+
min-width: 160px;
|
|
2247
|
+
padding: 6px 10px;
|
|
2248
|
+
font-size: 13px;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2176
2251
|
.pinokio-pill {
|
|
2177
2252
|
display: inline-flex;
|
|
2178
2253
|
align-items: center;
|
|
@@ -2553,6 +2628,104 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
2553
2628
|
flex: 1;
|
|
2554
2629
|
overflow: hidden;
|
|
2555
2630
|
padding: 24px;
|
|
2631
|
+
display: flex;
|
|
2632
|
+
flex-direction: column;
|
|
2633
|
+
gap: 16px;
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
.pinokio-git-commit-actions {
|
|
2637
|
+
display: flex;
|
|
2638
|
+
justify-content: flex-end;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
.pinokio-git-commit-switch-btn {
|
|
2642
|
+
display: inline-flex;
|
|
2643
|
+
align-items: center;
|
|
2644
|
+
gap: 6px;
|
|
2645
|
+
background: var(--pinokio-custom-commit-action-bg);
|
|
2646
|
+
border: 1px solid var(--pinokio-custom-commit-action-border);
|
|
2647
|
+
color: var(--pinokio-custom-commit-action-color);
|
|
2648
|
+
padding: 6px 14px;
|
|
2649
|
+
border-radius: 6px;
|
|
2650
|
+
font-weight: 600;
|
|
2651
|
+
font-size: 13px;
|
|
2652
|
+
cursor: pointer;
|
|
2653
|
+
transition: background 0.15s ease, border 0.15s ease, transform 0.15s ease;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
.pinokio-git-commit-switch-btn:hover {
|
|
2657
|
+
background: var(--pinokio-custom-commit-action-hover);
|
|
2658
|
+
transform: translateY(-1px);
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
.pinokio-git-commit-switch-btn:focus-visible {
|
|
2662
|
+
outline: 2px solid var(--pinokio-custom-commit-action-color);
|
|
2663
|
+
outline-offset: 3px;
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
.pinokio-custom-terminal-overlay {
|
|
2667
|
+
position: fixed;
|
|
2668
|
+
inset: 0;
|
|
2669
|
+
background: rgba(9, 11, 15, 0.7);
|
|
2670
|
+
display: flex;
|
|
2671
|
+
align-items: center;
|
|
2672
|
+
justify-content: center;
|
|
2673
|
+
z-index: 999999;
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
.pinokio-custom-terminal-modal {
|
|
2677
|
+
width: min(900px, 92vw);
|
|
2678
|
+
height: min(620px, 88vh);
|
|
2679
|
+
background: var(--pinokio-modal-surface-bg, #101522);
|
|
2680
|
+
color: var(--pinokio-modal-title-color);
|
|
2681
|
+
border-radius: 18px;
|
|
2682
|
+
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.55);
|
|
2683
|
+
display: flex;
|
|
2684
|
+
flex-direction: column;
|
|
2685
|
+
overflow: hidden;
|
|
2686
|
+
border: 1px solid rgba(76, 137, 251, 0.2);
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
.pinokio-custom-terminal-header {
|
|
2690
|
+
display: flex;
|
|
2691
|
+
justify-content: space-between;
|
|
2692
|
+
align-items: center;
|
|
2693
|
+
padding: 16px 20px;
|
|
2694
|
+
background: rgba(30, 41, 59, 0.8);
|
|
2695
|
+
border-bottom: 1px solid rgba(76, 137, 251, 0.25);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
.pinokio-custom-terminal-header h3 {
|
|
2699
|
+
margin: 0;
|
|
2700
|
+
font-size: 15px;
|
|
2701
|
+
font-weight: 600;
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
.pinokio-custom-terminal-close {
|
|
2705
|
+
background: none;
|
|
2706
|
+
border: none;
|
|
2707
|
+
font-size: 26px;
|
|
2708
|
+
cursor: pointer;
|
|
2709
|
+
padding: 6px 10px;
|
|
2710
|
+
border-radius: 12px;
|
|
2711
|
+
color: rgba(226, 232, 240, 0.7);
|
|
2712
|
+
transition: background 0.2s ease, color 0.2s ease;
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
.pinokio-custom-terminal-close:hover {
|
|
2716
|
+
background: rgba(148, 163, 184, 0.18);
|
|
2717
|
+
color: #f8fafc;
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
.pinokio-custom-terminal-body {
|
|
2721
|
+
flex: 1;
|
|
2722
|
+
background: #0b1120;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
.pinokio-custom-terminal-body iframe {
|
|
2726
|
+
width: 100%;
|
|
2727
|
+
height: 100%;
|
|
2728
|
+
border: none;
|
|
2556
2729
|
}
|
|
2557
2730
|
|
|
2558
2731
|
.pinokio-git-commit-item {
|
|
@@ -2800,6 +2973,15 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
2800
2973
|
overflow: auto;
|
|
2801
2974
|
flex-wrap: nowrap;
|
|
2802
2975
|
}
|
|
2976
|
+
/* Keep minimized header horizontal and compact on small screens */
|
|
2977
|
+
header.navheader.minimized,
|
|
2978
|
+
header.navheader.minimized h1 {
|
|
2979
|
+
display: inline-flex;
|
|
2980
|
+
flex-direction: row;
|
|
2981
|
+
}
|
|
2982
|
+
header.navheader.minimized h1 .btn2 {
|
|
2983
|
+
width: auto;
|
|
2984
|
+
}
|
|
2803
2985
|
.appcanvas {
|
|
2804
2986
|
margin-left: 0;
|
|
2805
2987
|
flex: 1 1 auto;
|
|
@@ -2848,10 +3030,12 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
2848
3030
|
#fs-fork-btn .fs-status-label i {
|
|
2849
3031
|
font-size: 1rem;
|
|
2850
3032
|
}
|
|
2851
|
-
#fs-status .fs-status-btn .disk-usage
|
|
2852
|
-
#fs-status .fs-status-btn .badge {
|
|
3033
|
+
#fs-status .fs-status-btn .disk-usage {
|
|
2853
3034
|
display: none;
|
|
2854
3035
|
}
|
|
3036
|
+
#fs-status .git-changes .badge {
|
|
3037
|
+
display: inline-flex;
|
|
3038
|
+
}
|
|
2855
3039
|
}
|
|
2856
3040
|
/*
|
|
2857
3041
|
@media only screen and (max-width: 800px) {
|
|
@@ -2904,7 +3088,8 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
2904
3088
|
<button class='btn2' id='screenshot' data-tippy-content="take a screenshot"><i class="fa-solid fa-camera"></i></button>
|
|
2905
3089
|
<div class='sep'></div>
|
|
2906
3090
|
<div class='mode-selector'>
|
|
2907
|
-
<a class="btn2 <%=type === 'review' ? 'selected' : ''%>" href="<%=review_tab%>"><div><i class="fa-regular fa-message"></i></div><div class='caption'>
|
|
3091
|
+
<a class="btn2 <%=type === 'review' ? 'selected' : ''%>" href="<%=review_tab%>"><div><i class="fa-regular fa-message"></i></div><div class='caption'>Forum</div></a>
|
|
3092
|
+
<a class="btn2 <%=type === 'files' ? 'selected' : ''%>" href="<%=files_tab%>"><div><i class="fa-solid fa-file-lines"></i></div><div class='caption'>Files</div></a>
|
|
2908
3093
|
<a class="btn2 <%=type === 'browse' ? 'selected' : ''%>" href="<%=dev_tab%>"><div><i class="fa-solid fa-code"></i></div><div class='caption'>Dev</div></a>
|
|
2909
3094
|
<a class="btn2 <%=type === 'run' ? 'selected' : ''%>" href="<%=run_tab%>"><div><i class="fa-solid fa-circle-play"></i></div><div class='caption'>Run</div></a>
|
|
2910
3095
|
</div>
|
|
@@ -2931,6 +3116,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
2931
3116
|
</div>
|
|
2932
3117
|
</div>
|
|
2933
3118
|
<% } else { %>
|
|
3119
|
+
<% if (type !== 'files') { %>
|
|
2934
3120
|
<aside class='active'>
|
|
2935
3121
|
<!--
|
|
2936
3122
|
<div class='header-top header-item'>
|
|
@@ -2963,15 +3149,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
2963
3149
|
<div class='loader'><i class='fa-solid fa-angle-right'></i></div>
|
|
2964
3150
|
-->
|
|
2965
3151
|
</a>
|
|
2966
|
-
|
|
2967
|
-
<div class='tab'>
|
|
2968
|
-
<i class="fa-solid fa-file-lines"></i>
|
|
2969
|
-
<div class='display'>Files</div>
|
|
2970
|
-
<div class='tab-metric'>
|
|
2971
|
-
<span class='disk-usage tab-metric__value' data-path="/">--</span>
|
|
2972
|
-
</div>
|
|
2973
|
-
</div>
|
|
2974
|
-
</a>
|
|
3152
|
+
|
|
2975
3153
|
<div class="dynamic <%=type==='run' ? '' : 'selected'%>">
|
|
2976
3154
|
<div class='submenu'>
|
|
2977
3155
|
<% if (plugin_menu) { %>
|
|
@@ -3006,6 +3184,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3006
3184
|
</div>
|
|
3007
3185
|
</div>
|
|
3008
3186
|
</aside>
|
|
3187
|
+
<% } %>
|
|
3009
3188
|
<% if (type === "run") { %>
|
|
3010
3189
|
<div class='appcanvas_filler'></div>
|
|
3011
3190
|
<% } %>
|
|
@@ -3024,6 +3203,62 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3024
3203
|
</div>
|
|
3025
3204
|
</div>
|
|
3026
3205
|
-->
|
|
3206
|
+
<div class='fs-status-dropdown fs-open-explorer'>
|
|
3207
|
+
<button class='fs-status-btn' data-filepath="<%=path%>" type='button'>
|
|
3208
|
+
<span class='fs-status-label'>
|
|
3209
|
+
<i class="fa-solid fa-folder-open"></i>
|
|
3210
|
+
<span class='fs-status-title'>Open in File Explorer</span>
|
|
3211
|
+
</span>
|
|
3212
|
+
</button>
|
|
3213
|
+
</div>
|
|
3214
|
+
<div class='fs-status-dropdown git-changes'>
|
|
3215
|
+
<button id='fs-changes-btn' class='fs-status-btn revealer' data-group='#fs-changes-menu' type='button'>
|
|
3216
|
+
<span class='fs-status-label'><i class="fa-solid fa-code-compare"></i> Changes</span>
|
|
3217
|
+
<div class='badge'></div>
|
|
3218
|
+
</button>
|
|
3219
|
+
<div class='fs-dropdown-menu submenu hidden' id='fs-changes-menu'></div>
|
|
3220
|
+
</div>
|
|
3221
|
+
<div class='fs-status-dropdown git-fork'>
|
|
3222
|
+
<button id='fs-fork-btn' class='fs-status-btn revealer' data-group='#fs-fork-menu' type='button'>
|
|
3223
|
+
<span class='fs-status-label'>
|
|
3224
|
+
<i class="fa-solid fa-code-branch"></i>
|
|
3225
|
+
<span class='fs-status-title'>Fork</span>
|
|
3226
|
+
</span>
|
|
3227
|
+
</button>
|
|
3228
|
+
<div class='fs-dropdown-menu submenu hidden' id='fs-fork-menu'></div>
|
|
3229
|
+
</div>
|
|
3230
|
+
<div class='fs-status-dropdown git-publish'>
|
|
3231
|
+
<button id='fs-push-btn' class='fs-status-btn revealer' data-group='#fs-push-menu' type='button'>
|
|
3232
|
+
<span class='fs-status-label'>
|
|
3233
|
+
<i class="fa-brands fa-github"></i>
|
|
3234
|
+
<span class='fs-status-title'>Publish</span>
|
|
3235
|
+
</span>
|
|
3236
|
+
</button>
|
|
3237
|
+
<div class='fs-dropdown-menu submenu hidden' id='fs-push-menu'></div>
|
|
3238
|
+
</div>
|
|
3239
|
+
</div>
|
|
3240
|
+
<% } else if (type === 'files') { %>
|
|
3241
|
+
<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%>">
|
|
3242
|
+
<!--
|
|
3243
|
+
<div class='fs-status-dropdown nested-menu git blue'>
|
|
3244
|
+
<button type='button' class='fs-status-btn frame-link reveal'>
|
|
3245
|
+
<span class='fs-status-label'>
|
|
3246
|
+
<i class="fa-brands fa-git-alt"></i>
|
|
3247
|
+
Git
|
|
3248
|
+
</span>
|
|
3249
|
+
</button>
|
|
3250
|
+
<div class='fs-dropdown-menu submenu hidden' id='git-repos'>
|
|
3251
|
+
</div>
|
|
3252
|
+
</div>
|
|
3253
|
+
-->
|
|
3254
|
+
<div class='fs-status-dropdown fs-open-explorer'>
|
|
3255
|
+
<button class='fs-status-btn' data-filepath="<%=path%>" type='button'>
|
|
3256
|
+
<span class='fs-status-label'>
|
|
3257
|
+
<i class="fa-solid fa-folder-open"></i>
|
|
3258
|
+
<span class='fs-status-title'>Open in File Explorer</span>
|
|
3259
|
+
</span>
|
|
3260
|
+
</button>
|
|
3261
|
+
</div>
|
|
3027
3262
|
<div class='fs-status-dropdown git-changes'>
|
|
3028
3263
|
<button id='fs-changes-btn' class='fs-status-btn revealer' data-group='#fs-changes-menu' type='button'>
|
|
3029
3264
|
<span class='fs-status-label'><i class="fa-solid fa-code-compare"></i> Changes</span>
|
|
@@ -3052,6 +3287,9 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3052
3287
|
</div>
|
|
3053
3288
|
<% } %>
|
|
3054
3289
|
<main class='browserview'>
|
|
3290
|
+
<% if (type === 'files') { %>
|
|
3291
|
+
<iframe class='selected' src="<%=editor_tab%>"></iframe>
|
|
3292
|
+
<% } %>
|
|
3055
3293
|
</main>
|
|
3056
3294
|
</div>
|
|
3057
3295
|
</div>
|
|
@@ -3295,6 +3533,8 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3295
3533
|
return false
|
|
3296
3534
|
}
|
|
3297
3535
|
|
|
3536
|
+
const isIPv4Host = (host) => /^(\d{1,3}\.){3}\d{1,3}$/.test((host || '').trim())
|
|
3537
|
+
|
|
3298
3538
|
const extractProjectSlug = (node) => {
|
|
3299
3539
|
if (!node) {
|
|
3300
3540
|
return ""
|
|
@@ -3449,22 +3689,36 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3449
3689
|
if (!trimmed) {
|
|
3450
3690
|
return ""
|
|
3451
3691
|
}
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3692
|
+
// If it's already a URL, ensure it's HTTPS and not an IP host
|
|
3693
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
3694
|
+
try {
|
|
3695
|
+
const parsed = new URL(trimmed)
|
|
3696
|
+
const host = (parsed.hostname || '').toLowerCase()
|
|
3697
|
+
if (!host || isIPv4Host(host)) {
|
|
3698
|
+
return ""
|
|
3699
|
+
}
|
|
3700
|
+
// Only accept domains (prefer *.localhost) for HTTPS targets
|
|
3701
|
+
if (!(host === 'localhost' || host.endsWith('.localhost') || host.includes('.'))) {
|
|
3702
|
+
return ""
|
|
3703
|
+
}
|
|
3704
|
+
let pathname = parsed.pathname || ""
|
|
3705
|
+
if (pathname === "/") pathname = ""
|
|
3706
|
+
const search = parsed.search || ""
|
|
3707
|
+
return `https://${host}${pathname}${search}`
|
|
3708
|
+
} catch (_) {
|
|
3709
|
+
return ""
|
|
3710
|
+
}
|
|
3456
3711
|
}
|
|
3712
|
+
// Not a full URL: accept plain domains (prefer *.localhost), reject IPs
|
|
3457
3713
|
try {
|
|
3458
|
-
const
|
|
3459
|
-
if (!
|
|
3714
|
+
const hostCandidate = trimmed.split('/')[0].toLowerCase()
|
|
3715
|
+
if (!hostCandidate || isIPv4Host(hostCandidate)) {
|
|
3460
3716
|
return ""
|
|
3461
3717
|
}
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
pathname = ""
|
|
3718
|
+
if (!(hostCandidate === 'localhost' || hostCandidate.endsWith('.localhost') || hostCandidate.includes('.'))) {
|
|
3719
|
+
return ""
|
|
3465
3720
|
}
|
|
3466
|
-
|
|
3467
|
-
return `https://${parsed.host}${pathname}${search}`
|
|
3721
|
+
return `https://${hostCandidate}`
|
|
3468
3722
|
} catch (_) {
|
|
3469
3723
|
return ""
|
|
3470
3724
|
}
|
|
@@ -3523,7 +3777,8 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3523
3777
|
const ensureRouterInfoMapping = async () => {
|
|
3524
3778
|
const now = Date.now()
|
|
3525
3779
|
if (!tabLinkRouterInfoPromise || now > tabLinkRouterInfoExpiry) {
|
|
3526
|
-
|
|
3780
|
+
// Use lightweight router mapping to avoid favicon/installed overhead
|
|
3781
|
+
tabLinkRouterInfoPromise = fetch("/info/router", {
|
|
3527
3782
|
method: "GET",
|
|
3528
3783
|
headers: {
|
|
3529
3784
|
"Accept": "application/json"
|
|
@@ -3545,6 +3800,8 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3545
3800
|
: []
|
|
3546
3801
|
const portMap = new Map()
|
|
3547
3802
|
const hostPortMap = new Map()
|
|
3803
|
+
const externalHttpByExtPort = new Map() // ext port -> Set of host:port (external_ip)
|
|
3804
|
+
const externalHttpByIntPort = new Map() // internal port -> Set of host:port (external_ip)
|
|
3548
3805
|
const hostAliasPortMap = new Map()
|
|
3549
3806
|
if (data?.router && typeof data.router === "object") {
|
|
3550
3807
|
Object.entries(data.router).forEach(([dial, hosts]) => {
|
|
@@ -3689,11 +3946,29 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3689
3946
|
mergeTargets(entry.external_domain)
|
|
3690
3947
|
mergeTargets(entry.https_href)
|
|
3691
3948
|
mergeTargets(entry.app_href)
|
|
3692
|
-
|
|
3693
|
-
mergeTargets(entry.internal_router)
|
|
3694
|
-
mergeTargets(entry.match)
|
|
3695
|
-
mergeTargets(entry.host)
|
|
3949
|
+
// Some rewrite mapping entries expose domain candidates under `hosts`
|
|
3696
3950
|
mergeTargets(entry.hosts)
|
|
3951
|
+
// Internal router can also include domain aliases (e.g., comfyui.localhost)
|
|
3952
|
+
mergeTargets(entry.internal_router)
|
|
3953
|
+
|
|
3954
|
+
// Record external http host:port candidates by external and internal ports for later
|
|
3955
|
+
if (entry.external_ip && typeof entry.external_ip === 'string') {
|
|
3956
|
+
const parsed = parseHostPort(entry.external_ip)
|
|
3957
|
+
if (parsed && parsed.port) {
|
|
3958
|
+
const keyExt = parsed.port
|
|
3959
|
+
if (!externalHttpByExtPort.has(keyExt)) {
|
|
3960
|
+
externalHttpByExtPort.set(keyExt, new Set())
|
|
3961
|
+
}
|
|
3962
|
+
externalHttpByExtPort.get(keyExt).add(`${parsed.host}:${parsed.port}`)
|
|
3963
|
+
const keyInt = String(entry.internal_port || '')
|
|
3964
|
+
if (keyInt) {
|
|
3965
|
+
if (!externalHttpByIntPort.has(keyInt)) {
|
|
3966
|
+
externalHttpByIntPort.set(keyInt, new Set())
|
|
3967
|
+
}
|
|
3968
|
+
externalHttpByIntPort.get(keyInt).add(`${parsed.host}:${parsed.port}`)
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3697
3972
|
|
|
3698
3973
|
if (httpsTargets.size === 0) {
|
|
3699
3974
|
return
|
|
@@ -3769,14 +4044,18 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3769
4044
|
|
|
3770
4045
|
return {
|
|
3771
4046
|
portMap,
|
|
3772
|
-
hostPortMap
|
|
4047
|
+
hostPortMap,
|
|
4048
|
+
externalHttpByExtPort,
|
|
4049
|
+
externalHttpByIntPort
|
|
3773
4050
|
}
|
|
3774
4051
|
})
|
|
3775
4052
|
.catch(() => {
|
|
3776
4053
|
tabLinkRouterHttpsActive = null
|
|
3777
4054
|
return {
|
|
3778
4055
|
portMap: new Map(),
|
|
3779
|
-
hostPortMap: new Map()
|
|
4056
|
+
hostPortMap: new Map(),
|
|
4057
|
+
externalHttpByExtPort: new Map(),
|
|
4058
|
+
externalHttpByIntPort: new Map()
|
|
3780
4059
|
}
|
|
3781
4060
|
})
|
|
3782
4061
|
tabLinkRouterInfoExpiry = now + 3000
|
|
@@ -3829,7 +4108,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3829
4108
|
const projectSlug = extractProjectSlug(link).toLowerCase()
|
|
3830
4109
|
const entries = []
|
|
3831
4110
|
const entryByUrl = new Map()
|
|
3832
|
-
const addEntry = (type, label, url) => {
|
|
4111
|
+
const addEntry = (type, label, url, opts = {}) => {
|
|
3833
4112
|
if (!url) {
|
|
3834
4113
|
return
|
|
3835
4114
|
}
|
|
@@ -3854,13 +4133,16 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3854
4133
|
return
|
|
3855
4134
|
}
|
|
3856
4135
|
if (entryByUrl.has(canonical)) {
|
|
4136
|
+
const existing = entryByUrl.get(canonical)
|
|
4137
|
+
if (opts && opts.qr === true) existing.qr = true
|
|
3857
4138
|
return
|
|
3858
4139
|
}
|
|
3859
4140
|
const entry = {
|
|
3860
4141
|
type,
|
|
3861
4142
|
label,
|
|
3862
4143
|
url: canonical,
|
|
3863
|
-
display: formatDisplayUrl(canonical)
|
|
4144
|
+
display: formatDisplayUrl(canonical),
|
|
4145
|
+
qr: opts && opts.qr === true
|
|
3864
4146
|
}
|
|
3865
4147
|
entryByUrl.set(canonical, entry)
|
|
3866
4148
|
entries.push(entry)
|
|
@@ -3874,11 +4156,11 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3874
4156
|
addEntry("url", "URL", baseHref)
|
|
3875
4157
|
}
|
|
3876
4158
|
|
|
3877
|
-
const httpCandidates = new
|
|
4159
|
+
const httpCandidates = new Map() // url -> { qr: boolean }
|
|
3878
4160
|
const httpsCandidates = new Set()
|
|
3879
4161
|
|
|
3880
4162
|
if (isHttpUrl(baseHref)) {
|
|
3881
|
-
httpCandidates.
|
|
4163
|
+
httpCandidates.set(canonicalizeUrl(baseHref), { qr: false })
|
|
3882
4164
|
} else if (isHttpsUrl(baseHref)) {
|
|
3883
4165
|
httpsCandidates.add(canonicalizeUrl(baseHref))
|
|
3884
4166
|
}
|
|
@@ -3896,7 +4178,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3896
4178
|
const normalizedPath = pathname.toLowerCase()
|
|
3897
4179
|
if (normalizedPath.includes(`/asset/api/${projectSlug}`)) {
|
|
3898
4180
|
const fallbackHttp = `http://127.0.0.1:42000${pathname}`
|
|
3899
|
-
httpCandidates.
|
|
4181
|
+
httpCandidates.set(canonicalizeUrl(fallbackHttp), { qr: false })
|
|
3900
4182
|
}
|
|
3901
4183
|
} catch (_) {
|
|
3902
4184
|
// ignore fallback errors
|
|
@@ -3920,15 +4202,16 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3920
4202
|
if (isHttpsUrl(canonical)) {
|
|
3921
4203
|
httpsCandidates.add(canonical)
|
|
3922
4204
|
} else if (isHttpUrl(canonical)) {
|
|
3923
|
-
httpCandidates.
|
|
4205
|
+
const prev = httpCandidates.get(canonical)
|
|
4206
|
+
httpCandidates.set(canonical, { qr: prev ? prev.qr === true : false })
|
|
3924
4207
|
}
|
|
3925
4208
|
})
|
|
3926
4209
|
})
|
|
3927
4210
|
}
|
|
3928
4211
|
|
|
4212
|
+
const routerData = await ensureRouterInfoMapping()
|
|
3929
4213
|
if (httpCandidates.size > 0) {
|
|
3930
|
-
|
|
3931
|
-
httpCandidates.forEach((httpUrl) => {
|
|
4214
|
+
Array.from(httpCandidates.keys()).forEach((httpUrl) => {
|
|
3932
4215
|
const mapped = collectHttpsUrlsFromRouter(httpUrl, routerData)
|
|
3933
4216
|
mapped.forEach((httpsUrl) => {
|
|
3934
4217
|
httpsCandidates.add(httpsUrl)
|
|
@@ -3936,6 +4219,28 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3936
4219
|
})
|
|
3937
4220
|
}
|
|
3938
4221
|
|
|
4222
|
+
// Add external 192.168.* http host:port candidates mapped from the same internal port as base HTTP
|
|
4223
|
+
try {
|
|
4224
|
+
const base = new URL(baseHref, location.origin)
|
|
4225
|
+
let basePort = base.port
|
|
4226
|
+
if (!basePort) {
|
|
4227
|
+
basePort = base.protocol.toLowerCase() === 'https:' ? '443' : '80'
|
|
4228
|
+
}
|
|
4229
|
+
const samePortHosts = routerData && routerData.externalHttpByIntPort ? routerData.externalHttpByIntPort.get(basePort) : null
|
|
4230
|
+
if (samePortHosts && samePortHosts.size > 0) {
|
|
4231
|
+
samePortHosts.forEach((hostport) => {
|
|
4232
|
+
try {
|
|
4233
|
+
const hpUrl = `http://${hostport}${base.pathname || '/'}${base.search || ''}`
|
|
4234
|
+
const canonical = canonicalizeUrl(hpUrl)
|
|
4235
|
+
if (isHttpUrl(canonical)) {
|
|
4236
|
+
const prev = httpCandidates.get(canonical)
|
|
4237
|
+
httpCandidates.set(canonical, { qr: true || (prev ? prev.qr === true : false) })
|
|
4238
|
+
}
|
|
4239
|
+
} catch (_) {}
|
|
4240
|
+
})
|
|
4241
|
+
}
|
|
4242
|
+
} catch (_) {}
|
|
4243
|
+
|
|
3939
4244
|
const httpsList = Array.from(httpsCandidates).sort()
|
|
3940
4245
|
|
|
3941
4246
|
if (httpsList.length > 0) {
|
|
@@ -3950,17 +4255,20 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3950
4255
|
}
|
|
3951
4256
|
const hostPort = parsed.port ? `${parsed.hostname}:${parsed.port}` : parsed.hostname
|
|
3952
4257
|
const httpUrl = `http://${hostPort}${parsed.pathname || "/"}${parsed.search || ""}`
|
|
3953
|
-
|
|
4258
|
+
const key = canonicalizeUrl(httpUrl)
|
|
4259
|
+
const prev = httpCandidates.get(key)
|
|
4260
|
+
httpCandidates.set(key, { qr: prev ? prev.qr === true : false })
|
|
3954
4261
|
} catch (_) {
|
|
3955
4262
|
// ignore failures
|
|
3956
4263
|
}
|
|
3957
4264
|
})
|
|
3958
4265
|
}
|
|
3959
4266
|
|
|
3960
|
-
const httpList = Array.from(httpCandidates).sort()
|
|
4267
|
+
const httpList = Array.from(httpCandidates.keys()).sort()
|
|
3961
4268
|
|
|
3962
4269
|
httpList.forEach((url) => {
|
|
3963
|
-
|
|
4270
|
+
const meta = httpCandidates.get(url) || { qr: false }
|
|
4271
|
+
addEntry("http", "HTTP", url, { qr: meta.qr === true })
|
|
3964
4272
|
})
|
|
3965
4273
|
httpsList.forEach((url) => {
|
|
3966
4274
|
addEntry("https", "HTTPS", url)
|
|
@@ -4060,6 +4368,26 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4060
4368
|
tabLinkHideTimer = null
|
|
4061
4369
|
}
|
|
4062
4370
|
|
|
4371
|
+
// Show lightweight loading popover immediately while mapping fetch runs
|
|
4372
|
+
try {
|
|
4373
|
+
const pop = ensureTabLinkPopoverEl()
|
|
4374
|
+
pop.innerHTML = ''
|
|
4375
|
+
const header = document.createElement('div')
|
|
4376
|
+
header.className = 'tab-link-popover-header'
|
|
4377
|
+
header.innerHTML = `<i class="fa-solid fa-arrow-up-right-from-square"></i><span>Open in browser</span>`
|
|
4378
|
+
const item = document.createElement('div')
|
|
4379
|
+
item.className = 'tab-link-popover-item'
|
|
4380
|
+
const label = document.createElement('span')
|
|
4381
|
+
label.className = 'label'
|
|
4382
|
+
label.textContent = 'Loading…'
|
|
4383
|
+
const value = document.createElement('span')
|
|
4384
|
+
value.className = 'value muted'
|
|
4385
|
+
value.textContent = 'Discovering routes'
|
|
4386
|
+
item.append(label, value)
|
|
4387
|
+
pop.append(header, item)
|
|
4388
|
+
positionTabLinkPopover(pop, link)
|
|
4389
|
+
} catch (_) {}
|
|
4390
|
+
|
|
4063
4391
|
let entries
|
|
4064
4392
|
try {
|
|
4065
4393
|
entries = await buildTabLinkEntries(link)
|
|
@@ -4143,7 +4471,6 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4143
4471
|
entries.forEach((entry) => {
|
|
4144
4472
|
const item = document.createElement("button")
|
|
4145
4473
|
item.type = "button"
|
|
4146
|
-
item.className = "tab-link-popover-item"
|
|
4147
4474
|
item.setAttribute("data-url", entry.url)
|
|
4148
4475
|
const labelSpan = document.createElement("span")
|
|
4149
4476
|
labelSpan.className = "label"
|
|
@@ -4151,7 +4478,24 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
4151
4478
|
const valueSpan = document.createElement("span")
|
|
4152
4479
|
valueSpan.className = "value"
|
|
4153
4480
|
valueSpan.textContent = entry.display
|
|
4154
|
-
|
|
4481
|
+
|
|
4482
|
+
if (entry.type === 'http' && entry.qr === true) {
|
|
4483
|
+
item.className = "tab-link-popover-item qr-inline"
|
|
4484
|
+
const textCol = document.createElement('div')
|
|
4485
|
+
textCol.className = 'textcol'
|
|
4486
|
+
textCol.append(labelSpan, valueSpan)
|
|
4487
|
+
const qrImg = document.createElement('img')
|
|
4488
|
+
qrImg.className = 'qr'
|
|
4489
|
+
qrImg.alt = 'QR'
|
|
4490
|
+
qrImg.decoding = 'async'
|
|
4491
|
+
qrImg.loading = 'lazy'
|
|
4492
|
+
qrImg.src = `/qr?data=${encodeURIComponent(entry.url)}&s=4&m=0`
|
|
4493
|
+
item.append(textCol, qrImg)
|
|
4494
|
+
} else {
|
|
4495
|
+
item.className = "tab-link-popover-item"
|
|
4496
|
+
// Keep label and value as direct children so column layout applies
|
|
4497
|
+
item.append(labelSpan, valueSpan)
|
|
4498
|
+
}
|
|
4155
4499
|
popover.appendChild(item)
|
|
4156
4500
|
})
|
|
4157
4501
|
|
|
@@ -6517,7 +6861,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
6517
6861
|
}
|
|
6518
6862
|
});
|
|
6519
6863
|
*/
|
|
6520
|
-
<% if (type === "browse") { %>
|
|
6864
|
+
<% if (type === "browse" || type === "files") { %>
|
|
6521
6865
|
const repoStatusCache = new Map()
|
|
6522
6866
|
let lastRepoList = []
|
|
6523
6867
|
let currentChanges = []
|
|
@@ -7143,10 +7487,43 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7143
7487
|
const showIframeView = (src) => {
|
|
7144
7488
|
const iframeMarkup = `<iframe src="${src}" frameborder="0"></iframe>`
|
|
7145
7489
|
const diffBody = document.querySelector('.pinokio-modal-body--diff')
|
|
7490
|
+
let iframeContainer = null
|
|
7146
7491
|
if (diffBody) {
|
|
7147
7492
|
diffBody.classList.remove('pinokio-modal-body--diff')
|
|
7148
7493
|
diffBody.classList.add('pinokio-modal-body--iframe')
|
|
7149
7494
|
diffBody.innerHTML = iframeMarkup
|
|
7495
|
+
iframeContainer = diffBody
|
|
7496
|
+
} else {
|
|
7497
|
+
const activeSwal = Swal.isVisible()
|
|
7498
|
+
const fallbackHtml = `
|
|
7499
|
+
<div class="pinokio-modal-surface pinokio-modal-surface--iframe">
|
|
7500
|
+
<div class="pinokio-modal-body pinokio-modal-body--iframe"></div>
|
|
7501
|
+
</div>
|
|
7502
|
+
`
|
|
7503
|
+
const launchPromise = Swal.fire({
|
|
7504
|
+
html: fallbackHtml,
|
|
7505
|
+
customClass: {
|
|
7506
|
+
popup: 'pinokio-modern-modal',
|
|
7507
|
+
htmlContainer: 'pinokio-modern-html',
|
|
7508
|
+
closeButton: 'pinokio-modern-close'
|
|
7509
|
+
},
|
|
7510
|
+
backdrop: 'rgba(9,11,15,0.65)',
|
|
7511
|
+
width: 'min(720px, 90vw)',
|
|
7512
|
+
showConfirmButton: false,
|
|
7513
|
+
showCloseButton: true,
|
|
7514
|
+
buttonsStyling: false,
|
|
7515
|
+
focusConfirm: false,
|
|
7516
|
+
})
|
|
7517
|
+
if (!activeSwal) {
|
|
7518
|
+
launchPromise.then(() => {})
|
|
7519
|
+
}
|
|
7520
|
+
const container = Swal.getHtmlContainer()
|
|
7521
|
+
if (container) {
|
|
7522
|
+
iframeContainer = container.querySelector('.pinokio-modal-body--iframe')
|
|
7523
|
+
if (iframeContainer) {
|
|
7524
|
+
iframeContainer.innerHTML = iframeMarkup
|
|
7525
|
+
}
|
|
7526
|
+
}
|
|
7150
7527
|
}
|
|
7151
7528
|
const commitFooter = document.querySelector('.pinokio-modal-footer--commit')
|
|
7152
7529
|
if (commitFooter && commitFooter.parentNode) {
|
|
@@ -7616,7 +7993,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7616
7993
|
}
|
|
7617
7994
|
|
|
7618
7995
|
const historyData = await response.json()
|
|
7619
|
-
displayGitHistory(historyData, { repoName: repoName || null, repoParam: repoParam || null })
|
|
7996
|
+
displayGitHistory(historyData, { repoName: repoName || null, repoParam: repoParam || null, repoData })
|
|
7620
7997
|
} catch (error) {
|
|
7621
7998
|
console.error('Failed to load git history:', error)
|
|
7622
7999
|
Swal.fire({
|
|
@@ -7629,9 +8006,14 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7629
8006
|
|
|
7630
8007
|
const displayGitHistory = (historyData, options = {}) => {
|
|
7631
8008
|
const repoName = options && typeof options === 'object' ? options.repoName : null
|
|
8009
|
+
const repoData = options && typeof options === 'object' ? options.repoData : null
|
|
7632
8010
|
const commits = historyData.log || []
|
|
7633
8011
|
const remote = historyData.remote || ''
|
|
7634
8012
|
const currentRef = historyData.ref || 'HEAD'
|
|
8013
|
+
const branchEntries = Array.isArray(historyData.branches) ? historyData.branches : []
|
|
8014
|
+
const isOid = (s) => typeof s === 'string' && /^[0-9a-f]{7,40}$/i.test(s)
|
|
8015
|
+
const realBranchEntries = branchEntries.filter((entry) => entry && typeof entry.branch === 'string' && entry.branch.length > 0 && !isOid(entry.branch))
|
|
8016
|
+
const selectedBranchName = (historyData && typeof historyData.branch === 'string' && !isOid(historyData.branch)) ? historyData.branch : null
|
|
7635
8017
|
|
|
7636
8018
|
const commitCountLabel = `${commits.length} commit${commits.length === 1 ? '' : 's'}`
|
|
7637
8019
|
const lastCommit = commits[0]
|
|
@@ -7659,6 +8041,26 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7659
8041
|
</div>
|
|
7660
8042
|
</div>
|
|
7661
8043
|
${metaBadges.length ? `<div class="pinokio-history-meta">${metaBadges.join('')}</div>` : ''}
|
|
8044
|
+
<div class="pinokio-history-latest-banner" data-history-latest-banner>
|
|
8045
|
+
<div class="pinokio-history-latest-text">
|
|
8046
|
+
Currently viewing ${escapeHtml(currentRef)}
|
|
8047
|
+
</div>
|
|
8048
|
+
<div class="pinokio-history-actions">
|
|
8049
|
+
<button type="button" class="pinokio-history-latest-btn" data-history-return-head>
|
|
8050
|
+
<i class="fa-solid fa-arrow-rotate-left"></i> Return to newest commit
|
|
8051
|
+
</button>
|
|
8052
|
+
${realBranchEntries.length ? `
|
|
8053
|
+
<select class="pinokio-modal-input pinokio-history-branch-select" data-history-branch-select aria-label="Select branch">
|
|
8054
|
+
${realBranchEntries.map(e => `
|
|
8055
|
+
<option value="${escapeHtml(e.branch)}"${selectedBranchName === e.branch ? ' selected' : ''}>${escapeHtml(e.branch)}</option>
|
|
8056
|
+
`).join('')}
|
|
8057
|
+
</select>
|
|
8058
|
+
<button type="button" class="pinokio-history-latest-btn" data-history-branch-switch>
|
|
8059
|
+
<i class="fa-solid fa-code-branch"></i> Switch
|
|
8060
|
+
</button>
|
|
8061
|
+
` : ''}
|
|
8062
|
+
</div>
|
|
8063
|
+
</div>
|
|
7662
8064
|
<div class="pinokio-modal-body pinokio-modal-body--history">
|
|
7663
8065
|
${commits.length === 0 ?
|
|
7664
8066
|
'<div class="pinokio-history-empty">No commits found</div>' :
|
|
@@ -7689,8 +8091,76 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7689
8091
|
if (commitInfo) {
|
|
7690
8092
|
await showCommitDiffModal(commitInfo)
|
|
7691
8093
|
}
|
|
7692
|
-
|
|
8094
|
+
})
|
|
7693
8095
|
})
|
|
8096
|
+
|
|
8097
|
+
const returnBtn = document.querySelector('[data-history-return-head]')
|
|
8098
|
+
const banner = document.querySelector('[data-history-latest-banner]')
|
|
8099
|
+
const branchSelect = document.querySelector('[data-history-branch-select]')
|
|
8100
|
+
const branchSwitchBtn = document.querySelector('[data-history-branch-switch]')
|
|
8101
|
+
if (typeof showIframeView === 'function') {
|
|
8102
|
+
const repoParam = options && typeof options === 'object' ? options.repoParam : null
|
|
8103
|
+
let checkoutCwd = null
|
|
8104
|
+
if (historyData && typeof historyData.dir === 'string' && historyData.dir.length > 0) {
|
|
8105
|
+
checkoutCwd = historyData.dir
|
|
8106
|
+
} else if (repoParam) {
|
|
8107
|
+
checkoutCwd = repoParam
|
|
8108
|
+
}
|
|
8109
|
+
|
|
8110
|
+
const isOid = (s) => typeof s === 'string' && /^[0-9a-f]{7,40}$/i.test(s)
|
|
8111
|
+
const branchEntries = Array.isArray(historyData.branches) ? historyData.branches : []
|
|
8112
|
+
const realBranches = branchEntries
|
|
8113
|
+
.map((entry) => entry && typeof entry.branch === 'string' ? entry.branch : null)
|
|
8114
|
+
.filter((name) => name && !isOid(name))
|
|
8115
|
+
|
|
8116
|
+
// Wire the branch selector
|
|
8117
|
+
if (branchSelect && checkoutCwd) {
|
|
8118
|
+
branchSelect.addEventListener('change', () => {
|
|
8119
|
+
const value = branchSelect.value
|
|
8120
|
+
if (!value) return
|
|
8121
|
+
const url = `/run/scripts/git/checkout.json?cwd=${encodeURIComponent(checkoutCwd)}&commit=${encodeURIComponent(value)}&callback_target=parent&callback=$location.href`
|
|
8122
|
+
openCheckoutTerminal(url)
|
|
8123
|
+
})
|
|
8124
|
+
}
|
|
8125
|
+
if (branchSwitchBtn && branchSelect && checkoutCwd) {
|
|
8126
|
+
branchSwitchBtn.addEventListener('click', () => {
|
|
8127
|
+
const value = branchSelect.value
|
|
8128
|
+
if (!value) return
|
|
8129
|
+
const url = `/run/scripts/git/checkout.json?cwd=${encodeURIComponent(checkoutCwd)}&commit=${encodeURIComponent(value)}&callback_target=parent&callback=$location.href`
|
|
8130
|
+
openCheckoutTerminal(url)
|
|
8131
|
+
})
|
|
8132
|
+
}
|
|
8133
|
+
|
|
8134
|
+
// Fix "Return to newest commit" target selection
|
|
8135
|
+
if (returnBtn) {
|
|
8136
|
+
let checkoutTarget = null
|
|
8137
|
+
if (repoData && typeof repoData.branch === 'string' && repoData.branch.length > 0 && !isOid(repoData.branch)) {
|
|
8138
|
+
checkoutTarget = repoData.branch
|
|
8139
|
+
} else if (historyData && typeof historyData.branch === 'string' && historyData.branch.length > 0 && !isOid(historyData.branch)) {
|
|
8140
|
+
checkoutTarget = historyData.branch
|
|
8141
|
+
} else if (realBranches.length > 0) {
|
|
8142
|
+
const prefer = ['main', 'master', 'develop', 'dev']
|
|
8143
|
+
checkoutTarget = prefer.find((n) => realBranches.includes(n)) || realBranches[0]
|
|
8144
|
+
}
|
|
8145
|
+
|
|
8146
|
+
if (checkoutCwd && checkoutTarget) {
|
|
8147
|
+
const checkoutUrl = `/run/scripts/git/checkout.json?cwd=${encodeURIComponent(checkoutCwd)}&commit=${encodeURIComponent(checkoutTarget)}&callback_target=parent&callback=$location.href`
|
|
8148
|
+
returnBtn.addEventListener('click', () => {
|
|
8149
|
+
openCheckoutTerminal(checkoutUrl)
|
|
8150
|
+
})
|
|
8151
|
+
} else {
|
|
8152
|
+
returnBtn.disabled = true
|
|
8153
|
+
if (banner) {
|
|
8154
|
+
banner.classList.add('pinokio-history-latest-banner--disabled')
|
|
8155
|
+
}
|
|
8156
|
+
}
|
|
8157
|
+
}
|
|
8158
|
+
} else if (banner) {
|
|
8159
|
+
const parent = banner.parentNode
|
|
8160
|
+
if (parent) {
|
|
8161
|
+
parent.removeChild(banner)
|
|
8162
|
+
}
|
|
8163
|
+
}
|
|
7694
8164
|
}
|
|
7695
8165
|
})
|
|
7696
8166
|
}
|
|
@@ -7731,6 +8201,20 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7731
8201
|
alert('No file changes detected.')
|
|
7732
8202
|
return
|
|
7733
8203
|
}
|
|
8204
|
+
|
|
8205
|
+
const commitOid = typeof diffData.oid === 'string' ? diffData.oid : (Array.isArray(changes) && typeof changes[0]?.ref === 'string' ? changes[0].ref : null)
|
|
8206
|
+
const commitCheckoutCwd = (() => {
|
|
8207
|
+
const url = typeof diffData.git_commit_url === 'string' ? new URL(diffData.git_commit_url, window.location.origin) : null
|
|
8208
|
+
if (!url) {
|
|
8209
|
+
return null
|
|
8210
|
+
}
|
|
8211
|
+
const cwdParam = url.searchParams.get('cwd')
|
|
8212
|
+
return cwdParam && cwdParam.length > 0 ? cwdParam : null
|
|
8213
|
+
})()
|
|
8214
|
+
|
|
8215
|
+
const checkoutUrl = commitOid && commitCheckoutCwd
|
|
8216
|
+
? `/run/scripts/git/checkout.json?cwd=${encodeURIComponent(commitCheckoutCwd)}&commit=${encodeURIComponent(commitOid)}&callback_target=parent&callback=$location.href`
|
|
8217
|
+
: null
|
|
7734
8218
|
|
|
7735
8219
|
// Create custom overlay modal
|
|
7736
8220
|
const overlay = document.createElement('div')
|
|
@@ -7755,6 +8239,13 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7755
8239
|
<div class="pinokio-git-diff-empty-state">Select a file to view its changes</div>
|
|
7756
8240
|
</div>
|
|
7757
8241
|
</div>
|
|
8242
|
+
${checkoutUrl ? `
|
|
8243
|
+
<div class="pinokio-git-commit-actions">
|
|
8244
|
+
<button type="button" class="pinokio-git-commit-switch-btn">
|
|
8245
|
+
<i class="fa-solid fa-clock-rotate-left"></i> Switch to this version
|
|
8246
|
+
</button>
|
|
8247
|
+
</div>
|
|
8248
|
+
` : ''}
|
|
7758
8249
|
</div>
|
|
7759
8250
|
</div>
|
|
7760
8251
|
`
|
|
@@ -7763,14 +8254,23 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7763
8254
|
|
|
7764
8255
|
// Add close handler
|
|
7765
8256
|
const closeBtn = overlay.querySelector('.pinokio-custom-commit-close')
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
8257
|
+
let escHandler = null
|
|
8258
|
+
const cleanupOverlay = () => {
|
|
8259
|
+
if (escHandler) {
|
|
8260
|
+
document.removeEventListener('keydown', escHandler)
|
|
8261
|
+
escHandler = null
|
|
8262
|
+
}
|
|
8263
|
+
if (overlay.parentNode) {
|
|
8264
|
+
overlay.parentNode.removeChild(overlay)
|
|
8265
|
+
}
|
|
8266
|
+
}
|
|
8267
|
+
|
|
8268
|
+
closeBtn.addEventListener('click', cleanupOverlay)
|
|
7769
8269
|
|
|
7770
8270
|
// Close on overlay click
|
|
7771
8271
|
overlay.addEventListener('click', (e) => {
|
|
7772
8272
|
if (e.target === overlay) {
|
|
7773
|
-
|
|
8273
|
+
cleanupOverlay()
|
|
7774
8274
|
}
|
|
7775
8275
|
})
|
|
7776
8276
|
|
|
@@ -7798,16 +8298,66 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
7798
8298
|
}
|
|
7799
8299
|
})
|
|
7800
8300
|
})
|
|
8301
|
+
|
|
8302
|
+
if (checkoutUrl) {
|
|
8303
|
+
const switchBtn = overlay.querySelector('.pinokio-git-commit-switch-btn')
|
|
8304
|
+
if (switchBtn) {
|
|
8305
|
+
switchBtn.addEventListener('click', () => {
|
|
8306
|
+
cleanupOverlay()
|
|
8307
|
+
showIframeView(checkoutUrl)
|
|
8308
|
+
})
|
|
8309
|
+
}
|
|
8310
|
+
}
|
|
7801
8311
|
|
|
7802
8312
|
// Handle escape key
|
|
7803
|
-
|
|
8313
|
+
escHandler = (e) => {
|
|
7804
8314
|
if (e.key === 'Escape') {
|
|
7805
|
-
|
|
7806
|
-
document.removeEventListener('keydown', escHandler)
|
|
8315
|
+
cleanupOverlay()
|
|
7807
8316
|
}
|
|
7808
8317
|
}
|
|
7809
8318
|
document.addEventListener('keydown', escHandler)
|
|
7810
8319
|
}
|
|
8320
|
+
|
|
8321
|
+
const openCheckoutTerminal = (src) => {
|
|
8322
|
+
const overlay = document.createElement('div')
|
|
8323
|
+
overlay.className = 'pinokio-custom-terminal-overlay'
|
|
8324
|
+
overlay.innerHTML = `
|
|
8325
|
+
<div class="pinokio-custom-terminal-modal">
|
|
8326
|
+
<div class="pinokio-custom-terminal-header">
|
|
8327
|
+
<h3>Git Checkout</h3>
|
|
8328
|
+
<button class="pinokio-custom-terminal-close">×</button>
|
|
8329
|
+
</div>
|
|
8330
|
+
<div class="pinokio-custom-terminal-body">
|
|
8331
|
+
<iframe src="${src}" frameborder="0"></iframe>
|
|
8332
|
+
</div>
|
|
8333
|
+
</div>
|
|
8334
|
+
`
|
|
8335
|
+
|
|
8336
|
+
const cleanup = () => {
|
|
8337
|
+
document.removeEventListener('keydown', escHandler)
|
|
8338
|
+
if (overlay.parentNode) {
|
|
8339
|
+
overlay.parentNode.removeChild(overlay)
|
|
8340
|
+
}
|
|
8341
|
+
}
|
|
8342
|
+
|
|
8343
|
+
const closeBtn = overlay.querySelector('.pinokio-custom-terminal-close')
|
|
8344
|
+
closeBtn.addEventListener('click', cleanup)
|
|
8345
|
+
|
|
8346
|
+
overlay.addEventListener('click', (event) => {
|
|
8347
|
+
if (event.target === overlay) {
|
|
8348
|
+
cleanup()
|
|
8349
|
+
}
|
|
8350
|
+
})
|
|
8351
|
+
|
|
8352
|
+
const escHandler = (event) => {
|
|
8353
|
+
if (event.key === 'Escape') {
|
|
8354
|
+
cleanup()
|
|
8355
|
+
}
|
|
8356
|
+
}
|
|
8357
|
+
|
|
8358
|
+
document.addEventListener('keydown', escHandler)
|
|
8359
|
+
document.body.appendChild(overlay)
|
|
8360
|
+
}
|
|
7811
8361
|
|
|
7812
8362
|
const createCommitItem = (commitData) => {
|
|
7813
8363
|
const commit = commitData.commit
|