pinokiod 1.0.16 → 1.0.18

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.
@@ -4,6 +4,7 @@ const path = require('path')
4
4
  const _ = require('lodash')
5
5
  const git = require('isomorphic-git')
6
6
  const http = require('isomorphic-git/http/node')
7
+ const Lproxy = require('lproxy')
7
8
  //const { glob, globSync, globStream, globStreamSync, Glob, } = require('glob')
8
9
  const { glob, sync, hasMagic } = require('glob-gitignore')
9
10
  const fastq = require('fastq')
@@ -20,6 +21,54 @@ class Api {
20
21
  this.running = {}
21
22
  this.done = {}
22
23
  this.waiter = {}
24
+ this.proxies = {}
25
+ this.lproxy = new Lproxy()
26
+ }
27
+ async startProxy(scriptPath, uri, name) {
28
+ console.log("startProxy", { scriptPath, uri, proxies: this.proxies })
29
+ let proxy_uri = await this.lproxy.start(uri)
30
+ console.log({ proxy_uri, uri })
31
+ if (this.proxies[scriptPath]) {
32
+ this.proxies[scriptPath].push({ target: uri, proxy: proxy_uri, name})
33
+ } else {
34
+ this.proxies[scriptPath] = [{ target: uri, proxy: proxy_uri, name }]
35
+ }
36
+ return {
37
+ target: uri,
38
+ proxy: proxy_uri
39
+ }
40
+ console.log("started", { proxies: this.proxies })
41
+ }
42
+ stopProxy(config) {
43
+ console.log("stopProxy", { config, proxies: this.proxies })
44
+ if (config.script) {
45
+ // stop all proxies for the script
46
+ if (this.proxies[config.script]) {
47
+ for(let p of this.proxies[config.script]) {
48
+ this.stopProxy({ uri: p.target })
49
+ }
50
+ }
51
+ } else if (config.uri) {
52
+
53
+ // stop the proxy
54
+ this.lproxy.stop(config.uri)
55
+
56
+ // remove the uri from the proxies array
57
+ for(let key in this.proxies) {
58
+ let p = this.proxies[key]
59
+ if (p.includes(config.uri)) {
60
+ let index;
61
+ for(let i = 0; i<p.length; i++) {
62
+ if (p[i].target === config.uri) {
63
+ index = i
64
+ break;
65
+ }
66
+ }
67
+ this.proxies[key].splice(index, 1)
68
+ }
69
+ }
70
+ }
71
+ console.log("stopped", { proxies: this.proxies })
23
72
  }
24
73
  async init() {
25
74
  console.log("api.init", { homedir: this.kernel.homedir })
@@ -56,6 +105,9 @@ class Api {
56
105
  // if any process is in a "wait" state, resume it
57
106
  this.kernel.resumeprocess(requestPath)
58
107
 
108
+ // stop all proxies
109
+ this.stopProxy({ script: requestPath })
110
+
59
111
  // if there are any pending waiters, delete them
60
112
 
61
113
  if (this.waiter[requestPath]) {
@@ -0,0 +1,27 @@
1
+ class P {
2
+ async start (req, ondata, kernel) {
3
+ /*
4
+ {
5
+ "method": "proxy.start",
6
+ "params": {
7
+ "name",
8
+ "uri": "http://localhost:8192"
9
+ }
10
+ }
11
+ */
12
+ let response = await kernel.api.startProxy(req.parent.path, req.params.uri, req.params.name)
13
+ return response
14
+ }
15
+ async stop (req, ondata, kernel) {
16
+ /*
17
+ {
18
+ "method": "proxy.stop",
19
+ "params": {
20
+ "uri": "http://localhost:8192"
21
+ }
22
+ }
23
+ */
24
+ kernel.api.stopProxy({ uri: req.params.uri })
25
+ }
26
+ }
27
+ module.exports = P
@@ -5,7 +5,8 @@ const decompress = require('decompress');
5
5
  class Ffmpeg {
6
6
  async install(req, ondata) {
7
7
  await this.kernel.bin.exec({
8
- message: "conda install -y -c conda-forge ffmpeg"
8
+ message: "conda install -y -c conda-forge ffmpeg",
9
+ conda: "base"
9
10
  }, ondata)
10
11
  }
11
12
  async installed() {
@@ -13,7 +14,8 @@ class Ffmpeg {
13
14
  }
14
15
  async uninstall(req, ondata) {
15
16
  await this.kernel.bin.exec({
16
- message: "conda remove ffmpeg"
17
+ message: "conda remove ffmpeg",
18
+ conda: "base"
17
19
  }, ondata)
18
20
  }
19
21
  }
@@ -3,7 +3,7 @@ class LLVM {
3
3
  if (this.kernel.platform === 'darwin') {
4
4
  await bin.exec({ message: "brew install llvm" }, ondata)
5
5
  } else {
6
- await bin.exec({ message: "conda install -y -c conda-forge llvm" }, ondata)
6
+ await bin.exec({ message: "conda install -y -c conda-forge llvm", conda: "base" }, ondata)
7
7
  }
8
8
  }
9
9
  async installed() {
@@ -14,7 +14,7 @@ class LLVM {
14
14
  }
15
15
  }
16
16
  async uninstall(req, ondata) {
17
- await this.kernel.bin.exec({ message: "conda remove llvm" }, ondata)
17
+ await this.kernel.bin.exec({ message: "conda remove llvm", conda: "base" }, ondata)
18
18
  }
19
19
  }
20
20
  module.exports = LLVM
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -30,11 +30,13 @@
30
30
  "glob": "^10.3.3",
31
31
  "glob-gitignore": "^1.0.14",
32
32
  "handlebars": "^4.7.7",
33
+ "http-proxy": "^1.18.1",
33
34
  "http-terminator": "^3.2.0",
34
35
  "isomorphic-git": "^1.23.0",
35
36
  "jsonpath-plus": "^7.2.0",
36
37
  "key-store": "^1.2.0",
37
38
  "lodash": "^4.17.21",
39
+ "lproxy": "^0.0.1",
38
40
  "marked": "^5.0.1",
39
41
  "mime-types": "^2.1.35",
40
42
  "multer": "^1.4.5-lts.1",
package/server/index.js CHANGED
@@ -741,7 +741,8 @@ class Server {
741
741
  //current: encodeURIComponent(req.originalUrl),
742
742
  current: req.originalUrl,
743
743
  editorUrl,
744
- execUrl: "~" + req.originalUrl.replace(/^\/_api/, "\/api")
744
+ execUrl: "~" + req.originalUrl.replace(/^\/_api/, "\/api"),
745
+ proxies: this.kernel.api.proxies[filepath],
745
746
  }
746
747
  console.log("RESULT", result)
747
748
 
@@ -0,0 +1,266 @@
1
+ // from https://github.com/sindresorhus/normalize-url
2
+ const normalizeURL = (() => {
3
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
4
+ const DATA_URL_DEFAULT_MIME_TYPE = 'text/plain';
5
+ const DATA_URL_DEFAULT_CHARSET = 'us-ascii';
6
+
7
+ const testParameter = (name, filters) => filters.some(filter => filter instanceof RegExp ? filter.test(name) : filter === name);
8
+
9
+ const normalizeDataURL = (urlString, {stripHash}) => {
10
+ const match = /^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$/.exec(urlString);
11
+
12
+ if (!match) {
13
+ throw new Error(`Invalid URL: ${urlString}`);
14
+ }
15
+
16
+ let {type, data, hash} = match.groups;
17
+ const mediaType = type.split(';');
18
+ hash = stripHash ? '' : hash;
19
+
20
+ let isBase64 = false;
21
+ if (mediaType[mediaType.length - 1] === 'base64') {
22
+ mediaType.pop();
23
+ isBase64 = true;
24
+ }
25
+
26
+ // Lowercase MIME type
27
+ const mimeType = (mediaType.shift() || '').toLowerCase();
28
+ const attributes = mediaType
29
+ .map(attribute => {
30
+ let [key, value = ''] = attribute.split('=').map(string => string.trim());
31
+
32
+ // Lowercase `charset`
33
+ if (key === 'charset') {
34
+ value = value.toLowerCase();
35
+
36
+ if (value === DATA_URL_DEFAULT_CHARSET) {
37
+ return '';
38
+ }
39
+ }
40
+
41
+ return `${key}${value ? `=${value}` : ''}`;
42
+ })
43
+ .filter(Boolean);
44
+
45
+ const normalizedMediaType = [
46
+ ...attributes,
47
+ ];
48
+
49
+ if (isBase64) {
50
+ normalizedMediaType.push('base64');
51
+ }
52
+
53
+ if (normalizedMediaType.length > 0 || (mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE)) {
54
+ normalizedMediaType.unshift(mimeType);
55
+ }
56
+
57
+ return `data:${normalizedMediaType.join(';')},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : ''}`;
58
+ };
59
+
60
+ function normalizeUrl(urlString, options) {
61
+ options = {
62
+ defaultProtocol: 'http:',
63
+ normalizeProtocol: true,
64
+ forceHttp: false,
65
+ forceHttps: false,
66
+ stripAuthentication: true,
67
+ stripHash: false,
68
+ stripTextFragment: true,
69
+ stripWWW: true,
70
+ removeQueryParameters: [/^utm_\w+/i],
71
+ removeTrailingSlash: true,
72
+ removeSingleSlash: true,
73
+ removeDirectoryIndex: false,
74
+ removeExplicitPort: false,
75
+ sortQueryParameters: true,
76
+ ...options,
77
+ };
78
+
79
+ urlString = urlString.trim();
80
+
81
+ // Data URL
82
+ if (/^data:/i.test(urlString)) {
83
+ return normalizeDataURL(urlString, options);
84
+ }
85
+
86
+ if (/^view-source:/i.test(urlString)) {
87
+ throw new Error('`view-source:` is not supported as it is a non-standard protocol');
88
+ }
89
+
90
+ const hasRelativeProtocol = urlString.startsWith('//');
91
+ const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString);
92
+
93
+ // Prepend protocol
94
+ if (!isRelativeUrl) {
95
+ urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, options.defaultProtocol);
96
+ }
97
+
98
+ const urlObject = new URL(urlString);
99
+
100
+ if (options.forceHttp && options.forceHttps) {
101
+ throw new Error('The `forceHttp` and `forceHttps` options cannot be used together');
102
+ }
103
+
104
+ if (options.forceHttp && urlObject.protocol === 'https:') {
105
+ urlObject.protocol = 'http:';
106
+ }
107
+
108
+ if (options.forceHttps && urlObject.protocol === 'http:') {
109
+ urlObject.protocol = 'https:';
110
+ }
111
+
112
+ // Remove auth
113
+ if (options.stripAuthentication) {
114
+ urlObject.username = '';
115
+ urlObject.password = '';
116
+ }
117
+
118
+ // Remove hash
119
+ if (options.stripHash) {
120
+ urlObject.hash = '';
121
+ } else if (options.stripTextFragment) {
122
+ urlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, '');
123
+ }
124
+
125
+ // Remove duplicate slashes if not preceded by a protocol
126
+ // NOTE: This could be implemented using a single negative lookbehind
127
+ // regex, but we avoid that to maintain compatibility with older js engines
128
+ // which do not have support for that feature.
129
+ if (urlObject.pathname) {
130
+ // TODO: Replace everything below with `urlObject.pathname = urlObject.pathname.replace(/(?<!\b[a-z][a-z\d+\-.]{1,50}:)\/{2,}/g, '/');` when Safari supports negative lookbehind.
131
+
132
+ // Split the string by occurrences of this protocol regex, and perform
133
+ // duplicate-slash replacement on the strings between those occurrences
134
+ // (if any).
135
+ const protocolRegex = /\b[a-z][a-z\d+\-.]{1,50}:\/\//g;
136
+
137
+ let lastIndex = 0;
138
+ let result = '';
139
+ for (;;) {
140
+ const match = protocolRegex.exec(urlObject.pathname);
141
+ if (!match) {
142
+ break;
143
+ }
144
+
145
+ const protocol = match[0];
146
+ const protocolAtIndex = match.index;
147
+ const intermediate = urlObject.pathname.slice(lastIndex, protocolAtIndex);
148
+
149
+ result += intermediate.replace(/\/{2,}/g, '/');
150
+ result += protocol;
151
+ lastIndex = protocolAtIndex + protocol.length;
152
+ }
153
+
154
+ const remnant = urlObject.pathname.slice(lastIndex, urlObject.pathname.length);
155
+ result += remnant.replace(/\/{2,}/g, '/');
156
+
157
+ urlObject.pathname = result;
158
+ }
159
+
160
+ // Decode URI octets
161
+ if (urlObject.pathname) {
162
+ try {
163
+ urlObject.pathname = decodeURI(urlObject.pathname);
164
+ } catch {}
165
+ }
166
+
167
+ // Remove directory index
168
+ if (options.removeDirectoryIndex === true) {
169
+ options.removeDirectoryIndex = [/^index\.[a-z]+$/];
170
+ }
171
+
172
+ if (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) {
173
+ let pathComponents = urlObject.pathname.split('/');
174
+ const lastComponent = pathComponents[pathComponents.length - 1];
175
+
176
+ if (testParameter(lastComponent, options.removeDirectoryIndex)) {
177
+ pathComponents = pathComponents.slice(0, -1);
178
+ urlObject.pathname = pathComponents.slice(1).join('/') + '/';
179
+ }
180
+ }
181
+
182
+ if (urlObject.hostname) {
183
+ // Remove trailing dot
184
+ urlObject.hostname = urlObject.hostname.replace(/\.$/, '');
185
+
186
+ // Remove `www.`
187
+ if (options.stripWWW && /^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(urlObject.hostname)) {
188
+ // Each label should be max 63 at length (min: 1).
189
+ // Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
190
+ // Each TLD should be up to 63 characters long (min: 2).
191
+ // It is technically possible to have a single character TLD, but none currently exist.
192
+ urlObject.hostname = urlObject.hostname.replace(/^www\./, '');
193
+ }
194
+ }
195
+
196
+ // Remove query unwanted parameters
197
+ if (Array.isArray(options.removeQueryParameters)) {
198
+ // eslint-disable-next-line unicorn/no-useless-spread -- We are intentionally spreading to get a copy.
199
+ for (const key of [...urlObject.searchParams.keys()]) {
200
+ if (testParameter(key, options.removeQueryParameters)) {
201
+ urlObject.searchParams.delete(key);
202
+ }
203
+ }
204
+ }
205
+
206
+ if (!Array.isArray(options.keepQueryParameters) && options.removeQueryParameters === true) {
207
+ urlObject.search = '';
208
+ }
209
+
210
+ // Keep wanted query parameters
211
+ if (Array.isArray(options.keepQueryParameters) && options.keepQueryParameters.length > 0) {
212
+ // eslint-disable-next-line unicorn/no-useless-spread -- We are intentionally spreading to get a copy.
213
+ for (const key of [...urlObject.searchParams.keys()]) {
214
+ if (!testParameter(key, options.keepQueryParameters)) {
215
+ urlObject.searchParams.delete(key);
216
+ }
217
+ }
218
+ }
219
+
220
+ // Sort query parameters
221
+ if (options.sortQueryParameters) {
222
+ urlObject.searchParams.sort();
223
+
224
+ // Calling `.sort()` encodes the search parameters, so we need to decode them again.
225
+ try {
226
+ urlObject.search = decodeURIComponent(urlObject.search);
227
+ } catch {}
228
+ }
229
+
230
+ if (options.removeTrailingSlash) {
231
+ urlObject.pathname = urlObject.pathname.replace(/\/$/, '');
232
+ }
233
+
234
+ // Remove an explicit port number, excluding a default port number, if applicable
235
+ if (options.removeExplicitPort && urlObject.port) {
236
+ urlObject.port = '';
237
+ }
238
+
239
+ const oldUrlString = urlString;
240
+
241
+ // Take advantage of many of the Node `url` normalizations
242
+ urlString = urlObject.toString();
243
+
244
+ if (!options.removeSingleSlash && urlObject.pathname === '/' && !oldUrlString.endsWith('/') && urlObject.hash === '') {
245
+ urlString = urlString.replace(/\/$/, '');
246
+ }
247
+
248
+ // Remove ending `/` unless removeSingleSlash is false
249
+ if ((options.removeTrailingSlash || urlObject.pathname === '/') && urlObject.hash === '' && options.removeSingleSlash) {
250
+ urlString = urlString.replace(/\/$/, '');
251
+ }
252
+
253
+ // Restore relative protocol, if applicable
254
+ if (hasRelativeProtocol && !options.normalizeProtocol) {
255
+ urlString = urlString.replace(/^http:\/\//, '//');
256
+ }
257
+
258
+ // Remove http/https
259
+ if (options.stripProtocol) {
260
+ urlString = urlString.replace(/^(?:https?:)?\/\//, '');
261
+ }
262
+
263
+ return urlString;
264
+ }
265
+ return normalizeUrl
266
+ })();
@@ -14,6 +14,7 @@
14
14
  <script src="/nav.js"></script>
15
15
  <script src="/opener.js"></script>
16
16
  <script src="/report.js"></script>
17
+ <script src="/normalize.js"></script>
17
18
  <% if (agent === "electron") { %>
18
19
  <link href="/electron.css" rel="stylesheet"/>
19
20
  <% } %>
@@ -205,6 +206,9 @@ body .frame-link.selected {
205
206
  background: rgba(0,0,100,0.06) !important;
206
207
  */
207
208
  }
209
+ .frame-link.selected .del {
210
+ color: white;
211
+ }
208
212
  body.dark .frame-link.selected {
209
213
  background: rgba(0,0,0,0.2) !important;
210
214
  }
@@ -260,6 +264,9 @@ body.dark .grid-btns .btn2 {
260
264
  .tab i {
261
265
  margin-right: 10px;
262
266
  }
267
+ .temp-menu {
268
+ border-top: 1px solid rgba(0,0,0,0.1);
269
+ }
263
270
  .temp-menu .del i {
264
271
  margin: 0;
265
272
  }
@@ -293,6 +300,7 @@ nav .logo {
293
300
  cursor: auto;
294
301
  display: flex;
295
302
  align-items: center;
303
+ width: 100%;
296
304
  }
297
305
  body.dark .header-item.cursor.header-top {
298
306
  background: none !important;
@@ -333,25 +341,23 @@ body.dark #new-tab {
333
341
  <div class='tab'><i class="fa-regular fa-folder-open"></i> Files</div>
334
342
  </a>
335
343
  </div>
336
- <div class='menu'>
344
+ <div class='m h menu' data-type='h'>
337
345
  <% if (config.menu) { %>
338
346
  <%- include('./partials/menu', { menu: config.menu }) %>
339
347
  <% } %>
340
348
  </div>
341
- <div class='temp-menu'>
342
- <%tabs.forEach((tab) => { %>
343
- <a target="<%=tab%>" href="<%=tab%>" class="btn header-item frame-link">
349
+ <div class='m s temp-menu' data-type='s'>
350
+ <%tabs.forEach((tab, i) => { %>
351
+ <a target="s<%=i%>" href="<%=tab%>" class="btn header-item frame-link" data-index="<%=i%>">
344
352
  <div class='tab'>
345
353
  <i class="fa-solid fa-link"></i><div class='display'><%=tab%></div><div class='flexible'></div><button class='btn2 del'><i class="fa-solid fa-xmark"></i></button>
346
354
  </div>
347
355
  </a>
348
356
  <%})%>
349
357
  </div>
350
- <!--
351
358
  <div id='new-tab' class='btn header-item'>
352
359
  <i class="fa-solid fa-plus"></i>
353
360
  </div>
354
- -->
355
361
  <div class='flexible'></div>
356
362
  <div class='grid-btns'>
357
363
  <a class='btn2' href="/?mode=explore" id='explore'><div><i class="fa-solid fa-globe"></i></div><div>Discover</div></a>
@@ -372,7 +378,9 @@ body.dark #new-tab {
372
378
  <button class='btn2 logo' id='forward'><i class="fa-solid fa-arrow-right"></i></button>
373
379
  <button class='btn2 logo' id='reload'><i class="fa-solid fa-rotate-right"></i></button>
374
380
  <div id='location'></div>
375
- <a class='btn2 hidden' id='open-browser' target="_blank" href=""><i class="fa-solid fa-square-arrow-up-right"></i> External Browser</a>
381
+ <div class='flexible'></div>
382
+ <a class='btn2' id='open-browser' target="_blank" href=""><i class="fa-solid fa-square-arrow-up-right"></i> External Browser</a>
383
+ <button class='btn2' id='clone-tab'><i class='fa-regular fa-copy'></i> Clone</button>
376
384
  </div>
377
385
  <!--
378
386
  <div class='flexible'></div>
@@ -398,7 +406,6 @@ let cursorIndex = 0;
398
406
  const getTarget = (href) => {
399
407
  let u
400
408
  if (href.startsWith("http")) {
401
- // u = new URL(href)
402
409
  return href
403
410
  } else if (href.startsWith("/")) {
404
411
  u = new URL("http://localhost" + href)
@@ -420,8 +427,12 @@ const renderCursor = () => {
420
427
  }
421
428
  const nav = (type) => {
422
429
  if (location.hash && location.hash.length > 0) {
423
- let id = getTarget(location.hash.slice(1))
424
- let target = document.querySelector("aside [target='" + id + "']")
430
+ // let id = getTarget(location.hash.slice(1))
431
+ // let target = document.querySelector("aside [target='" + id + "']")
432
+ let id = location.hash.slice(1)
433
+ let type = id[0]
434
+ let index = id.slice(1)
435
+ let target = document.querySelector(`aside .${type} [data-index='${index}']`)
425
436
  let targetFrame = document.querySelector(`iframe[name='${target.target}']`)
426
437
  console.log({ target, targetFrame })
427
438
  if (type === "back") {
@@ -441,8 +452,12 @@ const renderSelection = (e, force) => {
441
452
  if (location.hash && location.hash.length > 0) {
442
453
  console.log("# 2")
443
454
  document.querySelector(".bar").classList.remove("hidden")
444
- let id = getTarget(location.hash.slice(1))
455
+ //let id = getTarget(location.hash.slice(1))
456
+ let id = location.hash.slice(1)
457
+ let type = id[0]
458
+ let index = id.slice(1)
445
459
 
460
+ console.log({ type, index })
446
461
 
447
462
  // select the selected link
448
463
  // document.querySelectorAll(".frame-link").forEach((el) => {
@@ -464,22 +479,32 @@ const renderSelection = (e, force) => {
464
479
 
465
480
  if (!target) {
466
481
  // find target by id
467
- target = document.querySelector("aside [target='" + id + "']")
482
+ //target = document.querySelector("aside [target='" + id + "']")
483
+ target = document.querySelector(`aside .${type} [data-index='${index}']`)
468
484
  }
469
485
 
470
486
  if (target) {
471
487
  console.log("# 3")
472
488
 
473
- //document.querySelector("#location").innerHTML = id
474
- document.querySelector("#location").innerHTML = location.hash.slice(1)
475
489
 
476
- if (location.hash.slice(1).startsWith("/")) {
477
- document.querySelector("#open-browser").classList.add("hidden")
478
- } else {
479
- document.querySelector("#open-browser").classList.remove("hidden")
480
- //document.querySelector("#open-browser").href = id
481
- document.querySelector("#open-browser").href = location.hash.slice(1)
482
- }
490
+ let url = target.href
491
+ document.querySelector("#open-browser").href = url
492
+ document.querySelector("#clone-tab").setAttribute("data-href", url)
493
+ document.querySelector("#location").innerHTML = url
494
+
495
+ // document.querySelector("#location").innerHTML = location.hash.slice(1)
496
+ //
497
+ // if (location.hash.slice(1).startsWith("/")) {
498
+ // //document.querySelector("#open-browser").classList.add("hidden")
499
+ // document.querySelector("#open-browser").classList.remove("hidden")
500
+ // document.querySelector("#open-browser").href = location.hash.slice(1)
501
+ // } else {
502
+ // document.querySelector("#open-browser").classList.remove("hidden")
503
+ // //document.querySelector("#open-browser").href = id
504
+ // document.querySelector("#open-browser").href = location.hash.slice(1)
505
+ // }
506
+ // document.querySelector("#clone-tab").setAttribute("data-href", location.hash.slice(1))
507
+
483
508
 
484
509
 
485
510
  target.classList.add("selected")
@@ -507,9 +532,14 @@ const renderSelection = (e, force) => {
507
532
  console.log("# 5", { src: targetFrame.src, href: target.href, target: target.target })
508
533
  // triggered with a click
509
534
  // do not reload iframe if the href and the src is the same
510
- if (targetFrame.src.includes(target.target)) {
511
- // if (targetFrame.src.includes(target.href)) {
512
- // if (targetFrame.src === target.href) {
535
+ if (target.closest(".h")) {
536
+ if (targetFrame.src.includes(target.target)) {
537
+ // if (targetFrame.src.includes(target.href)) {
538
+ // if (targetFrame.src === target.href) {
539
+ e.preventDefault()
540
+ e.stopPropagation()
541
+ }
542
+ } else if (target.closest(".s")) {
513
543
  e.preventDefault()
514
544
  e.stopPropagation()
515
545
  }
@@ -544,6 +574,10 @@ const renderSelection = (e, force) => {
544
574
  let frame = document.createElement("iframe")
545
575
  frame.name = target.target
546
576
  frame.src = target.href
577
+ frame.setAttribute(
578
+ "allow",
579
+ "clipboard-read *; clipboard-write *; accelerometer *; ambient-light-sensor *; autoplay *; battery *; camera *; display-capture *; gamepad *; geolocation *; gyroscope *; hid *; identity-credentials-get *; microphone *; midi *; otp-credentials *; serial *;"
580
+ )
547
581
  document.querySelector("main").appendChild(frame)
548
582
  loaded[target.target] = true
549
583
  }
@@ -578,16 +612,33 @@ document.querySelector("#forward").addEventListener("click", (e) => {
578
612
  nav("forward")
579
613
  })
580
614
 
581
- /*
582
615
  document.querySelector("#new-tab").addEventListener("click", async (e) => {
583
- const url = prompt("url")
616
+ const u = prompt("url")
617
+
618
+ let o = {}
619
+ if (/^[0-9.:]/.test(u)) {
620
+ o.forceHttp = true
621
+ } else if (/^localhost/.test(u)) {
622
+ o.forceHttp = true
623
+ } else {
624
+ o.forceHttps = true
625
+ }
626
+ const url = normalizeURL(u, o)
584
627
 
585
628
  if (url && url.trim().length > 0) {
586
629
  console.log("url", url)
587
- await addTab(url)
630
+ const { tab, frame } = await addTab(url)
631
+ tab.click()
632
+ }
633
+ })
634
+ document.querySelector("#clone-tab").addEventListener("click", async (e) => {
635
+ const url = e.target.getAttribute("data-href")
636
+ if (url && url.trim().length > 0) {
637
+ console.log("url", url)
638
+ const { tab, frame } = await addTab(url)
639
+ tab.click()
588
640
  }
589
641
  })
590
- */
591
642
 
592
643
  const syncTabs = async () => {
593
644
  let tabs = []
@@ -611,11 +662,16 @@ const syncTabs = async () => {
611
662
  })
612
663
  }
613
664
  const addTab = async (url) => {
665
+ debugger
614
666
  let item = document.createElement("a")
615
667
  //let id = "ts" + Date.now()
616
- let id = url
668
+ let index = document.querySelectorAll("aside .s .frame-link").length
669
+
670
+ let id = "s" + index
671
+ // let id = url
617
672
  item.target = id
618
673
  item.href = url
674
+ item.setAttribute("data-index", index)
619
675
  item.className = "btn header-item frame-link"
620
676
  item.innerHTML = `<div class='tab'><i class="fa-solid fa-link"></i><div class='display'>${url}</div><div class='flexible'></div><button class='btn2 del'><i class="fa-solid fa-xmark"></i></button></div>`
621
677
 
@@ -623,11 +679,19 @@ const addTab = async (url) => {
623
679
 
624
680
  let frame = document.createElement("iframe")
625
681
  frame.name = id
682
+ frame.setAttribute(
683
+ "allow",
684
+ "clipboard-read *; clipboard-write *; accelerometer *; ambient-light-sensor *; autoplay *; battery *; camera *; display-capture *; gamepad *; geolocation *; gyroscope *; hid *; identity-credentials-get *; microphone *; midi *; otp-credentials *; serial *;"
685
+ )
626
686
  document.querySelector("main").appendChild(frame)
627
687
 
628
688
  await syncTabs()
629
689
 
630
690
 
691
+ return {
692
+ tab: item,
693
+ frame
694
+ }
631
695
 
632
696
  }
633
697
 
@@ -694,7 +758,10 @@ document.querySelector("aside").addEventListener("click", async (e) => {
694
758
  // console.log("ID", id, target.href)
695
759
  // location.hash = id
696
760
 
697
- location.hash = target.getAttribute("href")
761
+ //location.hash = target.getAttribute("href")
762
+ let index = target.getAttribute("data-index")
763
+ let type = target.closest(".m").getAttribute("data-type")
764
+ location.hash = type + index
698
765
 
699
766
  document.querySelectorAll(".frame-link").forEach((el) => {
700
767
  el.classList.remove("selected")
@@ -759,6 +759,11 @@ const reloadMemory = async () => {
759
759
  <div id='save' class='btn disabled'>
760
760
  <span class='save'><i class="fa-solid fa-check"></i> Save</span>
761
761
  </div>
762
+ <% if (proxies) { %>
763
+ <% proxies.forEach((proxy) => { %>
764
+ <a class='btn' target="_blank" href="<%=proxy.proxy%>"><i class='fa-solid fa-up-right-from-square'></i> Proxy <%=proxy.name ? "(" + proxy.name + ")" : proxy.proxy%></a>
765
+ <% }) %>
766
+ <% } %>
762
767
  </div>
763
768
  <% } %>
764
769
  </header>
@@ -739,6 +739,11 @@ const reloadMemory = async () => {
739
739
  <span class='stop hidden'><i class="fa-solid fa-stop"></i> Stop</span>
740
740
  </div>
741
741
  <button class='btn' id='open-fs' data-filepath="<%=filepath%>"><i class="fa-solid fa-eye"></i> View File</button>
742
+ <% if (proxies) { %>
743
+ <% proxies.forEach((proxy) => { %>
744
+ <a class='btn' target="_blank" href="<%=proxy.proxy%>"><i class='fa-solid fa-up-right-from-square'></i> Proxy <%=proxy.name ? "(" + proxy.name + ")" : proxy.proxy%></a>
745
+ <% }) %>
746
+ <% } %>
742
747
  <!--
743
748
  <button class='btn' id='source' data-editor-url="<%=editorUrl%>">Navigate</button>
744
749
  -->
@@ -10,7 +10,7 @@
10
10
  </div>
11
11
  <% } else { %>
12
12
  <% if (item.btn) { %>
13
- <a data-index="<%=index%>" target="<%=item.src%>" data-mode="<%=item.mode%>" href="<%=item.href%>" class='btn header-item frame-link'>
13
+ <a data-index="<%=index%>" target="h<%=index%>" data-mode="<%=item.mode%>" href="<%=item.href%>" class='btn header-item frame-link'>
14
14
  <div class='tab'><%-item.btn%></div>
15
15
  <% if (item.running) { %>
16
16
  <div class='loader'>