pinokiod 1.0.17 → 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.
- package/kernel/api/index.js +52 -1
- package/kernel/api/proxy/index.js +4 -8
- package/package.json +1 -1
- package/server/index.js +2 -1
- package/server/public/normalize.js +266 -0
- package/server/views/browser.ejs +94 -31
- package/server/views/editor.ejs +5 -0
- package/server/views/fullscreen_editor.ejs +5 -0
- package/server/views/partials/menu.ejs +1 -1
package/kernel/api/index.js
CHANGED
|
@@ -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,7 +21,54 @@ class Api {
|
|
|
20
21
|
this.running = {}
|
|
21
22
|
this.done = {}
|
|
22
23
|
this.waiter = {}
|
|
23
|
-
this.
|
|
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 })
|
|
24
72
|
}
|
|
25
73
|
async init() {
|
|
26
74
|
console.log("api.init", { homedir: this.kernel.homedir })
|
|
@@ -57,6 +105,9 @@ class Api {
|
|
|
57
105
|
// if any process is in a "wait" state, resume it
|
|
58
106
|
this.kernel.resumeprocess(requestPath)
|
|
59
107
|
|
|
108
|
+
// stop all proxies
|
|
109
|
+
this.stopProxy({ script: requestPath })
|
|
110
|
+
|
|
60
111
|
// if there are any pending waiters, delete them
|
|
61
112
|
|
|
62
113
|
if (this.waiter[requestPath]) {
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
const Lproxy = require('lproxy')
|
|
2
1
|
class P {
|
|
3
|
-
constructor() {
|
|
4
|
-
this.proxy = new Lproxy()
|
|
5
|
-
}
|
|
6
2
|
async start (req, ondata, kernel) {
|
|
7
3
|
/*
|
|
8
4
|
{
|
|
9
5
|
"method": "proxy.start",
|
|
10
6
|
"params": {
|
|
7
|
+
"name",
|
|
11
8
|
"uri": "http://localhost:8192"
|
|
12
9
|
}
|
|
13
10
|
}
|
|
14
11
|
*/
|
|
15
|
-
let
|
|
16
|
-
|
|
17
|
-
return { proxy, url }
|
|
12
|
+
let response = await kernel.api.startProxy(req.parent.path, req.params.uri, req.params.name)
|
|
13
|
+
return response
|
|
18
14
|
}
|
|
19
15
|
async stop (req, ondata, kernel) {
|
|
20
16
|
/*
|
|
@@ -25,7 +21,7 @@ class P {
|
|
|
25
21
|
}
|
|
26
22
|
}
|
|
27
23
|
*/
|
|
28
|
-
|
|
24
|
+
kernel.api.stopProxy({ uri: req.params.uri })
|
|
29
25
|
}
|
|
30
26
|
}
|
|
31
27
|
module.exports = P
|
package/package.json
CHANGED
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
|
+
})();
|
package/server/views/browser.ejs
CHANGED
|
@@ -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="<%=
|
|
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
|
-
<
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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 (
|
|
511
|
-
|
|
512
|
-
// if (targetFrame.src
|
|
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
|
}
|
|
@@ -546,7 +576,7 @@ const renderSelection = (e, force) => {
|
|
|
546
576
|
frame.src = target.href
|
|
547
577
|
frame.setAttribute(
|
|
548
578
|
"allow",
|
|
549
|
-
"clipboard-read
|
|
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 *;"
|
|
550
580
|
)
|
|
551
581
|
document.querySelector("main").appendChild(frame)
|
|
552
582
|
loaded[target.target] = true
|
|
@@ -582,16 +612,33 @@ document.querySelector("#forward").addEventListener("click", (e) => {
|
|
|
582
612
|
nav("forward")
|
|
583
613
|
})
|
|
584
614
|
|
|
585
|
-
/*
|
|
586
615
|
document.querySelector("#new-tab").addEventListener("click", async (e) => {
|
|
587
|
-
const
|
|
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)
|
|
588
627
|
|
|
589
628
|
if (url && url.trim().length > 0) {
|
|
590
629
|
console.log("url", url)
|
|
591
|
-
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()
|
|
592
640
|
}
|
|
593
641
|
})
|
|
594
|
-
*/
|
|
595
642
|
|
|
596
643
|
const syncTabs = async () => {
|
|
597
644
|
let tabs = []
|
|
@@ -615,11 +662,16 @@ const syncTabs = async () => {
|
|
|
615
662
|
})
|
|
616
663
|
}
|
|
617
664
|
const addTab = async (url) => {
|
|
665
|
+
debugger
|
|
618
666
|
let item = document.createElement("a")
|
|
619
667
|
//let id = "ts" + Date.now()
|
|
620
|
-
let
|
|
668
|
+
let index = document.querySelectorAll("aside .s .frame-link").length
|
|
669
|
+
|
|
670
|
+
let id = "s" + index
|
|
671
|
+
// let id = url
|
|
621
672
|
item.target = id
|
|
622
673
|
item.href = url
|
|
674
|
+
item.setAttribute("data-index", index)
|
|
623
675
|
item.className = "btn header-item frame-link"
|
|
624
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>`
|
|
625
677
|
|
|
@@ -627,11 +679,19 @@ const addTab = async (url) => {
|
|
|
627
679
|
|
|
628
680
|
let frame = document.createElement("iframe")
|
|
629
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
|
+
)
|
|
630
686
|
document.querySelector("main").appendChild(frame)
|
|
631
687
|
|
|
632
688
|
await syncTabs()
|
|
633
689
|
|
|
634
690
|
|
|
691
|
+
return {
|
|
692
|
+
tab: item,
|
|
693
|
+
frame
|
|
694
|
+
}
|
|
635
695
|
|
|
636
696
|
}
|
|
637
697
|
|
|
@@ -698,7 +758,10 @@ document.querySelector("aside").addEventListener("click", async (e) => {
|
|
|
698
758
|
// console.log("ID", id, target.href)
|
|
699
759
|
// location.hash = id
|
|
700
760
|
|
|
701
|
-
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
|
|
702
765
|
|
|
703
766
|
document.querySelectorAll(".frame-link").forEach((el) => {
|
|
704
767
|
el.classList.remove("selected")
|
package/server/views/editor.ejs
CHANGED
|
@@ -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="<%=
|
|
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'>
|