pinokiod 3.214.0 → 3.215.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/shell.js CHANGED
@@ -525,8 +525,8 @@ class Shell {
525
525
  return new Promise((resolve, reject) => {
526
526
  const config = {
527
527
  name: 'xterm-color',
528
- cols: this.cols,
529
- rows: this.rows,
528
+ cols: 1000,
529
+ rows: Math.max(this.rows || 24, 24),
530
530
  //cols: 1000,
531
531
  //rows: 30,
532
532
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "3.214.0",
3
+ "version": "3.215.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -753,9 +753,17 @@
753
753
  stripTransientQueryParams();
754
754
  }
755
755
 
756
+ let resizeScheduled = false;
756
757
  function onResize() {
757
- applyLayout();
758
- attachGutterHandlers();
758
+ if (resizeScheduled) {
759
+ return;
760
+ }
761
+ resizeScheduled = true;
762
+ requestAnimationFrame(() => {
763
+ resizeScheduled = false;
764
+ applyLayout();
765
+ attachGutterHandlers();
766
+ });
759
767
  }
760
768
 
761
769
  initLayout();
@@ -0,0 +1,86 @@
1
+ ;(function() {
2
+ if (typeof window === 'undefined') {
3
+ return
4
+ }
5
+ if (window.PinokioTouch) {
6
+ return
7
+ }
8
+
9
+ const TOUCH_FOCUS_KEY = '__pinokioTouchFocusHandler'
10
+ const TOUCH_OPTIONS = (() => {
11
+ let passiveSupported = false
12
+ try {
13
+ const opts = Object.defineProperty({}, 'passive', {
14
+ get() {
15
+ passiveSupported = true
16
+ return true
17
+ }
18
+ })
19
+ window.addEventListener('test-passive', null, opts)
20
+ window.removeEventListener('test-passive', null, opts)
21
+ } catch (_) {}
22
+ return passiveSupported ? { passive: true } : false
23
+ })()
24
+
25
+ const isTouchLikeEvent = (event) => {
26
+ if (!event || typeof event !== 'object') {
27
+ return false
28
+ }
29
+ const pointerType = typeof event.pointerType === 'string' ? event.pointerType.toLowerCase() : ''
30
+ if (pointerType) {
31
+ return pointerType === 'touch' || pointerType === 'pen'
32
+ }
33
+ const touches = event.touches || event.changedTouches
34
+ return Boolean(touches && touches.length > 0)
35
+ }
36
+
37
+ const bindPointer = (target, handler, options = {}) => {
38
+ if (!target || typeof target.addEventListener !== 'function' || typeof handler !== 'function') {
39
+ return () => {}
40
+ }
41
+ const pointerOpts = Object.prototype.hasOwnProperty.call(options, 'pointer') ? options.pointer : undefined
42
+ const touchOpts = Object.prototype.hasOwnProperty.call(options, 'touch') ? options.touch : TOUCH_OPTIONS
43
+ target.addEventListener('pointerdown', handler, pointerOpts)
44
+ target.addEventListener('touchstart', handler, touchOpts)
45
+ return () => {
46
+ target.removeEventListener('pointerdown', handler, pointerOpts)
47
+ target.removeEventListener('touchstart', handler, touchOpts)
48
+ }
49
+ }
50
+
51
+ const bindTerminalFocus = (term, container) => {
52
+ if (!term || typeof term.focus !== 'function' || !container) {
53
+ return () => {}
54
+ }
55
+
56
+ const existing = container[TOUCH_FOCUS_KEY]
57
+ if (typeof existing === 'function') {
58
+ existing()
59
+ }
60
+
61
+ const handler = (event) => {
62
+ if (!isTouchLikeEvent(event)) {
63
+ return
64
+ }
65
+ term.focus()
66
+ }
67
+
68
+ const unbind = bindPointer(container, handler)
69
+ container[TOUCH_FOCUS_KEY] = unbind
70
+
71
+ return () => {
72
+ if (typeof unbind === 'function') {
73
+ unbind()
74
+ }
75
+ if (container[TOUCH_FOCUS_KEY] === unbind) {
76
+ delete container[TOUCH_FOCUS_KEY]
77
+ }
78
+ }
79
+ }
80
+
81
+ window.PinokioTouch = {
82
+ bindPointer,
83
+ bindTerminalFocus,
84
+ isTouchLikeEvent
85
+ }
86
+ })()
@@ -14,6 +14,7 @@
14
14
  <link href="/filepond-plugin-image-edit.min.css" rel="stylesheet" />
15
15
  <link href="/style.css" rel="stylesheet"/>
16
16
  <script src="/modalinput.js"></script>
17
+ <script src="/pinokio-touch.js"></script>
17
18
  <% if (agent === "electron") { %>
18
19
  <link href="/electron.css" rel="stylesheet"/>
19
20
  <% } %>
@@ -3437,6 +3438,8 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3437
3438
  let tabLinkRouterInfoPromise = null
3438
3439
  let tabLinkRouterInfoExpiry = 0
3439
3440
  let tabLinkRouterHttpsActive = null
3441
+ let tabLinkPeerInfoPromise = null
3442
+ let tabLinkPeerInfoExpiry = 0
3440
3443
 
3441
3444
  const ensureTabLinkPopoverEl = () => {
3442
3445
  if (!tabLinkPopoverEl) {
@@ -3475,6 +3478,27 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3475
3478
  return tabLinkPopoverEl
3476
3479
  }
3477
3480
 
3481
+ const ensurePeerInfo = async () => {
3482
+ const now = Date.now()
3483
+ if (!tabLinkPeerInfoPromise || now > tabLinkPeerInfoExpiry) {
3484
+ tabLinkPeerInfoPromise = fetch("/pinokio/peer", {
3485
+ method: "GET",
3486
+ headers: {
3487
+ "Accept": "application/json"
3488
+ }
3489
+ })
3490
+ .then((response) => {
3491
+ if (!response.ok) {
3492
+ throw new Error("Failed to load peer info")
3493
+ }
3494
+ return response.json()
3495
+ })
3496
+ .catch(() => null)
3497
+ tabLinkPeerInfoExpiry = now + 3000
3498
+ }
3499
+ return tabLinkPeerInfoPromise
3500
+ }
3501
+
3478
3502
  const canonicalizeUrl = (value) => {
3479
3503
  try {
3480
3504
  const parsed = new URL(value, location.origin)
@@ -4114,6 +4138,24 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
4114
4138
  }
4115
4139
 
4116
4140
  const baseHref = link.href
4141
+ let canonicalBase = canonicalizeUrl(baseHref)
4142
+ if (canonicalBase && isHttpUrl(canonicalBase)) {
4143
+ canonicalBase = ensureHttpDirectoryUrl(canonicalBase)
4144
+ }
4145
+ let parsedBaseUrl = null
4146
+ let sameOrigin = false
4147
+ let basePortNormalized = ""
4148
+ try {
4149
+ parsedBaseUrl = new URL(baseHref, location.origin)
4150
+ sameOrigin = parsedBaseUrl.origin === location.origin
4151
+ if (parsedBaseUrl) {
4152
+ basePortNormalized = parsedBaseUrl.port
4153
+ if (!basePortNormalized) {
4154
+ const proto = parsedBaseUrl.protocol ? parsedBaseUrl.protocol.toLowerCase() : "http:"
4155
+ basePortNormalized = proto === "https:" ? "443" : "80"
4156
+ }
4157
+ }
4158
+ } catch (_) {}
4117
4159
  const projectSlug = extractProjectSlug(link).toLowerCase()
4118
4160
  const entries = []
4119
4161
  const entryByUrl = new Map()
@@ -4129,10 +4171,11 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
4129
4171
  return
4130
4172
  }
4131
4173
  let skip = false
4174
+ const allowSameOrigin = opts && opts.allowSameOrigin === true
4132
4175
  try {
4133
4176
  const parsed = new URL(canonical)
4134
4177
  const originLower = parsed.origin.toLowerCase()
4135
- if (originLower === location.origin.toLowerCase()) {
4178
+ if (!allowSameOrigin && originLower === location.origin.toLowerCase()) {
4136
4179
  skip = true
4137
4180
  }
4138
4181
  } catch (_) {
@@ -4158,25 +4201,29 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
4158
4201
  }
4159
4202
 
4160
4203
  if (isHttpUrl(baseHref)) {
4161
- addEntry("http", "HTTP", baseHref)
4204
+ addEntry("http", "HTTP", baseHref, { allowSameOrigin: true })
4162
4205
  } else if (isHttpsUrl(baseHref)) {
4163
- addEntry("https", "HTTPS", baseHref)
4206
+ addEntry("https", "HTTPS", baseHref, { allowSameOrigin: true })
4164
4207
  } else {
4165
- addEntry("url", "URL", baseHref)
4208
+ addEntry("url", "URL", baseHref, { allowSameOrigin: true })
4166
4209
  }
4167
4210
 
4168
4211
  const httpCandidates = new Map() // url -> { qr: boolean }
4169
4212
  const httpsCandidates = new Set()
4170
4213
 
4171
4214
  if (isHttpUrl(baseHref)) {
4172
- httpCandidates.set(canonicalizeUrl(baseHref), { qr: false })
4215
+ httpCandidates.set(canonicalBase || canonicalizeUrl(baseHref), { qr: false })
4173
4216
  } else if (isHttpsUrl(baseHref)) {
4174
- httpsCandidates.add(canonicalizeUrl(baseHref))
4217
+ if (canonicalBase) {
4218
+ httpsCandidates.add(canonicalBase)
4219
+ } else {
4220
+ httpsCandidates.add(canonicalizeUrl(baseHref))
4221
+ }
4175
4222
  }
4176
4223
 
4177
4224
  if (projectSlug) {
4178
4225
  try {
4179
- const baseUrl = new URL(baseHref, location.origin)
4226
+ const baseUrl = parsedBaseUrl || new URL(baseHref, location.origin)
4180
4227
  let pathname = baseUrl.pathname || "/"
4181
4228
  if (pathname.endsWith("/index.html")) {
4182
4229
  pathname = pathname.slice(0, -"/index.html".length)
@@ -4230,7 +4277,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
4230
4277
 
4231
4278
  // Add external 192.168.* http host:port candidates mapped from the same internal port as base HTTP
4232
4279
  try {
4233
- const base = new URL(baseHref, location.origin)
4280
+ const base = parsedBaseUrl || new URL(baseHref, location.origin)
4234
4281
  let basePort = base.port
4235
4282
  if (!basePort) {
4236
4283
  basePort = base.protocol.toLowerCase() === 'https:' ? '443' : '80'
@@ -4283,6 +4330,65 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
4283
4330
  addEntry("https", "HTTPS", url)
4284
4331
  })
4285
4332
 
4333
+ if (sameOrigin) {
4334
+ try {
4335
+ const peerInfo = await ensurePeerInfo()
4336
+ const peerHost = peerInfo && typeof peerInfo.host === "string" ? peerInfo.host.trim() : ""
4337
+ if (peerHost) {
4338
+ const peerHostLower = peerHost.toLowerCase()
4339
+ // Skip loopback-style peers
4340
+ if (peerHostLower !== "localhost" && !peerHostLower.startsWith("127.")) {
4341
+ const baseUrl = parsedBaseUrl || new URL(baseHref, location.origin)
4342
+ const baseHostLower = (baseUrl.hostname || "").toLowerCase()
4343
+ if (peerHostLower !== baseHostLower) {
4344
+ const baseProtocol = baseUrl.protocol ? baseUrl.protocol.toLowerCase() : "http:"
4345
+ const scheme = baseProtocol === "https:" ? "https://" : "http://"
4346
+ const port = baseUrl.port || (baseProtocol === "https:" ? "443" : "80")
4347
+ const hostPort = port ? `${peerHostLower}:${port}` : peerHostLower
4348
+ const pathSegment = baseUrl.pathname || "/"
4349
+ const searchSegment = baseUrl.search || ""
4350
+ const fallbackUrl = `${scheme}${hostPort}${pathSegment}${searchSegment}`
4351
+ const label = baseProtocol === "https:" ? "HTTPS" : "HTTP"
4352
+ addEntry(baseProtocol === "https:" ? "https" : "http", label, fallbackUrl, { qr: true })
4353
+ }
4354
+ }
4355
+ }
4356
+ } catch (_) {}
4357
+
4358
+ const matchesBasePort = (value) => {
4359
+ if (!basePortNormalized) {
4360
+ return true
4361
+ }
4362
+ try {
4363
+ const parsed = new URL(value, location.origin)
4364
+ let port = parsed.port
4365
+ if (!port) {
4366
+ const proto = parsed.protocol ? parsed.protocol.toLowerCase() : "http:"
4367
+ port = proto === "https:" ? "443" : "80"
4368
+ }
4369
+ return port === basePortNormalized
4370
+ } catch (_) {
4371
+ return false
4372
+ }
4373
+ }
4374
+
4375
+ const filteredEntries = entries.filter((entry) => {
4376
+ if (!entry || !entry.url) {
4377
+ return false
4378
+ }
4379
+ if (entry.url === canonicalBase) {
4380
+ return true
4381
+ }
4382
+ if (entry.qr === true) {
4383
+ return matchesBasePort(entry.url)
4384
+ }
4385
+ return false
4386
+ })
4387
+ if (filteredEntries.length > 0) {
4388
+ return filteredEntries
4389
+ }
4390
+ }
4391
+
4286
4392
  return entries
4287
4393
  }
4288
4394
 
@@ -4350,10 +4456,22 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
4350
4456
 
4351
4457
  let sameOrigin = false
4352
4458
  let canonicalBase = canonicalizeUrl(link.href)
4459
+ if (canonicalBase && isHttpUrl(canonicalBase)) {
4460
+ canonicalBase = ensureHttpDirectoryUrl(canonicalBase)
4461
+ }
4462
+ let basePortNormalized = ""
4353
4463
  try {
4354
4464
  const linkUrl = new URL(link.href, location.href)
4355
4465
  sameOrigin = linkUrl.origin === location.origin
4356
4466
  canonicalBase = canonicalizeUrl(linkUrl.href)
4467
+ if (canonicalBase && isHttpUrl(canonicalBase)) {
4468
+ canonicalBase = ensureHttpDirectoryUrl(canonicalBase)
4469
+ }
4470
+ basePortNormalized = linkUrl.port
4471
+ if (!basePortNormalized) {
4472
+ const proto = linkUrl.protocol ? linkUrl.protocol.toLowerCase() : "http:"
4473
+ basePortNormalized = proto === "https:" ? "443" : "80"
4474
+ }
4357
4475
  } catch (_) {
4358
4476
  hideTabLinkPopover({ immediate: true })
4359
4477
  return
@@ -4416,6 +4534,23 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
4416
4534
 
4417
4535
  if (sameOrigin) {
4418
4536
  const slug = extractProjectSlug(link).toLowerCase()
4537
+ const matchesBasePort = (value) => {
4538
+ if (!basePortNormalized) {
4539
+ return true
4540
+ }
4541
+ try {
4542
+ const parsed = new URL(value, location.origin)
4543
+ let port = parsed.port
4544
+ if (!port) {
4545
+ const proto = parsed.protocol ? parsed.protocol.toLowerCase() : "http:"
4546
+ port = proto === "https:" ? "443" : "80"
4547
+ }
4548
+ return port === basePortNormalized
4549
+ } catch (_) {
4550
+ return false
4551
+ }
4552
+ }
4553
+
4419
4554
  if (slug) {
4420
4555
  entries = entries.filter((entry) => {
4421
4556
  if (!entry || !entry.url) {
@@ -4424,6 +4559,9 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
4424
4559
  if (entry.url === canonicalBase) {
4425
4560
  return true
4426
4561
  }
4562
+ if (entry.qr === true) {
4563
+ return matchesBasePort(entry.url)
4564
+ }
4427
4565
  try {
4428
4566
  const parsed = new URL(entry.url)
4429
4567
  const hostLower = parsed.hostname ? parsed.hostname.toLowerCase() : ""
@@ -4452,9 +4590,33 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
4452
4590
  return false
4453
4591
  })
4454
4592
  } else {
4455
- entries = entries.filter((entry) => entry.url === canonicalBase)
4593
+ entries = entries.filter((entry) => {
4594
+ if (!entry || !entry.url) {
4595
+ return false
4596
+ }
4597
+ if (entry.url === canonicalBase) {
4598
+ return true
4599
+ }
4600
+ if (entry.qr === true) {
4601
+ return matchesBasePort(entry.url)
4602
+ }
4603
+ return false
4604
+ })
4456
4605
  }
4457
4606
 
4607
+ entries = entries.filter((entry) => {
4608
+ if (!entry || !entry.url) {
4609
+ return false
4610
+ }
4611
+ if (entry.url === canonicalBase) {
4612
+ return true
4613
+ }
4614
+ if (entry.qr === true) {
4615
+ return matchesBasePort(entry.url)
4616
+ }
4617
+ return false
4618
+ })
4619
+
4458
4620
  const hasAlternate = entries.some((entry) => entry.url !== canonicalBase)
4459
4621
  if (!hasAlternate) {
4460
4622
  hideTabLinkPopover({ immediate: true })
@@ -5692,7 +5854,9 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
5692
5854
  targetFrame.classList.remove("hidden")
5693
5855
  let mode = target.getAttribute("data-mode")
5694
5856
  if (mode === "refresh") {
5695
- targetFrame.src = target.href
5857
+ if (targetFrame.src !== target.href) {
5858
+ targetFrame.src = target.href
5859
+ }
5696
5860
  } else {
5697
5861
  if (eventParam) {
5698
5862
  if (target.closest(".h")) {
@@ -5700,7 +5864,9 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
5700
5864
  eventParam.stopPropagation()
5701
5865
  let sub = subUrlOf(target.href, targetFrame.src)
5702
5866
  if (!sub) {
5703
- targetFrame.src = target.href
5867
+ if (targetFrame.src !== target.href) {
5868
+ targetFrame.src = target.href
5869
+ }
5704
5870
  }
5705
5871
  } else if (target.closest(".s")) {
5706
5872
  // load unconditionally => .s doesn't change
@@ -5713,7 +5879,9 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
5713
5879
  }
5714
5880
  } else {
5715
5881
  if (force) {
5716
- targetFrame.src = target.href
5882
+ if (targetFrame.src !== target.href) {
5883
+ targetFrame.src = target.href
5884
+ }
5717
5885
  }
5718
5886
  }
5719
5887
  let frameHref
@@ -6538,10 +6706,31 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
6538
6706
  }
6539
6707
  }
6540
6708
 
6709
+ const isTouchLikeEvent = window.PinokioTouch.isTouchLikeEvent
6710
+ const handleTouchSelection = (event) => {
6711
+ if (!isTouchLikeEvent(event)) {
6712
+ return
6713
+ }
6714
+ const link = event.target?.closest?.(".frame-link") || null
6715
+ if (!link) {
6716
+ return
6717
+ }
6718
+ if (link.classList.contains("reveal") || link.hasAttribute("data-action") || link.hasAttribute("data-filepath") || link.hasAttribute("data-confirm")) {
6719
+ return
6720
+ }
6721
+ if (event.target?.closest?.(".del") || event.target?.closest?.(".shutdown")) {
6722
+ return
6723
+ }
6724
+ renderSelection({ explicitTarget: link })
6725
+ }
6541
6726
  const navContainers = [document.querySelector("aside"), document.querySelector("#fs-status")]
6542
6727
  navContainers.forEach((container) => {
6543
6728
  if (container) {
6544
6729
  container.addEventListener("click", handleMenuClick)
6730
+ window.PinokioTouch.bindPointer(container, handleTouchSelection, {
6731
+ pointer: true,
6732
+ touch: { passive: true, capture: true }
6733
+ })
6545
6734
  }
6546
6735
  })
6547
6736
  setupTabLinkHover()
@@ -80,6 +80,7 @@ body {
80
80
  </style>
81
81
  <script src="/noty.js"></script>
82
82
  <script src="/notyq.js"></script>
83
+ <script src="/pinokio-touch.js"></script>
83
84
  <script src="/xterm.js"></script>
84
85
  <script src="/xterm-addon-fit.js"></script>
85
86
  <script src="/xterm-addon-web-links.js"></script>
@@ -158,6 +159,8 @@ document.addEventListener("DOMContentLoaded", () => {
158
159
  term.loadAddon(fitAddon);
159
160
  term.loadAddon(new WebLinksAddon.WebLinksAddon());
160
161
  term.open(document.querySelector("#terminal"))
162
+ const terminalContainer = document.querySelector("#terminal")
163
+ window.PinokioTouch.bindTerminalFocus(term, terminalContainer)
161
164
  fitAddon.fit();
162
165
 
163
166
  let socket = new Socket()
@@ -14,6 +14,7 @@
14
14
  <script src="/sweetalert2.js"></script>
15
15
  <script src="/Socket.js"></script>
16
16
  <script src="/terminal-settings.js"></script>
17
+ <script src="/pinokio-touch.js"></script>
17
18
  <script src="/common.js"></script>
18
19
  <script src="/he.js"></script>
19
20
  <script src="/opener.js"></script>
@@ -688,6 +689,8 @@ document.addEventListener("DOMContentLoaded", async () => {
688
689
  window.PinokioTerminalSettings.register(term, { baseConfig })
689
690
  }
690
691
  term.open(document.querySelector("#terminal"))
692
+ const terminalContainer = document.querySelector("#terminal")
693
+ window.PinokioTouch.bindTerminalFocus(term, terminalContainer)
691
694
 
692
695
 
693
696
  term.attachCustomKeyEventHandler(event => {
@@ -1498,6 +1498,7 @@ body.dark .ace-editor {
1498
1498
  <script src="/normalize.js"></script>
1499
1499
  <script src="/Socket.js"></script>
1500
1500
  <script src="/terminal-settings.js"></script>
1501
+ <script src="/pinokio-touch.js"></script>
1501
1502
  <script src="/noty.js"></script>
1502
1503
  <script src="/notyq.js"></script>
1503
1504
  <script src="/xterm.js"></script>
@@ -2667,6 +2668,8 @@ const createTerm = async (_theme) => {
2667
2668
  window.PinokioTerminalSettings.register(term, { baseConfig })
2668
2669
  }
2669
2670
  term.open(document.querySelector("#terminal2"))
2671
+ const terminalContainer = document.querySelector("#terminal2")
2672
+ window.PinokioTouch.bindTerminalFocus(term, terminalContainer)
2670
2673
  document.querySelector("#terminal-container").classList.remove("hidden")
2671
2674
 
2672
2675
  term.attachCustomKeyEventHandler(event => {
@@ -125,6 +125,7 @@ body {
125
125
  <script src="/xterm-theme.js"></script>
126
126
  <script src="/Socket.js"></script>
127
127
  <script src="/terminal-settings.js"></script>
128
+ <script src="/pinokio-touch.js"></script>
128
129
  <!--
129
130
  <script src="/install.js"></script>
130
131
  -->
@@ -179,6 +180,8 @@ const createTerm = async (_theme) => {
179
180
  window.PinokioTerminalSettings.register(term, { baseConfig })
180
181
  }
181
182
  term.open(document.querySelector("#terminal"))
183
+ const terminalContainer = document.querySelector("#terminal")
184
+ window.PinokioTouch.bindTerminalFocus(term, terminalContainer)
182
185
 
183
186
  term.attachCustomKeyEventHandler(event => {
184
187
  if (event.ctrlKey && event.key === 'c' && term.hasSelection()) {
@@ -123,14 +123,23 @@
123
123
  <script>
124
124
  (function () {
125
125
  const root = document.documentElement;
126
+ let lastViewportHeight = null;
126
127
  const updateViewportHeight = () => {
127
128
  const viewport = window.visualViewport;
128
129
  const height = viewport ? viewport.height : window.innerHeight;
129
130
  if (!height || !Number.isFinite(height)) {
130
131
  return;
131
132
  }
132
- root.style.setProperty('--layout-viewport-height', `${Math.max(0, height).toFixed(2)}px`);
133
- window.dispatchEvent(new Event('pinokio:viewport-change'));
133
+ const clampedHeight = Math.max(0, height);
134
+ const roundedHeight = Number(clampedHeight.toFixed(2));
135
+ if (lastViewportHeight === roundedHeight) {
136
+ return;
137
+ }
138
+ lastViewportHeight = roundedHeight;
139
+ root.style.setProperty('--layout-viewport-height', `${roundedHeight}px`);
140
+ window.dispatchEvent(new CustomEvent('pinokio:viewport-change', {
141
+ detail: { height: roundedHeight },
142
+ }));
134
143
  };
135
144
 
136
145
  const resizeEvents = ['resize', 'orientationchange'];
@@ -14,6 +14,7 @@
14
14
  <script src="/sweetalert2.js"></script>
15
15
  <script src="/Socket.js"></script>
16
16
  <script src="/terminal-settings.js"></script>
17
+ <script src="/pinokio-touch.js"></script>
17
18
  <script src="/common.js"></script>
18
19
  <script src="/he.js"></script>
19
20
  <script src="/opener.js"></script>
@@ -238,6 +239,8 @@ const createTerm = async (_theme) => {
238
239
  window.PinokioTerminalSettings.register(term, { baseConfig })
239
240
  }
240
241
  term.open(document.querySelector("#terminal"))
242
+ const terminalContainer = document.querySelector("#terminal")
243
+ window.PinokioTouch.bindTerminalFocus(term, terminalContainer)
241
244
 
242
245
  term.attachCustomKeyEventHandler(event => {
243
246
  if (event.ctrlKey && event.key === 'c' && term.hasSelection()) {
@@ -982,6 +982,7 @@ body.dark .appcanvas {
982
982
  <script src="/normalize.js"></script>
983
983
  <script src="/Socket.js"></script>
984
984
  <script src="/terminal-settings.js"></script>
985
+ <script src="/pinokio-touch.js"></script>
985
986
  <script src="/noty.js"></script>
986
987
  <script src="/notyq.js"></script>
987
988
  <script src="/xterm.js"></script>
@@ -1581,6 +1582,8 @@ async function displayResults(config) {
1581
1582
  window.PinokioTerminalSettings.register(term, { baseConfig })
1582
1583
  }
1583
1584
  term.open(document.querySelector("#terminal"))
1585
+ const terminalContainer = document.querySelector("#terminal")
1586
+ window.PinokioTouch.bindTerminalFocus(term, terminalContainer)
1584
1587
  term.attachCustomKeyEventHandler(event => {
1585
1588
  console.log({ event })
1586
1589
  if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
@@ -15,6 +15,7 @@
15
15
  <script src="/Socket.js"></script>
16
16
  <script src="/terminal_input_tracker.js"></script>
17
17
  <script src="/terminal-settings.js"></script>
18
+ <script src="/pinokio-touch.js"></script>
18
19
  <script src="/common.js"></script>
19
20
  <script src="/he.js"></script>
20
21
  <script src="/opener.js"></script>
@@ -460,7 +461,6 @@ document.addEventListener("DOMContentLoaded", async () => {
460
461
  })
461
462
  firstPacketReceived = true
462
463
  }
463
- console.log({ packet, type: packet.type })
464
464
  if (packet.type === 'start') {
465
465
  console.log("refreshParent", packet)
466
466
  refreshParent(packet)
@@ -1187,6 +1187,7 @@ document.addEventListener("DOMContentLoaded", async () => {
1187
1187
  dropOverlay.className = "terminal-drop-overlay"
1188
1188
  dropOverlay.textContent = "Drop files to upload"
1189
1189
  terminalContainer.appendChild(dropOverlay)
1190
+ window.PinokioTouch.bindTerminalFocus(term, terminalContainer)
1190
1191
  const dedupeClipboardFiles = (inputs) => {
1191
1192
  const seen = new Set()
1192
1193
  const results = []
@@ -15,6 +15,7 @@
15
15
  <script src="/Socket.js"></script>
16
16
  <script src="/terminal_input_tracker.js"></script>
17
17
  <script src="/terminal-settings.js"></script>
18
+ <script src="/pinokio-touch.js"></script>
18
19
  <script src="/common.js"></script>
19
20
  <script src="/he.js"></script>
20
21
  <script src="/opener.js"></script>
@@ -1283,6 +1284,7 @@ document.addEventListener("DOMContentLoaded", async () => {
1283
1284
  dropOverlay.className = "terminal-drop-overlay"
1284
1285
  dropOverlay.textContent = "Drop files to upload"
1285
1286
  terminalContainer.appendChild(dropOverlay)
1287
+ window.PinokioTouch.bindTerminalFocus(term, terminalContainer)
1286
1288
  const dedupeClipboardFiles = (inputs) => {
1287
1289
  const seen = new Set()
1288
1290
  const results = []