total5 0.0.1 → 0.0.2

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/edit.js ADDED
@@ -0,0 +1,462 @@
1
+ // Total.js Edit
2
+ // The MIT License
3
+ // Copyright 2020-2024 (c) Peter Širka <petersirka@gmail.com>
4
+
5
+ 'use strict';
6
+
7
+ const SKIP = /\/\.git\//;
8
+ const VERSION = 1;
9
+ const HEADER = '> Total.js Code Editor';
10
+ const DIVIDER = '----------------------------------------------------';
11
+
12
+ exports.init = function(url) {
13
+
14
+ var client = F.websocketclient();
15
+
16
+ client.options.reconnect = 10000;
17
+ client.options.reconnectserver = true;
18
+
19
+ client.on('message', function(msg) {
20
+
21
+ if (msg.TYPE === 'init') {
22
+ console.log(DIVIDER);
23
+ console.log(HEADER + ': Welcome to "' + msg.name + ' (' + msg.version + ')"');
24
+ console.log('> Project: "' + msg.project + '"');
25
+ console.log(DIVIDER);
26
+ return;
27
+ }
28
+
29
+ F.action('editor', msg).callback(function(err, response) {
30
+
31
+ if (err) {
32
+ msg.error = err;
33
+ } else {
34
+ msg.response = response;
35
+ msg.success = true;
36
+ }
37
+
38
+ client.send(msg);
39
+ });
40
+ });
41
+
42
+ client.on('open', function() {
43
+ client.send({ TYPE: 'init', version: VERSION });
44
+ });
45
+
46
+ client.on('close', function(e) {
47
+
48
+ if (e === 4004) {
49
+ console.log(HEADER + ': 404 project not found');
50
+ // Tries again in 10 second interval
51
+ // client.destroy();
52
+ return;
53
+ }
54
+
55
+ if (e === 4009) {
56
+ console.log(HEADER + ': 409 project is already open');
57
+ client.destroy();
58
+ return;
59
+ }
60
+
61
+ });
62
+
63
+ client.on('error', function(err) {
64
+ console.log(HEADER + ':', err.message);
65
+ });
66
+
67
+ client.connect(url.replace(/^http/, 'ws'));
68
+
69
+ };
70
+
71
+ F.newaction('editor', {
72
+ name: 'Code editor',
73
+ input: '*TYPE,path,data,nocompress:Boolean',
74
+ action: function($, model) {
75
+
76
+ switch (model.TYPE) {
77
+
78
+ case 'ping':
79
+ $.success(VERSION);
80
+ break;
81
+
82
+ // Browse files
83
+ case 'browse':
84
+ browse($, model);
85
+ break;
86
+
87
+ // Download
88
+ case 'download':
89
+ download($, model);
90
+ break;
91
+
92
+ // import
93
+ case 'import':
94
+ customimport($, model);
95
+ break;
96
+
97
+ // upload
98
+ case 'send':
99
+ send($, model);
100
+ break;
101
+
102
+ // Creates file/directory
103
+ case 'create':
104
+ create($, model);
105
+ break;
106
+
107
+ // Loads file
108
+ case 'load':
109
+ load($, model);
110
+ break;
111
+
112
+ // Saves file
113
+ case 'save':
114
+ save($, model);
115
+ break;
116
+
117
+ // Removes file
118
+ case 'remove':
119
+ remove($, model);
120
+ break;
121
+
122
+ // Modifies file
123
+ case 'modify':
124
+ modify($, model);
125
+ break;
126
+
127
+ // Reads info about the file
128
+ case 'info':
129
+ info($, model);
130
+ break;
131
+
132
+ // Reads log file
133
+ case 'log':
134
+ log($, model);
135
+ break;
136
+
137
+ case 'rename':
138
+ rename($, model);
139
+ break;
140
+
141
+ case 'upload':
142
+ upload($, model);
143
+ break;
144
+
145
+ // Clears log
146
+ case 'logclear':
147
+ clearlog($, model);
148
+ break;
149
+
150
+ case 'wiki':
151
+ wiki($, model);
152
+ break;
153
+
154
+ case 'ip':
155
+ ipaddress($, model);
156
+ break;
157
+
158
+ default:
159
+ $.invalid(400);
160
+ break;
161
+ }
162
+ }
163
+ });
164
+
165
+ function mkdir(path, callback) {
166
+ F.Fs.mkdir(path, { recursive: true }, callback);
167
+ }
168
+
169
+ function browse($, model) {
170
+ var path = F.path.root();
171
+ var m = (model.data || '{}').parseJSON() || EMPTYARRAY;
172
+ var skip = m.skip ? new RegExp(m.skip) : null;
173
+ var validator;
174
+
175
+ if (m.type === 'localization')
176
+ validator = ((path, dir) => dir ? (path.endsWith('/databases') || path.endsWith('/node_modules') || path.endsWith('/tmp') || path.endsWith('/.git') || path.endsWith('/.src') || path.endsWith('/logs')) ? false : true : true);
177
+ else
178
+ validator = n => !SKIP.test(n) && (!skip || !skip.test(n));
179
+
180
+ F.TUtils.ls(path, function(files, directories) {
181
+
182
+ for (var i = 0; i < files.length; i++)
183
+ files[i] = files[i].substring(path.length);
184
+
185
+ for (var i = 0; i < directories.length; i++)
186
+ directories[i] = directories[i].substring(path.length);
187
+
188
+ if (m.type === 'localization') {
189
+ var allowed = { html: 1, js: 1 };
190
+ files = files.remove(n => allowed[F.TUtils.getExtension(n)] != 1);
191
+ }
192
+
193
+ $.callback({ files: files, directories: directories });
194
+
195
+ }, validator, true);
196
+ }
197
+
198
+ function log($, model) {
199
+ var filename = F.Path.normalize(F.path.root(model.path));
200
+ F.Fs.stat(filename, function(err, stats) {
201
+ if (stats) {
202
+ var start = stats.size - (1024 * 4); // Max. 4 kB
203
+ if (start < 0)
204
+ start = 0;
205
+ var buffer = [];
206
+ F.Fs.createReadStream(filename, { start: start < 0 ? 0 : start }).on('data', chunk => buffer.push(chunk)).on('end', function() {
207
+ $.callback(Buffer.concat(buffer).toString('utf8'));
208
+ });
209
+ } else {
210
+ $.callback('');
211
+ }
212
+ });
213
+ }
214
+
215
+ function clearlog($, model) {
216
+ var filename = F.path.root(model.path);
217
+ F.Fs.truncate(filename, NOOP);
218
+ $.success();
219
+ }
220
+
221
+ function load($, model) {
222
+ var filename = F.Path.normalize(F.path.root(model.path));
223
+ F.Fs.readFile(filename, function(err, data) {
224
+
225
+ if (err) {
226
+ $.invalid(err);
227
+ return;
228
+ }
229
+
230
+ var index = -1;
231
+
232
+ while (true) {
233
+ index += 1;
234
+ if (data.length <= index || data[index] !== 0)
235
+ break;
236
+ }
237
+
238
+ if (index !== -1)
239
+ data = data.slice(index);
240
+
241
+ encodedata(model, data, function(err, buffer) {
242
+ if (err)
243
+ $.invalid(err);
244
+ else
245
+ $.callback({ type: F.TUtils.contentTypes[F.TUtils.getExtension(model.path)], data: buffer.toString('base64') });
246
+ });
247
+ });
248
+ }
249
+
250
+ function save($, model) {
251
+
252
+ // Tries to create a folder
253
+ var filename = F.path.root(model.path);
254
+ var name = F.TUtils.getName(model.path);
255
+ var directory = F.Path.normalize(filename.substring(0, filename.length - name.length));
256
+
257
+ mkdir(directory, function() {
258
+ decodedata(model, function(err, buffer) {
259
+ if (err)
260
+ $.invalid(err);
261
+ else
262
+ F.Fs.writeFile(F.Path.normalize(filename), buffer, $.done());
263
+ });
264
+ });
265
+ }
266
+
267
+ function remove($, model) {
268
+ var filename = F.Path.normalize(F.path.root(model.path));
269
+ try {
270
+ var stats = F.Fs.lstatSync(filename);
271
+ if (stats.isFile()) {
272
+ F.Fs.unlink(filename, NOOP);
273
+ } else {
274
+ if (stats.isDirectory())
275
+ F.path.rmdir(filename);
276
+ else
277
+ F.Fs.unlink(filename, NOOP);
278
+ }
279
+ } catch (e) {}
280
+
281
+ $.success();
282
+ }
283
+
284
+ function info($, model) {
285
+ var filename = F.Path.normalize(F.path.root(model.path));
286
+ F.Fs.lstat(filename, $.callback);
287
+ }
288
+
289
+ function download($, model) {
290
+ var filename = F.Path.normalize(F.path.root(model.path));
291
+ var ext = F.TUtils.getExtension(model.path);
292
+ F.Fs.lstat(filename, function(err, stats) {
293
+ if (err || stats.isDirectory() || stats.isSocket()) {
294
+ $.status = 400;
295
+ $.invalid('400', 'error-file');
296
+ } else {
297
+ // Max. 5 MB
298
+ if (stats.size > (1024 * 1024 * 5)) {
299
+ $.invalid('Too large');
300
+ } else {
301
+ F.Fs.readFile(filename, function(err, data) {
302
+ if (err) {
303
+ $.invalid(err);
304
+ } else {
305
+ encodedata(model, data, function(err, buffer) {
306
+ if (err)
307
+ $.invalid(err);
308
+ else
309
+ $.callback({ type: F.TUtils.contentTypes[ext], data: buffer.toString('base64') });
310
+ });
311
+ }
312
+ });
313
+ }
314
+ }
315
+ });
316
+ }
317
+
318
+ function send($, model) {
319
+ var filename = F.Path.normalize(F.path.root(model.path));
320
+ F.Fs.fstat(filename, function() {
321
+ var opt = {};
322
+ opt.method = 'GET';
323
+ opt.url = model.data;
324
+ opt.files = [{ name: F.TUtils.getName(filename), filename: filename }];
325
+ opt.callback = $.done();
326
+ REQUEST(opt);
327
+ });
328
+ }
329
+
330
+ function customimport($, model) {
331
+ var filename = F.Path.normalize(F.path.root(model.path));
332
+ DOWNLOAD(model.data, filename, $.done());
333
+ }
334
+
335
+ const SchemaRename = F.TUtils.jsonschema('newpath:String,oldpath:String');
336
+
337
+ function rename($, model) {
338
+
339
+ var data = SchemaRename.transform((model.data || '{}').parseJSON());
340
+ if (data.error) {
341
+ $.invalid(data.error);
342
+ return;
343
+ }
344
+
345
+ data = data.response;
346
+
347
+ data.newpath = F.Path.normalize(F.path.root(data.newpath));
348
+ data.oldpath = F.Path.normalize(F.path.root(data.oldpath));
349
+
350
+ mkdir(F.Path.dirname(data.newpath), function() {
351
+ F.Fs.rename(data.oldpath, data.newpath, $.done());
352
+ });
353
+ }
354
+
355
+ function create($, model) {
356
+
357
+ // model.path {String}
358
+ // model.data {Object}
359
+ // model.data.clone {String}
360
+ // model.data.folder {Boolean}
361
+
362
+ var filename = F.Path.normalize(F.path.root(model.path));
363
+ var data = (model.data || '{}').parseJSON();
364
+
365
+ F.Fs.lstat(filename, function(err) {
366
+
367
+ if (err) {
368
+ // file not found
369
+ // we can continue
370
+ if (data.folder) {
371
+ if (model.clone)
372
+ F.Fs.cp(F.Path.normalize(F.path.root(data.clone)), filename, { recursive: true, force: true }, $.done());
373
+ else
374
+ mkdir(filename, $.done());
375
+ } else {
376
+ var name = F.TUtils.getName(filename);
377
+ mkdir(filename.substring(0, filename.length - name.length), function() {
378
+ if (data.clone)
379
+ F.Fs.copyFile(F.Path.normalize(F.path.root(data.clone)), filename, $.done());
380
+ else
381
+ F.Fs.writeFile(filename, '', $.done());
382
+ });
383
+ }
384
+ } else
385
+ $.invalid('path', model.path + ' already exists');
386
+ });
387
+ }
388
+
389
+ function upload($, model) {
390
+ var name = F.TUtils.getName(model.path);
391
+ var filename = F.Path.normalize(F.path.root(model.path));
392
+ var directory = F.Path.normalize(F.path.root(model.path.substring(0, model.length - name.length)));
393
+ mkdir(directory, function() {
394
+ decodedata(model, function(err, buffer) {
395
+ if (err)
396
+ $.invalid(err);
397
+ else
398
+ F.Fs.writeFile(filename, buffer, $.done());
399
+ });
400
+ });
401
+ }
402
+
403
+ function modify($, model) {
404
+ var filename = F.path.root(model.path);
405
+ var dt = new Date();
406
+ F.Fs.utimes(filename, dt, dt, NOOP);
407
+ $.success();
408
+ }
409
+
410
+ function wiki($) {
411
+
412
+ var path = F.Path.normalize(PATH.root());
413
+
414
+ F.TUtils.ls(path, function(files) {
415
+
416
+ var builder = [];
417
+
418
+ files.wait(function(item, next) {
419
+
420
+ if (item.substring(item.length - 3) === '.js') {
421
+ F.Fs.readFile(item, function(err, buffer) {
422
+ if (buffer) {
423
+ builder.push(buffer.toString('utf8'));
424
+ builder.push('');
425
+ }
426
+ next();
427
+ });
428
+ } else
429
+ next();
430
+
431
+ }, function() {
432
+ $.callback(builder.join('\n'));
433
+ $.cancel();
434
+ });
435
+
436
+ }, path => (/schemas|controllers/).test(path));
437
+
438
+ }
439
+
440
+ function ipaddress($) {
441
+ var opt = {};
442
+ opt.url = 'https://ipecho.net/plain';
443
+ opt.callback = function(err, response) {
444
+ $.callback(response.body || 'undefined');
445
+ $.cancel();
446
+ };
447
+ REQUEST(opt);
448
+ }
449
+
450
+ function decodedata(model, callback) {
451
+ if (model.nocompress)
452
+ callback(null, Buffer.from(model.data, 'base64'));
453
+ else
454
+ F.Zlib.inflate(Buffer.from(model.data, 'base64'), callback);
455
+ }
456
+
457
+ function encodedata(model, buffer, callback) {
458
+ if (model.nocompress)
459
+ callback(null, buffer);
460
+ else
461
+ F.Zlib.deflate(buffer, callback);
462
+ }
package/error.html ADDED
@@ -0,0 +1,49 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>@{model.status}</title>
5
+ <meta charset="utf-8" />
6
+ <meta name="format-detection" content="telephone=no" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
8
+ <meta name="robots" content="all,follow" />
9
+ <style type="text/css">
10
+
11
+ /*auto*/
12
+ html,body { height: 100%; width: 100%; overflow: hidden; font-family: Arial; font-smoothing:antialiased; background-color: white; margin: 0; padding: 0; color: black; }
13
+ .table { display: table; width: 100%; height: 100%; table-layout: fixed; }
14
+ .body { padding: 20px; }
15
+ .cell { display: table-cell; vertical-align: middle; text-align: center; width: 100%; height: 100%; font-size: 30px; }
16
+ .cell b { font-size: 60px; background-color: #000; border-bottom: 5px solid #D0D0D0; color: #FFF; position: relative; display: inline-block; padding: 10px 15px; border-radius: 10px; margin-bottom: 20px; animation: anim 0.5s forwards 2s; }
17
+ .error { font-size: 11px; color: gray; width: 90%; max-width: 800px; margin: 20px auto; background-color: #F0F0F0; padding: 10px; border-radius: 2px; text-align: left; font-family: monospace; overflow: auto; }
18
+ #url { font-size: 12px; color: gray; margin: 5px 0; }
19
+
20
+ @keyframes anim {
21
+ 0% { transform: rotate(0deg); }
22
+ 50% { transform: rotate(-3deg) scale(1.3); }
23
+ 100% { transform: rotate(5deg) scale(1); }
24
+ }
25
+
26
+ @media(max-width: 768px) {
27
+ .cell b { margin-bottom: 10px; }
28
+ .cell { font-size: 25px; }
29
+ }
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <div class="table">
34
+ <div class="cell">
35
+ <div class="body">
36
+ <b>@{model.code}</b>
37
+ <div class="status">@{model.status}</div>
38
+ <div id="url"></div>
39
+ @{if model.error}<div class="error">@{model.error.replace(/\n/g, '<br>')}</div>@{fi}
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ <script>
45
+ document.getElementById('url').innerHTML = location.href;
46
+ </script>
47
+
48
+ </body>
49
+ </html>