total5 0.0.13-9 → 0.0.13

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/builders.js CHANGED
@@ -171,6 +171,7 @@ Options.prototype.publish = function(value) {
171
171
  F.stats.performance.publish++;
172
172
  F.TTMS.cache.socket.send({ type: 'publish', id: name, data: tmp }, client => client.tmsready && client.$subscribers[name]);
173
173
  }
174
+
174
175
  return self;
175
176
  };
176
177
 
@@ -1237,7 +1238,7 @@ exports.newaction = function(name, obj) {
1237
1238
  name = obj.id || obj.name;
1238
1239
  }
1239
1240
 
1240
- var url = name;
1241
+ let url = name;
1241
1242
 
1242
1243
  if (F.actions[name])
1243
1244
  F.actions[name].remove();
@@ -1254,6 +1255,8 @@ exports.newaction = function(name, obj) {
1254
1255
  obj.options.csrf = obj.csrf;
1255
1256
  obj.options.encrypt = obj.encrypt;
1256
1257
  obj.options.compress = obj.compress;
1258
+ obj.audit = obj.audit === true ? NOOP : (obj.audit ? Tangular.compile(obj.audit) : null);
1259
+ obj.called = 0;
1257
1260
 
1258
1261
  if (obj.cache)
1259
1262
  obj.cache = parseactioncache(obj, obj.cache);
@@ -1284,16 +1287,16 @@ exports.newaction = function(name, obj) {
1284
1287
 
1285
1288
  if (obj.publish) {
1286
1289
 
1287
- var tmsschema = obj.publish == true ? (obj.input || obj.output) : obj.publish;
1290
+ let tmsschema = obj.publish == true ? (obj.input || obj.output) : obj.publish;
1288
1291
 
1289
1292
  if (typeof(tmsschema) === 'string') {
1290
1293
  if (tmsschema[0] === '+')
1291
1294
  tmsschema = (obj.input || obj.output) + ',' + tmsschema.substring(1);
1292
1295
 
1293
- var keys = tmsschema.split(',');
1296
+ let keys = tmsschema.split(',');
1294
1297
  obj.$publish = [];
1295
- for (var key of keys) {
1296
- var index = key.indexOf(':');
1298
+ for (let key of keys) {
1299
+ let index = key.indexOf(':');
1297
1300
  obj.$publish.push(index === -1 ? key : key.substring(0, index));
1298
1301
  }
1299
1302
  }
@@ -1382,14 +1385,14 @@ ActionCaller.prototype.exec = function() {
1382
1385
  $.id = action.id;
1383
1386
  $.error = self.error;
1384
1387
  $.controller = self.controller;
1385
- $.fields = action.fields;
1386
1388
  $.user = self.options.user;
1387
1389
  $.config = action.config || EMPTYOBJECT;
1388
1390
 
1391
+ action.called++;
1392
+
1389
1393
  $.$callback = function(err, response) {
1390
1394
 
1391
1395
  if (!err) {
1392
-
1393
1396
  if (action.jsoutput)
1394
1397
  response = action.jsoutput.transform(response).response;
1395
1398
 
@@ -1406,11 +1409,17 @@ ActionCaller.prototype.exec = function() {
1406
1409
  // close
1407
1410
  self.cancel();
1408
1411
  } else {
1412
+
1409
1413
  $.response[$.id] = response;
1410
1414
  meta.response && self.finish && self.finish(response);
1411
- var key = '@' + $.id;
1415
+
1416
+ if (action.audit)
1417
+ F.audit('actions', $, action.audit(null, $) || '');
1418
+
1419
+ let key = '@' + $.id;
1412
1420
  if (F.$events[key])
1413
1421
  F.emit(key, $, response);
1422
+
1414
1423
  self.exec();
1415
1424
  }
1416
1425
  };
package/changelog.txt CHANGED
@@ -24,6 +24,20 @@
24
24
  - fixed a problem with case-sensitive characters in the route parameters
25
25
  - fixed markdown parser
26
26
  - added missing ViewEngine `@{meta(...)}` method
27
+ - fixed `expires` argument in the `$.cookie(key, value, expires|options)` method
28
+ - fixed HTMLParser for XML, added support for `<!CDATA[[`
29
+ - added `outerHTML` property for the HTMLParser element
30
+ - improved `HTMLParser.text()` method
31
+ - added a new view engine property `@{version}` that appends `?ts=start_app_timestamp`
32
+ - extended `@{import()}` method now supports `@style_name.css` and `@script_name.js` (due to HTTP cache reasons, it automatically appends the argument `?ts=start_app_timestamp` to the URL)
33
+ - fixed loading extensions
34
+ - fixed unit-tests for HTMLParser
35
+ - added `Total.syslog(msg)` method for internal app logging
36
+ - added auto audit logs to Actions in the form: `NEWACTION({ ... audit: 'Executed by {{ $.user.name }}' })`
37
+ - added `called {Number}` property per action in the `F.actions` collection
38
+ - fixed `CONF.$root` functionality
39
+ - improved `F.backup()` by adding internal ignore list for `.DS_Store` and `.gitignore` files
40
+ - fixed `CONF.$root` in proxies
27
41
 
28
42
  ========================
29
43
  0.0.12
package/controller.js CHANGED
@@ -794,15 +794,15 @@ Controller.prototype.clear = function() {
794
794
 
795
795
  Controller.prototype.cookie = function(name, value, expires, options) {
796
796
 
797
- var ctrl = this;
798
- var arr;
797
+ let ctrl = this;
798
+ let arr;
799
799
 
800
800
  if (value === undefined) {
801
801
 
802
802
  if (ctrl.cookies)
803
803
  return F.TUtils.decodeURIComponent(ctrl.cookies[name] || '');
804
804
 
805
- var cookie = ctrl.headers.cookie;
805
+ let cookie = ctrl.headers.cookie;
806
806
  if (!cookie) {
807
807
  ctrl.cookies = F.EMPTYOBJECT;
808
808
  return '';
@@ -821,11 +821,11 @@ Controller.prototype.cookie = function(name, value, expires, options) {
821
821
  return name ? F.TUtils.decodeURIComponent(ctrl.cookies[name] || '') : '';
822
822
  }
823
823
 
824
- var cookiename = name + '=';
825
- var builder = [cookiename + value];
826
- var type = typeof(expires);
824
+ let cookiename = name + '=';
825
+ let builder = [cookiename + value];
826
+ let type = typeof(expires);
827
827
 
828
- if (expires && type === 'object') {
828
+ if (expires && !(expires instanceof Date) && type === 'object') {
829
829
  options = expires;
830
830
  expires = options.expires || options.expire || null;
831
831
  }
@@ -849,7 +849,7 @@ Controller.prototype.cookie = function(name, value, expires, options) {
849
849
  if (options.httpOnly || options.httponly || options.HttpOnly)
850
850
  builder.push('HttpOnly');
851
851
 
852
- var same = options.security || options.samesite || F.config.$cookiesamesite;
852
+ let same = options.security || options.samesite || F.config.$cookiesamesite;
853
853
 
854
854
  switch (same) {
855
855
  case 1:
@@ -866,7 +866,7 @@ Controller.prototype.cookie = function(name, value, expires, options) {
866
866
 
867
867
  // Cookie, already, can be in array, resulting in duplicate 'set-cookie' header
868
868
  if (arr.length) {
869
- var l = cookiename.length;
869
+ let l = cookiename.length;
870
870
  for (let i = 0; i < arr.length; i++) {
871
871
  if (arr[i].substring(0, l) === cookiename) {
872
872
  arr.splice(i, 1);
@@ -896,6 +896,9 @@ Controller.prototype.resume = function() {
896
896
  if (ctrl.isfile) {
897
897
 
898
898
  var path = ctrl.uri.key;
899
+ if (CONF.$root)
900
+ path = path.substring(CONF.$root.length - 1);
901
+
899
902
  if (path[1] === '_') {
900
903
 
901
904
  let tmp = path.substring(1);
package/htmlparser.js CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  'use strict';
6
6
 
7
+ const BLOCK = { DIV: 1, P: 1, H1: 1, H2: 1, H3: 1, H4: 1, H5: 1, H6: 1, UL: 1, OL: 1, TABLE: 1, SECTION: 1, HEADER: 1, FOOTER: 1 };
8
+
7
9
  function HTMLElement() {
8
10
  this.children = [];
9
11
  }
@@ -12,8 +14,11 @@ HTMLElement.prototype = {
12
14
  get innerHTML() {
13
15
  return this.toString();
14
16
  },
17
+ get outerHTML() {
18
+ return this.toString(false, true);
19
+ },
15
20
  get innerText() {
16
- return this.toString().removeTags();
21
+ return this.text();
17
22
  }
18
23
  };
19
24
 
@@ -154,7 +159,10 @@ function extendarr(output) {
154
159
  };
155
160
 
156
161
  output.text = function() {
157
- return output.html().removeTags();
162
+ let builder = [];
163
+ for (let item of this)
164
+ builder.push(item.text());
165
+ return builder.join('\n');
158
166
  };
159
167
 
160
168
  return output;
@@ -430,11 +438,29 @@ HTMLElement.prototype.prepend = function(str) {
430
438
  return dom;
431
439
  };
432
440
 
433
- HTMLElement.prototype.text = function() {
434
- return this.html().removeTags().decode();
441
+ HTMLElement.prototype.text = function(formatted) {
442
+ let self = this;
443
+ let builder = [];
444
+ let browse = function(children, newline) {
445
+ for (let item of children) {
446
+ switch (item.tagName) {
447
+ case 'TEXT':
448
+ if (builder.length)
449
+ builder.push(newline ? '\n' : '');
450
+ if (item.textContent)
451
+ builder.push(item.textContent.decode());
452
+ break;
453
+ default:
454
+ browse(item.children, self.xml ? true : BLOCK[item.tagName]);
455
+ break;
456
+ }
457
+ }
458
+ };
459
+ browse(self.children, 0);
460
+ return builder.join('');
435
461
  };
436
462
 
437
- HTMLElement.prototype.toString = HTMLElement.prototype.html = function(formatted) {
463
+ HTMLElement.prototype.toString = function(formatted, outer) {
438
464
 
439
465
  let self = this;
440
466
  let builder = [];
@@ -457,7 +483,7 @@ HTMLElement.prototype.toString = HTMLElement.prototype.html = function(formatted
457
483
  switch (item.tagName) {
458
484
  case 'TEXT':
459
485
  if (item.textContent)
460
- builder.push(indent + item.textContent);
486
+ builder.push((item.cdata ? '<![CDATA[' : '') + indent + item.textContent + (item.cdata ? ']]>' : ''));
461
487
  break;
462
488
  default:
463
489
  tag = item.raw;
@@ -475,10 +501,15 @@ HTMLElement.prototype.toString = HTMLElement.prototype.html = function(formatted
475
501
  }
476
502
  };
477
503
 
478
- browse(self.tagName ? [self] : self.children, 0);
504
+ // browse(self.tagName ? [self] : self.children, 0);
505
+ browse(outer && self.tagName ? [self] : self.children, 0);
479
506
  return builder.join(formatted ? '\n' : '');
480
507
  };
481
508
 
509
+ HTMLElement.prototype.html = function(formatted = false) {
510
+ return this.toString(formatted, false);
511
+ };
512
+
482
513
  function removeComments(html) {
483
514
  let tagBeg = '<!--';
484
515
  let tagEnd = '-->';
@@ -534,9 +565,16 @@ function parseHTML(html, trim, onerror, isxml) {
534
565
  let beg = str.indexOf('<');
535
566
  let end = -1;
536
567
  let tmp;
537
-
538
- if (beg !== -1)
539
- end = str.indexOf('>', beg + 1);
568
+ let cdata = false;
569
+
570
+ if (beg !== -1) {
571
+ if (str.substring(beg, beg + 5) === '<![CD') {
572
+ // CDATA
573
+ cdata = true;
574
+ end = str.indexOf(']]>', beg + 6)
575
+ } else
576
+ end = str.indexOf('>', beg + 1);
577
+ }
540
578
 
541
579
  if (beg === -1 || end === -1) {
542
580
  if (parent) {
@@ -550,7 +588,6 @@ function parseHTML(html, trim, onerror, isxml) {
550
588
 
551
589
  if (beg > 0) {
552
590
  tmp = str.substring(0, beg);
553
-
554
591
  if (trim)
555
592
  tmp = tmp.trim();
556
593
 
@@ -558,20 +595,27 @@ function parseHTML(html, trim, onerror, isxml) {
558
595
  parent.children.push(makeText(parent, tmp));
559
596
  }
560
597
 
561
- let node = str.substring(beg + 1, end);
598
+ let node = str.substring(beg + (cdata ? 9 : 1), end);
562
599
  let dom = new HTMLElement();
563
600
 
564
601
  dom.xml = isxml;
565
602
 
566
603
  // Doctype or xml?
567
- if (node[0] === '!' || node[0] === '?')
604
+ if (!cdata && (node[0] === '!' || node[0] === '?'))
568
605
  return str.substring(end + 1);
569
606
 
570
- if (node[node.length - 1] === '/') {
607
+ if (!cdata && node[node.length - 1] === '/') {
571
608
  node = node.substring(0, node.length - 1);
572
609
  dom.unpair = true;
573
610
  }
574
611
 
612
+ if (cdata) {
613
+ let txt = makeText(dom, node);
614
+ txt.cdata = true;
615
+ parent.children.push(txt);
616
+ return str.substring(end + 3);
617
+ }
618
+
575
619
  let tag = node;
576
620
  let index = -1;
577
621
 
package/index.js CHANGED
@@ -233,7 +233,6 @@ global.DEF = {};
233
233
  F.path.directory = (type, path) => path ? F.path.$join(F.temporary.directories[type], path) : F.temporary.directories[type];
234
234
  F.path.tmp = F.path.temp = path => path ? F.path.$join(F.temporary.directories.tmp, path) : F.temporary.directories.tmp;
235
235
  F.path.exists = (path, callback) => callback ? (F.Fs.lstat(path, (err, stats) => callback(err ? false : true, stats ? stats.size : 0, stats ? stats.isFile() : false))) : new Promise(resolve => F.path.exists(path, resolve));
236
-
237
236
  F.path.$join = function(directory, path) {
238
237
  var key = '$' + directory;
239
238
  if (!F.temporary.path[key])
@@ -277,6 +276,10 @@ global.DEF = {};
277
276
  } catch {}
278
277
  };
279
278
 
279
+ F.virtualpath = function(path) {
280
+ return F.config.$root ? (F.config.$root + (path[0] === '/' ? path.substring(1) : path)) : path;
281
+ };
282
+
280
283
  })(global.F);
281
284
 
282
285
  function mailsendforce(message) {
@@ -536,8 +539,9 @@ function unlink(arr, callback) {
536
539
 
537
540
  F.loadconfig = function(value) {
538
541
 
539
- var cfg = F.TUtils.parseConfig(value);
540
- var smtp = null;
542
+ let cfg = F.TUtils.parseConfig(value);
543
+ let smtp = null;
544
+ let isapi = false;
541
545
 
542
546
  for (let key in cfg) {
543
547
 
@@ -597,11 +601,20 @@ F.loadconfig = function(value) {
597
601
  for (var m of tmp)
598
602
  F.config.$httpfiles[m] = true;
599
603
  continue;
604
+ case '$api':
605
+ F.config.$apibk = val || '';
606
+ break;
600
607
  }
601
608
 
602
609
  F.config[key] = cfg[key];
603
610
  }
604
611
 
612
+ if (cfg.$root) {
613
+ if (!F.config.$apibk)
614
+ F.config.$apibk = F.config.$api;
615
+ F.config.$api = cfg.$root + F.config.$apibk.substring(1);
616
+ }
617
+
605
618
  if (!F.config.secret_uid)
606
619
  F.config.secret_uid = (F.config.name).crc32(true) + '';
607
620
 
@@ -830,11 +843,11 @@ F.load = async function(types, callback) {
830
843
 
831
844
  files.sort(function(a) {
832
845
 
833
- if (a.type === 'middleware')
834
- return 1;
846
+ if (a.type === 'module')
847
+ return -1;
835
848
 
836
849
  if (a.type === 'middleware')
837
- return -1;
850
+ return 1;
838
851
 
839
852
  return 0;
840
853
  });
@@ -867,6 +880,7 @@ F.load = async function(types, callback) {
867
880
  case 'controllers':
868
881
  case 'schemas':
869
882
  case 'actions':
883
+ case 'extensions':
870
884
  case 'models':
871
885
  case 'definitions':
872
886
  case 'middleware':
@@ -1411,7 +1425,7 @@ F.componentator = function(name, components, removeprev = true, attrs = '') {
1411
1425
  let relative = 'ui-' + (removeprev ? (nameid + '-') : '') + meta.components.makeid() + '.min.js';
1412
1426
  let filename = F.path.public(relative);
1413
1427
 
1414
- F.repo[meta.name] = '/' + relative;
1428
+ F.repo[meta.name] = (F.config.$root || '/') + relative;
1415
1429
 
1416
1430
  if (removeprev) {
1417
1431
  F.Fs.readdir(F.path.public(), function(err, files) {
@@ -1449,6 +1463,11 @@ F.componentator = function(name, components, removeprev = true, attrs = '') {
1449
1463
 
1450
1464
  };
1451
1465
 
1466
+ F.syslog = function(msg) {
1467
+ NOW = new Date();
1468
+ console.log('INFO ======= ' + (NOW.format('yyyy-MM-dd HH:mm:ss')) + ': ' + msg);
1469
+ };
1470
+
1452
1471
  F.error = function(err, name, url) {
1453
1472
 
1454
1473
  if (!arguments.length)
@@ -1504,7 +1523,7 @@ F.merge = function(url) {
1504
1523
  if (url[0] !== '/')
1505
1524
  url = '/' + url;
1506
1525
 
1507
- url = url.toLowerCase();
1526
+ url = F.virtualpath(url.toLowerCase());
1508
1527
 
1509
1528
  var ext = F.TUtils.getExtension(url);
1510
1529
  var key = url.substring(1).replace(/\//g, '-').replace(/\.(js|html|css)$/, '') + '-min.' + ext;
@@ -2285,6 +2304,7 @@ F.backup = function(filename, files, callback, filter) {
2285
2304
  var unlink = typeof(filename) === 'string' ? F.Fs.unlink : (filename, callback) => callback();
2286
2305
  var concat = [];
2287
2306
  var gzipoptions = { memLevel: 9 };
2307
+ var ignorelist = ['.ds_store', '.gitignore', '.npmignore', '.git'];
2288
2308
 
2289
2309
  unlink(filename, function() {
2290
2310
 
@@ -2322,6 +2342,15 @@ F.backup = function(filename, files, callback, filter) {
2322
2342
 
2323
2343
  files.wait(function(item, next) {
2324
2344
 
2345
+ let lowerfilename = item.toLowerCase();
2346
+
2347
+ for (let ignore of ignorelist) {
2348
+ if (lowerfilename.includes(ignore)) {
2349
+ next();
2350
+ return;
2351
+ }
2352
+ }
2353
+
2325
2354
  var file = F.Path.join(path, item);
2326
2355
 
2327
2356
  if (F.is)
@@ -2900,6 +2929,7 @@ process.on('message', function(msg, h) {
2900
2929
  F.syshash = (__dirname + '-' + F.Os.hostname() + '-' + F.Os.platform() + '-' + F.Os.arch() + '-' + F.Os.release() + '-' + F.Os.tmpdir() + JSON.stringify(process.versions)).md5();
2901
2930
  F.isLE = F.Os.endianness ? F.Os.endianness() === 'LE' : true;
2902
2931
  F.isWindows = F.Os.platform().substring(0, 3).toLowerCase() === 'win';
2932
+ F.stamp = Date.now().toString(36);
2903
2933
 
2904
2934
  F.config.$total5 = F.Path.dirname(require.resolve('./index'));
2905
2935
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total5",
3
- "version": "0.0.13-9",
3
+ "version": "0.0.13",
4
4
  "description": "Total.js framework v5",
5
5
  "main": "index.js",
6
6
  "directories": {
package/routing.js CHANGED
@@ -63,8 +63,13 @@ function Route(url, action, size) {
63
63
  action = null;
64
64
  }
65
65
 
66
+ let skiproot = false;
67
+
66
68
  // Apply a default API endpoint
67
- url = url.replace(/\?/g, F.config.$api).replace(/\/{2,}/g, '/');
69
+ if (url.includes('?')) {
70
+ skiproot = true;
71
+ url = url.replace(/\?/g, F.config.$api).replace(/\/{2,}/g, '/');
72
+ }
68
73
 
69
74
  var t = this;
70
75
 
@@ -100,6 +105,9 @@ function Route(url, action, size) {
100
105
  return;
101
106
  }
102
107
 
108
+ if (!skiproot && t.url2 && CONF.$root)
109
+ t.url2 = CONF.$root + t.url2.substring(1);
110
+
103
111
  t.url = exports.split(t.url2, true);
104
112
  url = url.substring(index + 1);
105
113
  t.id = t.method + '/' + t.url.join('/');
@@ -271,8 +279,18 @@ function Route(url, action, size) {
271
279
  t.action = null;
272
280
  }
273
281
 
274
- if (!t.view && !t.action && t.method !== 'FILE' && t.method !== 'SOCKET')
275
- t.view = t.url[0] && t.url[0] !== '/' ? t.url[0] : 'index';
282
+ if (!t.view && !t.action && t.method !== 'FILE' && t.method !== 'SOCKET') {
283
+
284
+ let arr = t.url;
285
+
286
+ if (F.config.$root) {
287
+ arr = t.url.join('~').substring(F.config.$root.length - 1).split('~');
288
+ if (arr[0] == '')
289
+ arr[0] = '/';
290
+ }
291
+
292
+ t.view = arr[0] && arr[0] !== '/' ? arr[0] : 'index';
293
+ }
276
294
 
277
295
  if (t.wildcard)
278
296
  t.priority -= 50;
@@ -847,6 +865,9 @@ Proxy.prototype.remove = function() {
847
865
 
848
866
  exports.proxy = function(url, target) {
849
867
 
868
+ // Apply proxy
869
+ url = F.virtualpath(url);
870
+
850
871
  if (!target) {
851
872
  let index = F.routes.proxies.TfindIndex('url', url.toLowerCase());
852
873
  if (index !== -1)
package/test.js CHANGED
@@ -2,6 +2,9 @@
2
2
  // The MIT License
3
3
  // Copyright 2023 (c) Peter Širka <petersirka@gmail.com>
4
4
 
5
+ if (!global.Total)
6
+ require('total5');
7
+
5
8
  var Test = { items: [], count: 0 };
6
9
 
7
10
  Test.start = function(message) {
@@ -44,7 +47,7 @@ Test.run = function(callback) {
44
47
  if (callback)
45
48
  callback();
46
49
  else
47
- process.exit(0);
50
+ setTimeout(() => process.exit(0), 2);
48
51
  });
49
52
  };
50
53
 
package/viewengine.js CHANGED
@@ -372,29 +372,24 @@ function prepare(command, dcommand, functions) {
372
372
  case '!session':
373
373
  case '!user':
374
374
  return 'self.safehtml(' + command.substring(1) + ')';
375
-
376
375
  case 'host':
377
376
  case 'hostname':
378
377
  return 'self.hostname';
379
-
380
378
  case 'href':
381
379
  return command.includes('(') ? ('self.' + command) : 'self.href()';
382
-
383
380
  case 'url':
384
381
  return 'self.' + command;
385
-
386
382
  case 'title':
387
383
  case 'description':
388
384
  case 'keywords':
389
385
  case 'author':
390
386
  return command.includes('(') ? ('self.' + command) : '((repository[\'' + command + '\'] || \'\') + \'\').safehtml()';
391
-
392
387
  case 'meta':
393
388
  return command.includes('(') ? ('self.' + command) : ('self.' + command + '()');
394
-
395
389
  case 'title2':
396
390
  return 'self.' + command;
397
-
391
+ case 'version':
392
+ return '\'?ts=\'+F.' + command;
398
393
  case '!title':
399
394
  case '!description':
400
395
  case '!keywords':
@@ -823,10 +818,10 @@ function makehtmlmeta(self) {
823
818
 
824
819
  View.prototype.import = function() {
825
820
 
826
- var builder = '';
827
- var self = this;
821
+ let builder = '';
822
+ let self = this;
828
823
 
829
- for (var m of arguments) {
824
+ for (let m of arguments) {
830
825
 
831
826
  switch (m) {
832
827
  case 'meta':
@@ -841,7 +836,7 @@ View.prototype.import = function() {
841
836
  let ext = m.substring(m.length - 3);
842
837
  if (ext === 'ico')
843
838
  ext = 'x-icon';
844
- builder += '<link rel="icon" href="/' + m + '" type="image/' + ext + '" />';
839
+ builder += '<link rel="icon" href="' + F.virtualpath('/' + m) + '" type="image/' + ext + '" />';
845
840
  break;
846
841
  default:
847
842
 
@@ -853,22 +848,29 @@ View.prototype.import = function() {
853
848
  continue;
854
849
  }
855
850
 
856
- if (!m.includes('+')) {
857
- let absolute = m[0] === '/';
858
- let key = absolute ? m : ('/' + m);
859
- if (REG_CHECKCSS.test(m)) {
860
- tmp = '<link rel="stylesheet" href="' + (absolute ? m : ('/' + (F.routes.virtual[key] ? '' : 'css/') + m)) + '" />';
861
- } else {
862
- tmp = '<scri' + 'pt src="' + (absolute ? m : ('/' + (F.routes.virtual[key] ? '' : 'js/') + m)) + '"></scr' + 'ipt>';
863
- }
864
- } else {
851
+ let version = '';
852
+
853
+ if (m[0] === '@') {
854
+ m = m.substring(1);
855
+ version = '?ts=' + F.stamp;
856
+ }
857
+
858
+ if (m.includes('+')) {
865
859
  let iscss = REG_CHECKCSS.test(m);
866
860
  let path = '/' + F.TUtils.random_string(10).toLowerCase() + '-min.' + (iscss ? 'css' : 'js');
867
861
  F.merge(path, m);
868
862
  if (iscss)
869
- tmp = '<link rel="stylesheet" href="' + path + '" />';
863
+ tmp = '<link rel="stylesheet" href="' + F.virtualpath(path) + version + '" />';
870
864
  else
871
- tmp = '<scri' + 'pt src="' + path + '"></scr' + 'ipt>';
865
+ tmp = '<scri' + 'pt src="' + F.virtualpath(path) + version + '"></scr' + 'ipt>';
866
+ } else {
867
+ let absolute = m[0] === '/';
868
+ let key = absolute ? m : ('/' + m);
869
+ if (REG_CHECKCSS.test(m)) {
870
+ tmp = '<link rel="stylesheet" href="' + F.virtualpath(absolute ? m : ('/' + (F.routes.virtual[key] ? '' : 'css/') + m)) + version + '" />';
871
+ } else {
872
+ tmp = '<scri' + 'pt src="' + F.virtualpath(absolute ? m : ('/' + (F.routes.virtual[key] ? '' : 'js/') + m)) + version + '"></scr' + 'ipt>';
873
+ }
872
874
  }
873
875
 
874
876
  F.temporary.views[key] = tmp;