pinokiod 3.306.0 → 3.308.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/api/htmlmodal/index.js +5 -1
- package/kernel/api/index.js +16 -4
- package/kernel/environment.js +5 -0
- package/kernel/prototype.js +22 -23
- package/kernel/util.js +32 -9
- package/package.json +1 -1
- package/server/index.js +88 -14
- package/server/public/common.js +57 -2
- package/server/public/files-app/app.css +4 -0
- package/server/public/files-app/app.js +80 -1
- package/server/public/htmlmodal.js +5 -4
- package/server/public/tab-idle-notifier.js +19 -1
- package/server/views/app.ejs +252 -46
- package/server/views/index.ejs +3 -2
|
@@ -32,12 +32,16 @@ class HtmlModalAPI {
|
|
|
32
32
|
req.params = {}
|
|
33
33
|
}
|
|
34
34
|
const packet = this.buildPacket(req, action)
|
|
35
|
+
const awaitKey = this.resolveParentPath(req)
|
|
35
36
|
if (options.forceAwait === false) {
|
|
36
37
|
packet.await = false
|
|
37
38
|
}
|
|
39
|
+
if (packet.await) {
|
|
40
|
+
packet.awaitKey = awaitKey
|
|
41
|
+
}
|
|
38
42
|
ondata(packet, 'htmlmodal')
|
|
39
43
|
if (packet.await) {
|
|
40
|
-
const waitKey =
|
|
44
|
+
const waitKey = packet.awaitKey || awaitKey
|
|
41
45
|
const response = await kernel.api.wait(waitKey)
|
|
42
46
|
return response
|
|
43
47
|
}
|
package/kernel/api/index.js
CHANGED
|
@@ -277,10 +277,22 @@ class Api {
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
respond(req) {
|
|
280
|
-
let requestPath
|
|
281
|
-
|
|
282
|
-
this.
|
|
283
|
-
|
|
280
|
+
let requestPath
|
|
281
|
+
try {
|
|
282
|
+
requestPath = this.filePath(req.uri)
|
|
283
|
+
} catch (_) {
|
|
284
|
+
requestPath = req.uri
|
|
285
|
+
}
|
|
286
|
+
const candidates = [requestPath]
|
|
287
|
+
if (req && req.uri && req.uri !== requestPath) {
|
|
288
|
+
candidates.push(req.uri)
|
|
289
|
+
}
|
|
290
|
+
for (const key of candidates) {
|
|
291
|
+
if (this.waiter[key]) {
|
|
292
|
+
this.waiter[key].resolve(req.response)
|
|
293
|
+
delete this.waiter[key]
|
|
294
|
+
return
|
|
295
|
+
}
|
|
284
296
|
}
|
|
285
297
|
}
|
|
286
298
|
wait(scriptPath) {
|
package/kernel/environment.js
CHANGED
|
@@ -587,6 +587,11 @@ const init = async (options, kernel) => {
|
|
|
587
587
|
await fs.promises.writeFile(destination, rendered_recipe)
|
|
588
588
|
}
|
|
589
589
|
}
|
|
590
|
+
await fs.promises.writeFile(path.resolve(root, ".geminiignore"), `ENVIRONMENT
|
|
591
|
+
!/logs
|
|
592
|
+
!/GEMINI.md
|
|
593
|
+
!/SPEC.md
|
|
594
|
+
!/app`)
|
|
590
595
|
}
|
|
591
596
|
|
|
592
597
|
const gitDir = path.resolve(root, ".git")
|
package/kernel/prototype.js
CHANGED
|
@@ -14,38 +14,37 @@ class Proto {
|
|
|
14
14
|
|
|
15
15
|
// if ~/pinokio/prototype doesn't exist, clone
|
|
16
16
|
let exists = await this.kernel.exists("prototype/system")
|
|
17
|
-
if (
|
|
17
|
+
if (exists) {
|
|
18
|
+
await this.kernel.exec({
|
|
19
|
+
message: "git pull",
|
|
20
|
+
path: this.kernel.path("prototype/system")
|
|
21
|
+
}, (e) => {
|
|
22
|
+
process.stdout.write(e.raw)
|
|
23
|
+
})
|
|
24
|
+
} else {
|
|
18
25
|
console.log("prototype doesn't exist. cloning...")
|
|
19
26
|
await fs.promises.mkdir(this.kernel.path("prototype"), { recursive: true }).catch((e) => { })
|
|
20
27
|
await this.kernel.exec({
|
|
21
|
-
//message: "git clone https://github.com/peanutcocktail/prototype system",
|
|
22
|
-
//message: "git clone https://github.com/pinokiocomputer/prototype system",
|
|
23
28
|
message: "git clone https://github.com/pinokiocomputer/proto system",
|
|
24
29
|
path: this.kernel.path("prototype")
|
|
25
30
|
}, (e) => {
|
|
26
31
|
process.stdout.write(e.raw)
|
|
27
32
|
})
|
|
28
33
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
path: this.kernel.path("prototype"),
|
|
44
|
-
filename: "PTERM.md"
|
|
45
|
-
}, (e) => {
|
|
46
|
-
process.stdout.write(e.raw)
|
|
47
|
-
})
|
|
48
|
-
}
|
|
34
|
+
await this.kernel.download({
|
|
35
|
+
uri: "https://raw.githubusercontent.com/pinokiocomputer/home/refs/heads/main/docs/README.md",
|
|
36
|
+
path: this.kernel.path("prototype"),
|
|
37
|
+
filename: "PINOKIO.md"
|
|
38
|
+
}, (e) => {
|
|
39
|
+
process.stdout.write(e.raw)
|
|
40
|
+
})
|
|
41
|
+
await this.kernel.download({
|
|
42
|
+
uri: "https://raw.githubusercontent.com/pinokiocomputer/pterm/refs/heads/main/README.md",
|
|
43
|
+
path: this.kernel.path("prototype"),
|
|
44
|
+
filename: "PTERM.md"
|
|
45
|
+
}, (e) => {
|
|
46
|
+
process.stdout.write(e.raw)
|
|
47
|
+
})
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
async ai() {
|
package/kernel/util.js
CHANGED
|
@@ -770,15 +770,38 @@ function u2p(urlPath) {
|
|
|
770
770
|
}
|
|
771
771
|
|
|
772
772
|
function classifyChange(head, workdir, stage) {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
773
|
+
// isomorphic-git statusMatrix codes:
|
|
774
|
+
// 0: absent, 1: unmodified, 2: modified, 3: added
|
|
775
|
+
const headExists = head !== 0;
|
|
776
|
+
const workdirMissing = workdir === 0;
|
|
777
|
+
const workdirUnmodified = workdir === 1;
|
|
778
|
+
const workdirTouched = !workdirMissing && !workdirUnmodified; // modified or added
|
|
779
|
+
const stageMissing = stage === 0;
|
|
780
|
+
const stageUnmodified = stage === 1;
|
|
781
|
+
const stageTouched = !stageMissing && !stageUnmodified; // staged change (added/modified/deleted)
|
|
782
|
+
|
|
783
|
+
// Untracked file: nothing in HEAD, something in workdir, nothing staged
|
|
784
|
+
if (!headExists && workdirTouched && stageMissing) return 'untracked';
|
|
785
|
+
|
|
786
|
+
// Added (staged): nothing in HEAD, staged entry present
|
|
787
|
+
if (!headExists && stageTouched) return 'added (staged)';
|
|
788
|
+
|
|
789
|
+
// Deleted
|
|
790
|
+
if (headExists && workdirMissing) {
|
|
791
|
+
if (stageMissing || stageUnmodified) return 'deleted (unstaged)';
|
|
792
|
+
return 'deleted (staged)';
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Modified
|
|
796
|
+
if (headExists && workdirTouched) {
|
|
797
|
+
if (stageMissing || stageUnmodified) return 'modified (unstaged)';
|
|
798
|
+
if (!workdirUnmodified && stageTouched) return 'modified (staged + unstaged)';
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Staged-only modification (workdir clean, stage touched)
|
|
802
|
+
if (headExists && workdirUnmodified && stageTouched) return 'modified (staged)';
|
|
803
|
+
|
|
804
|
+
if (headExists && workdirUnmodified && stageUnmodified) return 'clean';
|
|
782
805
|
return `unknown (${head},${workdir},${stage})`;
|
|
783
806
|
}
|
|
784
807
|
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -24,6 +24,7 @@ const fse = require('fs-extra')
|
|
|
24
24
|
const QRCode = require('qrcode')
|
|
25
25
|
const axios = require('axios')
|
|
26
26
|
const crypto = require('crypto')
|
|
27
|
+
const system = require('systeminformation')
|
|
27
28
|
const serveIndex = require('./serveIndex')
|
|
28
29
|
const registerFileRoutes = require('./routes/files')
|
|
29
30
|
const TerminalApi = require('../kernel/api/terminal')
|
|
@@ -647,6 +648,25 @@ class Server {
|
|
|
647
648
|
async getGit(ref, filepath) {
|
|
648
649
|
const dir = this.kernel.path("api", filepath)
|
|
649
650
|
|
|
651
|
+
const gitDirPath = path.join(dir, '.git')
|
|
652
|
+
let gitDirExists = false
|
|
653
|
+
try {
|
|
654
|
+
const gitStats = await fs.promises.stat(gitDirPath)
|
|
655
|
+
gitDirExists = gitStats.isDirectory()
|
|
656
|
+
} catch (_) {
|
|
657
|
+
gitDirExists = false
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
let hasHead = false
|
|
661
|
+
if (gitDirExists) {
|
|
662
|
+
try {
|
|
663
|
+
await git.resolveRef({ fs, dir, ref: 'HEAD' })
|
|
664
|
+
hasHead = true
|
|
665
|
+
} catch (_) {
|
|
666
|
+
hasHead = false
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
650
670
|
let branchList = []
|
|
651
671
|
try {
|
|
652
672
|
branchList = await git.listBranches({ fs, dir })
|
|
@@ -752,6 +772,8 @@ class Server {
|
|
|
752
772
|
log,
|
|
753
773
|
branch: currentBranch,
|
|
754
774
|
branches,
|
|
775
|
+
gitDirExists,
|
|
776
|
+
hasHead,
|
|
755
777
|
dir,
|
|
756
778
|
detached: isDetached,
|
|
757
779
|
logError: logError ? String(logError.message || logError) : null
|
|
@@ -3480,6 +3502,54 @@ class Server {
|
|
|
3480
3502
|
throw new Error("Invalid path: " + config.home)
|
|
3481
3503
|
}
|
|
3482
3504
|
|
|
3505
|
+
const findExistingAncestor = async (p) => {
|
|
3506
|
+
let current = p
|
|
3507
|
+
while (true) {
|
|
3508
|
+
if (await fse.pathExists(current)) {
|
|
3509
|
+
return current
|
|
3510
|
+
}
|
|
3511
|
+
const parent = path.dirname(current)
|
|
3512
|
+
if (!parent || parent === current) {
|
|
3513
|
+
return null
|
|
3514
|
+
}
|
|
3515
|
+
current = parent
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
const normalizeMountPath = (p) => {
|
|
3520
|
+
if (!p) return null
|
|
3521
|
+
const normalized = path.normalize(p)
|
|
3522
|
+
const { root } = path.parse(normalized)
|
|
3523
|
+
if (normalized === root) {
|
|
3524
|
+
return root.replace(/\\/g, '/')
|
|
3525
|
+
}
|
|
3526
|
+
return normalized.replace(/[\\/]+$/g, '').replace(/\\/g, '/')
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
const resolvedHome = path.resolve(config.home)
|
|
3530
|
+
const ancestor = await findExistingAncestor(resolvedHome)
|
|
3531
|
+
if (!ancestor) {
|
|
3532
|
+
throw new Error("Invalid path: unable to locate parent volume for " + config.home)
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
const mounts = await system.fsSize().catch(() => [])
|
|
3536
|
+
const normalizedAncestor = normalizeMountPath(ancestor)
|
|
3537
|
+
let bestMount = null
|
|
3538
|
+
for (const volume of mounts) {
|
|
3539
|
+
const mountPath = normalizeMountPath(volume.mount)
|
|
3540
|
+
if (!mountPath || !normalizedAncestor) continue
|
|
3541
|
+
const isParent = mountPath === "/" ? normalizedAncestor.startsWith("/") : (normalizedAncestor === mountPath || normalizedAncestor.startsWith(mountPath + "/"))
|
|
3542
|
+
if (isParent) {
|
|
3543
|
+
if (!bestMount || mountPath.length > bestMount.mount.length) {
|
|
3544
|
+
bestMount = { mount: mountPath, type: (volume.type || '').toLowerCase() }
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
if (bestMount && bestMount.type.includes("exfat")) {
|
|
3550
|
+
throw new Error("Pinokio home cannot be located on an exFAT drive. Please choose a different location.")
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3483
3553
|
// // check if the destination already exists => throw error
|
|
3484
3554
|
// let exists = await fse.pathExists(config.home)
|
|
3485
3555
|
// if (exists) {
|
|
@@ -5315,18 +5385,12 @@ class Server {
|
|
|
5315
5385
|
let list = this.getPeers()
|
|
5316
5386
|
let current_urls = await this.current_urls(req.originalUrl.slice(1))
|
|
5317
5387
|
let items = [{
|
|
5318
|
-
image: "/pinokio-black.png",
|
|
5319
|
-
name: "pinokio",
|
|
5320
|
-
title: "pinokio.co",
|
|
5321
|
-
description: "Connect with pinokio.co",
|
|
5322
|
-
url: "/connect/pinokio"
|
|
5323
|
-
}, {
|
|
5324
|
-
icon: "fa-brands fa-square-x-twitter",
|
|
5325
|
-
name: "x",
|
|
5326
|
-
title: "x.com",
|
|
5327
|
-
description: "Connect with X.com",
|
|
5328
|
-
url: "/connect/x"
|
|
5329
|
-
}, {
|
|
5388
|
+
// image: "/pinokio-black.png",
|
|
5389
|
+
// name: "pinokio",
|
|
5390
|
+
// title: "pinokio.co",
|
|
5391
|
+
// description: "Connect with pinokio.co",
|
|
5392
|
+
// url: "/connect/pinokio"
|
|
5393
|
+
// }, {
|
|
5330
5394
|
emoji: "🤗",
|
|
5331
5395
|
name: "huggingface",
|
|
5332
5396
|
title: "huggingface.co",
|
|
@@ -5338,6 +5402,12 @@ class Server {
|
|
|
5338
5402
|
title: "github.com",
|
|
5339
5403
|
description: "Connect with GitHub.com",
|
|
5340
5404
|
url: "/github"
|
|
5405
|
+
}, {
|
|
5406
|
+
icon: "fa-brands fa-square-x-twitter",
|
|
5407
|
+
name: "x",
|
|
5408
|
+
title: "x.com",
|
|
5409
|
+
description: "Connect with X.com",
|
|
5410
|
+
url: "/connect/x"
|
|
5341
5411
|
}]
|
|
5342
5412
|
let github_hosts = await this.get_github_hosts()
|
|
5343
5413
|
for(let i=0; i<items.length; i++) {
|
|
@@ -7396,8 +7466,12 @@ class Server {
|
|
|
7396
7466
|
mode = "shell"
|
|
7397
7467
|
break
|
|
7398
7468
|
}
|
|
7469
|
+
if (step.method === "app.launch") {
|
|
7470
|
+
mode = "launch"
|
|
7471
|
+
break
|
|
7472
|
+
}
|
|
7399
7473
|
}
|
|
7400
|
-
if (mode === "exec") {
|
|
7474
|
+
if (mode === "exec" || mode === "launch") {
|
|
7401
7475
|
item.type = "Open"
|
|
7402
7476
|
exec_menus.push(item)
|
|
7403
7477
|
} else if (mode === "shell") {
|
|
@@ -8797,7 +8871,7 @@ class Server {
|
|
|
8797
8871
|
let message = await this.setConfig(req.body)
|
|
8798
8872
|
res.json({ success: true, message })
|
|
8799
8873
|
} catch (e) {
|
|
8800
|
-
res.json({ error: e.
|
|
8874
|
+
res.json({ error: e && e.message ? e.message : e })
|
|
8801
8875
|
}
|
|
8802
8876
|
|
|
8803
8877
|
// update homedir
|
package/server/public/common.js
CHANGED
|
@@ -8,6 +8,49 @@ const createLauncherDebugLog = (...args) => {
|
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
const guardedRoutePrefixes = [
|
|
12
|
+
'/pinokio/launch/',
|
|
13
|
+
'/pinokio/browser/',
|
|
14
|
+
'/v/',
|
|
15
|
+
'/p/',
|
|
16
|
+
'/api/',
|
|
17
|
+
'/_api/',
|
|
18
|
+
'/run/',
|
|
19
|
+
'/tools',
|
|
20
|
+
'/bundle/',
|
|
21
|
+
'/init',
|
|
22
|
+
'/connect/',
|
|
23
|
+
'/github',
|
|
24
|
+
'/setup/',
|
|
25
|
+
'/requirements_check/',
|
|
26
|
+
'/agents',
|
|
27
|
+
'/network',
|
|
28
|
+
'/net/',
|
|
29
|
+
'/git/',
|
|
30
|
+
'/dev/',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function needsRequirementsGuard(targetUrl) {
|
|
34
|
+
try {
|
|
35
|
+
const url = typeof targetUrl === 'string' ? new URL(targetUrl, window.location.href) : targetUrl;
|
|
36
|
+
const path = url.pathname || '';
|
|
37
|
+
const query = url.searchParams || new URLSearchParams(url.search || '');
|
|
38
|
+
if (path === '/home') {
|
|
39
|
+
const mode = (query.get('mode') || '').toLowerCase();
|
|
40
|
+
return mode === 'download';
|
|
41
|
+
}
|
|
42
|
+
for (const prefix of guardedRoutePrefixes) {
|
|
43
|
+
if (path === prefix || path.startsWith(prefix)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
} catch (_) {
|
|
49
|
+
// Be safe and keep guarding if we cannot parse
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
11
54
|
function createMinimalLoadingSwal () {
|
|
12
55
|
if (typeof window === 'undefined' || typeof window.Swal === 'undefined') {
|
|
13
56
|
return () => {};
|
|
@@ -22,7 +65,7 @@ function createMinimalLoadingSwal () {
|
|
|
22
65
|
}
|
|
23
66
|
};
|
|
24
67
|
swal.fire({
|
|
25
|
-
html: "<i class='fa-solid fa-circle-notch fa-spin'></i>
|
|
68
|
+
html: "<i class='fa-solid fa-circle-notch fa-spin'></i> Backend still warming up...",
|
|
26
69
|
allowOutsideClick: false,
|
|
27
70
|
allowEscapeKey: false,
|
|
28
71
|
showConfirmButton: false,
|
|
@@ -97,8 +140,20 @@ if (onfinish) {
|
|
|
97
140
|
}
|
|
98
141
|
// The original task
|
|
99
142
|
*/
|
|
100
|
-
function wait_ready () {
|
|
143
|
+
function wait_ready (targetUrl = null) {
|
|
101
144
|
createLauncherDebugLog('wait_ready invoked');
|
|
145
|
+
let navTarget = null;
|
|
146
|
+
if (targetUrl) {
|
|
147
|
+
try {
|
|
148
|
+
navTarget = targetUrl instanceof URL ? targetUrl : new URL(targetUrl, window.location.href);
|
|
149
|
+
} catch (_) {
|
|
150
|
+
navTarget = null;
|
|
151
|
+
}
|
|
152
|
+
if (navTarget && !needsRequirementsGuard(navTarget)) {
|
|
153
|
+
createLauncherDebugLog('wait_ready short-circuit (unguarded route)', { path: navTarget.pathname });
|
|
154
|
+
return Promise.resolve({ ready: true, closeModal: null });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
102
157
|
return new Promise((resolve, reject) => {
|
|
103
158
|
check_ready().then((ready) => {
|
|
104
159
|
createLauncherDebugLog('wait_ready initial requirements readiness', ready);
|
|
@@ -399,6 +399,10 @@ body.dark .files-app__tab--stale .files-app__tab-label::after {
|
|
|
399
399
|
font-size: 13px;
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
+
.files-app__tree-action--open {
|
|
403
|
+
font-size: 13px;
|
|
404
|
+
}
|
|
405
|
+
|
|
402
406
|
.files-app__tree-action:hover {
|
|
403
407
|
background: rgba(127, 91, 243, 0.15);
|
|
404
408
|
color: var(--text-color);
|
|
@@ -12,6 +12,49 @@
|
|
|
12
12
|
return String(value).replace(/[^a-zA-Z0-9_-]/g, (char) => `\\${char}`);
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
const decodeBase64 = (value) => {
|
|
16
|
+
if (!value) return '';
|
|
17
|
+
try {
|
|
18
|
+
return window.atob(value);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
console.warn('Failed to decode workspace root', err);
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const joinFsPath = (base, relativePosix) => {
|
|
26
|
+
if (!base) return '';
|
|
27
|
+
if (!relativePosix) return base;
|
|
28
|
+
const separator = base.includes('\\') ? '\\' : '/';
|
|
29
|
+
const normalizedBase = base.endsWith(separator) ? base.slice(0, -1) : base;
|
|
30
|
+
const rel = relativePosix
|
|
31
|
+
.split('/')
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
.join(separator);
|
|
34
|
+
return `${normalizedBase}${separator}${rel}`;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
async function openInFileExplorer(path) {
|
|
38
|
+
if (!path) return;
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch('/openfs', {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/json',
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify({ path, mode: 'view' }),
|
|
46
|
+
});
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new Error(`Failed to open file explorer (${response.status})`);
|
|
49
|
+
}
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('Failed to open file explorer', error);
|
|
52
|
+
if (this && typeof setStatus === 'function') {
|
|
53
|
+
setStatus.call(this, 'Failed to open in file explorer', 'error');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
15
58
|
const createElement = (tag, className) => {
|
|
16
59
|
const el = document.createElement(tag);
|
|
17
60
|
if (className) {
|
|
@@ -44,6 +87,7 @@
|
|
|
44
87
|
initialPath: config.initialPath || '',
|
|
45
88
|
initialPathType: config.initialPathType || null,
|
|
46
89
|
workspaceRoot: config.workspaceRoot || '',
|
|
90
|
+
workspaceRootDecoded: decodeBase64(config.workspaceRoot || ''),
|
|
47
91
|
treeElements: new Map(),
|
|
48
92
|
sessions: new Map(),
|
|
49
93
|
openOrder: [],
|
|
@@ -453,8 +497,30 @@
|
|
|
453
497
|
label.textContent = entry.name;
|
|
454
498
|
row.appendChild(label);
|
|
455
499
|
|
|
500
|
+
const actions = createElement('span', 'files-app__tree-actions');
|
|
501
|
+
const absoluteFsPath = resolveAbsolutePath.call(this, entry.path);
|
|
502
|
+
if (absoluteFsPath) {
|
|
503
|
+
const openBtn = createElement('span', 'files-app__tree-action files-app__tree-action--open');
|
|
504
|
+
openBtn.setAttribute('role', 'button');
|
|
505
|
+
openBtn.setAttribute('aria-label', 'Open in file explorer');
|
|
506
|
+
openBtn.tabIndex = 0;
|
|
507
|
+
openBtn.title = 'Open in file explorer';
|
|
508
|
+
openBtn.innerHTML = '<i class="fa-solid fa-up-right-from-square"></i>';
|
|
509
|
+
const triggerOpen = (event) => {
|
|
510
|
+
event.preventDefault();
|
|
511
|
+
event.stopPropagation();
|
|
512
|
+
openInFileExplorer.call(this, absoluteFsPath);
|
|
513
|
+
};
|
|
514
|
+
openBtn.addEventListener('click', triggerOpen);
|
|
515
|
+
openBtn.addEventListener('keydown', (event) => {
|
|
516
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
517
|
+
triggerOpen(event);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
actions.appendChild(openBtn);
|
|
521
|
+
}
|
|
522
|
+
|
|
456
523
|
if (!entry.isRoot) {
|
|
457
|
-
const actions = createElement('span', 'files-app__tree-actions');
|
|
458
524
|
const renameBtn = createElement('span', 'files-app__tree-action files-app__tree-action--rename');
|
|
459
525
|
renameBtn.setAttribute('role', 'button');
|
|
460
526
|
renameBtn.setAttribute('aria-label', entry.type === 'directory' ? 'Rename folder' : 'Rename file');
|
|
@@ -492,6 +558,8 @@
|
|
|
492
558
|
}
|
|
493
559
|
});
|
|
494
560
|
actions.appendChild(deleteBtn);
|
|
561
|
+
}
|
|
562
|
+
if (actions.children.length > 0) {
|
|
495
563
|
row.appendChild(actions);
|
|
496
564
|
}
|
|
497
565
|
|
|
@@ -985,6 +1053,17 @@
|
|
|
985
1053
|
}
|
|
986
1054
|
}
|
|
987
1055
|
|
|
1056
|
+
function resolveAbsolutePath(relativePosix) {
|
|
1057
|
+
if (!this || !this.state) {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
const root = this.state.workspaceRootDecoded || '';
|
|
1061
|
+
if (!root) {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
return joinFsPath(root, relativePosix);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
988
1067
|
async function expandInitialPath(initialPath, initialType) {
|
|
989
1068
|
const segments = String(initialPath).split('/').filter(Boolean);
|
|
990
1069
|
if (segments.length === 0) {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
.htmlmodal-actions .btn.primary:hover { background: #0ea5e9; transform: translateY(-1px); }
|
|
36
36
|
.htmlmodal-actions .btn.secondary { background: rgba(148,163,184,0.18); color: #f8fafc; border-color: rgba(148,163,184,0.35); }
|
|
37
37
|
.htmlmodal-actions .btn.secondary:hover { background: rgba(148,163,184,0.3); }
|
|
38
|
-
.htmlmodal-actions .btn.link { background:
|
|
39
|
-
.htmlmodal-actions .btn.link:hover { color: #7dd3fc; }
|
|
38
|
+
.htmlmodal-actions .btn.link { background: rgba(56,189,248,0.12); border-color: rgba(56,189,248,0.55); color: #38bdf8; padding: 10px 18px; }
|
|
39
|
+
.htmlmodal-actions .btn.link:hover { background: rgba(56,189,248,0.2); color: #7dd3fc; transform: translateY(-1px); }
|
|
40
40
|
.htmlmodal-actions .btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
41
41
|
`
|
|
42
42
|
document.head.appendChild(style)
|
|
@@ -104,9 +104,10 @@
|
|
|
104
104
|
this.render(payload)
|
|
105
105
|
}
|
|
106
106
|
if (Object.prototype.hasOwnProperty.call(payload, 'await')) {
|
|
107
|
+
const awaitTarget = payload.awaitKey || packet.id
|
|
107
108
|
if (payload.await) {
|
|
108
|
-
this.current.awaiting =
|
|
109
|
-
} else if (this.current.awaiting ===
|
|
109
|
+
this.current.awaiting = awaitTarget
|
|
110
|
+
} else if (this.current.awaiting === awaitTarget) {
|
|
110
111
|
this.current.awaiting = null
|
|
111
112
|
}
|
|
112
113
|
}
|
|
@@ -689,6 +689,7 @@
|
|
|
689
689
|
hasRecentInput: false,
|
|
690
690
|
awaitingLive: false,
|
|
691
691
|
awaitingIdle: false,
|
|
692
|
+
autoDetected: false,
|
|
692
693
|
isLive: false,
|
|
693
694
|
notified: false,
|
|
694
695
|
lastInput: '',
|
|
@@ -1231,9 +1232,17 @@ const ensureTabAccessories = aggregateDebounce(() => {
|
|
|
1231
1232
|
}
|
|
1232
1233
|
if (changedAttribute === 'data-timestamp') {
|
|
1233
1234
|
updateActivityTimestamp(indicator, state);
|
|
1235
|
+
state.isLive = indicator.classList.contains(LIVE_CLASS);
|
|
1236
|
+
if (!state.hasRecentInput && !state.awaitingLive && !state.awaitingIdle && state.isLive && Number.isFinite(state.lastActivityTimestamp)) {
|
|
1237
|
+
state.commandStartTimestamp = state.commandStartTimestamp || state.lastActivityTimestamp || Date.now();
|
|
1238
|
+
state.awaitingIdle = true;
|
|
1239
|
+
state.autoDetected = true;
|
|
1240
|
+
state.notified = false;
|
|
1241
|
+
}
|
|
1234
1242
|
return;
|
|
1235
1243
|
}
|
|
1236
1244
|
|
|
1245
|
+
const wasLive = state.isLive;
|
|
1237
1246
|
const isLive = indicator.classList.contains(LIVE_CLASS);
|
|
1238
1247
|
state.isLive = isLive;
|
|
1239
1248
|
updateActivityTimestamp(indicator, state);
|
|
@@ -1246,11 +1255,18 @@ const ensureTabAccessories = aggregateDebounce(() => {
|
|
|
1246
1255
|
if (state.awaitingLive && state.hasRecentInput) {
|
|
1247
1256
|
state.awaitingLive = false;
|
|
1248
1257
|
state.awaitingIdle = true;
|
|
1258
|
+
} else if (!state.hasRecentInput && !state.awaitingLive && !state.awaitingIdle && !wasLive && Number.isFinite(state.lastActivityTimestamp)) {
|
|
1259
|
+
// Auto-run scenario: activity started without explicit terminal input.
|
|
1260
|
+
state.awaitingIdle = true;
|
|
1261
|
+
state.autoDetected = true;
|
|
1262
|
+
state.notified = false;
|
|
1249
1263
|
}
|
|
1250
1264
|
return;
|
|
1251
1265
|
}
|
|
1252
1266
|
|
|
1253
|
-
|
|
1267
|
+
const shouldProcessIdle = state.awaitingIdle && !state.notified && (state.hasRecentInput || state.autoDetected);
|
|
1268
|
+
|
|
1269
|
+
if (shouldProcessIdle) {
|
|
1254
1270
|
const activityTs = Number.isFinite(state.lastActivityTimestamp)
|
|
1255
1271
|
? state.lastActivityTimestamp
|
|
1256
1272
|
: Number(indicator.dataset?.timestamp);
|
|
@@ -1276,6 +1292,7 @@ const ensureTabAccessories = aggregateDebounce(() => {
|
|
|
1276
1292
|
state.hasRecentInput = false;
|
|
1277
1293
|
state.awaitingIdle = false;
|
|
1278
1294
|
state.awaitingLive = false;
|
|
1295
|
+
state.autoDetected = false;
|
|
1279
1296
|
state.commandStartTimestamp = 0;
|
|
1280
1297
|
state.lastLiveTimestamp = 0;
|
|
1281
1298
|
state.lastActivityTimestamp = 0;
|
|
@@ -1336,6 +1353,7 @@ const ensureTabAccessories = aggregateDebounce(() => {
|
|
|
1336
1353
|
state.hasRecentInput = true;
|
|
1337
1354
|
state.awaitingLive = true;
|
|
1338
1355
|
state.awaitingIdle = false;
|
|
1356
|
+
state.autoDetected = false;
|
|
1339
1357
|
state.notified = false;
|
|
1340
1358
|
state.lastInput = sanitisePreview(data.line || '');
|
|
1341
1359
|
state.commandStartTimestamp = Date.now();
|
package/server/views/app.ejs
CHANGED
|
@@ -1516,9 +1516,9 @@ body.dark #fs-changes-menu .git-changes-item.git-changes-item--active {
|
|
|
1516
1516
|
}
|
|
1517
1517
|
|
|
1518
1518
|
#fs-status .app-info-card img {
|
|
1519
|
-
width:
|
|
1520
|
-
height:
|
|
1521
|
-
border-radius:
|
|
1519
|
+
width: 25px;
|
|
1520
|
+
height: 25px;
|
|
1521
|
+
border-radius: 4px;
|
|
1522
1522
|
object-fit: cover;
|
|
1523
1523
|
}
|
|
1524
1524
|
|
|
@@ -2349,6 +2349,12 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
2349
2349
|
color: rgba(255, 255, 255, 0.45);
|
|
2350
2350
|
}
|
|
2351
2351
|
|
|
2352
|
+
.pinokio-modal-icon--warning {
|
|
2353
|
+
background: rgba(255, 189, 68, 0.16);
|
|
2354
|
+
color: #ffbd44;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
|
|
2352
2358
|
.fs-dropdown-item--disabled {
|
|
2353
2359
|
opacity: 0.5;
|
|
2354
2360
|
cursor: not-allowed;
|
|
@@ -3070,6 +3076,11 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3070
3076
|
<div class='container'>
|
|
3071
3077
|
<% if (type === "browse") { %>
|
|
3072
3078
|
<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%>">
|
|
3079
|
+
<div class="app-info" title="<%=config.title || name%>">
|
|
3080
|
+
<div class="app-info-card">
|
|
3081
|
+
<img src="<%= config.icon %>" onerror="this.onerror=null; this.src='/pinokio-black.png'" alt="Project icon">
|
|
3082
|
+
</div>
|
|
3083
|
+
</div>
|
|
3073
3084
|
<!--
|
|
3074
3085
|
<div class='fs-status-dropdown nested-menu git blue'>
|
|
3075
3086
|
<button type='button' class='fs-status-btn frame-link reveal'>
|
|
@@ -3126,6 +3137,11 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3126
3137
|
</div>
|
|
3127
3138
|
<% } else if (type === 'files') { %>
|
|
3128
3139
|
<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%>">
|
|
3140
|
+
<div class="app-info" title="<%=config.title || name%>">
|
|
3141
|
+
<div class="app-info-card">
|
|
3142
|
+
<img src="<%= config.icon %>" onerror="this.onerror=null; this.src='/pinokio-black.png'" alt="Project icon">
|
|
3143
|
+
</div>
|
|
3144
|
+
</div>
|
|
3129
3145
|
<!--
|
|
3130
3146
|
<div class='fs-status-dropdown nested-menu git blue'>
|
|
3131
3147
|
<button type='button' class='fs-status-btn frame-link reveal'>
|
|
@@ -3823,7 +3839,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3823
3839
|
if (typeof timestamp === "number") {
|
|
3824
3840
|
const relative = formatRelativeTime(timestamp)
|
|
3825
3841
|
updatedEl.dataset.timestamp = String(timestamp)
|
|
3826
|
-
const isLive = (Date.now() - timestamp) <
|
|
3842
|
+
const isLive = (Date.now() - timestamp) < 3000
|
|
3827
3843
|
updatedEl.classList.toggle('is-live', isLive)
|
|
3828
3844
|
if (label) {
|
|
3829
3845
|
label.textContent = isLive ? `${relative} · live` : `idle · ${relative}`
|
|
@@ -3856,7 +3872,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
|
|
|
3856
3872
|
const dot = updatedEl.querySelector('.indicator .dot')
|
|
3857
3873
|
const relative = formatRelativeTime(timestamp)
|
|
3858
3874
|
updatedEl.dataset.timestamp = String(timestamp)
|
|
3859
|
-
const isLive = (Date.now() - timestamp) <
|
|
3875
|
+
const isLive = (Date.now() - timestamp) < 3000
|
|
3860
3876
|
updatedEl.classList.toggle('is-live', isLive)
|
|
3861
3877
|
if (label) {
|
|
3862
3878
|
label.textContent = isLive ? `${relative} · live` : `idle · ${relative}`
|
|
@@ -4022,7 +4038,7 @@ const rerenderMenuSection = (container, html) => {
|
|
|
4022
4038
|
}
|
|
4023
4039
|
const label = node.querySelector('.indicator .label')
|
|
4024
4040
|
const relative = formatRelativeTime(value)
|
|
4025
|
-
const isLive = (now - value) <
|
|
4041
|
+
const isLive = (now - value) < 3000
|
|
4026
4042
|
node.classList.toggle('is-live', isLive)
|
|
4027
4043
|
if (label) {
|
|
4028
4044
|
label.textContent = isLive ? `${relative} · live` : `idle · ${relative}`
|
|
@@ -5923,6 +5939,196 @@ const rerenderMenuSection = (container, html) => {
|
|
|
5923
5939
|
forkMenu.appendChild(fragment)
|
|
5924
5940
|
}
|
|
5925
5941
|
|
|
5942
|
+
const fetchPublishPreflightState = async (repoParam) => {
|
|
5943
|
+
if (!repoParam) {
|
|
5944
|
+
return { ok: false, reason: 'missing-repo' }
|
|
5945
|
+
}
|
|
5946
|
+
const encodedParam = encodeRepoPath(repoParam)
|
|
5947
|
+
const infoUrl = `/info/git/HEAD/${encodedParam}`
|
|
5948
|
+
try {
|
|
5949
|
+
const response = await fetch(infoUrl, { cache: 'no-store' })
|
|
5950
|
+
if (!response.ok) {
|
|
5951
|
+
throw new Error(`HTTP ${response.status}`)
|
|
5952
|
+
}
|
|
5953
|
+
const payload = await response.json()
|
|
5954
|
+
const repoExists = typeof payload.gitDirExists === 'boolean' ? payload.gitDirExists : true
|
|
5955
|
+
const hasHead = typeof payload.hasHead === 'boolean'
|
|
5956
|
+
? payload.hasHead
|
|
5957
|
+
: (Array.isArray(payload.log) && payload.log.length > 0)
|
|
5958
|
+
let reason = null
|
|
5959
|
+
if (!repoExists) {
|
|
5960
|
+
reason = 'missing-git'
|
|
5961
|
+
} else if (!hasHead) {
|
|
5962
|
+
reason = 'missing-commit'
|
|
5963
|
+
}
|
|
5964
|
+
return {
|
|
5965
|
+
ok: !reason,
|
|
5966
|
+
reason,
|
|
5967
|
+
repoExists,
|
|
5968
|
+
hasHead,
|
|
5969
|
+
payload,
|
|
5970
|
+
}
|
|
5971
|
+
} catch (error) {
|
|
5972
|
+
console.error('publish preflight error:', error)
|
|
5973
|
+
return { ok: false, reason: 'network', error }
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
|
|
5977
|
+
async function getRepoData(repoParam) {
|
|
5978
|
+
if (!repoParam) {
|
|
5979
|
+
return null
|
|
5980
|
+
}
|
|
5981
|
+
let repoData = repoStatusCache.get(repoParam) || null
|
|
5982
|
+
if (!repoData && typeof check_git === 'function') {
|
|
5983
|
+
await check_git()
|
|
5984
|
+
repoData = repoStatusCache.get(repoParam) || null
|
|
5985
|
+
}
|
|
5986
|
+
return repoData
|
|
5987
|
+
}
|
|
5988
|
+
|
|
5989
|
+
async function ensureRemoteForPublish({ repoParam, repoName }) {
|
|
5990
|
+
const label = repoName || repoParam || 'Repository'
|
|
5991
|
+
const initialRepo = await getRepoData(repoParam)
|
|
5992
|
+
if (hasRemoteConfigured(initialRepo)) {
|
|
5993
|
+
return true
|
|
5994
|
+
}
|
|
5995
|
+
|
|
5996
|
+
const creationResult = await showCreateModal({ skipReload: true })
|
|
5997
|
+
if (!creationResult || creationResult.completed === false) {
|
|
5998
|
+
return false
|
|
5999
|
+
}
|
|
6000
|
+
|
|
6001
|
+
if (typeof check_git === 'function') {
|
|
6002
|
+
await check_git()
|
|
6003
|
+
}
|
|
6004
|
+
const refreshedRepo = await getRepoData(repoParam)
|
|
6005
|
+
if (hasRemoteConfigured(refreshedRepo)) {
|
|
6006
|
+
return true
|
|
6007
|
+
}
|
|
6008
|
+
|
|
6009
|
+
Swal.fire({
|
|
6010
|
+
icon: 'info',
|
|
6011
|
+
title: `${escapeHtml(label)} still needs a Git remote`,
|
|
6012
|
+
text: 'Add a remote (for example, origin) and try publishing again.'
|
|
6013
|
+
})
|
|
6014
|
+
return false
|
|
6015
|
+
}
|
|
6016
|
+
|
|
6017
|
+
const buildPublishPreflightCopy = (repoName, reason) => {
|
|
6018
|
+
const name = repoName && repoName.trim().length > 0 ? repoName.trim() : 'Repository'
|
|
6019
|
+
if (reason === 'missing-git') {
|
|
6020
|
+
return {
|
|
6021
|
+
title: `${name} isn't a Git repository yet`,
|
|
6022
|
+
message: 'Initialize git in this folder and save a version before publishing.',
|
|
6023
|
+
actionLabel: 'Review files'
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
if (reason === 'missing-commit') {
|
|
6027
|
+
return {
|
|
6028
|
+
title: `Create your first commit for ${name}`,
|
|
6029
|
+
message: 'Save a version on the main branch before publishing to GitHub.',
|
|
6030
|
+
actionLabel: 'Review files'
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
return {
|
|
6034
|
+
title: 'Unable to publish',
|
|
6035
|
+
message: 'Resolve the repository issues before retrying.',
|
|
6036
|
+
actionLabel: 'Review files'
|
|
6037
|
+
}
|
|
6038
|
+
}
|
|
6039
|
+
|
|
6040
|
+
const showPublishPreflightModal = ({ title, message, actionLabel, onAction }) => {
|
|
6041
|
+
const safeTitle = escapeHtml(title || 'Repository is not ready to publish')
|
|
6042
|
+
const safeMessage = escapeHtml(message || 'Resolve the issue and try again.')
|
|
6043
|
+
const safeLabel = escapeHtml(actionLabel || 'Review files')
|
|
6044
|
+
const html = `
|
|
6045
|
+
<div class="pinokio-modal-surface">
|
|
6046
|
+
<div class="pinokio-modal-header">
|
|
6047
|
+
<div class="pinokio-modal-icon pinokio-modal-icon--warning"><i class="fa-solid fa-circle-exclamation"></i></div>
|
|
6048
|
+
<div class="pinokio-modal-heading">
|
|
6049
|
+
<div class="pinokio-modal-title">${safeTitle}</div>
|
|
6050
|
+
<div class="pinokio-modal-subtitle">${safeMessage}</div>
|
|
6051
|
+
</div>
|
|
6052
|
+
</div>
|
|
6053
|
+
</div>
|
|
6054
|
+
`
|
|
6055
|
+
|
|
6056
|
+
Swal.fire({
|
|
6057
|
+
html,
|
|
6058
|
+
backdrop: 'rgba(9,11,15,0.65)',
|
|
6059
|
+
width: 'min(440px, 92vw)',
|
|
6060
|
+
showConfirmButton: true,
|
|
6061
|
+
showCancelButton: false,
|
|
6062
|
+
showCloseButton: true,
|
|
6063
|
+
buttonsStyling: false,
|
|
6064
|
+
focusConfirm: true,
|
|
6065
|
+
confirmButtonText: safeLabel,
|
|
6066
|
+
customClass: {
|
|
6067
|
+
popup: 'pinokio-modern-modal pinokio-publish-preflight-modal',
|
|
6068
|
+
htmlContainer: 'pinokio-modern-html',
|
|
6069
|
+
closeButton: 'pinokio-modern-close',
|
|
6070
|
+
confirmButton: 'pinokio-modern-confirm'
|
|
6071
|
+
}
|
|
6072
|
+
})
|
|
6073
|
+
.then(async (result) => {
|
|
6074
|
+
if (result.isConfirmed && typeof onAction === 'function') {
|
|
6075
|
+
try {
|
|
6076
|
+
await onAction()
|
|
6077
|
+
} catch (error) {
|
|
6078
|
+
console.error('preflight action failed:', error)
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
})
|
|
6082
|
+
}
|
|
6083
|
+
|
|
6084
|
+
const launchPublishPreflightDiffFlow = async ({ repoParam, repoName }) => {
|
|
6085
|
+
try {
|
|
6086
|
+
await showGitDiffModal({ repoParam, repoName, forceRefresh: true })
|
|
6087
|
+
} catch (error) {
|
|
6088
|
+
console.error('Failed to open diff modal for publish preflight:', error)
|
|
6089
|
+
return
|
|
6090
|
+
}
|
|
6091
|
+
try {
|
|
6092
|
+
const retryState = await fetchPublishPreflightState(repoParam)
|
|
6093
|
+
if (retryState.ok) {
|
|
6094
|
+
closeStatusDropdowns()
|
|
6095
|
+
const remoteReady = await ensureRemoteForPublish({ repoParam, repoName })
|
|
6096
|
+
if (remoteReady) {
|
|
6097
|
+
showPublishModal({ repoParam, repoName })
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
} catch (error) {
|
|
6101
|
+
console.error('Failed to retry publish after diff:', error)
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
|
|
6105
|
+
const handlePublishDropdownClick = async ({ repoParam, repoName }) => {
|
|
6106
|
+
closeStatusDropdowns()
|
|
6107
|
+
const preflight = await fetchPublishPreflightState(repoParam)
|
|
6108
|
+
if (preflight.ok) {
|
|
6109
|
+
const remoteReady = await ensureRemoteForPublish({ repoParam, repoName })
|
|
6110
|
+
if (remoteReady) {
|
|
6111
|
+
showPublishModal({ repoParam, repoName })
|
|
6112
|
+
}
|
|
6113
|
+
return
|
|
6114
|
+
}
|
|
6115
|
+
if (preflight.reason === 'network') {
|
|
6116
|
+
Swal.fire({
|
|
6117
|
+
icon: 'error',
|
|
6118
|
+
title: 'Unable to check repository state',
|
|
6119
|
+
text: 'Please try again in a moment.'
|
|
6120
|
+
})
|
|
6121
|
+
return
|
|
6122
|
+
}
|
|
6123
|
+
const copy = buildPublishPreflightCopy(repoName, preflight.reason)
|
|
6124
|
+
showPublishPreflightModal({
|
|
6125
|
+
title: copy.title,
|
|
6126
|
+
message: copy.message,
|
|
6127
|
+
actionLabel: copy.actionLabel,
|
|
6128
|
+
onAction: () => launchPublishPreflightDiffFlow({ repoParam, repoName })
|
|
6129
|
+
})
|
|
6130
|
+
}
|
|
6131
|
+
|
|
5926
6132
|
const renderPublishDropdown = (repos, options = {}) => {
|
|
5927
6133
|
if (!publishMenu) {
|
|
5928
6134
|
return
|
|
@@ -5972,21 +6178,11 @@ const rerenderMenuSection = (container, html) => {
|
|
|
5972
6178
|
<div class="${remoteClass}">${remoteDisplay}</div>
|
|
5973
6179
|
`
|
|
5974
6180
|
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
showCreateModal()
|
|
5981
|
-
})
|
|
5982
|
-
} else {
|
|
5983
|
-
item.addEventListener('click', (event) => {
|
|
5984
|
-
event.preventDefault()
|
|
5985
|
-
event.stopPropagation()
|
|
5986
|
-
closeStatusDropdowns()
|
|
5987
|
-
showPublishModal({ repoParam: key, repoName: name })
|
|
5988
|
-
})
|
|
5989
|
-
}
|
|
6181
|
+
item.addEventListener('click', (event) => {
|
|
6182
|
+
event.preventDefault()
|
|
6183
|
+
event.stopPropagation()
|
|
6184
|
+
handlePublishDropdownClick({ repoParam: key, repoName: name })
|
|
6185
|
+
})
|
|
5990
6186
|
|
|
5991
6187
|
fragment.appendChild(item)
|
|
5992
6188
|
})
|
|
@@ -7239,8 +7435,8 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7239
7435
|
reloadOnModalClose(publishModalPromise)
|
|
7240
7436
|
}
|
|
7241
7437
|
|
|
7242
|
-
const showCreateModal = () => {
|
|
7243
|
-
showCreateRepoModal()
|
|
7438
|
+
const showCreateModal = (options = {}) => {
|
|
7439
|
+
return showCreateRepoModal(options)
|
|
7244
7440
|
}
|
|
7245
7441
|
|
|
7246
7442
|
const showRemoteSelectionModal = (remotes, pushUri) => {
|
|
@@ -7363,7 +7559,9 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7363
7559
|
reloadOnModalClose(forkShellModalPromise)
|
|
7364
7560
|
}
|
|
7365
7561
|
|
|
7366
|
-
const showCreateRepoModal = () => {
|
|
7562
|
+
const showCreateRepoModal = (options = {}) => {
|
|
7563
|
+
const opts = typeof options === 'object' && options !== null ? options : {}
|
|
7564
|
+
const skipReload = Boolean(opts.skipReload)
|
|
7367
7565
|
const modalHtml = `
|
|
7368
7566
|
<div class="pinokio-modal-surface">
|
|
7369
7567
|
<div class="pinokio-modal-header">
|
|
@@ -7419,17 +7617,27 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7419
7617
|
}
|
|
7420
7618
|
})
|
|
7421
7619
|
|
|
7422
|
-
|
|
7620
|
+
if (!skipReload) {
|
|
7621
|
+
reloadOnModalClose(createRepoModalPromise, (result) => !result || result.isDismissed)
|
|
7622
|
+
}
|
|
7423
7623
|
|
|
7424
|
-
createRepoModalPromise.then((result) => {
|
|
7425
|
-
if (result.isConfirmed) {
|
|
7426
|
-
|
|
7427
|
-
showCreateRepoIframeModal(name, isPrivate)
|
|
7624
|
+
return createRepoModalPromise.then((result) => {
|
|
7625
|
+
if (!result || !result.isConfirmed) {
|
|
7626
|
+
return { completed: false, result }
|
|
7428
7627
|
}
|
|
7628
|
+
const { name, isPrivate } = result.value
|
|
7629
|
+
return showCreateRepoIframeModal(name, isPrivate, opts).then((iframeResult) => {
|
|
7630
|
+
if (iframeResult && iframeResult.completed === false) {
|
|
7631
|
+
return { completed: false, result: iframeResult }
|
|
7632
|
+
}
|
|
7633
|
+
return { completed: true, result: iframeResult }
|
|
7634
|
+
})
|
|
7429
7635
|
})
|
|
7430
7636
|
}
|
|
7431
7637
|
|
|
7432
|
-
const showCreateRepoIframeModal = (name, isPrivate) => {
|
|
7638
|
+
const showCreateRepoIframeModal = (name, isPrivate, options = {}) => {
|
|
7639
|
+
const opts = typeof options === 'object' && options !== null ? options : {}
|
|
7640
|
+
const skipReload = Boolean(opts.skipReload)
|
|
7433
7641
|
const createUri = document.querySelector('#fs-status').getAttribute('data-create-uri')
|
|
7434
7642
|
|
|
7435
7643
|
if (!createUri) {
|
|
@@ -7438,7 +7646,7 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7438
7646
|
text: 'Create repository URL not available.',
|
|
7439
7647
|
icon: 'error'
|
|
7440
7648
|
})
|
|
7441
|
-
return
|
|
7649
|
+
return Promise.resolve({ completed: false })
|
|
7442
7650
|
}
|
|
7443
7651
|
|
|
7444
7652
|
const urlParams = new URLSearchParams()
|
|
@@ -7475,7 +7683,11 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7475
7683
|
focusConfirm: false
|
|
7476
7684
|
})
|
|
7477
7685
|
|
|
7478
|
-
|
|
7686
|
+
if (!skipReload) {
|
|
7687
|
+
reloadOnModalClose(createRepoIframePromise)
|
|
7688
|
+
}
|
|
7689
|
+
|
|
7690
|
+
return createRepoIframePromise
|
|
7479
7691
|
}
|
|
7480
7692
|
|
|
7481
7693
|
async function showForkModalForRepo(repoParam) {
|
|
@@ -7808,9 +8020,6 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7808
8020
|
}
|
|
7809
8021
|
|
|
7810
8022
|
const repos = getRepoListSnapshot()
|
|
7811
|
-
const publishTargets = Array.isArray(repos)
|
|
7812
|
-
? repos.filter((repo) => hasRemoteConfigured(repo) && resolvePushUri(repo))
|
|
7813
|
-
: []
|
|
7814
8023
|
|
|
7815
8024
|
detachHandlers()
|
|
7816
8025
|
|
|
@@ -7822,7 +8031,8 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7822
8031
|
setPublishMenuMessage('Git integration unavailable')
|
|
7823
8032
|
setLabel('Publish')
|
|
7824
8033
|
disableDropdown()
|
|
7825
|
-
|
|
8034
|
+
const hasRepos = Array.isArray(repos) && repos.length > 0
|
|
8035
|
+
pushBtn.disabled = !hasRepos
|
|
7826
8036
|
if (pushBtn.disabled) {
|
|
7827
8037
|
pushBtn.classList.add('fs-status-btn--disabled')
|
|
7828
8038
|
} else {
|
|
@@ -7879,15 +8089,6 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7879
8089
|
pushBtn.disabled = false
|
|
7880
8090
|
pushBtn.classList.remove('fs-status-btn--disabled')
|
|
7881
8091
|
|
|
7882
|
-
if (publishTargets.length === 0) {
|
|
7883
|
-
setLabel('Create')
|
|
7884
|
-
pushBtn.setAttribute('title', 'Create a GitHub repository for this project')
|
|
7885
|
-
disableDropdown()
|
|
7886
|
-
pushBtn.addEventListener('click', showCreateModal)
|
|
7887
|
-
syncForkButton()
|
|
7888
|
-
return
|
|
7889
|
-
}
|
|
7890
|
-
|
|
7891
8092
|
setLabel('Publish')
|
|
7892
8093
|
pushBtn.removeAttribute('title')
|
|
7893
8094
|
enableDropdown()
|
|
@@ -7908,8 +8109,13 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7908
8109
|
|
|
7909
8110
|
// Initialize the publish/create button
|
|
7910
8111
|
updatePublishButton()
|
|
7911
|
-
|
|
8112
|
+
|
|
7912
8113
|
check_git()
|
|
8114
|
+
setInterval(() => {
|
|
8115
|
+
if (typeof check_git === 'function') {
|
|
8116
|
+
check_git()
|
|
8117
|
+
}
|
|
8118
|
+
}, 5000)
|
|
7913
8119
|
<% } %>
|
|
7914
8120
|
|
|
7915
8121
|
setInterval(() => {
|
package/server/views/index.ejs
CHANGED
|
@@ -761,12 +761,13 @@ window.PinokioHomeGuardNavigate = null;
|
|
|
761
761
|
document.addEventListener("DOMContentLoaded", () => {
|
|
762
762
|
// Run wait_ready for every /home navigation so we only leave when requirements are satisfied
|
|
763
763
|
const guardNavigate = (href, options = {}) => {
|
|
764
|
-
|
|
764
|
+
const url = new URL(href, location.href);
|
|
765
|
+
wait_ready(url).then(({ closeModal, ready }) => {
|
|
765
766
|
if (ready) {
|
|
766
767
|
if (closeModal) {
|
|
767
768
|
closeModal()
|
|
768
769
|
}
|
|
769
|
-
options?.replace ? location.replace(
|
|
770
|
+
options?.replace ? location.replace(url) : location.assign(url);
|
|
770
771
|
} else {
|
|
771
772
|
const callback = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash);
|
|
772
773
|
window.location.href = `/setup/dev?callback=${callback}`;
|